简体   繁体   中英

How to assign delegates as methods of some implemented interface

Say I have an interface that specifies two void methods with no parameters. How can I "plug" two System.Action(T) methods in some class that implements the interface? In my example below, this would be in the void PushFoo(Action bar1, Action bar2) method:

public interface IFoo
{
    void Bar1();
    void Bar2();
}

public class Bla
{
    Stack<IFoo> _fooStack = new Stack<IFoo>();

    public void PushFoo(IFoo foo)
    {
        _fooStack.Push(foo);
    }

    public void PushFoo(Action bar1, Action bar2)
    {
        IFoo foo = null;

        // assign bar1 and bar2 to foo
        //foo = ... ;

        _fooStack.Push(foo);
    }
}
public Class ActionableFoo : IFoo
{
    Action _bar1, _bar2;

    public ActionableFoo(Action b1, Action b2)
    {
        _bar1 = b1;
        _bar2 = b2;
    }

    public void Bar1() { if(_bar1 != null) _bar1(); }
    public void Bar2() { if(_bar2 != null) _bar2(); }
}

Then , in your example:

public void PushFoo(Action bar1, Action bar2)
{
    IFoo foo = new ActionableFoo(bar1, bar2);
    _fooStack.Push(foo);
}

This piqued my interest, so here's a method that uses reflection to build a wrapper around a set of delegates (or rather Func / Action s) that also implements a given interface.

Type GenerateInterfaceImplementator<TInterface>()
{
    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);

    var modBuilder = assBuilder.DefineDynamicModule(aName.Name);

    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();

    // Return an instance using the constructor we created.
    return wrapperType;
}

The function Type GenerateFuncOrAction(MethodInfo method) isn't shown here because it's horrible - you have to switch on the number of parameters the method has and also on whether it returns void or not.

The generator is called as follows:

public interface ITest
{
    void M1();
    string M2(int m2, string n2);
    string P { get; set; }
}

...

var iType = GenerateInterfaceImplementator<ITest>();
var instance = (ITest)Activator.CreateInstance(iType,
    new Action(() => { Console.WriteLine("M1 called");  return; }),
    new Func<int, string, string>((ij, xjx) => xjx + ij.ToString()),
    new Func<String>(() => "P getter called"),
    new Action<string>(s => { Console.WriteLine(s); }));

instance.M1();
Console.WriteLine(instance.M2(6, "you are number "));
instance.P = "P setter called";
Console.WriteLine(instance.P);

This is the first time I've really used Reflection.Emit so all comments welcome.

An issue is that you have to know the order in which the interface methods will be supplied by GetMethods .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM