我正在尝试将属性赋值作为表达式树传递给方法。该方法将调用表达式以便正确分配属性,然后嗅出刚刚分配的属性名称,以便我可以引发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);
}
}
答案 0 :(得分: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来专门从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'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));
}
答案 1 :(得分:3)
这是一个genraic解决方案,可以为您提供NextRow = Range("A1").End(xlDown).Row + 1
来从表达式中指定分配的左侧,以及要指定的值。
registry=https://registry.npmjs.org/
有了这个,问题中的代码就是
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));
很简单,不是吗?
答案 2 :(得分: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);
这可能不适合您的确切问题,但我希望这有助于某人,因为此问题是Google搜索该错误消息的最佳匹配。
我不确定为什么编译器不允许这样做。
答案 3 :(得分:0)
可能是你在.net 4.0中添加的Expression.Assign?