Type.GetInterfaces () только для объявленных интерфейсов - PullRequest
5 голосов
/ 20 марта 2012

Прежде всего, есть много вопросов, подобных этому, и, возможно, некоторые ОП даже задавали этот же вопрос. Проблема в том, что ни один ответ на эти вопросы (принятый или нет) на самом деле не отвечает на этот вопрос, по крайней мере, ни один, который я могу найти.

Как я могу определить интерфейсы, которые класс объявляет напрямую, а не те, которые наследуются ни родителями, ни объявленными интерфейсами?

, например

interface I {}
interface W : I {}
class C : W {}
class D : C, I {}
class E : D {}

Результаты:

  1. C объявляет W
  2. D объявляет I
  3. E не объявляет

Приемлемое решение может потребовать, чтобы интерфейсы имели хотя бы один метод.

Если вы считаете, что это действительно невозможно, будьте осторожны, чтобы не совершить эту ошибку, которую на самом деле можно сделать.

InterfaceMap обрабатывает много случаев, но не все (я привожу пример ниже, не разрешимый InterfaceMap). У меня была одна идея, но я не знаю, как ее реализовать, - декомпилировать байт-код класса и посмотреть, что объявлено, поскольку такие инструменты, как ILSpy, правильно идентифицируют каждый случай! Если вам нравится эта идея, пожалуйста, дайте мне ссылку на дополнительную информацию в этой области.

Я ожидаю, что некоторые из вас посоветуют мне очистить мой дизайн. Если это не ваш аргумент, остальная часть поста не имеет отношения к вам.

Часть цели моего проекта заключается в отслеживании потенциальных путей кода данных типов (во время выполнения). Чтобы программно определить, какой метод будет вызываться для целевого типа, без фактического вызова метода или создания экземпляра целевого типа, необходимо знать объявленные интерфейсы целевого типа, чтобы детерминистически решить эту проблему. «Нет», вы говорите? Рассмотрим:

interface I { int Foo(); }
class C : I { public int Foo() { return 1; } }
class D : C { public new int Foo() { return 2; } }
class E : D, I { }

C p = new E();
Assert.AreEqual(1 or 2, (p as I).Foo())

Правильный ответ 2, но если вы измените объявление E, чтобы оно не включало I напрямую, ответ будет 1. Теперь, конечно, это крайний случай, но это также правильный ответ. Поэтому мой движок не полностью совместим с потенциальным кодом пользователя. Попросить пользователей очистить свой код, чтобы использовать мой инструмент, недопустимо. (Обратите внимание, что существуют десятки более интересных правил приведения к интерфейсу, но я не буду их здесь обсуждать).

Ответы [ 5 ]

2 голосов
/ 21 марта 2012

Основываясь на полезной информации из комментариев, я смог окончательно показать, что это невозможно сделать с помощью отражения msft (хотя это можно сделать с помощью mono.cecil).Причина в том, что вызовы GetInterfaces делают собственный вызов, который возвращает предварительно сплющенные интерфейсы для целевого типа.

2 голосов
/ 20 марта 2012

Чтобы получить только заявленных интерфейсов для данного типа, вы можете использовать GetInterfaces для данного типа, тогда, если он имеет BaseType Вы можете использовать перечислитель Except для исключения интерфейсов базового типа ...

Это не проверено, но, возможно, что-то вроде этого метода расширения ...

public static IEnumerable<Type> GetDeclaredInterfaces(this Type t)
{
    var allInterfaces = t.GetInterfaces();
    var baseInterfaces = Enumerable.Empty<Type>();
    if (t.BaseType != null)
    {
        baseInterfaces = t.BaseType.GetInterfaces();
    }
    return allInterfaces.Except(baseInterfaces);
}
1 голос
/ 21 марта 2012

Чтобы программно определить, какой метод будет вызываться для целевого типа, без фактического вызова метода или создания экземпляра целевого типа, необходимо знать, что объявленные интерфейсы целевого типа необходимы для детерминированного решения этой проблемы.

Reflection API предоставляет именно эту функцию через Type.GetInterfaceMap(Type). Например, в вашем случае typeof(E).GetInterfaceMap(typeof(I)) предоставляет вам следующую информацию, которая позволяет вам напрямую определять сопоставление:

InterfaceMethods            TargetMethods
[0]: I.Foo()                [0]: E.I.Foo()

Ясно, что TargetMethods точно говорит вам, какой метод вызывается, если экземпляр имеет тип E во время выполнения.

Вы заметите, что метод интерфейса I.Foo() фактически отображается на метод типа E, который явно реализует I.Foo(). Этот метод был вставлен компилятором, и он просто вызывает D.Foo().

(Обновление: К сожалению, как вы можете видеть на скриншоте, я сделал второй Foo() виртуальным. Хотя я получаю точно такой же результат для не виртуальных.)

1 голос
/ 20 марта 2012

Как насчет этого?

Type type = typeof(E);
var interfaces = type.GetInterfaces()
    .Where(i => type.GetInterfaceMap(i).TargetMethods.Any(m => m.DeclaringType == type))
    .ToList();
0 голосов
/ 20 марта 2012

Проблема не в том, что GetInterfaces дает вам неверную информацию, я думаю. Проблема в том, что вы смотрите не на тот тип.

Если у вас есть код, подобный (p as I).Foo(), вы должны смотреть на методы typeof(I): не p.GetType(), ни typeof(C), ни typeof(E).

Размышление о I расскажет вам все, что вам нужно для правильного разрешения вызова, хотя это ни в коем случае не тривиально.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...