2011-04-25 92 views
12

我正在玩弄將屬性分配作爲表達樹傳遞給方法的想法。該方法將調用表達式,以便正確分配屬性,然後嗅出剛分配的屬性名稱,以便引發PropertyChanged事件。我的想法是,我希望能夠在我的WPF ViewModels中使用超薄自動屬性,並且仍然觸發PropertyChanged事件。使用ExpressionTree分配屬性

我與ExpressionTrees一個無知的人,所以我希望有人能指出我在正確的方向:

public class ViewModelBase { 
    public event Action<string> PropertyChanged = delegate { }; 

    public int Value { get; set; } 

    public void RunAndRaise(MemberAssignment Exp) { 
     Expression.Invoke(Exp.Expression); 
     PropertyChanged(Exp.Member.Name); 
    } 
} 

的問題是我不知道如何調用它。這個天真的嘗試被拒絕了編譯器,我敢肯定將是顯而易見的任何原因誰可以回答這個問題:

 ViewModelBase vm = new ViewModelBase(); 

     vm.RunAndRaise(() => vm.Value = 1); 

編輯

謝謝@svick了完美的答案。我移動了一個小東西,並將其變爲擴展方法。下面是與單元測試的完整代碼示例:

[TestClass] 
public class UnitTest1 { 
    [TestMethod] 
    public void TestMethod1() { 
     MyViewModel vm = new MyViewModel(); 
     bool ValuePropertyRaised = false; 
     vm.PropertyChanged += (s, e) => ValuePropertyRaised = e.PropertyName == "Value"; 

     vm.SetValue(v => v.Value, 1); 

     Assert.AreEqual(1, vm.Value); 
     Assert.IsTrue(ValuePropertyRaised); 
    } 
} 


public class ViewModelBase : INotifyPropertyChanged { 
    public event PropertyChangedEventHandler PropertyChanged = delegate { }; 

    public void OnPropertyChanged(string propertyName) { 
     PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

public class MyViewModel : ViewModelBase { 
    public int Value { get; set; } 
} 

public static class ViewModelBaseExtension { 
    public static void SetValue<TViewModel, TProperty>(this TViewModel vm, Expression<Func<TViewModel, TProperty>> exp, TProperty value) where TViewModel : ViewModelBase { 
     var propertyInfo = (PropertyInfo)((MemberExpression)exp.Body).Member; 
     propertyInfo.SetValue(vm, value, null); 
     vm.OnPropertyChanged(propertyInfo.Name); 
    } 
} 

回答

7

你不能這樣做。首先,lambda表達式只能轉換爲委託類型或Expression<T>

如果將方法的簽名(現在忽略其實現)更改爲public void RunAndRaise(Expression<Action> Exp),則編譯器會抱怨「表達式樹可能不包含賦值運算符」。

您可以通過使用lambda指定屬性並將其設置爲另一個參數的值來實現。 此外,我沒有找到一種方法來從表達式中訪問vm的值,因此您必須將其放入另一個參數中(因爲您需要在表達式中使用適當的繼承類型,所以不能使用this) )看到編輯

public static void SetAndRaise<TViewModel, TProperty>(
    TViewModel vm, Expression<Func<TViewModel, TProperty>> exp, TProperty value) 
    where TViewModel : ViewModelBase 
{ 
    var propertyInfo = (PropertyInfo)((MemberExpression)exp.Body).Member; 
    propertyInfo.SetValue(vm, value, null); 
    vm.PropertyChanged(propertyInfo.Name); 
} 

另一種可能性(和一個我喜歡更多)是專門使用lambda這樣提高從二傳手事件:

private int m_value; 
public int Value 
{ 
    get { return m_value; } 
    set 
    { 
     m_value = value; 
     RaisePropertyChanged(this, vm => vm.Value); 
    } 
} 

static void RaisePropertyChanged<TViewModel, TProperty>(
    TViewModel vm, Expression<Func<TViewModel, TProperty>> exp) 
    where TViewModel : ViewModelBase 
{ 
    var propertyInfo = (PropertyInfo)((MemberExpression)exp.Body).Member; 
    vm.PropertyChanged(propertyInfo.Name); 
} 

這樣,您就可以使用像往常一樣的屬性,你co如果你有它們,它也會爲計算出的屬性引發事件。

編輯:在閱讀過Matt Warren's series about implementing IQueryable<T>,我意識到我可以訪問的參考價值,從而簡化了RaisePropertyChanged()使用(雖然它不會幫助很多與你SetAndRaise()):

private int m_value; 
public int Value 
{ 
    get { return m_value; } 
    set 
    { 
     m_value = value; 
     RaisePropertyChanged(() => Value); 
    } 
} 

static void RaisePropertyChanged<TProperty>(Expression<Func<TProperty>> exp) 
{ 
    var body = (MemberExpression)exp.Body; 
    var propertyInfo = (PropertyInfo)body.Member; 
    var vm = (ViewModelBase)((ConstantExpression)body.Expression).Value; 
    vm.PropertyChanged(vm, new PropertyChangedEventArgs(propertyInfo.Name)); 
} 
+0

+1以獲得更完整的答案。 – Ani 2011-04-25 17:08:56

+0

已經+1了,現在接受 - 謝謝你的偉大答案 – 2011-04-25 17:52:33

+0

btw - 關於你的第二個答案,避免像這樣龐大的代碼是我試圖避免擺在首位。我接受了你的答案,並將它變成了一個擴展方法,它應該讓我設置aotu-properties並讓propertyChanged事件自動產生。再次感謝。 – 2011-04-25 17:58:33

0

可能ü意味着這是在.NET 4.0中添加Expression.Assign

+3

您不能使用lambda表示法創建'Assign'實例。 – Gabe 2011-04-25 17:06:49

1

一般爲expression tree may not contain an assignment operator問題的解決方案是創建一個本地制定者Action並調用一個由Expression

// raises "An expression tree may not contain an assignment operator" 
Expression<Action<string>> setterExpression1 = value => MyProperty = value; 

// works 
Action<string> setter = value => MyProperty = value; 
Expression<Action<string>> setterExpression2 = value => setter(value); 

這可能不適合你確切的問題,但我希望這可以幫助別人,因爲這個問題是谷歌搜索錯誤消息的最佳匹配。

我不確定爲什麼編譯器不允許這樣做。

3

這裏是一個通用的解決方案,它可以爲您提供Action用於從表達式分配以指定賦值的左側和要賦值的值。

public Expression<Action> Assignment<T>(Expression<Func<T>> lvalue, T rvalue) 
{ 
    var body = lvalue.Body; 
    var c = Expression.Constant(rvalue, typeof(T)); 
    var a = Expression.Assign(body, c); 
    return Expression.Lambda<Action>(a); 
} 

這種情況,在問題的代碼只是

ViewModelBase vm = new ViewModelBase(); 

vm.RunAndRaise(Assignment(() => vm.Value, 1)); 

如果更改像

public void RunAndRaise(Expression<Action> Exp) { 
    Exp.Compile()(); 
    PropertyChanged(Exp.Member.Name); 
} 

的定義,我們也可以說足夠

//Set the current thread name to "1234" 
Assignment(() => Thread.CurrentThread.Name, "1234")).Compile()(); 

簡單不是嗎?