How to decompose expression to satisfy generic property change method?

天涯浪子 提交于 2019-12-25 03:41:14

问题


I have a base EF entity class which implements INotifyPropertyChanged.

The base property, Id is my example:

  /// <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?
  }

...where SetValue is defined:

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

All samples I can find are expecting parameters. I prefer that the setter (SetValue call) is as simple as possible - i.e., is there a way to reduce the input parameter to 1?


回答1:


You should change

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

to

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

and your code compiles, but what you are doing is not optimal and trustful at all. And you'll get an InvalidCastException!

Compiling an Expression<T> on every call is not optimal, and, how can you tell that the user passes the lambda to the method like:

() => (Id != value)

and not

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

or

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

?

Also, value in your expression is not a ConstantExpression. The value itself is just a local variable to the set part of the property, and when passed to a lambda expression, is promoted to a class field (the value is captured - see here for more information). So what you have is a MemberExpression on both sides.

I highly recommend using this approach if you can't use .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;
    }
}

Using it is simple:

public class Person : EntityBase
{
    private int _id;
    public int Id
    {
        get { return _id; }
        set { ChangeAndNofity(ref _id, value, () => Id); }
    }
}



回答2:


There are various options that are simpler than what you've got (here are a few in rough order of how well I like each one):

  • Fody/PropertyChanged - This is a free, automatic code weaver that runs at compile time to automagically implement INotifyPropertyChanged on the properties of the classes you choose. No assemblies required at runtime.
  • INotifyPropertyChanged, The .NET 4.5 Way – Revisited
  • PostSharp - Automatically implementing INotifyPropertyChanged
  • INotifyPropertyChanged Interface documentation's code sample

Here's the core code snippet from "The .NET 4.5 Way":

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

Used like:

  /// <summary>
  /// Entity Id
  /// </summary>
  public int Id {
     get { return id; }
     set { SetValue(ref id, value); }
  }


来源:https://stackoverflow.com/questions/16154172/how-to-decompose-expression-to-satisfy-generic-property-change-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!