Weisen Sie Property einem ExpressionTree zu

c# expression-trees

Frage

Ich spiele mit der Idee, eine Eigenschaftszuweisung an eine Methode als Ausdrucksbaum zu übergeben. Die Methode würde den Ausdruck aufrufen, damit die Eigenschaft richtig zugewiesen wird, und dann den Namen der Eigenschaft aufspüren, die gerade zugewiesen wurde, damit ich das PropertyChanged-Ereignis auslösen kann. Die Idee ist, dass ich schlanke Auto-Eigenschaften in meinen WPF-ViewModels verwenden und das PropertyChanged-Ereignis immer noch abgefeuert haben könnte.

Ich bin ein Ignorant mit ExpressionTrees, also hoffe ich, dass jemand mich in die richtige Richtung weisen kann:

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

Das Problem ist, ich bin mir nicht sicher, wie ich das nennen soll. Dieser naive Versuch wurde vom Compiler aus Gründen abgelehnt, von denen ich sicher bin, dass sie jedem klar sind, der dies beantworten kann:

        ViewModelBase vm = new ViewModelBase();

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

BEARBEITEN

Danke @svick für die perfekte Antwort. Ich habe ein kleines Ding bewegt und es zu einer Erweiterungsmethode gemacht. Hier ist das vollständige Codebeispiel mit Komponententest:

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

Akzeptierte Antwort

Du kannst es nicht so machen. Erstens können Lambda-Ausdrücke nur in Delegattypen oder Expression<T> konvertiert werden.

Wenn Sie die Signatur der Methode (zum Ignorieren ihrer Implementierung) in public void RunAndRaise(Expression<Action> Exp) ändern, beschwert sich der Compiler darüber, dass "ein Ausdrucksbaum möglicherweise keinen Zuweisungsoperator enthält".

Sie können dies tun, indem Sie die Eigenschaft mit Lambda und den Wert angeben, den Sie in einem anderen Parameter festlegen möchten. Auch ich habe nicht einen Weg finden , um den Wert für den Zugriff auf vm aus dem Ausdruck, so muss man , dass in einem anderen Parameter setzen (Sie nicht verwenden können this für das, weil Sie die richtige geerbt Art in der Expression benötigen) : siehe Bearbeiten

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

Eine andere Möglichkeit (und eine, die ich mehr mag) ist, das Ereignis vom Setzer zu erheben, indem ich speziell Lambda wie folgt verwende:

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

Auf diese Weise können Sie die Eigenschaften wie gewohnt verwenden, und Sie können auch Ereignisse für berechnete Eigenschaften auslösen, wenn Sie sie hatten.

BEARBEITEN: Beim Lesen von Matt Warrens Serie über die Implementierung von IQueryable<T> erkannte ich, dass ich auf den referenzierten Wert zugreifen kann, was die Verwendung von RaisePropertyChanged() vereinfacht (obwohl es mit Ihrer SetAndRaise() nicht viel 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));
}

Beliebte Antwort

Hier ist eine generische Lösung, die Ihnen eine Action für die Zuweisung von einem Ausdruck geben kann, um die linke Seite der Zuweisung und einen zuzuweisenden Wert anzugeben.

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

damit ist der Code in der Frage einfach

ViewModelBase vm = new ViewModelBase();

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

Wenn Sie die Definition wie ändern

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

Wir können auch sagen

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

Einfach genug, nicht wahr?



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum