2010-05-15 34 views
7

Nó là tương đối dễ dàng để tạo một hàm lambda mà sẽ trả về giá trị của một tài sản từ một đối tượng, thậm chí bao gồm cả tài sản sâu ...Tạo hành động lambda từ biểu hiện chức năng

Func<Category, string> getCategoryName = new Func<Category, string>(c => c.Name); 

và điều này có thể được gọi là sau ...

string categoryName = getCategoryName(this.category); 

Nhưng, cho chỉ chức năng kết quả trên (hoặc các biểu hiện ban đầu được dùng để tạo ra các chức năng), ai cũng có thể cung cấp một cách dễ dàng để tạo ra các hành động đối lập ...

Action<Category, string> setCategoryName = new Action<Category, string>((c, s) => c.Name = s);

... mà sẽ cho phép giá trị tài sản tương tự được thiết lập như sau?

setCategoryName(this.category, ""); 

Lưu ý rằng tôi đang tìm cách tạo hành động theo chương trình từ hàm hoặc biểu thức - Tôi hy vọng rằng tôi đã chỉ ra rằng tôi đã biết cách tạo thủ công.

Tôi mở cửa cho các câu trả lời hoạt động ở cả hai .net 3.5 và 4.0.

Cảm ơn.

UPDATE:

Có lẽ tôi không được rõ ràng trong câu hỏi của tôi, vì vậy hãy để tôi cố gắng và chứng minh rõ ràng hơn những gì tôi đang cố gắng làm.

tôi có phương pháp sau đây (mà tôi đã tạo ra cho mục đích của câu hỏi này) ...

void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) { 

    Func<TObject, TValue> getValue = expression.Compile(); 
    TValue stuff = getValue(obj); 

    Expression<Action<TObject, TValue>> assignmentExpression = (o, v) => Expression<TObject>.Assign(expression, Expression.Constant(v, typeof(TValue))); 
    Action<TObject, TValue> setValue = assignmentExpression.Compile(); 

    setValue(obj, stuff); 

} 

Những gì tôi đang tìm kiếm là làm thế nào để tạo ra "assignmentExpression" trong mã để Tôi có thể biên dịch nó thành setValue? Tôi hình nó có liên quan đến Expression.Assign, nhưng tôi chỉ đơn giản là không thể làm việc ra sự kết hợp chính xác của các tham số để hoàn thành mã.

Kết quả cuối cùng là để có thể gọi

Category category = *<get object from somewhere>*; 
this.DoLambdaStuff(category, c => c.Name); 

và điều này đến lượt nó sẽ tạo ra một phương thức getter và setter cho "Tên" tài sản của đối tượng loại.

Phiên bản ở trên biên dịch, nhưng khi tôi gọi setValue(), kết quả là một ArgumentException với "Biểu thức phải có khả năng ghi".

Xin cảm ơn một lần nữa.

+0

Tôi không thực sự hiểu những gì bạn có nghĩa là bằng cách làm nó tự động chứ không phải bằng tay. Tuy nhiên, nếu bạn muốn thiết lập các thuộc tính và quyết định thuộc tính nào bạn muốn đặt trong thời gian chạy, bạn nên sử dụng sự phản chiếu. Hơn nữa, bạn có thể sử dụng cây biểu thức để xây dựng các biểu thức lambda thời gian chạy. – Henri

Trả lời

0

Điều này sẽ có thể sử dụng expression trees, có thể được tạo từ biểu thức lambda, sửa đổi và sau đó được biên dịch thành đại biểu.

+5

Đây là loại điều tôi đang tìm kiếm, tôi chỉ hy vọng cho một ví dụ! –

3

Ok, mã Tôi đang tìm kiếm một cái gì đó đi như thế này ...

ParameterExpression objectParameterExpression = Expression.Parameter(
    typeof(TObject)), 
    valueParameterExpression = Expression.Parameter(typeof(TValue) 
); 
Expression<Action<TObject, TValue>> setValueExpression = Expression.Lambda<Action<TObject, TValue>>(
    Expression.Block(
    Expression.Assign(
     Expression.Property(
     objectParameterExpression, 
     ((MemberExpression) expression.Body).Member.Name 
    ), 
     valueParameterExpression 
    ) 
), 
    objectParameterExpression, 
    valueParameterExpression 
); 
Action<TObject, TValue> setValue = setValueExpression.Compile(); 

Mã này hoạt động, nhưng chỉ đối với tài sản cạn (có nghĩa là, tài sản của đối tượng trực tiếp) nhưng không làm việc cho thuộc tính sâu - mặc dù hàm getter có thể làm việc cho các thuộc tính sâu. Sẽ rất thú vị nếu biết ai đó có thể giúp tôi sửa đổi điều này để làm việc với những đặc tính sâu sắc nhưng tôi sẽ nâng cao điều này như một câu hỏi riêng biệt.

+2

Mã này chỉ hoạt động trong .NET 4 vì nó yêu cầu hỗ trợ được cải thiện cho các cây biểu thức được giới thiệu trong .NET 4. –

2

Như Martin đã lưu ý ở trên, các thuộc tính "sâu" phá vỡ giải pháp của mình. Lý do tại sao nó không hoạt động là biểu thức này:

Expression.Property(
    objectParameterExpression 
, ((MemberExpression)expression.Body).Member.Name 
) 

Lý do cho nó không phải là ngay lập tức rõ ràng: lớp được thừa kế tuyên bố getter tài sản riêng của mình, mà chuyển hướng đến các lớp cơ sở, nhưng không xác định một setter tương ứng .

Để giải quyết vấn đề này, bạn cần định vị thuộc tính thông qua sự phản chiếu, tìm kiểu khai báo của nó và sau đó nhận thuộc tính tương ứng từ người khai báo. Không giống như thuộc tính mà bạn nhận được trên lớp dẫn xuất, thuộc tính từ người khai báo có một trình lấy và một bộ chuyển đổi, và do đó có thể gán được. (Điều này giả định rằng một setter của thuộc tính được khai báo ở tất cả: nếu người khai báo không cung cấp một setter, thì không ai khác có thể cung cấp nó.)

Đây là một triển khai về xương giải quyết vấn đề này. Thay thế cuộc gọi ở trên thành Expression.Property bằng cuộc gọi GetPropertyOrField bên dưới và giải pháp của Martin sẽ hoạt động.

private static MemberExpression GetPropertyOrField(Expression baseExpr, string name) { 
    if (baseExpr == null) { 
     throw new ArgumentNullException("baseExpr"); 
    } 
    if (string.IsNullOrWhiteSpace(name)) { 
     throw new ArgumentException("name"); 
    } 
    var type = baseExpr.Type; 
    var properties = type 
     .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
     .Where(p => p.Name.Equals(name)) 
     .ToArray(); 
    if (properties.Length == 1) { 
     var res = properties[0]; 
     if (res.DeclaringType != type) { 
      // Here is the core of the fix: 
      var tmp = res 
       .DeclaringType 
       .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
       .Where(p => p.Name.Equals(name)) 
       .ToArray(); 
      if (tmp.Length == 1) { 
       return Expression.Property(baseExpr, tmp[0]); 
      } 
     } 
     return Expression.Property(baseExpr, res); 
    } 
    if (properties.Length != 0) { 
     throw new NotSupportedException(name); 
    } 
    var fields = type 
     .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
     .Where(p => p.Name.Equals(name)) 
     .ToArray(); 
    if (fields.Length == 1) { 
     return Expression.Field(baseExpr, fields[0]); 
    } 
    if (fields.Length != 0) { 
     throw new NotSupportedException(name); 
    } 
    throw new ArgumentException(
     string.Format(
      "Type [{0}] does not define property/field called [{1}]" 
     , type 
     , name 
     ) 
    ); 
} 
5
void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) { 

    Func<TObject, TValue> getValue = expression.Compile(); 
    TValue stuff = getValue(obj); 

    var p = Expression.Parameter(typeof(TValue), "v"); 
    Expression<Action<TObject, TValue>> assignmentExpression = 
     Expression.Lambda<Action<TObject, TValue>>(Expression.Assign(expression.Body, p), expression.Parameters[0], p); 

    Action<TObject, TValue> setValue = assignmentExpression.Compile(); 

    setValue(obj, stuff); 
} 
+0

Bạn có thể giải thích mã của bạn làm gì không? – Math

+0

+1, đơn giản như vậy. – nawfal

+1

+1. Sử dụng mã này tôi có thể thiết lập giá trị cho 'trường private' và' thuộc tính riêng lồng nhau'. Kiểu trường/thuộc tính có thể là một 'struct' và nó chỉ hoạt động. Sẽ thực sự yêu một lời giải thích về cách thức hoạt động của nó. –

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