About Me

Hello, I'm an eclectic software professional working with enterprise software at SAP. This blog only contains my personal views, thoughts and opinions. It is not endorsed by SAP nor does it constitute any official communication of SAP.

Thursday, February 4, 2010

C# Quasi-Mixins Pattern

Extension methods are an interesting feature of C#. They allow for behavior to be added to existing classes. For example, a ToXml() method can be added to class Object, effectively making that method available everywhere:

public static class XmlSerializableExtension {
  public static string ToXml(this Object self) {
    if (self == null) throw new ArgumentNullException();
    var serializer = new XmlSerializer(self.GetType());
    using (var writer = new StringWriter()) {
      serializer.Serialize(writer, self);
      return writer.GetStringBuilder().ToString();
    }
  }
  // for completeness...
  public static T FromXml<T>(string xml) {
    var serializer = new XmlSerializer(typeof(T));
    return (T)serializer.Deserialize(new StringReader(xml));
  }
}

So, any instance can be serialized to XML simply like this:

var customer = new Customer { 
  Name = "Guybrush Threepwood", 
  Preferred = true };
var xml = customer.ToXml();

Now, certainly all classes shouldn't have the ability to serialize themselves to XML. It doesn't make sense or it will fail for many cases, like Int32, Thread or SqlCommand. So extending Object is a poor choice in most cases. It's better to be explicit about which classes to extend. An interesting solution to this is to create a marker interface, mark the classes or interfaces you want to extend, and extend the marker interface with the desired behavior. This is what it looks like:

public interface MXmlSerializable { }
public static class XmlSerializable {
  public static string ToXml(this MXmlSerializable self) {
    // all the same ...
  }
  public static T FromXml<T>(string xml) where T : MXmlSerializable {
    // all the same ...
  }
}

Wait! What's that M prefix in that interface? Shouldn't all interfaces start with I? Well, this is a very special kind of interface, it's not really a contract for classes to implement, it's just a marker that's used to inject code into the classes that use it. Since this is a kind of mixin, I prefer to use the M prefix. At the very least, this will tell its clients that something else is in the works...

This kind of mixin, like static classes, is just another workaround; what you really should see when confronted with it is this:

// CAUTION: invalid C# ahead
public mixin MXmlSerializable {
  public string ToXml() {
    // all the same, change self for this ...
  }
  public static T FromXml<T>(string xml) where T : MXmlSerializable {
    // all the same ...
  }
}

A class must "use" the mixin to "inherit" its methods:

public class Customer : MXmlSerializable, MOtherMixin, MYetAnotherMixin {
  public string Name { get; set; }
  public bool Preferred { get; set; }
}

Interesting... the Customer class has a number of these "M types". All those mixins' behaviors are "injected" into it. What's good about this is that orthogonal behaviors can be developed once and reused where needed.

A mixin will certainly provide methods, but it can also require methods or interfaces to be present on the target classes. The pattern to write a mixin, then, looks like this:

namespace Mixins {

  public interface MMyMixin /* required interfaces go here */ {
    // required methods go here
  }
  public static class MyMixin {
    // provided methods go here, as extension methods to the mixin interface
  }

}

namespace MixinCompositions {

  using Mixins;

  public class MyClass : MMyMixin { 

    // needs to implement the mixin's required interfaces

    // needs to fulfill the mixin's required methods

    // will get all the mixin's provided methods, callers need to import the mixin namespace to get them

  }

}

Some limitations that I can think of are:

  • Mixins can't inject state in a class, only behavior. Although, they can (awkwardly) require the classes to implement a property, and use it as their own. Update: Injecting state into a class can be done in the .NET Framework 4.0.
  • Mixin behaviors must be orthogonal to classes and other mixins. Mixins can't be stacked to refine behavior.
  • Mixins can't implement interfaces on behalf of classes
  • Clients of the target class must import the mixin namespace to access its methods.
  • That's why I call them quasi-mixins. But, it's still a useful technique to know about, until we get traits (pdf) into the language (I wish!).

    2 comments:

    1. Would you also implement the extension method in the "MInterface" namespace?

      ReplyDelete
    2. @Adam: yes! the extension methods and the interface are designed to represent ONE concept, that of a mixin. So they should be declared together. Although, that doesn't stop you from adding other methods later on in other assemblies if that makes sense as an extension to your design.

      ReplyDelete