Source-generated, awaitable INotifyPropertyChanged for .NET: await vm.SetNameAsync("Alice") returns only after every async handler has finished, with no reflection and NativeAOT support.
- Setters you can
await. Async validation, saving or coordinated state changes complete before the setter returns. CommunityToolkit.Mvvm and PropertyChanged.Fody only offer synchronous notifications. - 0 B when nobody listens. The generated setter creates event args only when there are subscribers; tests assert 0 B for reference-type and value-type properties. With subscribers, a change costs its args object, 40 B, plus a box per value-type value; tests pin both figures.
- Works under NativeAOT and trimming. All plumbing is generated at compile time; CI publishes and runs a NativeAOT sample on every build.
- Compile-time diagnostics. A class that is not
partial, a record, or a stray[ObservableProperty]is reported by the build, not discovered at runtime.
dotnet add package ZeroAlloc.NotifyThe source generator ships inside the package.
using ZeroAlloc.Notify;
var user = new UserViewModel();
user.PropertyChangedAsync += async (args, ct) =>
{
await Task.Delay(100, ct); // e.g. save to a server
Console.WriteLine($"{args.PropertyName}: {args.OldValue} -> {args.NewValue}, saved");
};
await user.SetNameAsync("Alice"); // returns after the handler has saved
await user.SetAgeAsync(30);
[NotifyPropertyChangedAsync]
public partial class UserViewModel
{
[ObservableProperty] private string _name = "";
[ObservableProperty] private int _age;
}The generator writes the Name and Age properties, the SetNameAsync and SetAgeAsync setters and the PropertyChangedAsync event.
- Async property notifications, before and after a change:
[NotifyPropertyChangedAsync]and[NotifyPropertyChangingAsync]. Observable properties - Async collection notifications with
[NotifyCollectionChangedAsync]. Collection changes - Async validation through
INotifyDataErrorInfoAsync. Validation - Parallel handlers by default, sequential on request with
[InvokeSequentially], and aCancellationTokenon every setter. Async notifications - Build-time checks, ZAN001 to ZAN006. Diagnostics
- Targets
netstandard2.0,net8.0andnet10.0. - Built on ZeroAlloc.AsyncEvents, the async event primitive behind every handler.
One property change with five handlers attached. .NET 10, BenchmarkDotNet, same run; the ratio is against a hand-written INotifyPropertyChanged, computed from the published means.
| Library | Ratio | Allocated | Async handlers |
|---|---|---|---|
Manual INotifyPropertyChanged |
1.00 | 24 B | no |
| PropertyChanged.Fody | 0.82 | 0 B | no |
| CommunityToolkit.Mvvm | 1.43 | 0 B | no |
| ZeroAlloc.Notify | 3.20 | 40 B | yes |
Awaitable handlers have a cost: Notify is the slowest of the four and allocates its args object when someone listens. For purely synchronous view models, Fody is the faster choice. Allocation breakdown, design notes and full numbers: docs/performance.md.
- Documentation: notify.zeroalloc.net
- Runnable example:
samples/ZeroAlloc.Notify.AotSmoke, published with NativeAOT in CI - Related packages: ZeroAlloc.AsyncEvents and the rest of the ZeroAlloc family
- Contributing: issues and pull requests are welcome on GitHub
- License: MIT