PropertyObservable - INotifyPropertyChanged 接口的基类。






4.50/5 (4投票s)
PropertyObservable 的介绍,它是一个实现 INotifyPropertyChanged 接口的基类。
引言
如果您是 .NET 开发人员,特别是 MVVM 的 WPF 开发人员,您一定知道 INotifyPropertyChanged
接口的重要性。这是一个几乎每天都会用到的核心接口。因此,如果我们优化 INotifyPropertyChanged
的基类实现,即使改进很小,也可能大大提高生产力。PropertyObservable 类是我对 INotifyPropertyChanged
的第二次实现。(您可以在这里看到第一次实现)它的设计目标是最大限度地提高生产力。
基本的 INotifyPropertyChanged 实现
INotifyPropertyChanged
是一个非常简单的接口。它只声明了 PropertyChanged
事件
// Notifies clients that a property value has changed.
public interface INotifyPropertyChanged
{
// Occurs when a property value changes.
event PropertyChangedEventHandler PropertyChanged;
}
PropertyChanged
事件可以用几行代码轻松实现,如下所示:
public class PropertyObservable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = PropertyChanged;
if (propertyChanged != null)
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
但这并非故事的全部。您必须正确编写属性,以便在它们被更改时触发 PropertyChanged
事件。
编写属性
如果您使用基本的 INotifyPropertyChanged
实现来编写属性,它看起来会是这样的:
public partial class Test : PropertyObservable
{
int _number;
public int Number
{
get { return _number; }
set
{
if (_number == value)
return;
_number = value;
OnPropertyChanged("Number");
}
}
}
我将称之为“原始属性”。原始属性本身并不差,但原始属性的 setter 有点冗长。PropertyObservable
类为此情况提供了名为 SetProperty
的方法。可以使用 SetProperty
方法重写原始属性的 setter,如下所示:
public partial class Test : PropertyObservable
{
int _number;
public int Number
{
get { return _number; }
set { SetProperty(ref _number, value, "Number"); }
}
}
如果您使用 .NET Framework 4.5 或更高版本,可以省略属性名称,如下所示:
public partial class Test : PropertyObservable
{
int _number;
public int Number
{
get { return _number; }
set { SetProperty(ref _number, value); }
}
}
为了简洁起见,我将假设您使用 .NET Framework 4.5 或更高版本。在大多数情况下,上述属性实现已经足够。它速度快,并且推荐所有用户使用。但它需要管理一个私有字段。此外,在我看来,当定义多个带注释的属性时,它看起来有点乱。
public partial class Test : PropertyObservable
{
int _number1;
/// <summary>
/// The number 1.
/// </summary>
public int Number1
{
get { return _number1; }
set { SetProperty(ref _number1, value); }
}
int _number2;
/// <summary>
/// The number 2.
/// </summary>
public int Number2
{
get { return _number2; }
set { SetProperty(ref _number2, value); }
}
}
无论如何,还有另一个 SetProperty
方法不需要私有字段。Number 属性可以不使用私有字段重写,如下所示:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(); }
set { SetProperty(value); }
}
}
GetProperty
方法检索存储在内部存储中的属性值。上述属性实现比之前的属性实现慢,但在我看来,它看起来很整洁,并且生产力略高。以下示例显示了两个带注释的属性:
public partial class Test : PropertyObservable
{
/// <summary>
/// The number 1.
/// </summary>
public int Number1
{
get { return GetProperty<int>(); }
set { SetProperty(value); }
}
/// <summary>
/// The number 2.
/// </summary>
public int Number2
{
get { return GetProperty<int>(); }
set { SetProperty(value); }
}
}
如果您需要设置默认属性值,可以使用 GetProperty
方法的 getDefault
参数:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(getDefault: () => 7); }
set { SetProperty(value); }
}
}
检查属性更改
当属性发生更改时运行自定义代码是很常见的。如果您需要知道属性何时发生更改,最著名的方法是处理 PropertyChanged
事件。
public partial class Test : PropertyObservable
{
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == "Number")
{
Debug.WriteLine("Number: " + Number);
return;
}
}
}
但是 PropertyChanged
事件有一个缺点。它不提供旧的属性值。因此 PropertyObservable
提供了另一个名为 PropertyValueChanged
的事件,该事件提供了旧的属性值。
public partial class Test : PropertyObservable
{
protected override void OnPropertyValueChanged(IPropertyValueChangedEventArgs e)
{
base.OnPropertyValueChanged(e);
if (e.PropertyName == "Number")
{
Debug.WriteLine("Number: {0} -> {1}", e.OldValue, e.NewValue);
return;
}
}
}
但请注意,仅当您使用 SetProperty
方法设置属性值时,才会引发该事件。此外,OldValue
和 NewValue
不是类型安全的。
有时为了代码的可管理性,在属性 setter 中处理属性更改是合理的。在原始 Number
属性 setter 中,上述代码可以重写为:
public partial class Test : PropertyObservable
{
int _number;
public int Number
{
get { return _number; }
set
{
if (_number == value)
return;
int oldValue = _number;
_number = value;
OnPropertyChanged("Number");
Debug.WriteLine("Number: {0} -> {1}", oldValue, value);
}
}
}
如果您在属性 setter 中使用 SetProperty
方法,则可以简化。SetProperty
方法在属性更改时返回 true。因此,上述代码可以重写为:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(); }
set
{
int oldValue = Number;
if (SetProperty(value))
Debug.WriteLine("Number: {0} -> {1}", oldValue, value);
}
}
}
或者您可以使用 SetProperty
方法的 onChanged
参数。它是一个回调委托,在属性更改时被调用。它还在其委托参数中提供了旧的属性值。因此,您无需缓存旧的属性值。
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(); }
set { SetProperty(value, onChanged: p => Debug.WriteLine("{0}: {1} -> {2}", p.PropertyName, p.OldValue, p.NewValue)); }
}
}
调整属性值
在设置属性值之前,经常需要对其进行调整,以修复无效输入或其他原因。调整属性值很容易。例如,以下代码显示了一个不允许负数的原始属性 setter:
public partial class Test : PropertyObservable
{
int _number;
public int Number
{
get { return _number; }
set
{
if (_number == value)
return;
if (value < 0)
{
Debug.WriteLine("Number: {0} >> 0", value);
value = 0;
if (_number == value)
return;
}
_number = value;
OnPropertyChanged("Number");
}
}
}
如果您在属性 setter 中使用 SetProperty
方法,它看起来会是这样:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(); }
set
{
if (Number == value)
return;
if (value < 0)
{
Debug.WriteLine("Number: {0} >> 0", value);
value = 0;
}
SetProperty(value);
}
}
}
SetProperty
方法还提供了 onChanging
参数,该参数在属性更改之前被调用。它允许您通过其委托参数覆盖新的属性值。因此,您可以使用 onChanging
参数,如下所示:
public partial class Test : PropertyObservable
{
public int Number
{
get { return GetProperty<int>(); }
set
{
SetProperty(value, onChanging: p =>
{
if (p.NewValue < 0)
{
Debug.WriteLine("{0}: {1} >> 0", p.PropertyName, p.NewValue);
p.NewValue = 0;
}
});
}
}
}
如果您想在集中位置调整属性值,可以使用 PropertyValueChanging
事件,如下所示:
public partial class Test : PropertyObservable
{
protected override void OnPropertyValueChanging(IPropertyValueChangingEventArgs e)
{
base.OnPropertyValueChanging(e);
if (e.PropertyName == "Number")
{
if (((int)e.NewValue != Number && (int)e.NewValue < 0)
{
Debug.WriteLine("Number: {0} >> 0", e.NewValue);
e.NewValue = 0;
}
return;
}
}
}
但请注意,与 PropertyValueChanged
事件一样,仅当您使用 SetProperty
方法设置属性值时,才会引发 PropertyValueChanging
事件。此外,OldValue
和 NewValue
不是类型安全的。并且 PropertyValueChanging
事件在 onChaning
回调被调用之前被引发。
支持的预处理器符号
您可以使用以下预处理器符号:
- NICENIS_4C:如果要在 .NET Framework 4 Client Profile 中进行编译,请定义此符号。