使用反射区分重载方法的泛型和非泛型版本

时间:2009-06-11 22:11:25

标签: c# .net generics reflection overloading

我在使用反射来区分泛型类的非泛型和泛型方法时遇到了一些麻烦。这是我正在使用的测试案例:

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>() );

5 个答案:

答案 0 :(得分:2)

不幸的是,System.Reflection没有提供一种很好的方法来将构造类型的方法与构造它的泛型类型定义上的相应方法相关联。我知道有两种解决方案,其中一种都不完美:

解决方案#1:static TypeBuilder.GetMethod。a static version of GetMethod on TypeBuilder接受通用类型定义的方法的泛型构造类型和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)Foo<T>.Bar(T)来表示相应的Type和MethodInfo对象,这些对象很容易获得,但会使示例过于复杂) 。

坏消息是,只有当泛型类型定义是TypeBuilder时才会有效,即当您发出泛型类型时。

解决方案#2:MetadataToken。一个鲜为人知的事实是类型成员在从泛型类型定义到泛型构造类型的过渡中保留其MetadataToken。因此,在您的示例中,Foo<int>.Bar(T-as-int)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。那将允许你这样做:

{{1}}

(这也不会编译,除非我非常幸运,并设法在第一次就把它弄好:))

这个解决方案的问题在于MetadataToken并不是为了那个(可能;文档有点吝啬)并且感觉就像一个肮脏的黑客。然而,它有效。

答案 1 :(得分:1)

当使用Foo&lt; int&gt;时,Bar(T)方法的类型为Bar(int),它与定义为参数的int的方法没有区别。

要获得Bar(T)的正确方法定义,您可以使用typeof(Foo&lt;&gt;)而不是typeof(Foo&lt; int&gt;)。

这将使您能够分辨出两者之间的区别。请尝试以下代码:


    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)

答案 2 :(得分:1)

尝试查看泛型类型定义:typeof(Foo&lt;&gt;)。方法将采用相同的顺序。

  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>() );

对于Foo&lt;&gt;中的两个Bar,ContainsGenericParameters属性为true。对于Foo中的两个Bar来说都是假的,所以它没用。

答案 3 :(得分:0)

答案 4 :(得分:0)

Eric Lippert指出,它们都不是通用方法;您的是通用的,但您传递的是该类的非泛型实例。因此,这些方法不像反射那样通用。

如果你改变

,你应该走在正确的轨道上
foo.GetType()

foo.GetGenericTypeDefinition()

有关详细信息,请参阅MSDN's documentation