2017-10-11 24 views
3

出於測試目的,我正在檢查引用類中的一系列方法簽名是否已在不同的靜態類上實現。對於大多數人以下工作:通用方法的Type.GetMethod()

private static IEnumerable<Signature> GetMethodSigs(Type type) 
{ 
    // Get MethodInfos, filter and project into signatures 
    var methods = type.GetMethods(
          BindingFlags.Public 
         | BindingFlags.DeclaredOnly 
         | BindingFlags.Static 
         | BindingFlags.Instance) 
        .Where(mi => !mi.Name.StartsWith("get_")) 
        .Where(mi => !mi.Name.StartsWith("set_")) 
        .Select(o => new Signature(o.Name, o.ReturnType, o.GetParameters().Select(pi => pi.ParameterType))); 

    return methods; 
} 

private static MethodInfo FindMethod(Type type, Signature sig) 
{ 
    MethodInfo member = type.GetMethod(
           sig.Name, 
           BindingFlags.Public | BindingFlags.Static, 
           null, 
           sig.ParameterTypes.ToArray(), 
           null); 
    return member; 
} 

public struct Signature 
{ 
    public string Name; 
    public Type ReturnType; 
    public IEnumerable<Type> ParameterTypes; 

    public Signature(string name, Type returnType, IEnumerable<Type> parameterTypes = null) 
    { 
     Name = name; 
     ReturnType = returnType; 
     ParameterTypes = parameterTypes; 
    } 
} 

這是一個測試類的一部分,但是想象一下下面的代碼在駕駛過程:

foreach(var sig in GetMethodSigs(typeof(ReferenceClass))) 
    Console.WriteLine(FindMethod(typeof(TestClass), sig)?.ToString()); 

以下方法簽名被拾起好上ReferenceClass,但FindMethod()不上TestClass找到一個等效的方法(這確實存在!):

public static void SomeMethod<T>(SomeDelegate<T> del) 

GetMethods()爲我提供了一個del參數類型(SomeDelegate`1),但這顯然不適合在GetMethod()中搜索,因爲FindMethod()返回null此輸入。

任何想法如何操縱從GetMethods()返回的值來搜索使用GetMethod()(泛型參數在代表中使用,而不是簡單的參數類型)的泛型方法?

(我知道有一種GetMethod()版本只是走一個名字,但一些方法名的重載,我需要搜索參數類型爲好。)

+0

可能是這樣的幫助:https://stackoverflow.com/questions/232535/how-do-i-use-reflection-to-call-a-generic-method –

+0

@EhsanSajjad不幸的是,這些問題的答案(和其他類似的問題)假設該方法可以通過使用名稱找到 - 我需要包含參數,這就是我遇到麻煩的地方。 –

+0

與其使用類型參數調用'GetMethod',爲什麼不調用'GetMethods'然後遍歷結果(匹配名稱)並比較參數類型? – BurnsBA

回答

1

相反的Type.GetMethod()重載之一,我結束了對目標類使用GetMethods()和使用擴展方法的每個參數類型比較通過所有成員循環(注意C#7僅本地函數):

public static bool Similar(this Type reference, Type type) 
{ 
    if (reference.IsGenericParameter && type.IsGenericParameter) 
    { 
     return reference.GenericParameterPosition == type.GenericParameterPosition; 
    } 

    return ComparableType(reference) == ComparableType(type); 

    Type ComparableType(Type cType) 
     => cType.IsGenericType ? cType.GetGenericTypeDefinition() : cType; 
} 

這考慮兩種類型是「相似」,如果:

  • 他們是簡單類型和使用==操作
  • 比較相等
  • 它們是泛型類型,並且在通用參數列表中具有相同索引的類型(即,在SomeMethod<T,S>(S parameter)中,唯一的參數類型將被認爲與SomeMethod<T1,T2>(T2 parm)中的相同,但是不是SomeMethod<T,S>(T parameter))。
  • 它們是具有嵌套泛型參數的相同類型。在這種情況下,事實上,它是一個通用型的注意,但沒有關於參數的進一步探討(等SomeMethod<T,S>(Action<T> parameter)參數類型將是「相似」 SomeMethod<T,S>(Action<S> parameter)

這是不理想,但事實證明這是一個令人驚訝的難題!它適用於我的使用案例,它覆蓋了我的所有案例,並且由於項目的性質(分析遺留代碼),不會出現新案例。在Type的下列擴展中,它旨在取代Type.GetMethod()並實現上面提到的循環:

public static MethodInfo GetMethodWithGenerics(
          this Type type, 
          string name, Type[] parameters, 
          BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) 
{ 
    var methods = type.GetMethods(flags); 

    foreach (var method in methods) 
    { 
     var parmeterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); 

     if (method.Name == name && parmeterTypes.Count() == parameters.Length) 
     { 
      bool match = true; 

      for (int i = 0; i < parameters.Length; i++) 
       match &= parmeterTypes[i].Similar(parameters[i]); 

      if (match) 
       return method; 
     } 
    } 

    return null; 
} 

正如BurnsBA在他的回答下面所說,似乎有一些基本問題與泛型的內置反射支持,似乎並沒有一個簡單的解決方案,我的原始問題。在考慮了BurnBA在這裏的回答以及在另一個問題上的he linked to之後,我得出了這個答案。對於任何希望產生比較完整版本的人來說,這個答案都會特別有用。

任何發現這個有用的人都應該考慮upvoting或者這兩者。

0

願你可以嘗試類似

string name = "MyFunction"; 
Type type = typeof(Program); 

MethodInfo member = type.GetMethods(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance) 
.Single(m => m.Name == name && m.GetParameters().Select(p => p.ParameterType.Name).SequenceEqual(new [] { typeof(MyClass<>).Name })); 

public void MyFunction<T>(MyClass<T> test){} 
1

我沒有很長一段時間,你是比較兩個不同的類定義注意:

foreach(var sig in GetMethodSigs(typeof(ReferenceClass))) 
    Console.WriteLine(FindMethod(typeof(TestClass), sig)?.ToString()); 

這是值得注意的,因爲我如果類是相同的,你的上面的代碼就可以工作。問題是泛型的處理方式(簡要看Type源代碼,看起來Equals使用引用比較,但==已卸載到我認爲的編譯器中。問題是,不同的泛型方法類型不是引用等效的。實際發生的事情是作爲練習留給讀者的)。

一個快速演示(你可以在交互式shell中運行這個)

public class D { public static void Method<T>(Action<T> t) { } } 
(new D()).GetType().GetMethod("Method").GetParameters()[0].ParameterType == (typeof(Action<>)) 

輸出:

false 

但是,如果檢查的類型,看起來都一樣:

> (new D()).GetType().GetMethod("Method").GetParameters()[0].ParameterType.ToString() 
"System.Action`1[T]" 
> (typeof(Action<>)).ToString() 
"System.Action`1[T]" 

好吧,這是問題的演示。您應該能夠解決此問題由GetMethodSigs改變你的SELECT語句來調用GetGenericTypeDefinition()每個參數類型:

.Select(o => new Signature(o.Name, o.ReturnType, o.GetParameters().Select(pi => pi.ParameterType.GetGenericTypeDefinition()))); 

你可以看到,現在下面顯示的平等:

> (new D()).GetType().GetMethod("Method").GetParameters()[0].ParameterType.GetGenericTypeDefinition() == (typeof(Action<>)).GetGenericTypeDefinition() 
true 

當然,這僅適用於泛型類型。您必須添加一些邏輯,僅在上述選擇語句中根據需要調用.GetGenericTypeDefinition()

更多來自https://stackoverflow.com/a/1855248/1462295


編輯:

使用從https://stackoverflow.com/a/7182379/1462295

代碼(注意空類T從上方)

public class D 
{ 
    public static void Method<TT>(Action<TT> t) { } 
    public static void Method<TT>(TT t) { } 
} 

public class RefD 
{ 
    public static void Method<TT>(Action<TT> t) { } 
    public static void Method<TT>(TT t) { } 
} 

foreach(var sig in GetMethodSigs(typeof(RefD))) 
    Console.WriteLine(GetMethodExt(typeof(D), sig.Name, sig.ParameterTypes.ToArray())?.ToString()); 

輸出

Void Method[TT](System.Action`1[TT]) 
Void Method[TT](TT) 
+0

根據Type.GetGenericTypeDefinition()的MSDN頁面,Type.IsGenericType指出調用這個方法是否合適。 –

+0

我可能有點倉促接受這個答案。雖然使用'typeof(Action <>)'演示現在顯示eqaulity,但'GetMethod()'顯然仍然無法與使用'GetGenericTypeDefinition()'創建的提供的參數相匹配。 (續) –

+0

爲了解決這個問題,我試着創建一個使用'GetMethods()'的新GetMethod()'擴展,然後遍歷返回的方法尋找名稱和參數上的匹配 - 我也可以使用'GetGenericTypeDefinition )'這些也是。這部分工作,但仍然不正確地匹配正常的泛型(形式爲'void SomeMethod (T parameter)')。我想知道是否將實例方法與通用方法進行比較。 –