Matching an interface's ProperyInfo with a cla

2019-05-10 10:24发布

I use a method similar to the following to get some precomputed metadata related to a Type's properties.

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) { ... }

As you would expect, it works when used as follows:

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

But not when used in the following generic method:

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

It fails because the declaring type of property in the first method is now IEmployee, so the property in the lambda doesn't match the property in the type. How can I get them to match, without relying on the names of the properties? (There can be multiple properties with the same name if interfaces are implemented explicitly, so p1.Name == p2.Name won't cut it).

3条回答
虎瘦雄心在
2楼-- · 2019-05-10 11:07

What you'd probably need is an InterfaceMapping. You can get that from the actual type by calling GetInterfaceMap(typeof(interface)), i.e.,

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

Now, the mapping will contain the fields InterfaceMethods which will contain the methods you see when reflecting the interface, and TargetMethods which are the class's implementing methods. Note that this maps the the getter methods from the interface to the getter methods from the target class. You'll need to find the proper interface property by mapping the getter method of the various properties of the class to the found getter method.

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

Caution: Note the use of BindingFlags.NonPublic and GetGetMethod(true) here, for accessing private members. If you've got an explicit interface implementation, there isn't really a public property matching the interface's property, instead there is a private property named Some.NameSpace.IEmployee.Name that is mapped (which is, of course, your explicit implementation).

When you've found the right property, you can just call

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

and you've got yourself a lambda expression that uses the class's properties rather than the interface's properties.

查看更多
Fickle 薄情
3楼-- · 2019-05-10 11:08

You'll need to get the member name from the lambda expression, and use reflection to get that member off of the type you've been given:

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.");
    }
}

Here's proof that it works:

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);
}

Console output:

Bill
查看更多
beautiful°
4楼-- · 2019-05-10 11:21

Does BindingFlags.FlattenHierarchy work? If not, you could always iterate through typeof(T).GetInterfaces and call GetProperties on each of them.

查看更多
登录 后发表回答