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

时间:2010-05-29 00:06:23

标签: .net performance reflection methodinfo func

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

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

public class MyClass {
    public string GetName() {
        return "My Name";
    }
}

在这里忍受我。我知道如果我有一个名为MyClass的{​​{1}}实例,我可以致电x。此外,我可以将x.GetName()变量设置为Func<string>

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

GetName

假设MethodInfo getName = x.GetType().GetMethod("GetName"); 不为空。然后,我无法进一步检查getNamegetName.ReturnType == typeof(string),并且此时,我不能确定我的getName.GetParameters().Length == 0对象所代表的方法肯定< / em>以某种方式投射到getName

我意识到有一个Func<string>,我也意识到我总是创建一个MethodInfo.Invoke,如:

Func<string>

我想我要问的是,是否有任何方法可以 Func<string> getNameFunc = () => getName.Invoke(x, null); 对象转换为它所代表的实际方法,从而产生性能成本进程中的反射,但 之后能够直接调用该方法(通过例如MethodInfo或类似的东西) 性能损失。

我想象的可能是这样的:

Func<string>

(我意识到这不存在;我想知道是否有喜欢它。)

6 个答案:

答案 0 :(得分:37)

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

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

Dictionary<Type, Func<object, string>> _methods;

然后使用传入对象的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);
}

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

答案 1 :(得分:15)

是的,这是可能的:

Func<string> func = (Func<string>)
                     Delegate.CreateDelegate(typeof(Func<string>), getName);

答案 2 :(得分:14)

这是我的答案,通过构建表达式树。与其他答案不同,结果(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();
    }
}

答案 3 :(得分:8)

最简单的方法是Delegate.CreateDelegate

Func<string> getNameFunc = (Func<string>) Delegate.CreateDelegate(
                                           typeof(Func<string>), x, getName);

请注意,这会将getNameFunc绑定到x,因此对于每个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();
}

你可以这样使用:

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

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

string result1 = getNameFuncForAny(x1);
string result2 = getNameFuncForAny(x2);

如果您不想与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();
}

答案 4 :(得分:1)

您可以构建一个表达式树,表示调用此方法的lambda,然后Compile(),以便进一步调用将与标准编译调用一样快。

或者,我很久以前根据一篇很棒的MSDN文章写了this method,这篇文章生成一个包装器,使用IL来调用任何MethodInfo方式比使用MethodInfo.DynamicInvoke更快,因为一旦代码生成,正常呼叫几乎没有开销。

答案 5 :(得分:0)

我的头顶方法之一就是使用动态。那么你可以这样:

if( /* This method can be a Func<string> */)
{
    dynamic methodCall = myObject;
    string response = methodCall.GetName();
}