C# feature request: implement interfaces on anonymous types

前端 未结 10 1813
走了就别回头了
走了就别回头了 2021-02-19 06:57

I am wondering what it would take to make something like this work:

using System;

class Program
{
    static void Main()
    {
        var f = new IFoo { 
              


        
10条回答
  •  眼角桃花
    2021-02-19 07:13

    I'm going to dump this here. I wrote it a while ago but IIRC it works OK.

    First a helper function to take a MethodInfo and return a Type of a matching Func or Action. You need a branch for each number of parameters, unfortunately, and I apparently stopped at three.

    static Type GenerateFuncOrAction(MethodInfo method)
    {
        var typeParams = method.GetParameters().Select(p => p.ParameterType).ToArray();
        if (method.ReturnType == typeof(void))
        {
            if (typeParams.Length == 0)
            {
                return typeof(Action);
            }
            else if (typeParams.Length == 1)
            {
                return typeof(Action<>).MakeGenericType(typeParams);
            }
            else if (typeParams.Length == 2)
            {
                return typeof(Action<,>).MakeGenericType(typeParams);
            }
            else if (typeParams.Length == 3)
            {
                return typeof(Action<,,>).MakeGenericType(typeParams);
            }
            throw new ArgumentException("Only written up to 3 type parameters");
        }
        else
        {
            if (typeParams.Length == 0)
            {
                return typeof(Func<>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray());
            }
            else if (typeParams.Length == 1)
            {
                return typeof(Func<,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray());
            }
            else if (typeParams.Length == 2)
            {
                return typeof(Func<,,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray());
            }
            else if (typeParams.Length == 3)
            {
                return typeof(Func<,,,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray());
            }
            throw new ArgumentException("Only written up to 3 type parameters");
        }
    }
    

    And now the method that takes an interface as a generic parameter and returns a Type that implements the interface and has a constructor (needs to be called via Activator.CreateInstance) taking a Func or Action for each method/ getter/setter. You need to know the right order to put them in the constructor, though. Alternatively (commented-out code) it can generate a DLL which you can then reference and use the type directly.

    static Type GenerateInterfaceImplementation()
    {
        var interfaceType = typeof(TInterface);
        var funcTypes = interfaceType.GetMethods().Select(GenerateFuncOrAction).ToArray();
    
        AssemblyName aName =
            new AssemblyName("Dynamic" + interfaceType.Name + "WrapperAssembly");
        var assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                aName,
                AssemblyBuilderAccess.Run/*AndSave*/); // to get a DLL
    
        var modBuilder = assBuilder.DefineDynamicModule(aName.Name/*, aName.Name + ".dll"*/); // to get a DLL
    
        TypeBuilder typeBuilder = modBuilder.DefineType(
            "Dynamic" + interfaceType.Name + "Wrapper",
                TypeAttributes.Public);
    
        // Define a constructor taking the same parameters as this method.
        var ctrBuilder = typeBuilder.DefineConstructor(
            MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
            CallingConventions.Standard,
            funcTypes);
    
    
        // Start building the constructor.
        var ctrGenerator = ctrBuilder.GetILGenerator();
        ctrGenerator.Emit(OpCodes.Ldarg_0);
        ctrGenerator.Emit(
            OpCodes.Call,
            typeof(object).GetConstructor(Type.EmptyTypes));
    
        // For each interface method, we add a field to hold the supplied
        // delegate, code to store it in the constructor, and an
        // implementation that calls the delegate.
        byte methodIndex = 0;
        foreach (var interfaceMethod in interfaceType.GetMethods())
        {
            ctrBuilder.DefineParameter(
                methodIndex + 1,
                ParameterAttributes.None,
                "del_" + interfaceMethod.Name);
    
            var delegateField = typeBuilder.DefineField(
                "del_" + interfaceMethod.Name,
                funcTypes[methodIndex],
                FieldAttributes.Private);
    
            ctrGenerator.Emit(OpCodes.Ldarg_0);
            ctrGenerator.Emit(OpCodes.Ldarg_S, methodIndex + 1);
            ctrGenerator.Emit(OpCodes.Stfld, delegateField);
    
            var metBuilder = typeBuilder.DefineMethod(
                interfaceMethod.Name,
                MethodAttributes.Public | MethodAttributes.Virtual |
                    MethodAttributes.Final | MethodAttributes.HideBySig |
                    MethodAttributes.NewSlot,
                interfaceMethod.ReturnType,
                interfaceMethod.GetParameters()
                    .Select(p => p.ParameterType).ToArray());
    
            var metGenerator = metBuilder.GetILGenerator();
            metGenerator.Emit(OpCodes.Ldarg_0);
            metGenerator.Emit(OpCodes.Ldfld, delegateField);
    
            // Generate code to load each parameter.
            byte paramIndex = 1;
            foreach (var param in interfaceMethod.GetParameters())
            {
                metGenerator.Emit(OpCodes.Ldarg_S, paramIndex);
                paramIndex++;
            }
            metGenerator.EmitCall(
                OpCodes.Callvirt,
                funcTypes[methodIndex].GetMethod("Invoke"),
                null);
    
            metGenerator.Emit(OpCodes.Ret);
            methodIndex++;
        }
    
        ctrGenerator.Emit(OpCodes.Ret);
    
        // Add interface implementation and finish creating.
        typeBuilder.AddInterfaceImplementation(interfaceType);
        var wrapperType = typeBuilder.CreateType();
        //assBuilder.Save(aName.Name + ".dll"); // to get a DLL
    
        return wrapperType;
    }
    

    You can use this as e.g.

    public interface ITest
    {
        void M1();
        string M2(int m2, string n2);
        string prop { get; set; }
    
        event test BoopBooped;
    }
    
    Type it = GenerateInterfaceImplementation();
    ITest instance = (ITest)Activator.CreateInstance(it,
        new Action(() => {Console.WriteLine("M1 called"); return;}),
        new Func((i, s) => "M2 gives " + s + i.ToString()),
        new Func(() => "prop value"),
        new Action(s => {Console.WriteLine("prop set to " + s);}),
        new Action(eh => {Console.WriteLine(eh("handler added"));}),
        new Action(eh => {Console.WriteLine(eh("handler removed"));}));
    
    // or with the generated DLL
    ITest instance = new DynamicITestWrapper(
        // parameters as before but you can see the signature
        );
    

提交回复
热议问题