Как реализовать INotifyPropertyChanged?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация INotifyPropertyChanged в C#
INotifyPropertyChanged — это ключевой интерфейс в .NET для реализации паттерна наблюдатель (Observer) в контексте привязки данных (data binding). Он позволяет объектам уведомлять клиентов (обычно UI-элементы WPF, WinForms, MAUI) об изменениях свойств, обеспечивая автоматическое обновление интерфейса.
Базовая реализация
Минимальная реализация требует:
- Реализации интерфейса
INotifyPropertyChanged - Объявления события
PropertyChanged - Метода для вызова этого события
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class Person : INotifyPropertyChanged
{
private string _name;
private int _age;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
public int Age
{
get => _age;
set
{
if (_age != value)
{
_age = value;
OnPropertyChanged();
}
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Ключевые аспекты реализации
1. Атрибут CallerMemberName
Атрибут [CallerMemberName] автоматически подставляет имя вызывающего свойства, что предотвращает ошибки из-за опечаток в строковых литералах.
2. Проверка на изменение значения
Условная проверка if (_field != value) предотвращает бесконечные циклы и лишние обновления UI.
3. Потокобезопасность события
protected virtual void OnPropertyChanged(string propertyName)
{
// Потокобезопасный вызов события
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Расширенные подходы
Использование базового класса
Для повторного использования кода создается базовый класс:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual bool SetProperty<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// Использование
public class Product : ObservableObject
{
private decimal _price;
public decimal Price
{
get => _price;
set => SetProperty(ref _price, value);
}
}
Уведомление о зависимых свойствах
public class Order : ObservableObject
{
private int _quantity;
private decimal _unitPrice;
public int Quantity
{
get => _quantity;
set
{
if (SetProperty(ref _quantity, value))
{
OnPropertyChanged(nameof(Total));
}
}
}
public decimal UnitPrice
{
get => _unitPrice;
set
{
if (SetProperty(ref _unitPrice, value))
{
OnPropertyChanged(nameof(Total));
}
}
}
public decimal Total => Quantity * UnitPrice;
}
Современные альтернативы
Источники генерации кода
В .NET 5+ и C# 9.0+ можно использовать source generators:
[ObservableObject]
public partial class Vehicle
{
[ObservableProperty]
private string _model;
[ObservableProperty]
private int _year;
}
// Код генерируется автоматически во время компиляции
Библиотеки
- CommunityToolkit.Mvvm (ранее Microsoft.Toolkit.Mvvm) — предоставляет атрибуты для генерации кода
- Fody/PropertyChanged — IL weaving для автоматической реализации
- ReactiveUI — реактивные расширения с INotifyPropertyChanged
Производительность и лучшие практики
- Избегайте частых обновлений — группируйте изменения с помощью
BeginUpdate()/EndUpdate() - Используйте пул аргументов для уменьшения аллокаций:
private static readonly PropertyChangedEventArgs NameEventArgs =
new PropertyChangedEventArgs(nameof(Name));
- Виртуализация событий для наследников
- Отписка от событий для предотвращения утечек памяти
Пример комплексной реализации
public class AdvancedViewModel : ObservableObject
{
private readonly Dictionary<string, PropertyChangedEventArgs> _eventArgsCache;
private bool _isUpdating;
public AdvancedViewModel()
{
_eventArgsCache = new Dictionary<string, PropertyChangedEventArgs>();
}
public void BeginUpdate() => _isUpdating = true;
public void EndUpdate() => _isUpdating = false;
protected override void OnPropertyChanged(string propertyName)
{
if (_isUpdating) return;
if (!_eventArgsCache.TryGetValue(propertyName, out var args))
{
args = new PropertyChangedEventArgs(propertyName);
_eventArgsCache[propertyName] = args;
}
PropertyChanged?.Invoke(this, args);
}
}
INotifyPropertyChanged остается фундаментальным механизмом для MVVM-архитектуры в .NET приложениях. Выбор конкретной реализации зависит от требований проекта: от простой ручной реализации для небольших объектов до использования source generators или библиотек для сложных enterprise-приложений. Ключевое — обеспечить корректное обновление UI при минимальных накладных расходах на производительность.