As indicated by the answers, I had to implement my own solution. For the benefit of others, I've presented it here:
The Extended PropertyChanged Event
This event has been specially designed to be backwards compatible with old propertyChanged events. It can be used interchangeably with the simple PropertyChangedEventArgs by callers. Of course, in such cases, it is the responsibility of the event handler to check if the passed PropertyChangedEventArgs can be downcast to a PropertyChangedExtendedEventArgs, if they want to use it. No downcasting is necessary if all they're interested in is the PropertyName property.
public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
{
public virtual T OldValue { get; private set; }
public virtual T NewValue { get; private set; }
public PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue)
: base(propertyName)
{
OldValue = oldValue;
NewValue = newValue;
}
}
Example 1
The user can now specify a more advanced NotifyPropertyChanged
method that allows property setters to pass in their old value:
public String testString
{
get { return testString; }
set
{
String temp = testString;
testValue2 = value;
NotifyPropertyChanged("TestString", temp, value);
}
}
Where your new NotifyPropertyChanged
method looks like this:
protected void NotifyPropertyChanged<T>(string propertyName, T oldvalue, T newvalue)
{
OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(propertyName, oldvalue, newvalue));
}
And OnPropertyChanged
is the same as always:
public virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(sender, e);
}
Example 2
Or if you prefer to use lambda expressions and do away with hard-coded property name strings entirely, you can use the following:
public String TestString
{
get { return testString; }
private set { SetNotifyingProperty(() => TestString, ref testString, value); }
}
Which is supported by the following magic:
protected void SetNotifyingProperty<T>(Expression<Func<T>> expression, ref T field, T value)
{
if (field == null || !field.Equals(value))
{
T oldValue = field;
field = value;
OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(GetPropertyName(expression), oldValue, value));
}
}
protected string GetPropertyName<T>(Expression<Func<T>> expression)
{
MemberExpression memberExpression = (MemberExpression)expression.Body;
return memberExpression.Member.Name;
}
Performance
If performance is a concern, see this question: Implementing NotifyPropertyChanged without magic strings.
In summary, the overhead is minimal. Adding the old value and switching to the extended event is about a 15% slowdown, still allowing for on the order of one million property notifications per second, and switching to lambda expressions is a 5 times slowdown allowing for approximately one hundred thousand property notifications per second. These figures are far from being able to form a bottleneck in any UI-driven application.
(Optional) The Extended PropertyChanged Interface
Note: You do not have to do this. You can still just implement the standard INotifyPropertyChanged interface.
If the programmer wants to create an event that requires notifying properties to include an old value and a new value, they would need to define and implement the following interface:
// Summary: Notifies clients that a property value is changing, but includes extended event infomation
/* The following NotifyPropertyChanged Interface is employed when you wish to enforce the inclusion of old and
* new values. (Users must provide PropertyChangedExtendedEventArgs, PropertyChangedEventArgs are disallowed.) */
public interface INotifyPropertyChangedExtended<T>
{
event PropertyChangedExtendedEventHandler<T> PropertyChanged;
}
public delegate void PropertyChangedExtendedEventHandler<T>(object sender, PropertyChangedExtendedEventArgs<T> e);
Now anyone hooking the PropertyChanged event needs to supply the extended args defined above. Note that depending on your use case, your UI may still require you to implement the basic INotifyPropertyChanged interface and event, which would conflict with this one. This is the sort of thing you would do if, for instance, you were building your own UI elements that hinged on this behaviour.
8 Years Later FAQ - How do I use it?
The above examples show how you would send the new property information, but not how you would consume them. 8 Years late, but here's an example of an implementation of the event (shout-out to @Paddy for filling in for the deficiency the past 6 years):
myNotifyingClass.PropertyChanged += OnSomePropertyChanged;
private void OnSomePropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Without casting 'e' is a standard PropertyChanged event
Debug.WriteLine($"'{e.PropertyName}' has changed.");
// If you just care to check whether a certain properties changed, do so as usual
if (e.PropertyName == nameof(SomeClass.Description))
{
myNotifyingClass.MarkAsDirty(); // For example
}
// If the old/new value are if interest, you can cast in those situations
if (e.PropertyName == nameof(SomeClass.SortKey))
{
// For example, use it to order by some new property first, but by the last property second.
if(e is PropertyChangedExtendedEventArgs<string> sortKeyChanged)
myNotifyingClass.OrderBy(sortKeyChanged.NewValue, then_by: sortKeyChanged.OldValue);
else
throw new Exception("I must have forgotten to use the extended args!");
}
// To support more general operations, see the note below on creating interfaces
}
As we note in the above example, there's not much we can do with these generic arguments without casting first. That's because 8 years ago, I may or may not have even known what covariance was. If you would like this to be even more useful, it may make sense to define some interfaces you can use to do type checking and extract property values without knowing the runtime type:
public interface IPropertyChangedExtendedEventArgs<out T> : IPropertyChangedEventArgs
{
public virtual T OldValue { get; }
public virtual T NewValue { get; }
}
public class PropertyChangedExtendedEventArgs<T> : IPropertyChangedExtendedEventArgs<T>
{
public virtual T OldValue { get; private set; }
public virtual T NewValue { get; private set; }
public PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue)
: base(propertyName)
{
OldValue = oldValue;
NewValue = newValue;
}
}
This is now much nicer to use:
if (e is IPropertyChangedExtendedEventArgs<object> anyProperty)
Console.WriteLine($"'{anyProperty.PropertyName}' has changed, " +
$"from '{anyProperty.OldValue}' to '{anyProperty.NewValue}'.");
I hope that clears things up!