使用ExpressionTree分配属性

Ada*_*kis 13 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是我不知道怎么称呼它.编译器拒绝了这种天真的尝试,原因是我肯定对任何能够回答这个问题的人都很明显:

        ViewModelBase vm = new ViewModelBase();

        vm.RunAndRaise(() => vm.Value = 1);
Run Code Online (Sandbox Code Playgroud)

编辑

谢谢@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

svi*_*ick 7

你不能这样做.首先,lambda表达式只能转换为委托类型或Expression<T>.

如果改变方法的签名(现在忽视了其实现)public void RunAndRaise(Expression<Action> Exp),编译器会抱怨说:"表达式树不能包含赋值运算符".

您可以通过使用lambda指定属性以及要在另一个参数中将其设置为的值来实现.另外,我没有找到一种方法来访问vm表达式的值,所以你必须把它放在另一个参数中(你不能用this它,因为你需要在表达式中使用正确的继承类型):see编辑

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);
}
Run Code Online (Sandbox Code Playgroud)

另一种可能性(以及我更喜欢的一种)是使用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);
}
Run Code Online (Sandbox Code Playgroud)

这样,您可以像往常一样使用属性,如果有的话,也可以为计算属性引发事件.

编辑:在阅读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));
}
Run Code Online (Sandbox Code Playgroud)