2009-11-03 20 views
29

Özellikleri belirtmek için lambda ifadeleri kullanan bir API geliştiriyorum.C#: Bir zincirdeki özelliklerin isimlerini bir lambda ifadesinden alma

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

böyle çağrılacak için::

Foo((String x) => x.Length); 
I (sadece bahsettiğim açıktır yapmak için bu basitleştirilmiş ve eksik olan) buna benzer kod bu ünlü parçası kullanıyorum

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

Foo kendi özellik adları içine yolunu bölmek gerekir ("Name" ve:

Şimdi bu gibi özellik adlarını zincirleme bir özellik yolunu belirtmek isteriz). Bunu makul bir çabayla yapmanın bir yolu var mı?


bir somehow similar looking question vardır, ama onlar orada lambda ifadeleri birleştirmek için çalışıyoruz düşünüyorum.

Another question ayrıca yuvalanmış özellik adları ile uğraşır, ancak ne hakkında konuştuklarını anlamıyorum.

cevap

27

Böyle bir şey mi var?

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

Vay, bu işleri ve oldukça basittir. Çok teşekkürler! –

+1

@StefanSteinegger Eski soru, biliyorum ... ama sadece ihtiyacınız olan isimler, 'expr.ToString(). Split ('.'). Atla (1)' daha basit olurdu :) – asgerhallas

+2

@asgerhallas: you başka bir cevap ekleyebilir. –

11

Eski bir soru, biliyorum ... ama sadece ihtiyacınız isimleri ise, bunu yapmak için daha basit bir yoludur:

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

DÜZENLEME:

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, bu benim için çalışmadı ('.ToString' sadece son özellik adını verdi). Kullanımda daha büyük bir kod örneğiniz var mı? – Pat

+0

@Pat I Bazı çalışma kodlarında düzenlenmiştir. Umarım yardımcı olur. Biraz geç olsa da :) – asgerhallas

+0

'ToString', çok yavaş olmanın dışında, kutulu değer türleri durumunda işe yaramaz. Sadece dikkat et. – nawfal

11

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); 
     } 
    } 
} 
ile biraz oynadım

Kullanımı:

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

ExpressionVisitor için teşekkürler. Bu gerçekten temiz bir çözümdü. Kilit kullanarak yerine yöntem çağrısı başına bir PathVisitor örneğini kişisel olarak oluşturabilirim, ancak dokümanlar önerilerle ilgili olarak ya bir yoldan ya da oluşturulacak ağır bir nesne değilse, bir şey söylemez. Bir Dispose() yoktur, bu bana çok fazla kaynak tutmuyor demektir. – angularsen

+0

Kilitlenmemiş bir sürümü değiştirdim, sınıfa genel paraşütleri getirdim, böylece TSource'u ve yapılandırılabilir dize ayracı ile bir dize döndüren bir kolaylık yöntemi belirtmeniz gerekiyor: https://gist.github.com/anjdreas/862c1cd9983d7525d2ddee0bb2706c3a – angularsen

+0

Evet Kilitleme orada aptal, cevabı günceller. –