.net propertychange notification handlers - strings vs. expressions
Using WPF has made me a fan of INotifyPropertyChanged. I like to use a helper that takes an expression and returns the name as a string (see example code below). In lots of applications I see by very proficient programmers, however, I see code that handles the strings raw (see 2nd example below). By proficient I mean MVP types who know how to use Expressions.
To me, the opportunity for having the compiler catch mistakes in addition to easy refactoring makes the Exression approach better. Is there an argument in favor of using raw strings that I am missing?
Cheers, Berryl
Expression helper example:
public static string GetPropertyName<T>(Expression<Func<T, object>> propertyExpression)
{
Check.RequireNotNull<object>(propertyExpression, "propertyExpression");
switch (propertyExpression.Body.NodeType)
{
case ExpressionType.MemberAccess:
return (propertyExpression.Body as MemberExpression).Member.Name;
case ExpressionType.Convert:
return ((propertyExpression.Body as UnaryExpression).Operand as MemberExpression).Member.Name;
}
var msg = string.Format("Expression NodeType: '{0}' does not refer to a property and is therefore not supported",
propertyExpression.Body.NodeType);
Check.Require(false, msg);
throw new InvalidOperationException(msg);
}
Raw strings example code (in some ViewModelBase type class):
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG"), DebuggerStepThrough]
public void VerifyPropertyName(string propertyName) {
// Verif开发者_开发知识库y that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null) {
string msg = "Invalid property name: " + propertyName;
if (ThrowOnInvalidPropertyName) throw new Exception(msg);
else Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
To me, the opportunity for having the compiler catch mistakes in addition to easy refactoring makes the Exression approach better. Is there an argument in favor of using raw strings that I am missing?
I agree, and personally, use the expression approach in my own code, in most cases.
However, there are two reasons I know of to avoid this:
It is less obvious, especially to less experienced .NET developers. Writing
RaisePropertyChanged(() => this.MyProperty );
is not always as obvious to people asRaisePropertyChanged("MyProperty");
, and doesn't match framework samples, etc.There is some performance overhead to using expressions. Personally, I don't feel this is really that meaningful of a reason, since this is usually used in data binding scenarios (which are already slow due to reflection usage), but it is potentially a valid concern.
The benefit of using the TypeDescriptor
approach is that it enables dynamic property scenarios based on ICustomTypeDescriptor implementations where the implementation can effectively create dynamic property metadata on the fly for a type that is being described. Consider a DataSet whose "properties" are determined by the result set it is populated with for example.
This is something that expressions does not provide however because it's based on actual type information (a good thing) as opposed to strings.
I wound up spending more time on this than I expected, but did find a solution that has a nice mix of safety/refactorability and performance. Good background reading and an alternate solution using reflection is here. I like Sacha Barber's solution even better (background here.
The idea is to use an expression helper for a property that will participate in change notification, but only take the hit once for it, by storing the resulting PropertyChangedEventArgs in your view model. For example:
private static PropertyChangedEventArgs mobilePhoneNumberChangeArgs =
ObservableHelper.CreateArgs<CustomerModel>(x => x.MobilePhoneNumber);
HTH,
Berryl
Stack walk is slow and lambda expression is even slower. We have solution similar to well known lambda expression but almost as fast as string literal. See http://zamboch.blogspot.com/2011/03/raising-property-changed-fast-and-safe.html
A CallerMemberName attribute was introduced in .net 4.5 This attribute can only be attached to optional string parameters and if the parameter is not used by caller in the function call then name of the caller will be passed in the string parameter
This removes the need to specify name of property when raising the PropertyChanged event thus it works with refactoring and because the changes are done at compile time there's no difference in performance.
Below is an example of implementation and more info can be found at http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute.aspx and http://msdn.microsoft.com/en-us/library/hh534540.aspx
public class DemoCustomer : INotifyPropertyChanged
{
string _customerName
public string CustomerName
{
get { return _customerNameValue;}
set
{
if (value != _customerNameValue)
{
_customerNameValue = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
精彩评论