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:
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!).
Would you also implement the extension method in the "MInterface" namespace?
ReplyDelete@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