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.
PropertyChangedis an announcement over the office speaker: “TheNameboard 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.
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:
personis created. Its_namefield starts as an empty string.- An event handler subscribes to
PropertyChanged. person.Name = "Ada"calls theNamesetter.- The setter calls
SetProperty(ref _name, "Ada"). SetPropertycompares""with"Ada". They differ.- It assigns
"Ada"to_name. - It calls
OnPropertyChanged(). Because of[CallerMemberName], the property name is"Name". - The event handler runs and prints
Name. - Assigning
"Ada"again compares equal to the current value, soSetPropertyreturns without raising an event. - Assigning
"Grace"differs, so the field changes and anotherNamenotification 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
FullNamechanged afterFirstNameorLastNamechanges. - 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.
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
| Approach | Boilerplate | Compile-time safety | Best fit |
|---|---|---|---|
| Raise the event in every setter | High | Depends on use of nameof | Small, one-off classes |
SetProperty base-class helper | Low | Yes with [CallerMemberName] and nameof | Most hand-written view models |
| Source generator | Very low in handwritten code | Yes; generated during build | Projects already using an MVVM toolkit |
| Runtime code weaving | Low in source code | Depends on tool configuration | Existing projects committed to that tooling |
| Related concept | Difference |
|---|---|
INotifyPropertyChanged | Reports that a property value changed. |
INotifyCollectionChanged | Reports changes to collection contents, such as add, remove, or move. |
ObservableCollection<T> | A collection implementation that raises collection-change notifications. |
| Plain auto-property | Stores 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.
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>.Defaultfor 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
SetPropertybefore 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.
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
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Access Modifiers and static: public, private, protected, and Defaults
Learn how C# public, private, protected, and default access control visibility, and how static differs from instance members.