簡體   English   中英

使用反射區分重載方法的泛型和非泛型版本

[英]Differentiating between generic and non-generic version of overloaded method using reflection

我在使用反射來區分泛型類的非泛型和泛型方法時遇到了一些麻煩。 這是我正在使用的測試用例:

public class Foo<T>
{
  public string Bar( T value ) { return "Called Bar(T)"; }

  public string Bar( int value ) { return "Called Bar(int)"; }

  public static void CallBar<TR>(Foo<TR> foo)
  {
      var fooInfo = foo.GetType()
         .GetMethods()
         .Where(x => !x.IsGenericMethod && // doesn't filter out Bar(T)!
                 x.Name == "Bar" &&
                 x.GetParameters().First().ParameterType == typeof(int))
         // !Two identical MethodInfo results, how to choose between them?
         // Is there a gauranteed canonical ordering? Or is it undefined?
         .First();

      Console.WriteLine(fooInfo.Invoke(foo, new object[]{ 0 }));
  }
}

// prints Bar(T)...
Foo<int>.CallBar( new Foo<int>() );

遺憾的是,System.Reflection沒有提供一種很好的方法來將構造類型的方法與構造它的泛型類型定義上的相應方法相關聯。 我知道有兩種解決方案,一種都不完美:

解決方案#1:靜態TypeBuilder.GetMethod。 TypeBuilder上一個靜態版本的GetMethod,它接受泛型類型定義的方法的泛型構造類型和MethodInfo,並返回指定泛型類型的相應方法。 在這個例子中,調用TypeBuilder.GetMethod(Foo<int>, Foo<T>.Bar(T))將為您提供Foo<int>.Bar(T-as-int) ,然后您可以使用它來消除它與它之間的歧義。 Foo<int>.Bar(int)

(上面的例子自然不會編譯;我使用Foo<int>Foo<T>.Bar(T)來表示相應的Type和MethodInfo對象,這些對象很容易獲得,但會使示例過於復雜) 。

壞消息是,只有在泛型類型定義是TypeBuilder時才會起作用,即當您發出泛型類型時。

解決方案#2:MetadataToken。 一個鮮為人知的事實是類型成員在從泛型類型定義到泛型構造類型的過渡中保留其MetadataToken。 因此在您的示例中, Foo<T>.Bar(T)Foo<int>.Bar(T-as-int)應該共享相同的MetadataToken。 那將允許你這樣做:

var barWithGenericParameterInfo = typeof(Foo<>).GetMethods()
   .Where(mi => mi.Name == "Bar" && 
          mi.GetParameters()[0].ParameterType.IsGenericParameter);

var mappedBarInfo = foo.GetType().GetMethods()
    .Where(mi => mi.MetadataToken == genericBarInfo.MetadataToken);

(這也不會編譯,除非我非常幸運並設法在第一次就把它弄好:))

這個解決方案的問題在於MetadataToken並不是為了那個(可能;文檔有點吝嗇)並且感覺就像一個骯臟的黑客。 不過,它有效。

使用Foo <int>時,Bar(T)方法的類型為Bar(int),它與定義為參數的int的方法沒有區別。

要獲得Bar(T)的正確方法定義,可以使用typeof(Foo <>)而不是typeof(Foo <int>)。

這將使您能夠分辨出兩者之間的區別。 請嘗試以下代碼:


    public static void CallBar<TR>(Foo<TR> foo)
    {
        Func<MethodInfo, bool> match = m => m.Name == "Bar";

        Type fooType = typeof(Foo<>);
        Console.WriteLine("{0}:", fooType);
        MethodInfo[] methods = fooType.GetMethods().Where(match).ToArray();

        foreach (MethodInfo mi in methods)
        {
            Console.WriteLine(mi);
        }

        Console.WriteLine();

        fooType = foo.GetType();
        Console.WriteLine("{0}:", fooType);
        methods = fooType.GetMethods().Where(match).ToArray();

        foreach (MethodInfo mi in methods)
        {
            Console.WriteLine(mi);
        }
    }

這將輸出:

System.String Bar(T)
System.String Bar(Int32)

System.String Bar(Int32)
System.String Bar(Int32)

嘗試查看泛型類型定義:typeof(Foo <>)。 方法將按相同的順序排列。

  public class Foo<T> {
    public string Bar(T value) { return "Called Bar(T)"; }
    public string Bar(int value) { return "Called Bar(int)"; }
    public static void CallBar<TR>(Foo<TR> foo) {

      var footinfo = typeof(Foo<>).GetMethods();
      int i;
      for (i = 0; i < footinfo.Count(); ++i) {
        if (footinfo[i].Name == "Bar" && footinfo[i].GetParameters()[0].ParameterType.IsGenericParameter == false)
          break;
      }

      Console.WriteLine(foo.GetType().GetMethods()[i].Invoke(foo, new object[] { 0 }));
    }
  }
  // prints Bar(int)...
  Foo<int>.CallBar( new Foo<int>() );

ContainsGenericParameters屬性對於Foo <>中的兩個Bar都是true,對於Foo中的兩個Bar都是false,所以它沒用。

正如Eric Lippert指出的那樣,它們都不是通用方法; 您的是通用的,但您傳遞的是該類的非泛型實例。 因此,這些方法不像反射那樣通用。

如果你改變,你應該走在正確的軌道上

foo.GetType()

foo.GetGenericTypeDefinition()

有關詳細信息,請參閱MSDN的文檔

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM