2010-07-19 5 views
2

J'utilise une méthode similaire à la suivante pour obtenir des métadonnées précalculées liées aux propriétés d'un type.Correspondance d'une propriété ProperyInfo avec une propriété PropertyInfo

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

Comme on peut s'y attendre, il fonctionne lorsqu'il est utilisé comme suit:

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

Mais pas quand il est utilisé dans la méthode générique suivante:

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

Il échoue parce que le type déclarant de property dans la première méthode est maintenant IEmployee, donc la propriété dans le lambda ne correspond pas à la propriété dans le type. Comment puis-je les faire correspondre, sans compter sur les noms des propriétés? (Il peut y avoir plusieurs propriétés avec le même nom si les interfaces sont implémentées explicitement, donc p1.Name == p2.Name ne le coupera pas).

Répondre

5

Ce que vous auriez probablement besoin est un InterfaceMapping. Vous pouvez obtenir que du type réel en appelant GetInterfaceMap(typeof(interface)), à savoir

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

Maintenant, la mise en correspondance contiendra les champs InterfaceMethods qui contiendra les méthodes que vous voyez en reflétant l'interface et TargetMethods qui appliquent les méthodes de la classe . Notez que cela associe les méthodes getter de l'interface aux méthodes getter de la classe cible. Vous devrez trouver la propriété d'interface appropriée en mappant la méthode getter des différentes propriétés de la classe à la méthode getter trouvée.

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 

Attention: Notez l'utilisation de BindingFlags.NonPublic et GetGetMethod(true) ici, pour accéder à des membres privés. Si vous avez une implémentation d'interface explicite, il n'y a pas vraiment de propriété publique correspondant à la propriété de l'interface, mais une propriété privée nommée Some.NameSpace.IEmployee.Name qui est mappée (ce qui est bien sûr votre implémentation explicite).

Lorsque vous avez trouvé la bonne propriété, vous pouvez simplement appeler

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

et vous avez vous-même une expression lambda qui utilise les propriétés de la classe plutôt que les propriétés de l'interface.

+0

Ce n'est pas joli, mais 'InterfaceMapping' a fait l'affaire. Merci. – Greg

0

Est-ce que BindingFlags.FlattenHierarchy fonctionne? Sinon, vous pouvez toujours parcourir typeof(T).GetInterfaces et appeler le GetProperties sur chacun d'eux.

+0

Je peux obtenir l'interface en appelant la propriété.DeclaringType, l'astuce consiste à trouver un moyen rapide d'identifier le PropertyInfo (des candidats) qui représente l'implémentation de la propriété de l'interface. – Greg

0

Vous aurez besoin d'obtenir le nom de membre de l'expression lambda, et utiliser la réflexion pour obtenir ce membre hors du type que vous avez reçu:

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

est ici la preuve que cela fonctionne:

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

sortie de la console:

Bill 
Questions connexes