Как я могу сопоставить два свойства PropertyInfos или методы?

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

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

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

(propertyOne == propertyTwo)

или

(methodOne == methodTwo)

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

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

  • Свойства/методы, объявленные на интерфейсах и реализованные в классах
  • Свойства/методы, объявленные в базовом классе (фактически) и переопределенные на производных классах
  • Свойства/методы, объявленные в базовом классе, переопределенные "новым" (в мире IL это ничего особенного 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 появляется только для того, чтобы вернуть null!

Ответ 1

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

  • Нет прямого способа увидеть, какой класс/интерфейс было объявлено ранее.
  • Поэтому, чтобы проверить, действительно ли свойство было объявлено в классе предков, нам нужно выполнить итерацию над предками и посмотреть, существует ли на них свойство.
  • То же самое касается интерфейсов, нам нужно позвонить 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

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

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

Ответ 3

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

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

Есть несколько отличий, хотя, главным образом, что нужно посмотреть на карту интерфейса вместо вызова 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;
    } 

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

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', также не будет использовать ключевое слово virtual, поскольку в любом случае "новый" - это бит-кромка, это маловероятно.

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

Ответ 4

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

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, что означает, что методы, объявленные в интерфейсе, могут не имеют то же имя в классе реализации, о котором ваш текущий подход, похоже, не учитывает.