使用ExpressionTree分配屬性

c# expression-trees

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

我是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);
    }
}

一般承認的答案

你不能這樣做。首先,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來專門從setter引發事件:

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

這樣,您可以像往常一樣使用屬性,如果有的話,還可以為計算屬性引發事件。

編輯:在閱讀Matt Warren關於實現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));
}

熱門答案

這是一個genraic解決方案,可以為您提供一個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()();

很簡單,不是嗎?



許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因