ИМХО, подход PostSharp, как и в принятом ответе, очень хорош и, конечно, является прямым ответом на заданный вопрос.
Однако для тех, кто не может или не хочет использовать инструмент, такой как PostSharp, для расширения синтаксиса языка C #, можно получить большую часть преимуществ, избегая повторения кода с помощью базового класса, который реализует INotifyPropertyChanged
. Есть много примеров, лежащих вокруг, но до сих пор ни один не был включен в этот полезный и хорошо отработанный вопрос, поэтому вот версия, которую я обычно использую:
/// <summary>
/// Base class for classes that need to implement <see cref="INotifyPropertyChanged"/>
/// </summary>
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
/// <summary>
/// Raised when a property value changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Updates a field for a named property
/// </summary>
/// <typeparam name="T">The type of the field</typeparam>
/// <param name="field">The field itself, passed by-reference</param>
/// <param name="newValue">The new value for the field</param>
/// <param name="propertyName">The name of the associated property</param>
protected void UpdatePropertyField<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
OnPropertyChanged(propertyName);
}
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">The name of the property that has been changed</param>
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.DynamicInvoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Используется, например, так:
private int _value;
public int Value
{
get { return _value; }
set { UpdatePropertyField(ref _value, value); }
}
Не так лаконично, как возможность просто применить атрибут кода к автоматически реализуемому свойству, как в подходе PostSharp, но все же имеет большое значение для ускорения реализации моделей представлений и других подобных типов.
Ключевые особенности, описанные выше, отличающие его от некоторых других реализаций:
- Равенство сравнивается с использованием
EqualityComparer<T>.Default
. Это гарантирует, что типы значений можно сравнивать без упаковки (общая альтернатива будет object.Equals(object, object)
). Экземпляр IEqualityComparer<T>
кэшируется, поэтому после первого сравнения для любого данного типа T
он очень эффективен.
- Метод
OnPropertyChanged()
- virtual
. Это позволяет производным типам легко и эффективно обрабатывать события изменения свойств централизованным образом, без необходимости подписываться на само событие PropertyChanged
(например, для нескольких уровней наследования), а также, конечно, дает производному типу лучший контроль над тем, как и когда он обрабатывает событие, измененное свойством, относительно фактического события PropertyChanged
.