2009-11-03 15 views
29

Zajmuję się tworzeniem interfejsu API, który używa wyrażeń lambda do określania właściwości. Używam tego słynnego fragmentu kodu podobnego do tego (to jest uproszczony i niepełny, żeby wyjaśnić, co mam na myśli):C#: Pobieranie nazw właściwości w łańcuchu z wyrażenia lambda

public void Foo<T, P>(Expression<Func<T, P>> action) 
{ 
    var expression = (MemberExpression)action.Body; 
    string propertyName = expression.Member.Name; 
    // ... 
} 

Aby nazwać tak:

Foo((String x) => x.Length); 

teraz chciałbym, aby określić ścieżkę nieruchomości przez łańcuchowym nazwy właściwości, na przykład:

Foo((MyClass x) => x.Name.Length); 

Foo powinien móc podzielić ścieżkę do jego nazwy własności ("Name" i "Length"). Czy jest jakiś sposób na zrobienie tego przy rozsądnym wysiłku?


Jest somehow similar looking question, ale myślę, że starają się łączyć wyrażenia lambda tam.

Another question również zajmuje się nazwami zagnieżdżonych właściwości, ale tak naprawdę nie rozumiem, o czym mówią.

Odpowiedz

27

Coś takiego?

public void Foo<T, P>(Expression<Func<T, P>> expr) 
{ 
    MemberExpression me; 
    switch (expr.Body.NodeType) 
    { 
     case ExpressionType.Convert: 
     case ExpressionType.ConvertChecked: 
      var ue = expr.Body as UnaryExpression; 
      me = ((ue != null) ? ue.Operand : null) as MemberExpression; 
      break; 
     default: 
      me = expr.Body as MemberExpression; 
      break; 
    } 

    while (me != null) 
    { 
     string propertyName = me.Member.Name; 
     Type propertyType = me.Type; 

     Console.WriteLine(propertyName + ": " + propertyType); 

     me = me.Expression as MemberExpression; 
    } 
} 
+1

Wow, to działa, i to jest dość proste. Wielkie dzięki! –

+1

@StefanSteinegger Stare pytanie, wiem ... ale jeśli to tylko nazwy, których potrzebujesz, 'expr.ToString(). Split (". '). Pomiń (1) 'byłoby jeszcze prostsze :) – asgerhallas

+2

@asgerhallas: ty może dodać kolejną odpowiedź. –

11

Stare pytanie, wiem ... ale jeśli to tylko nazwy trzeba, jeszcze prostszy sposób to zrobić to:

expr.ToString().Split('.').Skip(1) 

EDIT:

public class A 
{ 
    public B Property { get; set; } 
} 

public class B 
{ 
    public C field; 
} 

[Fact] 
public void FactMethodName() 
{ 
    var exp = (Expression<Func<A, object>>) (x => x.Property.field); 
    foreach (var part in exp.ToString().Split('.').Skip(1)) 
     Console.WriteLine(part); 

    // Output: 
    // Property 
    // field 
} 
+0

Hmmm, to nie zadziałało dla mnie (".ToString' dał tylko ostatnią nazwę nieruchomości). Czy masz większą próbkę kodu z użyciem? – Pat

+0

@Pat Edytowałem w pewnym działającym kodzie. Nadzieja, która pomaga. Choć trochę późno :) – asgerhallas

+0

'ToString' nie zadziała w przypadku, gdy wartości są boksowane, oprócz bycia bardzo powolnym. Po prostu uważaj. – nawfal

11

Grałem trochę z ExpressionVisitor:

public static class PropertyPath<TSource> 
{ 
    public static IReadOnlyList<MemberInfo> Get<TResult>(Expression<Func<TSource, TResult>> expression) 
    { 
     var visitor = new PropertyVisitor(); 
     visitor.Visit(expression.Body); 
     visitor.Path.Reverse(); 
     return visitor.Path; 
    } 

    private class PropertyVisitor : ExpressionVisitor 
    { 
     internal readonly List<MemberInfo> Path = new List<MemberInfo>(); 

     protected override Expression VisitMember(MemberExpression node) 
     { 
      if (!(node.Member is PropertyInfo)) 
      { 
       throw new ArgumentException("The path can only contain properties", nameof(node)); 
      } 

      this.Path.Add(node.Member); 
      return base.VisitMember(node); 
     } 
    } 
} 

Zastosowanie:

var path = string.Join(".", PropertyPath<string>.Get(x => x.Length).Select(p => p.Name)); 
+0

Dziękujemy za wyrażenie ExpressionVisitor. To było naprawdę czyste rozwiązanie. Osobiście utworzę instancję PathVisitor na wywołanie metody zamiast używać blokady, ale doktorzy nie mówią nic o zaleceniach tak czy inaczej, czy jest to ciężki obiekt do stworzenia. Nie ma funkcji Dispose(), więc oznacza dla mnie, że nie ma wielu zasobów. – angularsen

+0

Dodałem poprawioną wersję bez blokowania, przeniesiono ogólne parametry do klasy, więc wystarczy podać TSource i wygodną metodę, która zwraca ciąg z konfigurowalnym separatorem ciągów: https://gist.github.com/anjdreas/862c1cd9983d7525d2ddee0bb2706c3a – angularsen

+0

Tak , blokowanie jest tam głupie, aktualizując odpowiedź. –