Is it possible to implement mixins in C#?

前端 未结 9 605
感动是毒
感动是毒 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:33

    LinFu and Castle's DynamicProxy implement mixins. COP (Composite Oriented Programming) could be considered as making a whole paradigm out of mixins. This post from Anders Noras has useful informations and links.

    EDIT: This is all possible with C# 2.0, without extension methods

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

    There is an open source framework that enables you to implement mixins via C#. Have a look on http://remix.codeplex.com/.

    It is very easy to implement mixins with this framework. Just have a look on the samples and the "Additional Information" links given on the page.

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

    If you have a base class that can store data you can enforce compiler safety and use marker interfaces. That's more or less what "Mixins in C# 3.0" from the accepted answer proposes.

    public static class ModelBaseMixins
    {
        public interface IHasStuff{ }
    
        public static void AddStuff<TObjectBase>(this TObjectBase objectBase, Stuff stuff) where TObjectBase: ObjectBase, IHasStuff
        {
            var stuffStore = objectBase.Get<IList<Stuff>>("stuffStore");
            stuffStore.Add(stuff);
        }
    }
    

    The ObjectBase:

    public abstract class ObjectBase
    {
        protected ModelBase()
        {
            _objects = new Dictionary<string, object>();
        }
    
        private readonly Dictionary<string, object> _objects;
    
        internal void Add<T>(T thing, string name)
        {
            _objects[name] = thing;
        }
    
        internal T Get<T>(string name)
        {
            T thing = null;
            _objects.TryGetValue(name, out thing);
    
            return (T) thing;
        }
    

    So if you have a Class you can inherit from 'ObjectBase' and decorate with IHasStuff you can add sutff now

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

    You could also augment the extension method approach to incorporate state, in a pattern not unlike WPF's attached properties.

    Here is an example with minimum boilerplate. Note that no modification are required on the target classes, including adding interfaces, unless you need to deal with the target class polymorphically - in which case you end up with something very close to actual Multiple Inheritance.

    // Mixin class: mixin infrastructure and mixin component definitions
    public static class Mixin
    { 
        // =====================================
        // ComponentFoo: Sample mixin component
        // =====================================
    
        //  ComponentFooState: ComponentFoo contents
        class ComponentFooState
        {
            public ComponentFooState() {
                // initialize as you like
                this.Name = "default name";
            }
    
            public string Name { get; set; }
        }
    
        // ComponentFoo methods
    
        // if you like, replace T with some interface 
        // implemented by your target class(es)
    
        public static void 
        SetName<T>(this T obj, string name) {
            var state = GetState(component_foo_states, obj);
    
            // do something with "obj" and "state"
            // for example: 
    
            state.Name = name + " the " + obj.GetType();
    
    
        }
        public static string
        GetName<T>(this T obj) {
            var state = GetState(component_foo_states, obj);
    
            return state.Name; 
        }
    
        // =====================================
        // boilerplate
        // =====================================
    
        //  instances of ComponentFoo's state container class,
        //  indexed by target object
        static readonly Dictionary<object, ComponentFooState>
        component_foo_states = new Dictionary<object, ComponentFooState>();
    
        // get a target class object's associated state
        // note lazy instantiation
        static TState
        GetState<TState>(Dictionary<object, TState> dict, object obj) 
        where TState : new() {
            TState ret;
            if(!dict.TryGet(obj, out ret))
                dict[obj] = ret = new TState();
    
            return ret;
        }
    
    }
    

    Usage:

    var some_obj = new SomeClass();
    some_obj.SetName("Johny");
    Console.WriteLine(some_obj.GetName()); // "Johny the SomeClass"
    

    Note that it also works with null instances, since extension methods naturally do.

    You might also consider using a WeakDictionary implementation to avoid memory leaks caused by the collection's holding on to target class references as keys.

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

    I've found a workaround here, which while not entirely elegant, allows you to achieve fully observable mixin behavior. Additionally, IntelliSense still works!

    using System;
    using System.Runtime.CompilerServices; //needed for ConditionalWeakTable
    public interface MAgeProvider // use 'M' prefix to indicate mixin interface
    {
        // nothing needed in here, it's just a 'marker' interface
    }
    public static class AgeProvider // implements the mixin using extensions methods
    {
        static ConditionalWeakTable<MAgeProvider, Fields> table;
        static AgeProvider()
        {
            table = new ConditionalWeakTable<MAgeProvider, Fields>();
        }
        private sealed class Fields // mixin's fields held in private nested class
        {
            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
    }
    static 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();
        }
    }
    
    0 讨论(0)
  • 2020-11-29 18:45

    I needed something similar so I came up with the following using Reflection.Emit. In the following code a new type is dynamically generated which has a private member of type 'mixin'. All the calls to methods of 'mixin' interface are forwarded to this private member. A single parameter constructor is defined that takes an instance which implements the 'mixin' interface. Basically, it is equal to writing the following code for a given concrete type T and interface I:

    class Z : T, I
    {
        I impl;
    
        public Z(I impl)
        {
            this.impl = impl;
        }
    
        // Implement all methods of I by proxying them through this.impl
        // as follows: 
        //
        // I.Foo()
        // {
        //    return this.impl.Foo();
        // }
    }
    

    This is the class:

    public class MixinGenerator
    {
        public static Type CreateMixin(Type @base, Type mixin)
        {
            // Mixin must be an interface
            if (!mixin.IsInterface)
                throw new ArgumentException("mixin not an interface");
    
            TypeBuilder typeBuilder = DefineType(@base, new Type[]{mixin});
    
            FieldBuilder fb = typeBuilder.DefineField("impl", mixin, FieldAttributes.Private);
    
            DefineConstructor(typeBuilder, fb);
    
            DefineInterfaceMethods(typeBuilder, mixin, fb);
    
            Type t = typeBuilder.CreateType();
    
            return t;
        }
    
        static AssemblyBuilder assemblyBuilder;
        private static TypeBuilder DefineType(Type @base, Type [] interfaces)
        {
            assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.RunAndSave);
    
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(Guid.NewGuid().ToString());
    
            TypeBuilder b = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
                @base.Attributes,
                @base,
                interfaces);
    
            return b;
        }
        private static void DefineConstructor(TypeBuilder typeBuilder, FieldBuilder fieldBuilder)
        {
            ConstructorBuilder ctor = typeBuilder.DefineConstructor(
                MethodAttributes.Public, CallingConventions.Standard, new Type[] { fieldBuilder.FieldType });
    
            ILGenerator il = ctor.GetILGenerator();
    
            // Call base constructor
            ConstructorInfo baseCtorInfo =  typeBuilder.BaseType.GetConstructor(new Type[]{});
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Call, typeBuilder.BaseType.GetConstructor(new Type[0]));
    
            // Store type parameter in private field
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Stfld, fieldBuilder);
            il.Emit(OpCodes.Ret);
        }
    
        private static void DefineInterfaceMethods(TypeBuilder typeBuilder, Type mixin, FieldInfo instanceField)
        {
            MethodInfo[] methods = mixin.GetMethods();
    
            foreach (MethodInfo method in methods)
            {
                MethodInfo fwdMethod = instanceField.FieldType.GetMethod(method.Name,
                    method.GetParameters().Select((pi) => { return pi.ParameterType; }).ToArray<Type>());
    
                MethodBuilder methodBuilder = typeBuilder.DefineMethod(
                                                fwdMethod.Name,
                                                // Could not call absract method, so remove flag
                                                fwdMethod.Attributes & (~MethodAttributes.Abstract),
                                                fwdMethod.ReturnType,
                                                fwdMethod.GetParameters().Select(p => p.ParameterType).ToArray());
    
                methodBuilder.SetReturnType(method.ReturnType);
                typeBuilder.DefineMethodOverride(methodBuilder, method);
    
                // Emit method body
                ILGenerator il = methodBuilder.GetILGenerator();
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld, instanceField);
    
                // Call with same parameters
                for (int i = 0; i < method.GetParameters().Length; i++)
                {
                    il.Emit(OpCodes.Ldarg, i + 1);
                }
                il.Emit(OpCodes.Call, fwdMethod);
                il.Emit(OpCodes.Ret);
            }
        }
    }
    

    This is the usage:

    public interface ISum
    {
        int Sum(int x, int y);
    }
    
    public class SumImpl : ISum
    {
        public int Sum(int x, int y)
        {
            return x + y;
        }
    }
    
    public class Multiply
    {        
        public int Mul(int x, int y)
        {
            return x * y;
        }
    }
    
    // Generate a type that does multiply and sum
    Type newType = MixinGenerator.CreateMixin(typeof(Multiply), typeof(ISum));
    
    object instance = Activator.CreateInstance(newType, new object[] { new SumImpl() });
    
    int res = ((Multiply)instance).Mul(2, 4);
    Console.WriteLine(res);
    res = ((ISum)instance).Sum(1, 4);
    Console.WriteLine(res);
    
    0 讨论(0)
提交回复
热议问题