2010-05-15 160 views
7

這是比較容易地創建一個lambda函數將從對象返回屬性的值,甚至包括深性質拉姆達行動......創建一個從函數表達式

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

,這可以被稱爲如下...

string categoryName = getCategoryName(this.category); 

但是,鑑於上述所得的函數(或原本用於創建函數的表達式),任何人可以提供一種簡單的方法來建立相對的動作......

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

...這將使相同的屬性值,如下所示進行設置?

setCategoryName(this.category, ""); 

請注意,我在尋找一種方式,從功能或表達編程方式創建的動作 - 我希望我已經表明我已經知道如何手動創建它。

我很樂意接受在.net 3.5和4.0中工作的答案。

謝謝。

UPDATE:

也許我不是在清楚我的問題,所以讓我嘗試和更清楚地表明什麼,我試圖做的。

我有以下的方法(即我對這個問題的目的而創建)...

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

} 

我在找的是我怎麼在代碼中創建「assignmentExpression」,使我可以將它編譯成setValue?我認爲它與Expression.Assign有關,但我根本無法制定正確的參數組合來完成代碼。

最終的結果是能夠調用

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

,這反過來將創造一個getter和類別對象的「名稱」屬性的設置。

上面的版本編譯,但是當我調用setValue()時,它會導致一個ArgumentException,並且「表達式必須是可寫的」。

再次感謝。

+0

我真的不明白你這樣做手動自動反對的意思。但是,如果要設置屬性並決定要在運行時設置哪個屬性,則應使用反射。而且,您可以使用表達式樹來構建運行時lambda表達式。 – Henri 2010-05-15 16:27:01

回答

0

這應該是可能的使用expression trees,它可以從lambda表達式創建,修改,然後編譯成一個委託。

+5

這就是我正在尋找的東西,我只是希望有一個例子! – 2010-05-15 17:04:07

3

好吧,我找的代碼是這樣的......

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

此代碼的工作,但僅限於淺層的屬性(也就是眼前的對象的屬性),但不工作深層屬性 - 儘管吸氣功能可以用於深層屬性。知道是否有人能夠幫助我修改這個以適應深層的屬性會很有趣,但我會提出這個問題作爲一個單獨的問題。

+2

此代碼僅適用於.NET 4,因爲它需要改進對.NET 4中引入的表達式樹的支持。 – 2010-08-16 08:46:56

2

正如馬丁在上面提到的那樣,「深」特性打破了他的解決方案。爲什麼它不工作的原因是這樣的表達:

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

它的原因是不會立即明顯的:派生類中聲明瞭自己的財產getter,它重定向到基類,但沒有定義的相應設置方法。

爲了解決此問題,您需要通過反射找到屬性,查找其聲明類型,然後從聲明器中獲取相應的屬性。與您在派生類中獲得的屬性不同,聲明器中的屬性具有getter和setter,因此是可賦值的。 (這假設財產的設置者完全被聲明:如果申報者沒有提供setter,那麼沒有其他人可以提供它。)

這是一個解決此問題的框架實現。將上述呼叫替換爲Expression.Property,呼叫號碼爲GetPropertyOrField,Martin的解決方案即將生效。

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

您能解釋一下您的代碼是做什麼的? – Math 2013-11-21 18:53:53

+0

+1,就這麼簡單。 – nawfal 2014-01-01 02:32:42

+1

+1。使用這段代碼,我可以爲'private fields'和'nested private property'設置值。字段/屬性的類型也可以是'struct',它只是起作用。真的很喜歡解釋這是如何工作的。 – 2014-03-05 22:16:24