I\'ve heard that it\'s possible with extension methods, but I can\'t quite figure it out myself. I\'d like to see a specific example if possible.
Thanks!
Here is a mixin implementation I've just come up with. I'll probably use it with a library of mine.
It's probably been done before, somewhere.
It's all statically typed, with no dictionaries or something. It requires a little bit of extra code per type, you don't need any storage per instance. On the other hand, it also gives you the flexibility of changing the mixin implementation on the fly, if you so desire. No post-build, pre-build, mid-build tools.
It has some limitations, but it does allow things like overriding and so on.
We begin by defining a marker interface. Perhaps something will be added to it later:
public interface Mixin {}
This interface is implemented by mixins. Mixins are regular classes. Types do not inherit or implement mixins directly. They instead just expose an instance of the mixin using the interface:
public interface HasMixins {}
public interface Has<TMixin> : HasMixins
where TMixin : Mixin {
TMixin Mixin { get; }
}
Implementing this interface means supporting the mixin. It's important that it's implemented explicitly, since we're going to have several of these per type.
Now for a little trick using extension methods. We define:
public static class MixinUtils {
public static TMixin Mixout<TMixin>(this Has<TMixin> what)
where TMixin : Mixin {
return what.Mixin;
}
}
Mixout
exposes the mixin of the appropriate type. Now, to test this out, let's define:
public abstract class Mixin1 : Mixin {}
public abstract class Mixin2 : Mixin {}
public abstract class Mixin3 : Mixin {}
public class Test : Has<Mixin1>, Has<Mixin2> {
private class Mixin1Impl : Mixin1 {
public static readonly Mixin1Impl Instance = new Mixin1Impl();
}
private class Mixin2Impl : Mixin2 {
public static readonly Mixin2Impl Instance = new Mixin2Impl();
}
Mixin1 Has<Mixin1>.Mixin => Mixin1Impl.Instance;
Mixin2 Has<Mixin2>.Mixin => Mixin2Impl.Instance;
}
static class TestThis {
public static void run() {
var t = new Test();
var a = t.Mixout<Mixin1>();
var b = t.Mixout<Mixin2>();
}
}
Rather amusingly (though in retrospect, it does make sense), IntelliSense does not detect that the extension method Mixout
applies to Test
, but the compiler does accept it, as long as Test
actually has the mixin. If you try,
t.Mixout<Mixin3>();
It gives you a compilation error.
You can go a bit fancy, and define the following method too:
[Obsolete("The object does not have this mixin.", true)]
public static TSome Mixout<TSome>(this HasMixins something) where TSome : Mixin {
return default(TSome);
}
What this does is, a) display a method called Mixout
in IntelliSense, reminding you of its existence, and b) provide a somewhat more descriptive error message (generated by the Obsolete
attribute).
I usually employ this pattern:
public interface IColor
{
byte Red {get;}
byte Green {get;}
byte Blue {get;}
}
public static class ColorExtensions
{
public static byte Luminance(this IColor c)
{
return (byte)(c.Red*0.3 + c.Green*0.59+ c.Blue*0.11);
}
}
I have the two definitions in the same source file/namespace. That way the extensions are always available when the interface is used (with 'using').
This gives you a limited mixin as described in CMS' first link.
Limitations:
It's still sufficient for many situations.
It would be nice if they (MS) could add some compiler magic to auto-generate the extension class:
public interface IColor
{
byte Red {get;}
byte Green {get;}
byte Blue {get;}
// compiler generates anonymous extension class
public static byte Luminance(this IColor c)
{
return (byte)(c.Red*0.3 + c.Green*0.59+ c.Blue*0.11);
}
}
Although Jon's proposed compiler trick would be even nicer.
It really depends on what you mean by "mixin" - everyone seems to have a slightly different idea. The kind of mixin I'd like to see (but which isn't available in C#) is making implementation-through-composition simple:
public class Mixin : ISomeInterface
{
private SomeImplementation impl implements ISomeInterface;
public void OneMethod()
{
// Specialise just this method
}
}
The compiler would implement ISomeInterface just by proxying every member to "impl" unless there was another implementation in the class directly.
None of this is possible at the moment though :)