你能从MethodInfo对象得到一个Func <T>(或类似的)吗?

Dan*_*Tao 57 .net reflection performance func methodinfo

我意识到,一般来说,使用反射会产生性能影响.(事实上​​,我自己并不是反思的粉丝;这是一个纯粹的学术问题.)

假设存在一些看起来像这样的类:

public class MyClass {
    public string GetName() {
        return "My Name";
    }
}
Run Code Online (Sandbox Code Playgroud)

跟我来这儿.我知道如果我有一个MyClass被叫的实例x,我可以打电话x.GetName().此外,我可以设置一个Func<string>变量x.GetName.

现在这是我的问题.假设我知道上面的类叫做MyClass; 我有一些对象,x但我不知道它是什么.我可以GetName通过这样做来检查该对象是否有方法:

MethodInfo getName = x.GetType().GetMethod("GetName");
Run Code Online (Sandbox Code Playgroud)

假设getName不为空.然后,我不能进一步检查getName.ReturnType == typeof(string)getName.GetParameters().Length == 0,在这一点上,我岂不是很肯定的是,在我所代表的方法,getName对象可以肯定被转换为一个Func<string>,不知何故?

我意识到有一个MethodInfo.Invoke,我也意识到我总能创造一个Func<string>像:

Func<string> getNameFunc = () => getName.Invoke(x, null);
Run Code Online (Sandbox Code Playgroud)

我想我要问的是,是否有任何方法可以一个MethodInfo对象转到它所代表的实际方法,从而在该过程中产生反射的性能成本,但之后能够直接调用该方法(例如,通过a Func<string>或类似的东西)没有性能损失.

我想象的可能是这样的:

// obviously this would throw an exception if GetActualInstanceMethod returned
// something that couldn't be cast to a Func<string>
Func<string> getNameFunc = (Func<string>)getName.GetActualInstanceMethod(x);
Run Code Online (Sandbox Code Playgroud)

(我意识到这不存在;我想知道是否有类似的东西.)

And*_*tan 38

这种方式取代了我以前的答案,因为这虽然是一个稍微长一点的路径 - 给你一个快速的方法调用,并且与其他一些答案不同,它允许你通过不同的实例(如果你要遇到多个实例)相同类型).如果您不想这样,请查看底部的更新(或查看Ben M的答案).

这是一个测试方法,可以满足您的需求:

public class TestType
{
  public string GetName() { return "hello world!"; }
}

[TestMethod]
public void TestMethod2()
{
  object o = new TestType();

  var input = Expression.Parameter(typeof(object), "input");
  var method = o.GetType().GetMethod("GetName", 
    System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
  //you should check for null *and* make sure the return type is string here.
  Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string)));

  //now build a dynamic bit of code that does this:
  //(object o) => ((TestType)o).GetName();
  Func<object, string> result = Expression.Lambda<Func<object, string>>(
    Expression.Call(Expression.Convert(input, o.GetType()), method), input).Compile();

  string str = result(o);
  Assert.AreEqual("hello world!", str);
}
Run Code Online (Sandbox Code Playgroud)

一旦构建委托 - 您可以将其缓存在字典中:

Dictionary<Type, Func<object, string>> _methods;
Run Code Online (Sandbox Code Playgroud)

然后,您只需使用传入对象的Type(来自GetType())作为键,将其添加到字典中.在将来,您首先检查字典中是否有一个现成的委托(如果是这样,则调用它),否则先构建它,添加它,然后调用它.

顺便说一句,这是DLR为其动态调度机制所做的事情的非常高度简化的版本(在C#术语中,当您使用'dynamic'关键字时).

最后

如果像少数人提到的那样,你只是想直接将Func绑定到你收到的对象上,那么你可以这样做:

[TestMethod]
public void TestMethod3()
{
  object o = new TestType();

  var method = o.GetType().GetMethod("GetName", 
    System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);

  Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string)));

  //this time, we bake Expression.Constant(o) in.
  Func<string> result = Expression.Lambda<Func<string>>(
   Expression.Call(Expression.Constant(o), method)).Compile();

  string str = result(); //no parameter this time.
  Assert.AreEqual("hello world!", str);
}
Run Code Online (Sandbox Code Playgroud)

但请注意,一旦表达式树被抛弃,您需要确保o保留在范围内,否则您可能会得到一些令人讨厌的结果.最简单的方法是在委托的生命周期内保持本地引用(可能在类实例中).(由于Ben M的评论而删除)


Jul*_*anR 16

是的,这是可能的:

Func<string> func = (Func<string>)
                     Delegate.CreateDelegate(typeof(Func<string>), getName);
Run Code Online (Sandbox Code Playgroud)

  • 这个答案不是实例敏感的 (4认同)

Ben*_*n M 14

这是我的答案,通过构建表达式树.与其他答案不同,result(getNameFunc)是一个绑定到原始实例的函数 - 不必将其作为参数传递.

class Program
{
    static void Main(string[] args)
    {
        var p = new Program();
        var getNameFunc = GetStringReturningFunc(p, "GetName");
        var name = getNameFunc();
        Debug.Assert(name == p.GetName());
    }

    public string GetName()
    {
        return "Bob";
    }

    static Func<string> GetStringReturningFunc(object x, string methodName)
    {
        var methodInfo = x.GetType().GetMethod(methodName);

        if (methodInfo == null ||
            methodInfo.ReturnType != typeof(string) ||
            methodInfo.GetParameters().Length != 0)
        {
            throw new ArgumentException();
        }

        var xRef = Expression.Constant(x);
        var callRef = Expression.Call(xRef, methodInfo);
        var lambda = (Expression<Func<string>>)Expression.Lambda(callRef);

        return lambda.Compile();
    }
}
Run Code Online (Sandbox Code Playgroud)


Rub*_*ben 9

最简单的方法是通过Delegate.CreateDelegate:

Func<string> getNameFunc = (Func<string>) Delegate.CreateDelegate(
                                           typeof(Func<string>), x, getName);
Run Code Online (Sandbox Code Playgroud)

请注意,这会绑定getNameFuncx,因此对于每个x您需要创建新的委托实例.这个选项比Expression基于实例的复杂得多.但是,使用基于表达式的示例,可以创建Func<MyClass, string> getNameFuncForAny一次,您可以为每个实例重用一次MyClass.

要创建这样的getNameFuncForAny,您需要一个类似的方法

public Func<MyClass, string> GetInstanceMethod(MethodInfo method)
{
    ParameterExpression x = Expression.Parameter(typeof(MyClass), "it");
    return Expression.Lambda<Func<MyClass, string>>(
        Expression.Call(x, method), x).Compile();
}
Run Code Online (Sandbox Code Playgroud)

您可以这样使用:

Func<MyClass, string> getNameFuncForAny = GetInstanceMethod(getName);

MyClass x1 = new MyClass();
MyClass x2 = new MyClass();

string result1 = getNameFuncForAny(x1);
string result2 = getNameFuncForAny(x2);
Run Code Online (Sandbox Code Playgroud)

如果您不想被束缚Func<MyClass, string>,您可以定义

public TDelegate GetParameterlessInstanceMethod<TDelegate>(MethodInfo method)
{
    ParameterExpression x = Expression.Parameter(method.ReflectedType, "it");
    return Expression.Lambda<TDelegate>(
        Expression.Call(x, method), x).Compile();
}
Run Code Online (Sandbox Code Playgroud)

  • 也可以`Delegate.CreateDelegate(typeof(Func <MyClass,string>),getName)` - 我相信它被称为"开放委托". (2认同)