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, all four libraries in one BenchmarkDotNet run. Allocations are exact; the ratio is each library's time against a hand-written INotifyPropertyChanged in the same run. Absolute timings are not published.
| Library | Ratio | Allocated | Async handlers |
|---|---|---|---|
Manual INotifyPropertyChanged |
1.00 | 24 B | no |
| PropertyChanged.Fody | 0.93 | 0 B | no |
| CommunityToolkit.Mvvm | 1.81 | 0 B | no |
| ZeroAlloc.Notify | 1.47 | 40 B | yes |
Awaitable handlers have a cost: Notify is slower than hand-written code and Fody, and allocates its args object when someone listens. For purely synchronous view models, Fody is the faster choice. The .NET 8 ratios are in the performance doc. 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