How To Create Mixin Using C# 4.0

What is a mixin, and why is it useful?

A mixin is a class that adds functionality to other classes but which cannot itself be instantiated.

Mixins are useful in object-oriented languages that only support single inheritance as a sort of 'halfway house' between single and multiple inheritance.

How can we implement mixins in C#?

It can do so using a combination of interfaces and extension methods.

In order to implement a mixin, a class must first implement an interface that has been created for that mixin.  This interface may include members that the class has to implement itself or can be completely empty.

As a class can implement multiple interfaces, this does not affect any interfaces that it already implements.

A static class containing extension methods then provides the mixin's actual methods, which can be applied to any object that implements the associated interface. 

Methods and Feilds in Mixin 

No, they can have fields as well.

The trick here is to define a nested class within the mixin that contains the fields and then associate an instance of this nested class with each object that implements the mixin.

Won't that prevent the object from being garbage collected when it's no longer used?

Not if we use 'weak references'. These are references to an object that the garbage collector ignores when deciding whether an object can be collected.

How to clean weak references?

Prior to C# 4.0, you had to clean up the weak references manually, which was quite messy.

All this has changed with the introduction of the ConditionalWeakTable class in .NET Framework 4.0. When the object is garbage collected, the weak reference is automatically removed from the table together with the reference to the associated fields. The instance of the nested class can then be garbage collected normally.

Better still, the ConditionalWeakTable is thread-safe, so no additional locking is required if you're using mixins in a multi-threaded application. 

Example. Here's an example where we add the ability to calculate a current age to any class that needs it.

using System;
using System.Runtime.CompilerServices;
public interface MAgeProvider
{
    // Nothing needed in here, it's just a 'marker' interface
}
public static class AgeProvider
{
    private static ConditionalWeakTable<MAgeProvider, Fields> table;
    static AgeProvider()
    {
        table = new ConditionalWeakTable<MAgeProvider, Fields>();
    }
    private sealed class Fields
    {
        internal DateTime BirthDate = DateTime.UtcNow;
    }
    public static int GetAge(this MAgeProvider map)
    {
        DateTime dtNow = DateTime.UtcNow;
        DateTime dtBorn = table.GetOrCreateValue(map).BirthDate;
        int age = ((dtNow.Year - dtBorn.Year) * 372
                  + (dtNow.Month - dtBorn.Month) * 31
                  + (dtNow.Day - dtBorn.Day)) / 372;
        return age;
    }
    public static void SetBirthDate(this MAgeProvider map, DateTime birthDate)
    {
        table.GetOrCreateValue(map).BirthDate = birthDate;
    }
}
public abstract class Animal
{
    // Contents unimportant
}
public class Human : Animal, MAgeProvider
{
    public string Name;
    public Human(string name)
    {
        Name = name;
    }

    // Nothing needed in here to implement MAgeProvider
}
class Test
{
    static void Main()
    {
        Human h = new Human("Jim");
        h.SetBirthDate(new DateTime(1980, 1, 1));
        Console.WriteLine("Name {0}, Age = {1}", h.Name, h.GetAge());
        Human h2 = new Human("Fred");
        h2.SetBirthDate(new DateTime(1960, 6, 1));
        Console.WriteLine("Name {0}, Age = {1}", h2.Name, h2.GetAge());
        Console.ReadKey();
    }
}

Is Microsoft likely to add language support for mixins to C# in the future?

Judging by their response to a recent suggestion on Microsoft Connect, not anytime soon, though, they didn't rule them out completely.

The above example is a workaround I posted to that suggestion. The code would be nicer if we had 'extension properties'  as well as extension methods, which I do think is a possibility in the next version as it's a feature that is often requested.

In the meantime, we can do quite a bit with what we have, so if I've fired your imagination,  have fun.


Recommended Free Ebook
Similar Articles