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

EDIT

完璧な答えをお寄せいただきありがとうございます。私はちょっとしたことを動かして、それを拡張メソッドにしました。単体テストの完全なコードサンプルは次のとおりです。

[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)と、コンパイラは「式ツリーに代入演算子が含まれていない可能性がある」と不平を言う。

ラムダを使用してプロパティを指定し、別のパラメータで設定する値を指定することで、これを行うことができます。 また、式から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);
}

こうすることで、いつものようにプロパティを使うことができます。また、計算されたプロパティがある場合はイベントを発生させることもできます。

編集: IQueryable<T>実装についてのMatt Warrenのシリーズを読んでいるうちに、 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と代入するための値を与えるGeneraicソリューションを示します。

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
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ