How would you implement a "trait" design-pattern in C#?
Asked Answered
P

8

64

I know the feature doesn't exist in C#, but PHP recently added a feature called Traits which I thought was a bit silly at first until I started thinking about it.

Say I have a base class called Client. Client has a single property called Name.

Now I'm developing a re-usable application that will be used by many different customers. All customers agree that a client should have a name, hence it being in the base-class.

Now Customer A comes along and says he also need to track the client's Weight. Customer B doesn't need the Weight, but he wants to track Height. Customer C wants to track both Weight and Height.

With traits, we could make the both the Weight and the Height features traits:

class ClientA extends Client use TClientWeight
class ClientB extends Client use TClientHeight
class ClientC extends Client use TClientWeight, TClientHeight

Now I can meet all my customers' needs without adding any extra fluff to the class. If my customer comes back later and says "Oh, I really like that feature, can I have it too?", I just update the class definition to include the extra trait.

How would you accomplish this in C#?

Interfaces don't work here because I want concrete definitions for the properties and any associated methods, and I don't want to re-implement them for each version of the class.

(By "customer", I mean a literal person who has employed me as a developer, whereas by "client" I'm referring a programming class; each of my customers has clients that they want to record information about)

Permissive answered 23/5, 2012 at 23:22 Comment(10)
Well, you can quite perfectly simulate traits in C# by using marker interfaces and extension methods.Wellworn
@Wellworn Those are not traits and lack the ability to add new members (among other things). Nevertheless, extension methods are nifty.Venita
@Lucero: That would work for adding extra methods, but what if I want to store additional data on the client object as well?Permissive
@Mark, then you need to have some ability to dynamically store data on arbitrary objects, which is not a feature of the runtime. I'll add some info on my answer on that regard.Wellworn
@Wellworn And is an issue that is solved by Traits... ;-)Venita
@pst, sure, but in the end they don't add multiple inheritance either. The challenge therefore is mostly to efficiently leverage the tools and have the compiler give us a nice syntax - which is why I called it simulating. With my approach and an object that also supports the DLR (see my edit) you do get far in regards of trait simulation as long as you have control over the base classes; adding them to existing classes where the inheritance chain cannot be altered is however a different topic.Wellworn
@Wellworn "flattened" Traits such as those in Scala and Squeak are explicitly designed (at least in Odersky's view ;-) not to open up the MI can'o'worms. (An added benefit is they can run without an altered runtime environment.) Since the syntax of a language is tied to the repeated usability of certain constructs it must not be overlooked. Scala has a working version with Traits in the CLR; there is no reason to require the DLR as it's "all" or "only" in the language at this point (perhaps C# 6.0?). The usage of Extension methods shown is interesting, but ultimately a different tool.Venita
@Wellworn That is, "flattened" Traits explicitly do not alter the inheritance chain but they do alter the members present in the type assuming the traits. This can be implemented by-hand with proxy-methods to implementations (as well as by-hand declarations of data members) only, the by-hand approach is considered composition (it's just tedious to do it all manually). Extension methods work well in the precise opposite scenario; when the type itself cannot be altered.Venita
nroles gives you traits (kind of) with a c# post compiler: code.google.com/p/nrolesBowlds
Traits are coming to C# in the form of default interface methods. See this proposal and the corresponding issue. (I'd post an answer but I don't know enough about it yet to post anything meaningful.)Forgot
W
66

You can get the syntax by using marker interfaces and extension methods.

Prerequisite: the interfaces need to define the contract which is later used by the extension method. Basically the interface defines the contract for being able to "implement" a trait; ideally the class where you add the interface should already have all members of the interface present so that no additional implementation is required.

public class Client {
  public double Weight { get; }

  public double Height { get; }
}

public interface TClientWeight {
  double Weight { get; }
}

public interface TClientHeight {
  double Height { get; }
}

public class ClientA: Client, TClientWeight { }

public class ClientB: Client, TClientHeight { }

public class ClientC: Client, TClientWeight, TClientHeight { }

public static class TClientWeightMethods {
  public static bool IsHeavierThan(this TClientWeight client, double weight) {
    return client.Weight > weight;
  }
  // add more methods as you see fit
}

public static class TClientHeightMethods {
  public static bool IsTallerThan(this TClientHeight client, double height) {
    return client.Height > height;
  }
  // add more methods as you see fit
}

Use like this:

var ca = new ClientA();
ca.IsHeavierThan(10); // OK
ca.IsTallerThan(10); // compiler error

Edit: The question was raised how additional data could be stored. This can also be addressed by doing some extra coding:

public interface IDynamicObject {
  bool TryGetAttribute(string key, out object value);
  void SetAttribute(string key, object value);
  // void RemoveAttribute(string key)
}

public class DynamicObject: IDynamicObject {
  private readonly Dictionary<string, object> data = new Dictionary<string, object>(StringComparer.Ordinal);

  bool IDynamicObject.TryGetAttribute(string key, out object value) {
    return data.TryGet(key, out value);
  }

  void IDynamicObject.SetAttribute(string key, object value) {
    data[key] = value;
  }
}

And then, the trait methods can add and retrieve data if the "trait interface" inherits from IDynamicObject:

public class Client: DynamicObject { /* implementation see above */ }

public interface TClientWeight, IDynamicObject {
  double Weight { get; }
}

public class ClientA: Client, TClientWeight { }

public static class TClientWeightMethods {
  public static bool HasWeightChanged(this TClientWeight client) {
    object oldWeight;
    bool result = client.TryGetAttribute("oldWeight", out oldWeight) && client.Weight.Equals(oldWeight);
    client.SetAttribute("oldWeight", client.Weight);
    return result;
  }
  // add more methods as you see fit
}

Note: by implementing IDynamicMetaObjectProvider as well the object would even allow to expose the dynamic data through the DLR, making the access to the additional properties transparent when used with the dynamic keyword.

Wellworn answered 23/5, 2012 at 23:36 Comment(12)
So you're saying put all the data in the base class, and all the method implementations in extension methods that have hooks on the interfaces? It's curious solution, but perhaps workable. My only beef is that you're making the client classes carry a lot of "dead weight" (unused members). With some fancy serialization it won't need to be saved to disk, but it's still consuming memory.Permissive
"Sort of". I sure can't think of anything better within the C# language, so +1. I do not give this the same footing as a Trait, however. (A sever limitation is outlined by Mark.)Venita
Err.. I guess with C# properties I only have to implement the property for each derived class and I can store the data there. It's a little bit redundant, but I guess it's better than re-implementing all the methods too.Permissive
To complete this answer, I'd still like to see you define a concrete member variable (all I see is properties). I'm not sure if you intend for me to define them in Client, or redefine them multiple times in ClientB and ClientC as needed.Permissive
@Mark, see my updates for dynamic data storage (implementing the serialization is left as an excercise to the reader ;) ). Since interfaces cannot define contracts for fields you cannot use fields as part of the "trait", but of course the properties can be read-write! I'm not saying that C# has traits, but rather that the extension methods can serve as reusable code blocks for interfaces, so that re-implementation of the methods is not required; of course the code has to have all needed members readily available on the interface.Wellworn
@Wellworn awesome approach! Thanks for sharing it. I wonder, why your solution hasn't been accepted, since there is no other way to answer this question with standard C# means.Apelles
@NullAndVoid, glad to know that you like it! I don't know either why no solution (including mine) has been accepted though.Wellworn
@Wellworn ConditionalWeakTable will allow you to store additional data without controlling Client's base class. Essentially, you can add a static ConditionalWeakTable to TClientWeightMethods and weak-key on TClientWeight. I imagine this technique wasn't available when you wrote your answer, but now that it is, I figured it was share-worthy.Thus
@Lucero: The first line of your usage example is var c1 = new Class1();. Think you meant var c1 = new ClassA(); instead? Thanks for your helpful answer!Blackface
Thanks for the great and clever answer! Unfortunately, this approach will not work if you have more than one stateful trait because C# do not support multiple inheritance.Perceptual
Is there a mistake? How ca.IsHeavierThan(10) returns OK, while ClientA doesnt use either TClientWeightMethods or other classes ???Antihelix
Where to store data with traits? As @Stijn says: Traits are coming to C# in the form of default interface methods (probably in C# 8.0). Traits can only implement methods, including getters and setters, but cannot store any state. But they can force a class to implement properties. E.g.: interface NameTrait { string FirstName { get; set; } string LastName { get; set; } string FullName => $"{FirstName} {LastName}"; }. The class must implement (and store) FirstName and LastName. The trait implements FullName. This is how traits are supposed to work.Repercussion
C
18

Traits can be implemented in C# 8 by using default interface methods. Java 8 introduced default interface methods for this reason too.

Using C# 8, you can write almost exactly what you proposed in the question. The traits are implemented by the IClientWeight, IClientHeight interfaces that provide a default implementation for their methods. In this case, they just return 0:

public interface IClientWeight
{
    int getWeight()=>0;
}

public interface IClientHeight
{
    int getHeight()=>0;
}

public class Client
{
    public String Name {get;set;}
}

ClientA and ClientB have the traits but don't implement them. ClientC implements only IClientHeight and returns a different number, in this case 16 :

class ClientA : Client, IClientWeight{}
class ClientB : Client, IClientHeight{}
class ClientC : Client, IClientWeight, IClientHeight
{
    public int getHeight()=>16;
}

When getHeight() is called in ClientB through the interface, the default implementation is called. getHeight() can only be called through the interface.

ClientC implements the IClientHeight interface so its own method is called. The method is available through the class itself.

public class C {
    public void M() {        
        //Accessed through the interface
        IClientHeight clientB = new ClientB();        
        clientB.getHeight();

        //Accessed directly or through the class
        var clientC = new ClientC();        
        clientC.getHeight();
    }
}

This SharpLab.io example shows the code produced from this example

Many of the traits features described in the PHP overview on traits can be implemented easily with default interface methods. Traits (interfaces) can be combined. It's also possible to define abstract methods to force classes to implement certain requirements.

Let's say we want our traits to have sayHeight() and sayWeight() methods that return a string with the height or weight. They'd need some way to force exhibiting classes (term stolen from the PHP guide) to implement a method that returns the height and weight :

public interface IClientWeight
{
    abstract int getWeight();
    String sayWeight()=>getWeight().ToString();
}

public interface IClientHeight
{
    abstract int getHeight();
    String sayHeight()=>getHeight().ToString();
}

//Combines both traits
public interface IClientBoth:IClientHeight,IClientWeight{}

The clients now have to implement thet getHeight() or getWeight() method but don't need to know anything about the say methods.

This offers a cleaner way to decorate

SharpLab.io link for this sample.

Carleecarleen answered 23/11, 2018 at 16:44 Comment(10)
The fact that you need to cast it to the interface type seems to make the code alot more verbose. Do you know the reasons it was designed like this?Homeric
@Homeric From the docs it seems that the main reasons for implementing were API development and backward compatibility and interoperation with Swift and Android, not as a language feature for traits / mixins. I totally agree casting to the interface is an annoyance if you are looking for a mixins/traits/multiple inheritance style language features. Shame.Exemplary
@Exemplary and those features in Java are used for traits and mixins and versioning. The what's new page is just a short description and doesn't contain the reasons. You'll find those in the CSharplang Github repo, in the design meetings. The AndroidSDK uses DIMs to implement traits, and now, so does C#. OTOH, Android SDK interoperability is probably the most important motivation for this featureCarleecarleen
@Exemplary as for the syntax, it's a necessity, not an annoyance, to avoid inheritance triangles. Explicit interface implementations use the same syntax, for the same reasons - avoid collisions when two interfaces provide the same member.Carleecarleen
@PanagiotisKanavos Thanks yes. I mean I am sure there are very good reasons for all the decisions, it's just that personally from a more surface perspective I'd love to be able to "compose" classes in C# from multiple reusable bits of code without the need for boilerplate code (as I can in e.g. Python (multiple inheritance) or less (mixins)).Exemplary
It seems to me (a language architecture layman) that there need not be any major issues supporting this in C#. Surely the compiler could just handle a bit like partial classes - i.e. the compiler can Error if there's multiple definitions of same thing. Seems like should be really straightforward and would make my work days a lot more efficient. Anyhow I guess I can get something to work with Fody or similar. I just like to keep it minimal and DRY, and often find myself going to great lengths to get around this limitation in C#.Exemplary
One of the reasons why inherited 'trait' implementations must be accessed via an explicit interface reference is to avoid the potential diamond problem - more than one base interface / trait could expose the same method signature.Resolutive
Interestingly the pitch made positive mention of traits: learn.microsoft.com/en-us/dotnet/csharp/language-reference/… . Sadly this needless crippling of the feature removes such powerful usefulness. Diamond problem: Super easy solution: when a class has multiple interface implementations, forbid default implemented interface members from having any overlap (IFoo and IBar can't both implement int GetFoo() => ... signature). Easy peasy.Vaal
@NicholasPetersen You really think they didn’t think about this? How would you enforce this exactly on dynamically generated code at runtime? Not to mention you would break the existing codebases that rely on explicit definitions.Warbler
@Warbler you may be right. I marvel how any of these things can be implemented on the assembly level. Dynamic code as a major problem like you mention also makes sense. I'll take back the overconfident tone, but 1) every new feature has issues to overcome. 2) I can't help but seeing the diamond problem as having at least on a high-level some reasonable potential solutions. What results after a full look at the problem low-level and in-depth? I'll leave that to others.Vaal
V
10

C# language (at least to version 5) does not have support for Traits.

However, Scala has Traits and Scala runs on the JVM (and CLR). Therefore, it's not a matter of run-time, but simply that of the language.

Consider that Traits, at least at the Scala sense, can be thought of as "pretty magic to compile in proxy methods" (they do not affect the MRO, which is different from Mixins in Ruby). In C# the way to get this behavior would be to use interfaces and "lots of manual proxy methods" (e.g. composition).

This tedious process could be done with a hypothetical processor (perhaps automatic code generation for a partial class via templates?), but that's not C#.

Happy coding.

Venita answered 23/5, 2012 at 23:30 Comment(9)
I'm not exactly sure what this answers. Are you suggesting that I should hack together something to pre-process my C# code?Permissive
@Mark No. I was 1) Suggesting C#, the language, cannot support it (although perhaps with dynamic proxies? This level of magic is beyond me.) 2) That Traits do not affect the MRO and can be "simulated by hand"; that is, a Trait can be flattened into every Class it is mixed into, as with Composition.Venita
I don't know what an "MRO" is, can you explain that? And how would I "simulate them by hand"? That's what I'm asking about... I can't think of a nice way to encapsulate the extra functionality.Permissive
@Mark Ahh, Method Resolution Order. That is, Traits (again, in the Scala sense which are still based on Single Inheritance run-time) do not actually affect the class hierarchy. There is no "trait class" added to the [virtual] dispatch tables. The methods/properties in the Traits are copied (during completing) into the respective classes. Here is are some papers about traits as used in Scala. Ordersky presents that Traits can be used in a SI runtime, which is why they are "baked in" at compilation.Venita
@Mark This differs from a language like Ruby which will inject the "mixin" type (a form of traits) into the MRO (which is a form of alternating the class hierarchy, but with control and restrictions).Venita
I'm hesitant to upvote you because you haven't provided me with anything concrete yet, just a lot of talk about other languages. I'm trying to figure out how I can borrow some of these ideas from Scala....but that's all built-in to the language. How's it transferable?Permissive
@Mark Don't worry -- it doesn't provide any insight into how to "solve task XYZ" in C#. I am merely playing the advocate saying that Traits are not part of C#. Lucero's answer is a practical solution which may be useful here. Just be careful not to "overdo" extension methods. BTDT. The use of Interfaces is crucial to that approach, and ensuing sanity.Venita
Yes, but that much was covered in the first half of the first sentence in my question ;) Unless you're saying that these sorts of problems may be better-suited for a different language entirely (such as Scala). But that's quite a big shift from a language I otherwise love.Permissive
@Mark I get paid for using C# ;-) I have likes/dislike for both languages and both are suited for the same domain. That isn't to say I don't sorely miss certain aspects.... (from both, when in the other)Venita
L
10

I'd like to point to NRoles, an experiment with roles in C#, where roles are similar to traits.

NRoles uses a post-compiler to rewrite the IL and inject the methods into a class. This allows you to write code like that:

public class RSwitchable : Role
{
    private bool on = false;
    public void TurnOn() { on = true; }
    public void TurnOff() { on = false; }
    public bool IsOn { get { return on; } }
    public bool IsOff { get { return !on; } }
}

public class RTunable : Role
{
    public int Channel { get; private set; }
    public void Seek(int step) { Channel += step; }
}

public class Radio : Does<RSwitchable>, Does<RTunable> { }

where class Radio implements RSwitchable and RTunable. Behind the scenes, Does<R> is an interface with no members, so basically Radio compiles to an empty class. The post-compilation IL rewriting injects the methods of RSwitchable and RTunable into Radio, which can then be used as if it really derived from the two roles (from another assembly):

var radio = new Radio();
radio.TurnOn();
radio.Seek(42);

To use radio directly before rewriting happened (that is, in the same assembly as where the Radio type is declared), you have to resort to extensions methods As<R>():

radio.As<RSwitchable>().TurnOn();
radio.As<RTunable>().Seek(42);

since the compiler would not allow to call TurnOn or Seek directly on the Radio class.

Laird answered 2/9, 2014 at 9:9 Comment(0)
L
8

There is an academic project, developed by Stefan Reichart from the Software Composition Group at the University of Bern (Switzerland), which provides a true implementation of traits to the C# language.

Have a look at the paper (PDF) on CSharpT for the full description of what he has done, based on the mono compiler.

Here is a sample of what can be written:

trait TCircle
{
    public int Radius { get; set; }
    public int Surface { get { ... } }
}

trait TColor { ... }

class MyCircle
{
    uses { TCircle; TColor }
}
Laird answered 2/9, 2014 at 9:14 Comment(0)
P
4

Building on what Lucero suggested, I came up with this:

internal class Program
{
    private static void Main(string[] args)
    {
        var a = new ClientA("Adam", 68);
        var b = new ClientB("Bob", 1.75);
        var c = new ClientC("Cheryl", 54.4, 1.65);

        Console.WriteLine("{0} is {1:0.0} lbs.", a.Name, a.WeightPounds());
        Console.WriteLine("{0} is {1:0.0} inches tall.", b.Name, b.HeightInches());
        Console.WriteLine("{0} is {1:0.0} lbs and {2:0.0} inches.", c.Name, c.WeightPounds(), c.HeightInches());
        Console.ReadLine();
    }
}

public class Client
{
    public string Name { get; set; }

    public Client(string name)
    {
        Name = name;
    }
}

public interface IWeight
{
    double Weight { get; set; }
}

public interface IHeight
{
    double Height { get; set; }
}

public class ClientA : Client, IWeight
{
    public double Weight { get; set; }
    public ClientA(string name, double weight) : base(name)
    {
        Weight = weight;
    }
}

public class ClientB : Client, IHeight
{
    public double Height { get; set; }
    public ClientB(string name, double height) : base(name)
    {
        Height = height;
    }
}

public class ClientC : Client, IWeight, IHeight
{
    public double Weight { get; set; }
    public double Height { get; set; }
    public ClientC(string name, double weight, double height) : base(name)
    {
        Weight = weight;
        Height = height;
    }
}

public static class ClientExt
{
    public static double HeightInches(this IHeight client)
    {
        return client.Height * 39.3700787;
    }

    public static double WeightPounds(this IWeight client)
    {
        return client.Weight * 2.20462262;
    }
}

Output:

Adam is 149.9 lbs.
Bob is 68.9 inches tall.
Cheryl is 119.9 lbs and 65.0 inches.

It isn't quite as nice as I'd like, but it's not too bad either.

Permissive answered 30/3, 2015 at 20:5 Comment(1)
Still not as efficient as PHP doest it.Wieren
A
3

This is really an suggested extension to Lucero's answer where all the storage was in the base class.

How about using dependency properties for this?

This would have the effect of making the client classes light weight at run time when you have many properties that are not always set by every descendant. This is because the values are stored in a static member.

using System.Windows;

public class Client : DependencyObject
{
    public string Name { get; set; }

    public Client(string name)
    {
        Name = name;
    }

    //add to descendant to use
    //public double Weight
    //{
    //    get { return (double)GetValue(WeightProperty); }
    //    set { SetValue(WeightProperty, value); }
    //}

    public static readonly DependencyProperty WeightProperty =
        DependencyProperty.Register("Weight", typeof(double), typeof(Client), new PropertyMetadata());


    //add to descendant to use
    //public double Height
    //{
    //    get { return (double)GetValue(HeightProperty); }
    //    set { SetValue(HeightProperty, value); }
    //}

    public static readonly DependencyProperty HeightProperty =
        DependencyProperty.Register("Height", typeof(double), typeof(Client), new PropertyMetadata());
}

public interface IWeight
{
    double Weight { get; set; }
}

public interface IHeight
{
    double Height { get; set; }
}

public class ClientA : Client, IWeight
{
    public double Weight
    {
        get { return (double)GetValue(WeightProperty); }
        set { SetValue(WeightProperty, value); }
    }

    public ClientA(string name, double weight)
        : base(name)
    {
        Weight = weight;
    }
}

public class ClientB : Client, IHeight
{
    public double Height
    {
        get { return (double)GetValue(HeightProperty); }
        set { SetValue(HeightProperty, value); }
    }

    public ClientB(string name, double height)
        : base(name)
    {
        Height = height;
    }
}

public class ClientC : Client, IHeight, IWeight
{
    public double Height
    {
        get { return (double)GetValue(HeightProperty); }
        set { SetValue(HeightProperty, value); }
    }

    public double Weight
    {
        get { return (double)GetValue(WeightProperty); }
        set { SetValue(WeightProperty, value); }
    }

    public ClientC(string name, double weight, double height)
        : base(name)
    {
        Weight = weight;
        Height = height;
    }

}

public static class ClientExt
{
    public static double HeightInches(this IHeight client)
    {
        return client.Height * 39.3700787;
    }

    public static double WeightPounds(this IWeight client)
    {
        return client.Weight * 2.20462262;
    }
}
Agueda answered 24/5, 2012 at 10:57 Comment(1)
Why should we possibly use WPF classes here?Crab
C
0

This sounds like PHP's version of Aspect Oriented Programming. There are tools to help like PostSharp or MS Unity in some cases. If you want to roll-your-own, code-injection using C# Attributes is one approach, or as suggested extension methods for limited cases.

Really depends how complicated you want to get. If you are trying to build something complex I'd be looking at some of these tools to help.

Concourse answered 23/5, 2012 at 23:59 Comment(3)
Does AoP/PostSharp/Unity allow adding new members that become part of the static type system? (My limited AoP experience was just with annotation cut-points and similar..)Venita
PostSharp rewrites the IL code and should be able to do that, yes.Wellworn
Yes I believe so, via aspects for member/interface introduction (at the IL level as noted). My experience is limited also, but I've not had much practical opportunity to get too deep into this approach.Concourse

© 2022 - 2024 — McMap. All rights reserved.