Как мне надежно сравнить два PropertyInfos или метода? - PullRequest
12 голосов
/ 09 января 2011

То же самое для методов:

Мне даны два экземпляра PropertyInfo или методов, которые были извлечены из класса, в котором они находятся, через GetProperty() или GetMember() и т. Д. (Или, возможно, из MemberExpression).

Я хочу определить, действительно ли они ссылаются на то же свойство или тот же метод, поэтому

(propertyOne == propertyTwo)

или

(methodOne == methodTwo)

Очевидно, что это не такЕсли вы собираетесь работать, вы можете смотреть на одно и то же свойство, но оно могло быть извлечено из разных уровней иерархии классов (в этом случае обычно propertyOne != propertyTwo)

Конечно, я мог бы взглянуть наDeclaringType и повторно запросите свойство, но это начинает немного сбивать с толку, когда вы начинаете думать о

  • Свойства / Методы, объявленные на интерфейсах и реализованные на классах
  • Свойства / Методы, объявленныев базовом классе (виртуально) и переопределяется в производных классах
  • Свойства / методы, объявленные в базовом классе, переопределяются с помощью 'new' (в мире IL это ничегоg special iirc)

В конце концов, я просто хочу иметь возможность выполнять интеллектуальную проверку равенства между двумя свойствами или двумя методами, я уверен на 80%, что указанные выше пунктыне охватывать все крайние случаи, и хотя я мог бы просто сесть, написать кучу тестов и начать играть, я хорошо понимаю, что мои низкие знания о том, как эти концепции на самом деле реализуются, не отлично , и я надеюсь, что это уже отвеченная тема, и я просто отстой в поиске.

Лучший ответ дал бы мне несколько методов, которые достигают вышеуказанного, объясняя, какие крайние случаи былии почему: -)


Уточнение :

Буквально, я хочу убедиться, что они имеют одно и то же свойство, вот несколько примеров

public interface IFoo
{
     string Bar { get; set; }
}

public class Foo : IFoo
{
     string Bar { get; set; }
}

typeof(IFoo).GetProperty("Bar")

и

typeof(Foo).GetProperty("Bar")

Вернет две информации о свойствах, которые не равны:

public class BaseClass
{
     public string SomeProperty { get; set ; }
}

public class DerivedClass : BaseClass { }


typeof(BaseClass).GetMethod("SomeProperty")

и

typeof(DerivedClass).GetProperty("SomeProperty")

Я могу 'на самом деле помню, если эти два возвращают равные объекты пау, но в моем мире они равны.

Аналогично:

public class BaseClass
{
    public virtual SomeMethod() { }
}

public class DerivedClass
{
    public override SomeMethod() { }
}

typeof(BaseClass).GetMethod("SomeMethod")

и

typeof(DerivedClass).GetProperty("SomeMethod")

Опять же, они не будут совпадать - но я хочу, чтобы они(Я знаю, что они не совсем равны, но в моем домене они есть, потому что они ссылаются на одно и то же исходное свойство)

Я мог бы сделать это структурно, но это было бы «неправильно».

Дополнительные примечания :

Как вы вообще запрашиваете свойство, которое скрывает другое свойство?Кажется, одно из моих предыдущих предположений было неверным, что реализация по умолчанию GetProperty("name") будет ссылаться на текущий уровень по умолчанию.

BindingFlags.DeclaringType, кажется, просто чтобы в итоге вернуть ноль!

Ответы [ 4 ]

3 голосов
/ 09 января 2011

Взглянув на PropertyInfo объекты из вашего IFoo / Foo примера, мы можем прийти к следующим выводам:

  1. Нет прямого способа узнать, для какого класса / интерфейса было объявлено свойство изначально.
  2. Поэтому, чтобы проверить, действительно ли свойство было объявлено в классе предков, нам нужно перебрать предков и посмотреть, существует ли свойство и для них.
  3. То же самое касается интерфейсов, нам нужно позвонить Type.GetInterfaces и работать оттуда. Не забывайте, что интерфейсы могут реализовывать другие интерфейсы, поэтому это должно быть рекурсивно.

Итак, давайте попробуем. Во-первых, чтобы покрыть унаследованные свойства:

PropertyInfo GetRootProperty(PropertyInfo pi)
{
    var type = pi.DeclaringType;

    while (true) {
        type = type.BaseType;

        if (type == null) {
            return pi;
        }

        var flags = BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance |
                    BindingFlags.Public | BindingFlags.Static;
        var inheritedProperty = type.GetProperty(pi.Name, flags);

        if (inheritedProperty == null) {
            return pi;
        }

        pi = inheritedProperty;
    }
}

Теперь, чтобы охватить свойства, объявленные в интерфейсах (поиск в DFS):

PropertyInfo GetImplementedProperty(PropertyInfo pi)
{
    var type = pi.DeclaringType;
    var interfaces = type.GetInterfaces();

    if (interfaces.Length == 0) {
        return pi;
    }

    var flags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public;
    var query = from iface in interfaces
                let implementedProperty = iface.GetProperty(pi.Name, flags)
                where implementedProperty != pi
                select implementedProperty;

    return query.DefaultIfEmpty(pi).First();
}

Связывая их вместе:

PropertyInfo GetSourceProperty(PropertyInfo pi)
{
    var inherited = this.GetRootProperty(pi);
    if (inherited != pi) {
        return inherited;
    }

    var implemented = this.GetImplementedProperty(pi);
    if (implemented != pi) {
        return implemented;
    }

    return pi;
}

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

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

2 голосов
/ 10 января 2011

Итак, это было непростое печенье, и прежде чем углубляться в скучные подробности, я скажу это, в итоге я решил просто провести структурное сравнение, поскольку единственное место , которое упадет, этокогда участник скрывает другого члена с ключевым словом 'new' в C # - решил, что это небольшая проблема в этой системе по сравнению с множеством других проблем, которые в результате приводят к тому, что правильное решение этой проблемы вызываетнеправильно (и это действительно идет не так, поверьте мне).

Я принял ответ выше, потому что он был близок к решению проблемы - единственная проблема, которую он имеет, - это все еще структурный по своей природе (и я могу достигнуть этого путем проверки типа / имени / аргумента)

Хотя есть некоторые различия, главным образом, что необходимо смотреть на карту интерфейса вместо вызова GetProperty -это довольно важная деталь - вот мой пересмотренный метод, который при определенных обстоятельствах упадет.

private PropertyInfo GetImplementedProperty(PropertyInfo pi)
    {
        var type = pi.DeclaringType;
        var interfaces = type.GetInterfaces();

        for(int interfaceIndex = 0; interfaceIndex < interfaces.Length; interfaceIndex++)
        {
            var iface = interfaces[interfaceIndex];
            var interfaceMethods = type.GetInterfaceMap(iface).TargetMethods;

            MethodInfo matchingMethod = null;
            for (int x = 0; x < interfaceMethods.Length; x++)
            {
                if (pi.GetGetMethod().LooseCompare(interfaceMethods[x]) || pi.GetSetMethod().LooseCompare(interfaceMethods[x]))
                {
                    matchingMethod = type.GetInterfaceMap(iface).InterfaceMethods[x];
                    break; 
                }
            }
            if (matchingMethod == null) continue;

            var interfacePi = from i in interfaces
                              from property in i.GetProperties()
                              where property.GetGetMethod().LooseCompare(matchingMethod) || property.GetSetMethod().LooseCompare(matchingMethod)
                              select property;

            return interfacePi.First();
        }

        return pi;
    } 

В итоге я отказался от проверки, когдаra member скрывал другого члена и пошел на следующий хак:

private PropertyInfo GetRootProperty(PropertyInfo pi)
    {
        if ((pi.GetGetMethod().Attributes & MethodAttributes.Virtual) != MethodAttributes.Virtual) { return pi; }

        var type = pi.DeclaringType;

        while (true)
        {
            type = type.BaseType;

            if (type == null)
            {
                return pi;
            }

            var flags = BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance |
                        BindingFlags.Public | BindingFlags.Static;

            var inheritedProperty = type.GetProperty(pi.Name, flags);

            if (inheritedProperty == null)
            {
                return pi;
            }

            pi = inheritedProperty;
        }
    }

Здесь я предполагаю, что свойство / метод, использующий ключевое слово 'new', также не будет использовать виртуальное ключевое слово, видя какВ любом случае, 'new' - это крайний случай, это маловероятно.

Это все, что я получил до того, как решил, что он прошел мои тесты, и я был доволен этим.(И до того, как я решил просто выбрать структурную проверку ...) Я надеюсь, что это пригодится любому, кто столкнется с этим в будущем.

2 голосов
/ 10 января 2011

Я не совсем уверен, для чего вам это нужно, но я полагаю, что ваше определение равенства в этом случае таково: "Вызывает ли информация из двух методов один и тот же метод, если он вызывается"? Если это так, то вам действительно нужно указать тип в сравнении, например, рассмотрите это:

public interface IFoo
{
  void AMethod();
}

public interface IBar
{
  void AMethod();
}

public class FooBar : IFoo, IBar
{
  void AMethod();
}

Ясно, что typeof (IFoo) .GetMethod ("AMethod") и typeof (IBar) .GetMethod ("AMethod") не равны, они даже не связаны, НО они вызывают один и тот же метод на примере FooBar. Так что вам может понравиться метод сравнения, который принимает три аргумента:

bool WillInvokeSameMethodOnType(MethodInfo method1, MethodInfo method2, Type type)

Проверьте класс MethodInfoManager в FakeItEasy, это может быть то, что вы хотите: http://code.google.com/p/fakeiteasy/source/browse/Source/FakeItEasy/Core/MethodInfoManager.cs?r=8888fefbc508fb02d5435a3e33774500bec498b3

1 голос
/ 09 января 2011

Кажется, было бы проще проверить объявленные типы двух MemberInfo, которые вы хотите сравнить. В случае отношения базовый / подкласс, они должны представлять одну и ту же декларацию, если декларирующие типы одинаковы. В случае интерфейсов они должны быть одинаковыми, если объявленный тип интерфейса находится в списке интерфейсов другого:

Type type1 = methodInfo1.DeclaringType;
Type type2 = methodInfo2.DeclaringType;

bool same = type1 == type2 || 
    type1.IsInterface && type2.GetInterfaces.Contains(type1) ||
    type2.IsInterface && type1.GetInterfaces.Contains(type2);

Одной вещью, которую следует знать для интерфейсов, является «отображение интерфейса» - Type.GetInterfaceMap , что означает, что методы, объявленные в интерфейсе, могут не иметь того же имени в реализующем классе, как ваш текущий подход кажется, не принимать во внимание.

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