Сопоставление ProperyInfo интерфейса с PropertyInfo класса - PullRequest
3 голосов
/ 20 июля 2010

Я использую метод, подобный следующему, чтобы получить некоторые предварительно вычисленные метаданные, связанные со свойствами Типа.

MyData GetProperty<T, U>(Expression<Func<T, U>> member)
{
    // Get the property referenced in the lambda expression
    MemberExpression expression = member.Body as MemberExpression;
    PropertyInfo property = expression.Member as PropertyInfo;

    // get the properties in the type T
    PropertyInfo[] candidates = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

    // Find the match
    foreach (PropertyInfo candidate in candidates)
        if (candidate == property)
            return GetMetaData<T>(candidate);
    throw new Exception("Property not found.");
}

// Returns precomputed metadata
MyData GetMetaData<T>(PropertyInfo property) { ... }

Как и следовало ожидать, он работает, когда используется следующим образом:

var data = PropertyInfo((Employee e) => e.Name);

Но не при использовании в следующем универсальном методе:

void MyGenericMethod<T>(int id) where T : IEmployee
{
    var data = PropertyInfo((T e) => e.Name);
}

Сбой, потому что объявленный тип property в первом методе теперь IEmployee, поэтому свойство в лямбда-выражении несопоставить свойство в типе. Как я могу заставить их соответствовать , не полагаясь на имена свойств?(Может быть несколько свойств с одним и тем же именем, если интерфейсы реализованы явно, поэтому p1.Name == p2.Name не будет его сокращать).

Ответы [ 3 ]

6 голосов
/ 20 июля 2010

Возможно, вам понадобится InterfaceMapping . Вы можете получить это из фактического типа, вызвав GetInterfaceMap(typeof(interface)), т.е.

InterfaceMapping mapping = typeof(Employee).GetInterfaceMap(typeof(IEmployee));

Теперь отображение будет содержать поля InterfaceMethods, которые будут содержать методы, которые вы видите при отражении интерфейса, и TargetMethods, которые являются методами реализации класса. Обратите внимание, что это отображает методы получения из интерфейса на методы получения из целевого класса. Вам нужно будет найти правильное свойство интерфейса, сопоставив метод получения различных свойств класса с найденным методом получения.

Type interfaceType = typeof(IEmployee);
Type classType = typeof(Employee);
PropertyInfo nameProperty = interfaceType.GetProperty("Name");

MethodInfo nameGetter = nameProperty.GetGetMethod();
InterfaceMapping mapping = classType.GetInterfaceMap(interfaceType);

MethodInfo targetMethod = null;
for (int i = 0; i < mapping.InterfaceMethods.Length; i++)
{
    if (mapping.InterfaceMethods[i] == nameGetter)
    {
        targetMethod = mapping.TargetMethods[i];
        break;
    }
}

PropertyInfo targetProperty = null;
foreach (PropertyInfo property in classType.GetProperties(
    BindingFlags.Instance | BindingFlags.GetProperty | 
    BindingFlags.Public | BindingFlags.NonPublic))   // include non-public!
{
    if (targetMethod == property.GetGetMethod(true)) // include non-public!
    {
        targetProperty = property;
        break;
    }
}

// targetProperty is the actual property

Внимание: Обратите внимание на использование BindingFlags.NonPublic и GetGetMethod(true) здесь для доступа к закрытым членам. Если у вас есть явная реализация интерфейса, на самом деле нет открытого свойства, соответствующего свойству интерфейса, вместо этого есть частное свойство с именем Some.NameSpace.IEmployee.Name, которое отображается (что, конечно, является вашей явной реализацией).

Когда вы нашли нужную недвижимость, вы можете просто позвонить

ParameterExpression p = Expression.Parameter("e", typeof(T));
Expression<Func<T, U>> lambda = Expression.Lambda<Func<T, U>>(
    Expression.Property(p, targetProperty), p);

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

0 голосов
/ 20 июля 2010

Вам нужно будет получить имя члена из лямбда-выражения и использовать отражение, чтобы получить этот член из типа, который вам дали:

public static PropertyInfo PropInfo<TContainer, TMember>(
    Expression<Func<TContainer, TMember>> memberGetter)
{
    var memberName = GetExpressionMemberName(memberGetter);
    return typeof(TContainer).GetProperty(memberName);
}

public static string GetExpressionMemberName<TContainer, TMember>(
    Expression<Func<TContainer, TMember>> memberGetter)
{
    var expressionType = memberGetter.Body.NodeType;
    switch (expressionType)
    {
        case ExpressionType.MemberAccess:
            {
                var memberExpr = (MemberExpression) memberGetter.Body;
                return memberExpr.Member.Name;
            }
        case ExpressionType.Convert:
            {
                var convertExpr = (UnaryExpression) memberGetter.Body;
                var memberExpr = (MemberExpression) convertExpr.Operand;
                return memberExpr.Member.Name;
            }
        default:
            throw new InvalidOperationException("Expression {0} does not represent a simple member access.");
    }
}

Вот доказательство того, что это работает:

void Main()
{
    Console.WriteLine(
        MyGenericMethod<Employee>()
            .GetGetMethod()
                .Invoke(
                    new Employee {Name = "Bill"}, 
                    new object[] {}));
}

public class Employee : IEmployee {
    public string Name {get;set;} 
    string IEmployee.Name { get { throw new Exception(); } } 
}
public interface IEmployee {string Name {get;}}

public PropertyInfo MyGenericMethod<T>() where T : IEmployee
{
    return PropInfo((T e) => e.Name);
}

Выход на консоль:

Bill
0 голосов
/ 20 июля 2010

Работает ли BindingFlags.FlattenHierarchy? Если нет, вы всегда можете перебрать typeof(T).GetInterfaces и вызвать GetProperties для каждого из них.

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