【问题标题】:Assigning a lambda as a parameter to a generic method called through reflection将 lambda 作为参数分配给通过反射调用的泛型方法
【发布时间】:2015-07-17 15:42:35
【问题描述】:

考虑如下通用方法:

class SomeClass
{
    public static void SomeMethod<T>(Func<T>);
}

我想使用反射来调用这个方法。这是我能做到的:

_SomeMethod = typeof(SomeClass).GetMethod("SomeMethod",
    BindingFlags.Public | BindingFlags.Static);
Type type = typeof(SomeType); //Actually SomeType is extracted using reflection and it's not fixed
MethodInfo toBeCalled = _SomeMethod.MakeGenericMethod(type);
object obj = Activator.CreateInstance(type);
toBeCalled.Invoke(null, () => obj);

但它给出了编译错误:

Error CS1660: Cannot convert `lambda expression' to non-delegate type `object' (CS1660)

这是绝对可以接受的,但有什么解决方法?

请记住,使用 lambda 创建的 closure 是我需要的,所以不要删除它。

[更新]

澄清情况以使其有意义;使用 lambda 创建的仿函数是 SomeMethod 用于创建类 SomeType 的实例的实例化器。但我不想在仿函数中实际创建对象,而是从之前的实例化对象集合中返回它。最简单的方法是将 obj 放在 lambda 返回的闭包中,你不觉得吗?

【问题讨论】:

  • 那么obj 的类型是什么?如果您不知道所涉及的T,则很难了解如何从 lambda 表达式创建Func&lt;T&gt;。您总是只返回一个常量值,还是有时会有其他 lambda 表达式?更多信息会很有帮助。
  • @JonSkeet 我已经更新了问题
  • 但是您想创建一个始终只返回objFunc?没有比这更复杂的了吗?你说你想保持关闭 - 这是否意味着你要在之后更改 obj 的值?同样,更清晰会非常有帮助。
  • 我们可以更改SomeMethod()吗?
  • @JamesCurran 不,实际上SomeMethod 属于一个库,不是我写的!

标签: c# generics reflection lambda closures


【解决方案1】:

我认为您希望通过采用Func&lt;T&gt; 并且“func”始终是身份的已知对象或通用方法。再添加一个包装器可能会有所帮助:

public void SomeMethodInvokerRuntime(Type typeofSomeClass, object obj)
{
    var _SomeMethod = this.GetType().GetMethod("SomeMethodInvoker", 
        BindingFlags.Public | BindingFlags.Static);
    MethodInfo toBeCalled = _SomeMethod.MakeGenericMethod(obj.GetType());
    toBeCalled.Invoke(null, new[]{typeofSomeClass, obj});
}

public static void SomeMethodInvoker<T>(Type typeofSomeClass, T obj)
{
    var _SomeMethod = typeofSomeClass.GetMethod("SomeMethod",
          BindingFlags.Public | BindingFlags.Static);
    MethodInfo toBeCalled = _SomeMethod.MakeGenericMethod(typeof(T));
    Func<T> that = () => obj; // Main part - strongly typed delegate
    toBeCalled.Invoke(null, new[]{that});
}

示例用法:

static class Test
{
    public static void SomeMethod<T>(Func<T> f)
    {
        Console.WriteLine((T)(f()));
    }
}

SomeMethodInvoker(typeof(Test), 3);
object obj = "test";

SomeMethodInvokerRuntime(typeof(Test), obj);

或者,您可以构建表达式并编译成函数,如Jon Skeet's answer 所示。

【讨论】:

  • PS 如果这是 OP 正在寻找的,我可能会发布用于表达方法的示例。
  • 问题是你有Type typeofSomeClass,但没有T objT 需要在编译时设置,但直到运行时我们才知道。
  • @JamesCurran 但您已经知道如何调用未知类型的泛型函数(SomeMethodInvoker 上的MakeGenericMethod 并传递methodInfoOfSomeMethodInvoker.Invoke(null, obj)),所以我认为唯一的问题是如何构建Func&lt;some-run-time-type&gt;。 .. 这会提供完整的答案吗?
  • 忽略这个答案 - Jon Skeet 等了足够长的时间 :) 并发布了很棒的答案。
  • @AlexeiLevenkov 我更喜欢你的。它更简单,更容易理解。谢谢。
【解决方案2】:

这听起来不像你真的需要一个 lambda 表达式。您可以使用表达式树构建一个函数以非常轻松地返回一个常量值:

var expression = Expression.Constant(obj, type);
var delegateType = typeof(Func<>).MakeGenericType(type);
var func = Expression.Lambda(delegateType, expression).Compile();
toBeCalled.Invoke(null, func);

【讨论】:

    【解决方案3】:

    简化为最简单的形式,我们有这样的:

    void Main()
    {
        MethodInfoInvoke(()=> {} );
    }
    
    void MethodInfoInvoke(object func)
    {
    }
    

    失败并出现同样的错误。

    现在,如果我们在委托中传递 lambda,看起来会更快乐:

    void Main()
    {
        Object obj = new Object();
        Func<object> action = ()=> obj; ;
        MethodInfoInvoke(action);      // accepted!
    }
    

    【讨论】:

    • 你的减法不合适。你不能减少拥有SomeMethod&lt;T&gt;(Func&lt;T&gt;),引入这样的方法并尝试使用反射将lambda作为参数调用它,然后你会看到问题。那么你当前的解决方案就行不通了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多