Question

Implement INotifyPropertyChanged Gracefully in C#

csharp.netwinformsinotifypropertychanged

Question

Is there a cleaner way to implement INotifyPropertyChanged in C# than manually raising the PropertyChanged event in every property?

For example, it would be convenient if auto-properties supported syntax similar to this:

public string Name { get; set; notify; }

Can a class implement this behavior gracefully, or must every property explicitly raise PropertyChanged? If the language does not support it directly, can the repetitive notification code be generated automatically?

Short Answer

By the end of this page, you will understand why property-change notifications exist, how to implement them safely with a reusable SetProperty method, and when source generators or MVVM libraries can remove even more boilerplate.

Concept

INotifyPropertyChanged is a C# interface used by data-binding systems. It lets an object announce that one of its property values has changed.

For example, a UI might display Person.Name. If code later changes Name, the UI needs a signal so it knows to redraw the displayed value. That signal is the PropertyChanged event.

The interface is small:

using System.ComponentModel;

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler? PropertyChanged;
}

The important part is not merely raising an event. A good implementation should also:

  • Raise the event only when the value actually changes.
  • Provide the correct property name.
  • Avoid repeating nearly identical code in every setter.
  • Work correctly for different property types.

C# auto-properties such as public string Name { get; set; } generate a hidden backing field and simple getter/setter. They do not provide a built-in notify modifier. A setter must contain notification logic, either written directly, delegated to a helper, or generated by tooling.

This pattern is especially common in MVVM applications using WPF, WinUI, .NET MAUI, Avalonia, and similar frameworks.

Mental Model

Think of a property as a labelled notice board in an office.

  • The property value is the message currently pinned to the board.
  • A data-bound UI is a person watching that board.
  • PropertyChanged is an announcement over the office speaker: “The Name board has been updated.”

Without the announcement, the watcher may continue showing the old message. A reusable SetProperty helper acts like an office rule: compare the old and new messages, replace the message only when needed, then make exactly one announcement.

Take Quiz

Syntax and Examples

A common approach is to put the repeated logic in a base class.

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

public abstract class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged(
        [CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected 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;
    }
}

A class can inherit from it:

public class PersonViewModel : ObservableObject
{
    private string _name = string.Empty;

    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}

[CallerMemberName] fills in the caller's member name automatically. When the Name setter calls SetProperty, propertyName becomes "Name"; you do not need to write a fragile string literal.

Usage:

var person = new PersonViewModel();

person.PropertyChanged += (_, e) =>
{
    Console.WriteLine($"Changed: {e.PropertyName}");
};

person.Name = "Ada"; // Prints: Changed: Name
person.Name = "Ada"; // No event, because the value is unchanged

A backing field is necessary for this manual pattern. A normal auto-property does not expose its compiler-generated backing field, so there is no field that SetProperty(ref field, value) can update.

Step by Step Execution

Consider this code:

var person = new PersonViewModel();
person.PropertyChanged += (_, e) => Console.WriteLine(e.PropertyName);

person.Name = "Ada";
person.Name = "Ada";
person.Name = "Grace";

Execution trace:

  1. person is created. Its _name field starts as an empty string.
  2. An event handler subscribes to PropertyChanged.
  3. person.Name = "Ada" calls the Name setter.
  4. The setter calls SetProperty(ref _name, "Ada").
  5. SetProperty compares "" with "Ada". They differ.
  6. It assigns "Ada" to _name.
  7. It calls OnPropertyChanged(). Because of [CallerMemberName], the property name is "Name".
  8. The event handler runs and prints Name.
  9. Assigning "Ada" again compares equal to the current value, so SetProperty returns without raising an event.
  10. Assigning "Grace" differs, so the field changes and another Name notification is raised.

Skipping unchanged values prevents unnecessary UI updates and avoids triggering dependent work when nothing changed.

Real World Use Cases

Property notifications are useful whenever another part of an application must react to an updated value.

  • Form fields: Enable a Save button after a user changes a name or email address.
  • Desktop and mobile UI: Refresh text, check boxes, sliders, and status labels through data binding.
  • Calculated display values: Notify the UI that FullName changed after FirstName or LastName changes.
  • Validation: Re-evaluate validation messages when an editable value changes.
  • Commands: Update whether a command can execute after state changes.
  • Application state: Notify screens that a loading flag, error message, selected item, or progress value changed.

For example, a derived property needs an additional notification:

public class PersonViewModel : ObservableObject
{
    private string _firstName = string.Empty;

    public string FirstName
    {
        get => _firstName;
        set
        {
            if (SetProperty(ref _firstName, value))
            {
                OnPropertyChanged(nameof(Greeting));
            }
        }
    }

    public string Greeting => $"Hello, {FirstName}!";
}

Changing FirstName changes both FirstName and the computed Greeting, so both notifications are required.

Take Quiz

Real Codebase Usage

In production code, developers usually avoid writing PropertyChanged?.Invoke(...) in every setter. Common patterns include:

A shared observable base class

A base class with SetProperty centralizes equality checking and event raising. This is simple, explicit, and has no extra package requirement.

Guarding side effects with the return value

Because SetProperty returns true only for a real change, setters can safely trigger extra work only when needed.

public bool IsBusy
{
    get => _isBusy;
    set
    {
        if (SetProperty(ref _isBusy, value))
        {
            SaveCommand.NotifyCanExecuteChanged();
        }
    }
}

Notifying dependent properties

A change to one property can affect another calculated property. Raise a notification for the dependent property after the original property successfully changes.

Source generators

Source generators can generate observable-property code at compile time. For example, the CommunityToolkit.Mvvm package provides attributes that generate backing fields, properties, and notifications:

using CommunityToolkit.Mvvm.ComponentModel;

public partial class PersonViewModel : ObservableObject
{
    [ObservableProperty]
    private string name = string.Empty;
}

The generated code behaves like an observable Name property. The class must be partial so generated source can join it.

Framework or library base types

Some UI frameworks and MVVM libraries provide their own observable base classes or similar APIs. Use the convention already established by the project instead of mixing several notification systems unnecessarily.

Keep models and view models separate when appropriate

A database or API model does not always need notifications. Often, view models—the objects directly bound to a UI—implement INotifyPropertyChanged, while transport or persistence models remain plain classes.

Common Mistakes

Raising an event every time, even when the value is unchanged

This works but may cause unnecessary redraws, validation, or command updates.

// Less desirable
public string Name
{
    get => _name;
    set
    {
        _name = value;
        OnPropertyChanged();
    }
}

Prefer SetProperty, which compares values first.

Using string literals for property names

OnPropertyChanged("Nmae"); // Typo compiles but binding will not update.

Use [CallerMemberName] for the current property or nameof for another property:

OnPropertyChanged(nameof(Greeting));

Trying to pass an auto-property by ref

public string Name { get; set; } = string.Empty;

// SetProperty(ref Name, value); // Invalid: properties cannot be passed by ref this way.

Use an explicit backing field, or use a source generator that creates the field and property for you.

Forgetting dependent properties

If FullName uses FirstName, notifying only FirstName may leave the UI's FullName display stale. Notify FullName too.

Raising the event before assigning the new value

Subscribers commonly read the property when they receive the event. Assign the field first, then raise the event.

field = value;
OnPropertyChanged();

Assuming collection changes are covered

INotifyPropertyChanged reports that a property reference or value changed. It does not report items being added to a normal List<T>. For bindable collections, use a collection type that implements INotifyCollectionChanged, such as ObservableCollection<T>.

Forgetting null-safe event invocation

An event can have no subscribers. Use:

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

The ?. prevents a null-reference exception.

Comparisons

ApproachBoilerplateCompile-time safetyBest fit
Raise the event in every setterHighDepends on use of nameofSmall, one-off classes
SetProperty base-class helperLowYes with [CallerMemberName] and nameofMost hand-written view models
Source generatorVery low in handwritten codeYes; generated during buildProjects already using an MVVM toolkit
Runtime code weavingLow in source codeDepends on tool configurationExisting projects committed to that tooling
Related conceptDifference
INotifyPropertyChangedReports that a property value changed.
INotifyCollectionChangedReports changes to collection contents, such as add, remove, or move.
ObservableCollection<T>A collection implementation that raises collection-change notifications.
Plain auto-propertyStores and retrieves a value but does not announce changes.

A reusable helper is not language syntax, but it provides most of the desired convenience while keeping generated behavior visible and debuggable.

Take Quiz

Cheat Sheet

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

public abstract class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged(
        [CallerMemberName] string? propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    protected 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;
    }
}
private int _count;

public int Count
{
    get => _count;
    set => SetProperty(ref _count, value);
}
  • Use a backing field with the manual helper pattern.
  • Use EqualityComparer<T>.Default for general-purpose equality checks.
  • Use [CallerMemberName] to avoid hard-coded current-property names.
  • Use nameof(OtherProperty) for dependent-property notifications.
  • Assign the new value before raising the event.
  • Check the Boolean result from SetProperty before doing extra work.
  • Use ObservableCollection<T> when the contents of a collection must notify the UI.

FAQ

Does C# have a built-in notify keyword for properties?

No. Standard C# auto-properties do not have a notify modifier. You can write a custom setter, use a reusable helper, or use code-generation tooling.

Why does SetProperty use a backing field?

It needs a variable passed by reference so it can compare and update the stored value. A normal property is accessed through methods, not exposed as a ref field.

Is [CallerMemberName] required?

No, but it greatly reduces repeated property-name strings and prevents many spelling mistakes. You can pass a property name explicitly when necessary.

Should I always implement INotifyPropertyChanged?

No. Use it when consumers, commonly a UI binding system, need to react to changes. Plain data-transfer objects and immutable values often do not need it.

What should I do when one property affects another?

After a successful update, raise PropertyChanged for each dependent property using OnPropertyChanged(nameof(DependentProperty)).

Does INotifyPropertyChanged detect changes inside a list?

No. It only announces property changes. Use ObservableCollection<T> or another implementation of INotifyCollectionChanged to notify listeners about items added, removed, or moved.

Can source generators remove the backing-field boilerplate?

Yes. MVVM source generators can produce the backing field, setter, and notification code during compilation from an annotated field. Review the library's generated API and use it consistently within the project.

Take Quiz

Mini Project

Description

Build a small view model for a profile editor. It exposes editable first and last names plus a calculated display name. The project demonstrates reusable property notification code and notifications for derived values.

Goal

Create a view model that reports property changes only when values change and keeps a calculated display name updated.

Requirements

Requirement 1

Take Quiz

Keep learning

Related questions

View more