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

3

我在使用反射区分泛型类中的非泛型方法和泛型方法时遇到了一些问题。以下是我正在处理的测试用例:

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

10
这两个都不是通用的方法。如果这个“方法”是通用的,那么它应该是“string Bar<U>(...)”。 - Eric Lippert
5个回答

3
很不幸,System.Reflection没有提供一种好的方法来将构造类型上的方法与从中构造它的泛型类型定义上的相应方法对应起来。我知道两种解决方案,但都不完美: 解决方案1:静态的TypeBuilder.GetMethod。TypeBuilder上有一个静态版本的GetMethod,它接受一个泛型构造类型和一个泛型类型定义上的方法的MethodInfo,并返回指定泛型类型上的相应方法。在这个例子中,调用TypeBuilder.GetMethod(Foo, Foo.Bar(T))将给你Foo.Bar(T-as-int),然后你可以使用它来区分它和Foo.Bar(int)。 (以上示例自然无法编译;我使用Foo和Foo.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并不是为此而设计的(可能;文档有点简略),感觉像是一个肮脏的hack。尽管如此,它仍然有效。


我认为你在这里有些误解问题的重点。 - Paul Suart
怎么样?如果我理解正确,LBushkin想在从Foo<T>构造Foo<int>之后消除Bar(int)的两个版本之间的歧义。我给了他两种方法来区分它们,通过可靠地识别其中一个。你认为我漏掉了什么? - Avish
1
Avish - 这正是我想做的事情。这非常有帮助 - 我对在我的实现中使用未记录的泛型类型信息的副作用(特性?)并不感到兴奋。但这是一个开始的地方。谢谢。 - LBushkin
不用谢,我很高兴能够提供您需要的帮助。如果您能将此回答标记为已接受,我将不胜荣幸。 - Avish

1

当使用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)

1

尝试查看泛型类型定义: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,因此它是无用的。


0
正如Eric Lippert所指出的那样,它们都不是通用方法;你的类是通用的,但你正在传递一个非通用的类实例。因此,这些方法在反射看来并不是通用的。
如果你改变了,你应该会走上正确的轨道。
foo.GetType()

foo.GetGenericTypeDefinition()

更多信息,请参见MSDN文档



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接