2009-11-03 34 views
29

Tôi đang phát triển một API sử dụng các biểu thức lambda để chỉ định các thuộc tính. Tôi đang sử dụng mảnh nổi tiếng mã này tương tự với trang này (điều này là đơn giản và không đầy đủ, chỉ để làm những gì rõ ràng tôi đang nói về):C#: Lấy tên các thuộc tính trong một chuỗi từ biểu thức lambda

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

Để được gọi là như thế này:

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

Bây giờ tôi muốn chỉ định đường dẫn bất động sản bằng cách kết nối tên thuộc tính, như thế này:

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

Foo sẽ có thể để phân chia các đường dẫn vào tên tài sản của mình ("Name""Length"). Có cách nào để làm điều này với nỗ lực hợp lý không?


Có một somehow similar looking question, nhưng tôi nghĩ họ đang cố kết hợp các biểu thức lambda ở đó.

Another question cũng đang xử lý tên thuộc tính lồng nhau, nhưng tôi không thực sự hiểu những gì họ đang nói đến.

Trả lời

27

Một cái gì đó như thế này?

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

Chà, công trình này và nó khá đơn giản. Cảm ơn rất nhiều! –

+1

@StefanSteinegger Câu hỏi cũ, tôi biết ... nhưng nếu nó chỉ là tên bạn cần, 'expr.ToString(). Split ('.'). Skip (1)' sẽ đơn giản hơn :) – asgerhallas

+2

@asgerhallas: bạn có thể thêm một câu trả lời khác. –

11

Cũ câu hỏi, tôi biết ... nhưng nếu nó chỉ có tên mà bạn cần, một cách đơn giản hơn để làm điều đó là:

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, không hoạt động đối với tôi ('. ToString' chỉ cung cấp tên thuộc tính cuối cùng). Bạn có mẫu mã lớn hơn với mức sử dụng không? – Pat

+0

@Pat Tôi đã chỉnh sửa trong một số mã hoạt động. Hy vọng rằng sẽ giúp. Mặc dù hơi trễ một chút :) – asgerhallas

+0

'ToString' sẽ không hoạt động trong trường hợp các loại giá trị được đóng hộp, ngoài việc bị chậm khủng khiếp. Hãy cẩn thận. – nawfal

11

Tôi đã chơi một chút với 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); 
     } 
    } 
} 

Cách sử dụng:

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

Cảm ơn ExpressionVisitor. Đây là một giải pháp thực sự sạch sẽ. Cá nhân tôi sẽ tạo một cá thể PathVisitor cho mỗi cuộc gọi phương thức thay vì sử dụng khóa, nhưng các tài liệu không nói bất cứ điều gì về các đề xuất theo cách hoặc nếu nó là một đối tượng nặng để tạo ra. Nó không có một Dispose() để cho tôi biết nó không chứa nhiều tài nguyên. – angularsen

+0

Tôi đã thêm một phiên bản sửa đổi không có khóa, chuyển các tham số chung vào lớp, do đó bạn chỉ cần chỉ định TSource và phương thức tiện lợi trả về một chuỗi có dấu phân cách chuỗi: https://gist.github.com/anjdreas/862c1cd9983d7525d2ddee0bb2706c3a – angularsen

+0

Yeah , khóa là câm ở đó, cập nhật câu trả lời. –

Các vấn đề liên quan