It would be good to have ability to raise 'PropertyChanged' event without explicit specifying the name of changed property. I would like to do something like this:
public string MyString
{
get { return _myString; }
set
{
ChangePropertyAndNotify<string>(val=>_myString=val, value);
}
}
private void ChangePropertyAndNotify<T>(Action<T> setter, T value)
{
setter(value);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(setter.Method.Name));
}
}
In this case received name is a name of lambda-method: "<set_MyString>b__0".
Thank you.
Added C# 6 Answer
In C# 6 (and whatever version of VB comes with Visual Studio 2015) we have the nameof
operator which makes things easier than ever. In my original answer below, I use a C# 5 feature (caller info attributes) to handle the common case of "self-changed" notifications. The nameof
operator can be used in all cases, and is especially useful in the "related-property-changed" notification scenario.
For simplicity, I think I'll keep the caller info attribute approach for common self-changed notifications. Less typing means less chances for typos and copy/paste induced bugs... the compiler here ensures that you pick a valid type/member/variable, but it doesn't ensure you pick the correct one. It is simple to then use the new nameof
operator for related-property change notifications. The example below demonstrates a key behavior of caller info attributes... the attribute has no effect on a parameter if the parameter is specified by the caller (that is, the caller info is provided for the parameter value only when the parameter is omitted by the caller).
It is also worth observing that the nameof
operator can be used by PropertyChanged event handlers as well. Now you can compare the PropertyName
value in the event (which is a string
) to a particular property using the nameof
operator, eliminating more magic strings.
Reference info for nameof
here: https://msdn.microsoft.com/en-us/library/dn986596.aspx
Example:
public class Program
{
void Main()
{
var dm = new DataModel();
dm.PropertyChanged += propertyChangedHandler;
}
void propertyChangedHandler(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == nameof(DataModel.NumberSquared))
{
//do something spectacular
}
}
}
public class DataModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
public class DataModel : DataModelBase
{
//a simple property
string _something;
public string Something
{
get { return _something; }
set { _something = value; OnPropertyChanged(); }
}
//a property with another related property
int _number;
public int Number
{
get { return _number; }
set
{
_number = value;
OnPropertyChanged();
OnPropertyChanged(nameof(this.NumberSquared));
}
}
//a related property
public int NumberSquared { get { return Number * Number; } }
}
Original C# 5 answer
Since C# 5, best to use caller info attributes, this is resolved at compile time, no reflection necessary.
I implement this in a base class, derived classes just call the OnPropertyChanged
method from within their property setters. If some property implicitly changes another value, I can use the "Explicit" version of the method in the property setter as well, which then is no longer "safe" but is a rare situation that I just accept.
Alternatively you could use this method for self change notifications, and use the answer given by @Jehof for related property change notifications ... this would have the advantage of no magic strings, with the fastest execution for the common case of self change notifications.
This latest suggestion is implemented below (I think I'll start using it!)
public class DataModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
OnPropertyChangedExplicit(propertyName);
}
protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection)
{
var memberExpression = (MemberExpression)projection.Body;
OnPropertyChangedExplicit(memberExpression.Member.Name);
}
void OnPropertyChangedExplicit(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
public class DataModel : DataModelBase
{
//a simple property
string _something;
public string Something
{
get { return _something; }
set { _something = value; OnPropertyChanged(); }
}
//a property with another related property
int _number;
public int Number
{
get { return _number; }
set
{
_number = value;
OnPropertyChanged();
OnPropertyChanged(() => NumberSquared);
}
}
//a related property
public int NumberSquared { get { return Number * Number; } }
}