4

如何创建使用反射类型参数的闭包?针对 .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);
}
4

5 回答 5

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>运行时确定的

于 2012-07-12T20:30:30.750 回答
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 传递。

于 2012-07-12T19:02:19.143 回答
1

我确信在 .NET 3.5 上完全按照您的意愿去做是不可能的。

唯一接近的方法不使用 lambda,而是使用普通的旧方法。本质上,您将在类似于以下的类中定义您的操作:

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创建闭包。由于我们在编译时没有任何信息,因此只能通过反射与其实例进行交互。为了简化这些交互,请查看处理一些库的SO 问题,这些库将简化使用反射的工作。FooActionMethodFooTFoo

除此之外,我认为没有什么比你能做的更好了。谢谢你提出的好问题——这是一个很好的谜题!:)

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

于 2012-07-12T20:17:49.863 回答
0

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

于 2012-07-12T19:07:59.317 回答
0

如果 foo 实现了一个合适的接口,你可以Action在这个接口上工作。

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

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

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

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

于 2012-07-12T19:09:19.190 回答