← Назад к вопросам

Как реализовать INotifyPropertyChanged?

1.7 Middle🔥 161 комментариев
#Основы C# и .NET

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Реализация INotifyPropertyChanged в C#

INotifyPropertyChanged — это ключевой интерфейс в .NET для реализации паттерна наблюдатель (Observer) в контексте привязки данных (data binding). Он позволяет объектам уведомлять клиентов (обычно UI-элементы WPF, WinForms, MAUI) об изменениях свойств, обеспечивая автоматическое обновление интерфейса.

Базовая реализация

Минимальная реализация требует:

  1. Реализации интерфейса INotifyPropertyChanged
  2. Объявления события PropertyChanged
  3. Метода для вызова этого события
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

Производительность и лучшие практики

  1. Избегайте частых обновлений — группируйте изменения с помощью BeginUpdate()/EndUpdate()
  2. Используйте пул аргументов для уменьшения аллокаций:
private static readonly PropertyChangedEventArgs NameEventArgs = 
    new PropertyChangedEventArgs(nameof(Name));
  1. Виртуализация событий для наследников
  2. Отписка от событий для предотвращения утечек памяти

Пример комплексной реализации

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 при минимальных накладных расходах на производительность.

Как реализовать INotifyPropertyChanged? | PrepBro