2011-04-25 29 views
12

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

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

回答

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这样提高从二传手事件:

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

这样,您就可以使用像往常一样的属性,你co如果你有它们,它也会为计算出的属性引发事件。

编辑:在阅读过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)); 
} 
+0

+1以获得更完整的答案。 – Ani 2011-04-25 17:08:56

+0

已经+1了,现在接受 - 谢谢你的伟大答案 – 2011-04-25 17:52:33

+0

btw - 关于你的第二个答案,避免像这样庞大的代码是我试图避免摆在首位。我接受了你的答案,并将它变成了一个扩展方法,它应该让我设置aotu-properties并让propertyChanged事件自动产生。再次感谢。 – 2011-04-25 17:58:33

0

可能ü意味着这是在.NET 4.0中添加Expression.Assign

+3

您不能使用lambda表示法创建'Assign'实例。 – Gabe 2011-04-25 17:06:49

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

这可能不适合你确切的问题,但我希望这可以帮助别人,因为这个问题是谷歌搜索错误消息的最佳匹配。

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

3

这里是一个通用的解决方案,它可以为您提供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()(); 

简单不是吗?