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

5 回答 5

3

不幸的是 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>andFoo<T>.Bar(T)来表示相应的 Type 和 MethodInfo 对象,它们很容易获得,但会使示例过于复杂)。

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

解决方案#2:元数据令牌。一个鲜为人知的事实是,类型成员在从泛型类型定义到泛型构造类型的转换过程中保留其 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 不是为此而设计的(可能;文档对此有点吝啬),感觉就像一个肮脏的黑客。尽管如此,它仍然有效。

于 2009-06-12T13:28:17.513 回答
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)

于 2009-06-11T23:16:05.847 回答
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,因此它没有用。

于 2009-06-11T23:45:11.987 回答
0

根据文档,我认为 ContainsGenericParameters 是您正在寻找的内容:

http://msdn.microsoft.com/en-us/library/system.reflection.methodinfo.isgenericmethod.aspx http://msdn.microsoft.com/en-us/library/system.reflection.methodinfo.containsgenericparameters.aspx

于 2009-06-11T23:10:13.740 回答
0

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

如果你改变,你应该走在正确的轨道上

foo.GetType()

foo.GetGenericTypeDefinition()

有关详细信息,请参阅MSDN 的文档

于 2009-06-12T13:57:31.657 回答