根据回答,我必须实现自己的解决方案。为了让其他人受益,我在这里提供了它:
扩展PropertyChanged事件
该事件已经被特别设计为与旧的propertyChanged事件向后兼容。调用者可以与简单的PropertyChangedEventArgs交替使用。当然,在这种情况下,如果事件处理程序想要使用它,则需要检查传递的PropertyChangedEventArgs是否可以向下转换为PropertyChangedExtendedEventArgs。如果他们只对PropertyName属性感兴趣,则不需要进行任何向下转换。
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;
}
}
示例 1
现在用户可以指定更高级的 NotifyPropertyChanged
方法,允许属性设置器传入它们的旧值:
public String testString
{
get { return testString; }
set
{
String temp = testString;
testValue2 = value;
NotifyPropertyChanged("TestString", temp, value);
}
}
你的新NotifyPropertyChanged
方法应该长这样:
protected void NotifyPropertyChanged<T>(string propertyName, T oldvalue, T newvalue)
{
OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(propertyName, oldvalue, newvalue));
}
OnPropertyChanged
如往常一样:
public virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(sender, e);
}
示例2
或者,如果您更喜欢使用lambda表达式,并完全摆脱硬编码的属性名称字符串,可以使用以下代码:
public String TestString
{
get { return testString; }
private set { SetNotifyingProperty(() => TestString, ref testString, value); }
}
以下魔法支持此功能:
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;
}
性能
如果您关心性能问题,请参考这个问题:如何在不使用魔法字符串的情况下实现NotifyPropertyChanged。
总体而言,开销很小。添加旧值并切换到扩展事件大约会减慢15%,仍然允许每秒处理数百万个属性通知,并且切换到lambda表达式会减慢5倍,允许每秒处理约十万个属性通知。这些数字远远不能成为任何UI驱动应用程序的瓶颈。
(可选) 扩展的PropertyChanged接口
注意:您不必这样做。您仍然可以只实现标准的INotifyPropertyChanged接口。
如果程序员想要创建一个需要包括旧值和新值的属性通知事件,他们需要定义和实现以下接口:
public interface INotifyPropertyChangedExtended<T>
{
event PropertyChangedExtendedEventHandler<T> PropertyChanged;
}
public delegate void PropertyChangedExtendedEventHandler<T>(object sender, PropertyChangedExtendedEventArgs<T> e);
现在任何订阅 PropertyChanged 事件的人都需要提供上述定义的扩展参数。请注意,根据您的用例,您的 UI 可能仍然需要实现基本的 INotifyPropertyChanged 接口和事件,这可能与此冲突。如果您构建了依赖于此行为的自己的 UI 元素,则会执行此操作。
8 年后 FAQ - 我该如何使用它?
上面的示例显示了如何发送新的属性信息,但没有显示如何使用它们。虽然已经过去了 8 年,但以下是事件实现的示例(感谢 @Paddy 在过去的 6 年中填补了缺陷):
myNotifyingClass.PropertyChanged += OnSomePropertyChanged;
private void OnSomePropertyChanged(object sender, PropertyChangedEventArgs e)
{
Debug.WriteLine($"'{e.PropertyName}' has changed.");
if (e.PropertyName == nameof(SomeClass.Description))
{
myNotifyingClass.MarkAsDirty();
}
if (e.PropertyName == nameof(SomeClass.SortKey))
{
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!");
}
}
正如我们在上面的例子中所提到的,如果没有先进行强制类型转换,这些通用参数就没什么用处。这是因为8年前,我可能甚至不知道什么是协变性。如果您希望使其更加有用,可以定义一些接口来进行类型检查和提取属性值,而不需要知道运行时类型:
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;
}
}
这现在使用起来更加方便了:
if (e is IPropertyChangedExtendedEventArgs<object> anyProperty)
Console.WriteLine($"'{anyProperty.PropertyName}' has changed, " +
$"from '{anyProperty.OldValue}' to '{anyProperty.NewValue}'.");
我希望你能理解清楚!
INotifyPropertyChangedExtended<T>
而不是通常的INotifyPropertyChanged
,但我没有得到双向绑定。 - xavigonzaT
时,才有意义使用泛型接口,这似乎并不是很有用。此外,对于你的泛型NotifyPropertyChanged<T>()
方法,甚至不清楚你发布的代码应该如何编译,因为你甚至没有实现泛型接口,更别提在该接口中引发事件了。这个答案可能有一些有用的想法,但它是半成品的,会让初学者感到困惑。 - Peter Duniho