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

편집하다

완벽한 답변을 해주셔서 감사합니다. 나는 작은 것을 하나 옮겨서 그것을 확장 방법으로 만들었다. 단위 테스트가 포함 된 전체 코드 샘플은 다음과 같습니다.

[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> 구현에 대한 매트 워렌의 시리즈를 통해 읽는 동안, 나는 당신의 SetAndRaise() 많은 도움이되지 않지만 RaisePropertyChanged() 사용을 단순화하는 참조 된 값을 액세스 할 수 깨달았다 :

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는 합법적입니까? 예, 이유를 알아보십시오.