我正在玩一个将属性分配传递给方法作为表达式树的想法.该方法将调用表达式,以便正确分配属性,然后嗅出刚分配的属性名称,以便可以引发PropertyChanged事件.这个想法是,我希望能够在我的
WPF viewmodels中使用超薄的自动属性,并且仍然关闭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表达式只能转换为委托类型或表达式.
如果将方法的签名(现在忽略其实现)更改为public void RunAndRaise(Expression< Action> Exp),则编译器会抱怨“表达式树可能不包含赋值运算符”.
您可以通过使用lambda指定属性并将其设置为另一个参数的值来执行此操作.此外,我没有找到一种方法从表达式访问vm的值,所以你必须把它放在另一个参数(你不能使用这个,因为你需要正确的继承类型在表达式中) :看编辑
public static void SetAndRaise<Tviewmodel,TProperty>( Tviewmodel vm,TProperty value) where Tviewmodel : viewmodelBase { var propertyInfo = (PropertyInfo)((MemberExpression)exp.Body).Member; propertyInfo.SetValue(vm,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>> exp) where Tviewmodel : viewmodelBase { var propertyInfo = (PropertyInfo)((MemberExpression)exp.Body).Member; vm.PropertyChanged(propertyInfo.Name); }
这样,您可以像往常一样使用属性,如果有的话,您还可以为计算属性引发事件.
编辑:在阅读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)); }