使用ExpressionTree分配属性

我正在尝试将属性赋值作为表达式树传递给方法。 该方法将调用表达式以便正确分配属性,然后嗅出刚刚分配的属性名称,以便我可以引发PropertyChanged事件。 我的想法是,我希望能够在我的WPF ViewModel中使用超薄自动属性,并且仍然会触发PropertyChanged事件。

我是ExpressionTrees的无知者,所以我希望有人可以指出我正确的方向:

public class ViewModelBase { public event Action 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的完美答案。 我移动了一个小东西,并将其作为一种扩展方法。 以下是unit testing的完整代码示例:

 [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(this TViewModel vm, Expression<Func> exp, TProperty value) where TViewModel : ViewModelBase { var propertyInfo = (PropertyInfo)((MemberExpression)exp.Body).Member; propertyInfo.SetValue(vm, value, null); vm.OnPropertyChanged(propertyInfo.Name); } } 

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

如果将方法的签名(现在忽略其实现)更改为public void RunAndRaise(Expression Exp) ,编译器会抱怨“表达式树可能不包含赋值运算符”。

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

 public static void SetAndRaise( TViewModel vm, Expression> 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 vm, Expression> exp) where TViewModel : ViewModelBase { var propertyInfo = (PropertyInfo)((MemberExpression)exp.Body).Member; vm.PropertyChanged(propertyInfo.Name); } 

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

编辑:在阅读Matt Warren关于实现IQueryable的系列文章时 ,我意识到我可以访问引用的值,这简化了RaisePropertyChanged()的使用(虽然它对你的SetAndRaise()没有多大帮助):

 private int m_value; public int Value { get { return m_value; } set { m_value = value; RaisePropertyChanged(() => Value); } } static void RaisePropertyChanged(Expression> 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 Assignment(Expression> lvalue, T rvalue) { var body = lvalue.Body; var c = Expression.Constant(rvalue, typeof(T)); var a = Expression.Assign(body, c); return Expression.Lambda(a); } 

有了这个,问题中的代码就是这么简单

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

如果改变定义,如

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

我们也可以说

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

很简单,不是吗?

expression tree may not contain an assignment operator一般解决方案expression tree may not contain an assignment operator问题,即创建本地setter Action并通过Expression调用它:

 // raises "An expression tree may not contain an assignment operator" Expression> setterExpression1 = value => MyProperty = value; // works Action setter = value => MyProperty = value; Expression> setterExpression2 = value => setter(value); 

这可能不适合您的确切问题,但我希望这有助于某人,因为这个问题是谷歌搜索该错误消息的最佳匹配。

我不确定为什么编译器不允许这样做。

可能是你的意思是在.net 4.0中加入的Expression.Assign ?