开发者

Matching an interface's ProperyInfo with a class's PropertyInfo

开发者 https://www.devze.com 2023-01-08 12:14 出处:网络
I use a method similar to the following to get some pr开发者_如何学Goecomputed metadata related to a Type\'s properties.

I use a method similar to the following to get some pr开发者_如何学Goecomputed 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).


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.


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


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
0

精彩评论

暂无评论...
验证码 换一张
取 消