2011-04-25 8 views
12

Estoy jugando con la idea de pasar una asignación de propiedad a un método como árbol de expresiones. El método invocaría la expresión para que la propiedad se asignara correctamente, y luego olfatear el nombre de la propiedad que acaba de asignarse para poder levantar el evento PropertyChanged. La idea es que me gustaría poder usar propiedades automáticas delgadas en mis WPF ViewModels y aún tener activado el evento PropertyChanged.Asignar propiedad con un ExpressionTree

yo soy un ignorante con ExpressionTrees, así que estoy esperando que alguien me puede apuntar en la dirección correcta:

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

El problema es que no estoy seguro de cómo llamar a esto. Este ingenuo intento fue rechazado por el compilador por razones que estoy seguro será obvio para cualquier persona que pueda responder a esta:

 ViewModelBase vm = new ViewModelBase(); 

     vm.RunAndRaise(() => vm.Value = 1); 

EDITAR

Gracias @svick para la respuesta perfecta. Moví una pequeña cosa y la convertí en un método de extensión. Aquí está el ejemplo de código completo con unidad de prueba:

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

Respuesta

7

No puede hacerlo de esta manera. En primer lugar, las expresiones lambda solo se pueden convertir a tipos de delegado o Expression<T>.

Si cambia la firma del método (por ahora ignorando su implementación) a public void RunAndRaise(Expression<Action> Exp), el compilador se queja de que "Un árbol de expresiones no puede contener un operador de asignación".

Puede hacerlo especificando la propiedad usando lambda y el valor que desea establecer en otro parámetro. Además, no encontré una forma de acceder al valor vm de la expresión, por lo que debe ponerlo en otro parámetro (no puede usar this para eso, porque necesita el tipo heredado correcto en la expresión): ver editar

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

Otra posibilidad (y uno que me gusta más) es elevar el evento de la moda usando específicamente lambda como esto:

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

de esta manera, se puede utilizar el propiedades como de costumbre, y usted co uld también plantea eventos para las propiedades calculadas, si los tiene.

EDIT: Durante la lectura a través Matt Warren's series about implementing IQueryable<T>, me di cuenta de que puedo acceder al valor de referencia, lo que simplifica el uso de RaisePropertyChanged() (aunque no ayudará mucho con su 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 para una respuesta más completa. – Ani

+0

ya + 1ed, y ahora acepto, gracias por la gran respuesta –

+0

por cierto, con respecto a su segunda respuesta, evitando el código voluminoso como que es lo que estaba tratando de evitar en primer lugar. Tomé su respuesta y la convertí en un método de extensión, que debería permitirme establecer propiedades aotu y hacer que el evento propertyChanged se active automáticamente. Gracias de nuevo. –

0

Puede ser que significas Expression.Assign que se añadió en .NET 4.0?

+3

No puede crear instancias 'Assign' con notación lambda. – Gabe

1

Un general solución para el problema expression tree may not contain an assignment operator sería la creación de un regulador local de Action y llamar a que uno por el 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); 

Esto puede no ser adecuado para su problema exacto, pero espero que esto ayude a alguien, ya que esta pregunta es la mejor opción para buscar en Google ese mensaje de error.

No estoy seguro de por qué exactamente el compilador no permite esto.

3

Aquí hay una solución general que puede darle un Action para la asignación de una expresión para especificar el lado izquierdo de la asignación, y un valor para asignar.

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

con esto, el código en la pregunta es simplemente

ViewModelBase vm = new ViewModelBase(); 

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

Si cambia la definición como

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

También podemos decir lo suficiente

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

simple ¿No es así?

Cuestiones relacionadas