问题
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