Как разложить выражение, чтобы удовлетворить общий метод изменения свойства?

У меня есть базовый класс сущности EF, который реализует INotifyPropertyChanged.

Базовое свойство Id — мой пример:

  /// <summary>
  /// Entity Id
  /// </summary>
  public int Id {
     get { return id; }
     set { SetValue<int>(() => (Id != value), (v) => id = v); } // < can this be simplified into a single call?
  }

... где определено SetValue:

  protected void SetValue<TValue>(Expression<Func<bool>> evalExpr, Action<TValue> set) {
     // Compile() returns a Func<bool>
     var doSetValue = evalExpr.Compile();

     if (doSetValue()) {
        var expr = evalExpr.Body as BinaryExpression;
        //  this is not compiling - how do I decompose the expression to get what I need?
        var propertyName = ((PropertyExpression)expr.Left).Name;
        var assignValue = (TValue)((ConstantExpression)expr.Right).Value;

        set(assignValue);
        _propertyChangedHandler(this, new PropertyChangedEventArgs(propertyName));
     }
  }

Все образцы, которые я могу найти, ожидают параметров. Я предпочитаю, чтобы установщик (вызов SetValue) был как можно проще — т.е. есть ли способ уменьшить входной параметр до 1?


person IAbstract    schedule 22.04.2013    source источник


Ответы (2)


Вы должны изменить

var propertyName = ((PropertyExpression)expr.Left).Name;

to

var propertyName = ((MemberExpression)expr.Left).Member.Name;

и ваш код компилируется, но то, что вы делаете, совсем не оптимально и не вызывает доверия. И вы получите InvalidCastException!

Компиляция Expression<T> при каждом вызове не оптимальна, и как вы можете сказать, что пользователь передает лямбду методу, например:

() => (Id != value)

и нет

() => (id != value) // using the field instead of property

or

() => (value != Id) // passing the property as the second operand

?

Кроме того, value в вашем выражении не является ConstantExpression. Сам value — это просто локальная переменная для set части свойства, и при передаче в лямбда-выражение повышается до поля класса (значение фиксируется — см. здесь для получения дополнительной информации). Итак, у вас есть MemberExpression с обеих сторон.

Я настоятельно рекомендую использовать этот подход, если вы не можете использовать .NET 4.5 ([CallerMemberName]):

public class EntityBase : INotifyPropertyChanged
{
    protected virtual void OnPropertyChanged(string propName)
    {
        var h = PropertyChanged;
        if (h != null)
            h(this, new PropertyChangedEventArgs(propName));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected bool ChangeAndNofity<T>(ref T field, T value, Expression<Func<T>> memberExpression)
    {
        if (memberExpression == null)
        {
            throw new ArgumentNullException("memberExpression");
        }

        var body = memberExpression.Body as MemberExpression;
        if (body == null)
        {
            throw new ArgumentException("Lambda must return a property.");
        }

        if (EqualityComparer<T>.Default.Equals(field, value))
        {
            return false;
        }

        field = value;
        OnPropertyChanged(body.Member.Name);
        return true;
    }
}

Использовать его просто:

public class Person : EntityBase
{
    private int _id;
    public int Id
    {
        get { return _id; }
        set { ChangeAndNofity(ref _id, value, () => Id); }
    }
}
person Mohammad Dehghan    schedule 23.04.2013
comment
Хорошая мысль о доверии. В настоящее время я делаю это с большим количеством параметров, обеспечивающих правильное соблюдение цели метода SetValue. Спасибо. - person IAbstract; 23.04.2013
comment
бонус ... гораздо лучшее уведомление. Спасибо, сэр! - person IAbstract; 23.04.2013

Существуют различные варианты, которые проще, чем то, что у вас есть (вот несколько в примерном порядке того, насколько хорошо каждый из них мне нравится):

Вот основной фрагмент кода из «Путь .NET 4.5»:

protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
    if (object.Equals(storage, value)) return false;

    storage = value;
    this.OnPropertyChanged(propertyName);
    return true;
}

Используется как:

  /// <summary>
  /// Entity Id
  /// </summary>
  public int Id {
     get { return id; }
     set { SetValue(ref id, value); }
  }
person Tim S.    schedule 23.04.2013
comment
хххммм ... не могу использовать .Net 4.5 (пока). Я изучу ресурсы для решения, совместимого с .Net 4.0. Благодарю. - person IAbstract; 23.04.2013