2010-05-29 35 views
48

我知道,一般来说,使用反射会带来性能影响。 (我本人不是反射的粉丝可言,其实,这是一个纯粹的学术问题。)你能从MethodInfo对象中得到一个Func <T>(或类似的)吗?

假设存在某个类,看起来像这样:这里

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

包涵。我知道如果我有一个名为xMyClass实例,我可以拨打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>

我知道有一个MethodInfo.Invoke,我也知道我可以一直创建一个Func<string>,如:

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

我猜我问的是,如果有任何的路要走一个MethodInfo对象它所代表的实际方法,在处理中产生反射的性能代价,但在之后那个点能够呼叫我(通过例如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); 

+1

关于你在编辑中的评论 - 你会看到一个巨大的速度增加与解决方案,如thi s,因为动态编译的委托和静态编译的委托没有什么区别;一旦编制的开销被分解出来。因为我发现了表达树的东西,所以我一直在使用它们,并且很可能将它作为我的第一个.Net 3.5特性。在第4版中,它更好,因为您可以编写多语句代码 - 因为DLR所需的扩展名所需。 – 2010-05-30 18:02:48

回答

33

这种替换我以前的答案,因为这一点,虽然这是一个稍长的路线 - 为您提供了一个快速的方法调用,不像一些其他的答案,让你通过不同的实例(如果你将遇到同一类型的多个实例)。如果你不想要,请查看底部的更新(或查看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保持在范围内,否则你可能会得到一些令人讨厌的结果。最简单的方法是在代理的整个生命周期内保持本地引用(可能在类实例中)。 (已移除的奔M的评论结果)

+0

示例要遵循:) – 2010-05-29 00:13:35

+0

伟大的思想思考。 :-)但是有一个问题 - 为什么演员表达? – 2010-05-29 00:29:06

+0

哦,但坚持 - 他仍然必须传递一个参数到你的lambda表达式。我从最初的问题出发,将生成的函数绑定到实例而不必传递参数。 – 2010-05-29 00:32:13

0

一休我头上的方法将是使用动态。然后,您可以使这样的事情:

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

是的,这是可能的:

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

这个答案不是实例敏感的 – 2010-05-29 00:20:31

+0

@Ben M - 正是我在想什么 - 为什么我决定放弃Delegate.CreateDelegate操作 - 因为没有办法为第一个参数传递正确的参数类型。 – 2010-05-29 00:24:43

+1

公平点。但是,我认为你的解决方案不一定更好。如果最终在多个不同的实例上调用动态编译的实例,那么每个实例的大量编译代价可能比调用'MethodInfo'要昂贵得多。如果是这样,从性能角度来看,最好让GetName成为静态的,并且需要引用实例(所以明确的'this'指针)会导致'Func JulianR 2010-05-29 13:46:17

1

你可以建立代表一个lambda调用这个方法和表达式目录树,然后Compile()它,以便进一步调用将只是与标准编译调用一样快。

另外,我基于一个伟大的MSDN文章,使用IL调用任何MethodInfo方式,因为一旦生成的代码比MethodInfo.DynamicInvoke更快产生的包装写this method了好一会儿前,有超过正常的几乎没有开销呼叫。

12

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

+1 - 一个控制台应用程序,而不是测试项目...更好地复制/粘贴值! – 2010-05-29 00:43:18

+0

真棒答案,教会了我很多(以前没有做过很多表达树的工作)。我把它交给安德拉斯,因为他的回答来得早一些,但这也是非常有用的。谢谢! – 2010-05-30 16:00:48

6

要做到这一点,最简单的方法是通过Delegate.CreateDelegate

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

请注意,此结合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(); 
} 

的方法,您可以使用像这样:

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(); 
} 
+1

也可以'Delegate.CreateDelegate(typeof(Func ),getName)' - 我相信它被称为“公开委托”。 – 2017-05-18 14:00:53

相关问题