Я использую
public static bool IsSameAsProperty(PropertyInfo first, PropertyInfo second) =>
first.DeclaringType == second.DeclaringType && first.Name == second.Name;
Чтобы определить, соответствует ли отраженная информация о свойствах какому-либо свойству, я выбрал базовый класс.
Этот подход начал разваливаться, когда я пытаюсь ссылаться на свойства, определенные в интерфейсах.
Например, представьте следующий сценарий многоинтерфейсного наследования:
interface IAnimal : { bool IsHungry { get; } }
interface IDog : IAnimal { }
abstract class Animal : IAnimal { public bool IsHungry { get; set; } }
class Dog : Animal, IDog { }
Если я создаю выражения свойств, все перечисленные ниже действительны:
Expression<Func<object, bool>> propertyExpression;
propertyExpression = (IAnimal animal) => animal.IsHungry
propertyExpression = (Animal animal) => animal.IsHungry
propertyExpression = (IDog dog) => dog.IsHungry
propertyExpression = (Dog dog) => dog.IsHungry
Поскольку каждый из этих типов определяет или наследует свойство IsHungry
, все эти выражения допустимы. Можно даже утверждать, что все они ссылаются на одно и то же свойство (хотя я могу оценить тонкие различия между интерфейсом и объявлением экземпляра).
Моя проблема в том, что я хочу каким-то способом программно определить, что все эти свойства "исходят" из общего интерфейса IAnimal
и являются совместимыми. К сожалению, мой тест возвращает false
потому что:
IDog.IsHungry
имеет IDog.IsHungry
DeclaringType == typeof(IAnimal)
тогда какDog.IsHungry
имеет Dog.IsHungry
DeclaringType == typeof(Animal)
Я не могу придумать простой способ сравнения выражений интерфейса и свойств конкретного типа, не прибегая к простому сравнению Name
(которое склонно к ложным срабатываниям) - но я не могу придумать ничего, что не включает перечисление всех интерфейсы, унаследованные этими двумя типами и ищущие что-либо с таким именем свойства, которое есть в обоих наборах.
Вопрос: Можем ли мы создать функцию, которая возвращает true при сравнении любого из PropertyInfo, полученного из приведенных выше 4 выражений свойств. (например, определить, что все они представляют/реализуют одно и то же свойство базового интерфейса?)
Решение, которое я придумал, сравнивает метод получения/установки MethodInfo
с InterfaceMapping
. Он проходит все тесты, о которых я только мог подумать, но я не уверен на 100%, что не бывает странных угловых случаев, которых это не улавливает.
public static bool IsSameAsProperty(PropertyInfo first, PropertyInfo second)
{
if (first.DeclaringType == second.DeclaringType && first.Name == second.Name)
return true;
bool firstIsSecond = second.DeclaringType.IsAssignableFrom(first.DeclaringType);
bool secondIsFirst = first.DeclaringType.IsAssignableFrom(second.DeclaringType);
if (firstIsSecond || secondIsFirst)
{
PropertyInfo baseProp = firstIsSecond ? second : first;
PropertyInfo derivedProp = firstIsSecond ? first : second;
MethodInfo baseMethod, implMethod;
if (baseProp.GetMethod != null && derivedProp.GetMethod != null)
{
baseMethod = baseProp.GetMethod;
implMethod = derivedProp.GetMethod;
}
else if (baseProp.SetMethod != null && derivedProp.SetMethod != null)
{
baseMethod = baseProp.SetMethod;
implMethod = derivedProp.SetMethod;
}
else
{
return false;
}
// Is it somehow possible to create a situation where both get and set exist
// and the set method to be an implementation while the get method is not?
if (baseMethod.DeclaringType.IsInterface)
return IsInterfaceImplementation(implMethod, baseMethod);
else
return IsOverride(implMethod, baseMethod);
}
return false;
}
private static bool IsInterfaceImplementation(MethodInfo implMethod, MethodInfo interfaceMethod)
{
InterfaceMapping interfaceMap = implMethod.DeclaringType.GetInterfaceMap(interfaceMethod.DeclaringType);
int index = Array.IndexOf(interfaceMap.InterfaceMethods, interfaceMethod);
// I don't think this can ever be the case?
if (index == -1)
return false;
MethodInfo targetMethod = interfaceMap.TargetMethods[index];
return implMethod == targetMethod || IsOverride(implMethod, targetMethod);
}
private static bool IsOverride(MethodInfo implMethod, MethodInfo baseMethod)
{
return implMethod.GetBaseDefinition() == baseMethod.GetBaseDefinition();
}
Это, вероятно, приведет к ложным срабатываниям в случае использования new
ключевого слова для скрытия унаследованных свойств, но:
public static bool IsSameAsProperty(PropertyInfo first, PropertyInfo second) =>
first == second || // If default equals implementation returns true, no doubt
first.Name == second.Name && (
first.DeclaringType == second.DeclaringType ||
first.DeclaringType.IsAssignableFrom(second.DeclaringType) ||
second.DeclaringType.IsAssignableFrom(first.DeclaringType));
Я полагаю, что я мог бы быть немного более конкретным и проверить, если first.DeclaringType.IsInterface
и наоборот, но все же можно явно реализовать этот интерфейс и скрыть его свойство с новым с тем же именем, поэтому дополнительная проверка не будет сделать это "безопаснее".
Не уверен, смогу ли я сказать, представляет ли один экземпляр PropertyInfo
реализацию интерфейса другого.