Is it possible to implement mixins in C#?

前端 未结 9 625
感动是毒
感动是毒 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: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());
    
                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);
    

提交回复
热议问题