Is it possible to implement mixins in C#?

前端 未结 9 606
感动是毒
感动是毒 2020-11-29 17:50

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!

相关标签:
9条回答
  • 2020-11-29 18:48

    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).

    0 讨论(0)
  • 2020-11-29 18:50

    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:

    • no data fields
    • no properties (you'll have to call myColor.Luminance() with parentheses, extension properties anyone?)

    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.

    0 讨论(0)
  • 2020-11-29 18:54

    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 :)

    0 讨论(0)
提交回复
热议问题