如何使用反射类型创建委托闭包

时间:2012-07-12 18:48:39

标签: c# reflection

如何创建使用反射类型参数的闭包?定位.net 3.5

没有反思我会

void Main()
{
    int i = 0;
    Action<Foo> doSomething = (foo) => i += foo.GetNumber();
    var myFoo = new Foo();
    myFoo.UseFoo(doSomething);
    Console.WriteLine(i);
}

class Foo
{
    public int GetNumber() { return 4; }
    public void UseFoo(Action<Foo> doSomething)
    {
        doSomething(this);
    }
}

Foo是通过其他装配体反射获得的类型时,我该如何设置doSomething

void Main()
{
    Type fooType = GetType("Foo");
    int i = 0;
    object doSomething = // ???;

    var myFoo = Activator.CreateInstance(fooType);
    fooType.GetMethod("UseFoo").Invoke(myFoo, new object[] { doSomething });
    Console.WriteLine(i);
}

5 个答案:

答案 0 :(得分:2)

我使用DocXcz和Nikola Anusev的答案来达到这个

static void Main()
{
    var fooType = typeof(Foo); // get type via any method
    int i = 0;

    Action<object> doSomething = (foo) => i += (int)foo.GetType().GetMethod("GetNumber").Invoke(foo, null);
    var typedDoSomething = (typeof(Program)).GetMethod("DelegateHelper").MakeGenericMethod(fooType).Invoke(null, new object[] { doSomething });

    var myFoo = Activator.CreateInstance(fooType);
    fooType.GetMethod("UseFoo").Invoke(myFoo, new object[] { typedDoSomething });
    Console.WriteLine(i);
}

public static Action<T> DelegateHelper<T>(Action<object> generic)
{
    return x => generic(x);
}

基本上我需要使用通用辅助方法来将Action<object>转换为在运行时确定的Action<T>

答案 1 :(得分:1)

如果在编译时没有静态链接包含Foo类的程序集,那么你就不能在代码中使用Foo。

你必须在闭包中使用反射:

Action<object> doSomething = 
(foo) => i+= (int)foo.GetType().GetMethod("GetNumber").Invoke(foo, null);

这假设Foo的方法UseFoo是这样的,只适用于.NET 4.

public void UseFoo(Action<Foo> action)
{
    action(this);
}

当然,您应该将UseFoo的整个调用包含在try..catch块中,因为无法保证反射中的任何内容。

更新:这仅适用于.NET 4,其中Action可以作为UseFoo方法假定的Action传递。

答案 2 :(得分:1)

我确信在.NET 3.5上完全不能完全你想要的东西。

只有来自 close 的方法不会使用lambdas,而是使用普通的旧方法。基本上,您可以在类似于以下的类中定义您的操作:

public class FooActionMethods<TFoo>
{
    public static void DoSomething(TFoo foo)
    {
        int i = 0;
        int number = (int)typeof(TFoo).GetMethod("GetNumber").Invoke(foo, null);
        Console.WriteLine(i + number);
    }
}

然后,您可以调用,例如DoSomething方法:

Type fooType = // somehow, we get the type from other assembly
object fooInstance = Activator.CreateInstance(fooType);  

Type fooActionType = typeof(Action<>).MakeGenericType(fooType);
Type fooActionMethodsType = typeof(FooActionMethods<>).MakeGenericType(fooType);

Delegate action = Delegate.CreateDelegate(fooActionType, fooActionMethodsType, "DoSomething");

fooType.GetMethod("UseFoo").Invoke(fooInstance, new object[] { action });

由于整个FooActionMethod类是通用的,我们可以使用反射(MakeGenericType)并使用确切类型FooActionMethod创建已关闭的Foo。由于我们在编译时没有关于TFoo的信息,因此只能通过反射与其实例进行交互。要简化这些交互,请查看处理某些库的SO question,这些库可以简化反射工作。

除此之外,我认为你无能为力。感谢您提出的好问题 - 制作出色的拼图! :)

正如旁注,我也试图通过使用表达式树动态创建lambda表达式来解决这个问题。我无法找到一种方法来包含表达式树的闭包。否则,该方法也可以正常工作。

答案 3 :(得分:0)

C#中的闭包是由编译器生成的。如果您想为反射类型生成相同的代码,则必须手动生成代码。

答案 4 :(得分:0)

如果foo实现了合适的接口,您可以在此接口上使用Action

如果没有,您需要在委托中使用反射(更简单)或使用表达式API在运行时编译委托(更多工作,最大性能)。

当C#编译器为i创建闭包时,你需要继续这样做:

Action<int> incrementI = inc => i += inc;

现在,您可以在已编译的委托中使用增量i来增加i