63

我意识到,一般来说,使用反射会影响性能。(实际上,我本人根本不喜欢反思;这是一个纯粹的学术问题。)

假设存在一些如下所示的类:

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

在这里忍受我。我知道如果我有一个MyClass被调用的实例x,我可以调用x.GetName()。此外,我可以将Func<string>变量设置为x.GetName.

现在这是我的问题。假设我知道上面的类被调用MyClass;我有一些对象,x但我不知道它是什么。我可以通过执行以下操作来检查该对象是否具有GetName方法:

MethodInfo getName = x.GetType().GetMethod("GetName");

假设getName不为空。那么我不能进一步检查是否getName.ReturnType == typeof(string)getName.GetParameters().Length == 0,并且在这一点上,我不是很确定我的getName对象表示的方法肯定可以以Func<string>某种方式强制转换为 a 吗?

我意识到有一个MethodInfo.Invoke,我也意识到我总是可以创建一个Func<string>类似的:

Func<string> getNameFunc = () => getName.Invoke(x, null);

我想我要问的是是否有任何方法可以一个MethodInfo对象转到它所代表的实际方法,从而在过程中产生反射的性能成本,但那之后能够直接调用该方法(通过,例如, aFunc<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);

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

4

6 回答 6

42

这种替代了我之前的答案,因为尽管它是一条稍长的路线,但它为您提供了一个快速的方法调用,并且与其他一些答案不同,它允许您通过不同的实例(以防您遇到多个实例同类型)。如果您不希望这样,请查看底部的更新(或查看 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;

然后您所做的就是将其添加到字典中,使用传入对象的类型(来自 GetType())作为键。将来,您首先检查字典中是否有现成的委托(如果有,则调用它),否则您首先构建它,添加它,然后调用它。

顺便说一句,这是 DLR 为它的动态调度机制所做的事情的一个非常简化的版本(在 C# 术语中,这就是您使用“动态”关键字的时候)。

最后

如果,正如一些人所提到的,您只是想烘焙一个直接绑定到您收到的对象的 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 的评论而被删除)

于 2010-05-29T00:11:15.507 回答
21

是的,这是可能的:

Func<string> func = (Func<string>)
                     Delegate.CreateDelegate(typeof(Func<string>), getName);
于 2010-05-29T00:12:27.153 回答
17

通过构建表达式树,这是我的答案。与其他答案不同,结果 ( 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();
    }
}
于 2010-05-29T00:27:11.283 回答
11

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

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

请注意,这绑定getNameFunc到,因此您需要x为每个创建一个新的委托实例。x此选项比Expression基于 - 的示例要简单得多。但是,使用基于表达式的示例,可以创建Func<MyClass, string> getNameFuncForAny一次,您可以将其重复用于 a 的每个实例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();
}
于 2010-05-29T01:23:35.410 回答
1

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

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

于 2010-05-29T00:15:36.507 回答
0

我最想到的一种方法是使用动态。然后你可以这样:

if( /* This method can be a Func<string> */)
{
    dynamic methodCall = myObject;
    string response = methodCall.GetName();
}
于 2010-05-29T00:10:43.037 回答