Различение общей и неуниверсальной версии перегруженного метода с использованием отражения - PullRequest
3 голосов
/ 12 июня 2009

У меня возникли проблемы с использованием рефлексии для различения неуниверсального и универсального метода в универсальном классе. Вот тестовый пример, с которым я работаю:

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 ]

2 голосов
/ 12 июня 2009

К сожалению, System.Reflection не предоставляет хорошего способа соотнести метод для составного типа с соответствующим методом в определении универсального типа, из которого он был создан. Я знаю два решения, ни одно из которых не является идеальным:

Решение № 1: статический TypeBuilder.GetMethod. Существует статическая версия GetMethod для 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) для обозначения соответствующих объектов 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 не был предназначен для этого (вероятно, документация немного скудна), и это похоже на грязный хак. Тем не менее, это работает.

1 голос
/ 12 июня 2009

При использовании Foo метод Bar (T) вводится как Bar (int), без различия между ним и методом с int, определенным в качестве параметра.

Чтобы получить правильное определение метода Bar (T), вы можете использовать typeof (Foo <>) вместо typeof (Foo ).

Это позволит вам определить разницу между ними. Попробуйте следующий код:


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

1 голос
/ 12 июня 2009

Попробуйте посмотреть определение общего типа: 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 имеет значение true для обоих баров в Foo <> и false для обоих баров в Foo, поэтому оно бесполезно.

0 голосов
/ 12 июня 2009

Как указывает Эрик Липперт, ни один из них не является универсальным методом; ваш класс является универсальным, но вы передаете неуниверсальный экземпляр класса. Поэтому методы не являются универсальными в том виде, в каком их видит отражение.

Вы должны быть на правильном пути, если вы измените

foo.GetType()

до

foo.GetGenericTypeDefinition()

Для получения дополнительной информации см. Документация MSDN .

0 голосов
/ 12 июня 2009

Я думаю, что 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

...