Назначить свойство ExpressionTree

c# expression-trees

Вопрос

Я играю с идеей передачи присвоения свойства методу в качестве дерева выражений. Этот метод будет вызывать выражение так, чтобы свойство было назначено правильно, а затем вынюхило имя свойства, которое было только что назначено, поэтому я могу поднять событие 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);
    }
}

Принятый ответ

Вы не можете этого сделать. Во-первых, лямбда-выражения могут быть преобразованы только для делегирования типов или 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);
}

Другая возможность (и мне больше нравится) состоит в том, чтобы поднять событие из сеттера, используя, например, лямбда:

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

Таким образом, вы можете использовать свойства, как обычно, и вы также можете создавать события для вычисленных свойств, если бы вы их использовали.

EDIT: прочитав серию Matt Warren о внедрении 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));
}

Популярные ответы

Вот общее решение, которое может дать вам 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()();

Достаточно просто, не так ли?



Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему
Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему