2010-05-29 37 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

相關問題