SourceGeneration.ChangeTracking
1.0.2
dotnet add package SourceGeneration.ChangeTracking --version 1.0.2
NuGet\Install-Package SourceGeneration.ChangeTracking -Version 1.0.2
<PackageReference Include="SourceGeneration.ChangeTracking" Version="1.0.2" />
<PackageVersion Include="SourceGeneration.ChangeTracking" Version="1.0.2" />
<PackageReference Include="SourceGeneration.ChangeTracking" />
paket add SourceGeneration.ChangeTracking --version 1.0.2
#r "nuget: SourceGeneration.ChangeTracking, 1.0.2"
#:package SourceGeneration.ChangeTracking@1.0.2
#addin nuget:?package=SourceGeneration.ChangeTracking&version=1.0.2
#tool nuget:?package=SourceGeneration.ChangeTracking&version=1.0.2
ChangeTracking
SourceGeneration.ChangeTracking is a state management framework based on Source Generator, it supports AOT compilation.
Installing
This library uses C# preview features partial property, Before using this library, please ensure the following prerequisites are met:
- Visual Studio is version 17.11 preview 3 or higher.
- To enable C# language preview in your project, add the following to your .csproj file
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
Install-Package SourceGeneration.ChangeTracking -Version 1.0.0
dotnet add package SourceGeneration.ChangeTracking --version 1.0.0
Start
States source generator will generate partial class for your state type, you just need to add ChangeTrackingAttriute, The state type must be partial, The property must be partial and have a setter
[ChangeTracking]
public partial class Goods
{
public partial int Number { get; set; }
public partial double Price { get; set; } = 1.0;
public partial int Count { get; set; }
}
The partial class implement INotifyPropertyChanging, INotifyPropertyChanged and IChangeTracking
public partial class Goods : INotifyPropertyChanging, INotifyPropertyChanged, System.ComponentModel.IChangeTracking
{
//Properties partial implementation
}
States determines whether an object has been modified through two methods:
- Checking if the object reference has changed.
- Checking IChangeTracking.IsChanged property.
Tracking Changes
There are two ways for state tracking:
- Via ChangeTracker
- Using State
The ChangeTracker can track object changes.
[ChangeTracking]
public partial class Goods
{
public partial int Number { get; set; }
public partial double Price { get; set; } = 1.0;
public partial int Count { get; set; }
}
Goods state = new();
var tracker = new ChangeTracker<Goods>(state);
tracker.Watch(x => x.Price, x => Console.WriteLine($"Price has changed: {x}"));
tracker.Watch(x => x.Count, x => Console.WriteLine($"Count has changed: {x}"));
state.Count++;
tracker.AcceptChanges(); // output: Count has changed: 1
state.Price = 3.14;
tracker.AcceptChanges(); // output: Price has changed: 3.14
State
State internally implements CreateTracker method to simplify tracking operations, allowing it to monitor its own state. Based on ChangeTracking, we can build a state that subscribes to changes.
[ChangeTracking]
public partial class Goods : State<Goods>
{
public partial int Number { get; set; }
public partial double Price { get; set; } = 1.0;
public partial int Count { get; set; }
}
The State class can create a IChangeTracker.
Goods state = new();
int currentCount = 0;
//Create a IChangeTracker to tracking state changes
var tracker = state.CreateTracker();
//Watch price and count property
tracker.Watch(x => x.Price, x => Console.WriteLine($"Price has changed: {x}"));
tracker.Watch(x => x.Count, x => Console.WriteLine($"Count has changed: {x}"));
state.Count++;
state.AcceptChanges(); // output: Count has changed: 1
state.Price = 3.14;
state.AcceptChanges(); // output: Price has changed: 3.14
state.Number = 1;
state.AcceptChanges(); // no output, because the Number property was not watch
state.Count = 1;
state.AcceptChanges(); // no output, because the Count property has not changed
Predicate
tracker.Watch(
selector: x => x.Count,
predicate: x => x >= 10,
subscriber: x => Console.WriteLine($"Count changed: {x}"));
// no console ouput, the value is less than 10
state.Count = 9;
state.AcceptChanges();
// ouput Count changed: 10
state.Count = 10;
state.AcceptChanges();
Change Scope
States support change scope, You can specify the scope of the subscribed changes.
- ChangeTrackingScope.Instance
The subscription only be triggered when there are changes in instance reference of the object itself. - ChangeTrackingScope.InstanceProperty
default value
The subscription only be triggered when there are changes in the properties of the object itself. - ChangeTrackingScope.Cascading
The subscription will be triggered when there are changes in the properties of the object itself or in the properties of its property objects. - ChangeTrackingScope.Always
The subscription will be triggered whenever theUpdatemethod is called, regardless of whether the value has changed or not.
[ChangeTracking]
public partial class Goods : State<Goods>
{
public partial ChangeTrackingList<SubState> Tags { get; set; } = [];
}
[ChangeTracking]
public partial class SubState
{
public partial string? Tag { get; set; }
}
ChangeTrackingScope.Instance
// Watch Tags with scope `ChangeTrackingScope.Root`, it's default value
// The state will push last value when you subscribed
// ouput: Tags count has changed 0
using var disposable = tracker.Watch(
selector: x => x.Tags,
subscriber: x => Console.WriteLine("Tags collection instance has changed"),
scope: ChangeTrackingScope.Instance);
// no output, because Tags collection instance is not changed
state.Tags.Add(new SubState { Tag = "first tag" });
state.AcceptChanges();
// output: 'Tags collection instance has changed'
state.Tags = [];
state.AcceptChanges();
ChangeTrackingScope.InstanceProperty
// Watch Tags with scope `ChangeTrackingScope.Root`, it's default value
// The state will push last value when you subscribed
// ouput: Tags count has changed 0
using var disposable = tracker.Watch(
selector: x => x.Tags,
subscriber: x => Console.WriteLine($"Tags count has changed: {x.Count}"),
scope: ChangeTrackingScope.InstanceProperty);
// output: Tags count has changed: 1
state.Tags.Add(new SubState { Tag = "first tag" });
state.AcceptChanges();
// no output, because Tags property is not changed
state.Tags[0].Tag = "first tag has modified";
state.AcceptChanges();
ChangeTrackingScope.Cascading
// Watch Tags with scope `ChangeTrackingScope.Cascading`
// The state will push last value when you subscribed
// ouput: Tags value has changed: first tag has modified
using var disposable = tracker.Watch(
selector: x => x.Tags,
subscriber: x => Console.WriteLine($"Tags value has changed: {x[0].Tag}"),
scope: ChangeTrackingScope.Cascading);
// ouput: Tags value has changed: first tag has modified * 2
state.Tags[0].Tag = "first tag has modified * 2";
state.AcceptChanges();
Tracking QueryView
[ChangeTracking]
public partial class State
{
public partial ChangeTrackingList<int> List { get; set; } = [];
}
Goods state = new();
using var tracker = new ChangeTracker(state);
tracker.OnChange(()=> Console.WriteLine("Query results changed"))
tracker.Watch(
selector: x => x.List,
predicate: x => x > 3);
// no output, the added item does not meet the condition of being greater than 5.
state.Tags.Add(1)
tracker.AcceptChanges();
// output: 'Query results changed'
state.Tags.Add(4)
tracker.AcceptChanges();
Merge Changes
Some times we need to merge all changes,
you can use OnChange
MyState state = new MyState();
var tracker = state.CreateTracker();
tracker.Watch(x => x.Count);
tracker.Watch(x => x.Price);
tracker.OnChange(state =>
{
Console.WriteLine($"Count or Price has changed. Count={count}, Price={state.Price}");
});
//ouput: Count or Price has changed
state.Price = 3.14;
state.Count = 10;
state.AcceptChanges();
//no output, because Count has not changed
state.Count = 10;
state.AcceptChanges();
//no output, because property Number has not subscribed
state.Number = 3;
state.AcceptChanges();
//ouput: Count or Price has changed
state.Count = 11;
state.AcceptChanges();
DependencyInjection
The State class only has a parameterless constructor, making it easy to use dependency injection.
[ChangeTracking]
public partial class MyState(ILogger<MyState> logger) : State<MyState>
{
public partial int Count { get; set; }
public void Increment()
{
Count++;
State.AcceptChanges();
logger.LogInformation("Count Increment");
}
}
var services = new ServiceCollection();
services.AddLogging();
services.AddScoped<Goods>();
services.AddSingleton<MyState>();
Dispose & Unsubscribe
In most usage scenarios, when your page or component subscribes to the state, it must explicitly unsubscribe when the component is destroyed, otherwise it will result in a significant resource consumption.
Goods state = new();
var tracker = state.CreateTracker();
var disposable1 = state.Watch(x => x.Count);
var disposable2 = state.Watch(x => x.Tags.Count);
var disposable3 = state.OnChange(x => { });
disposable1.Dispose(); // unsubscribe: Count property watch
disposable2.Dispose(); // unsubscribe: Tags.Count property watch
disposable3.Dispose(); // unsubscribe: merge changed subscribe
tracker.Dispose(); // dispose tracker
Use in Blazor
You can use States in Blazor, it supports AOT compilation
//WebAssembly or Hybird
services.AddSingleton<Goods>();
//Server
services.AddScoped<Goods>();
Inject state into component
@inject Goods State
@implements IDisposable
<h1>Count: @State.Count</h1>
<button @onclick="Click">Add</button>
@code{
private IChangeTracker Tracker;
protected override void OnInitialized()
{
Tracker = State.CreateTracker();
Tracker.Watch(x => x.Count);
Tracker.OnChange(StateHasChanged);
}
private void Click()
{
State.Count++;
State.AcceptChanges();
}
public void Dispose()
{
Tracker.Dispose();
}
}
You can use the SourceGeneration.Blazor library to simplify this process, more information see Blazor.State repo
@inherits StateComponentBase
@inject Goods State
<h1>Count: @State.Count</h1>
<button @onclick="Click">Add</button>
@code{
private int Count;
protected override void OnStateBinding()
{
Watch(State, x => x.Count);
}
private void Click()
{
State.Count++;
State.AcceptChanges();
}
}
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
NuGet packages (3)
Showing the top 3 NuGet packages that depend on SourceGeneration.ChangeTracking:
| Package | Downloads |
|---|---|
|
SourceGeneration.Blazor.Statity
Blazor state management and action disapatch/subscribe extensions |
|
|
SourceGeneration.Blazor.State
Blazor state management and action disapatch/subscribe extensions |
|
|
SourceGeneration.ChangeTracking.DependencyInjection
ChangeTracking is a object change tracking and state management framework based on Source Generator, supports AOT compilation. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.2 | 180 | 10/30/2025 |
| 1.0.1 | 182 | 10/30/2025 |
| 1.0.0 | 278 | 7/20/2025 |
| 1.0.0-beta5.250513.3 | 240 | 5/13/2025 |
| 1.0.0-beta5.250513.2 | 223 | 5/13/2025 |
| 1.0.0-beta5.250513.1 | 222 | 5/12/2025 |
| 1.0.0-beta5.250513.0 | 226 | 5/12/2025 |
| 1.0.0-beta4.250505.0 | 135 | 5/5/2025 |
| 1.0.0-beta4.250310.1 | 175 | 3/10/2025 |
| 1.0.0-beta4.250303.1 | 91 | 3/2/2025 |
| 1.0.0-beta4.250302.1 | 91 | 3/2/2025 |
| 1.0.0-beta4.250107.1 | 90 | 1/7/2025 |
| 1.0.0-beta3.241113.1 | 85 | 11/13/2024 |
| 1.0.0-beta3.241108.1 | 77 | 11/8/2024 |
| 1.0.0-beta3.240831.1 | 101 | 8/31/2024 |
| 1.0.0-beta2.240822.1 | 107 | 8/22/2024 |
| 1.0.0-beta2.240812.2 | 98 | 8/12/2024 |
| 1.0.0-beta2.240812.1 | 102 | 8/12/2024 |
| 1.0.0-beta2.240804.1 | 70 | 8/4/2024 |
| 1.0.0-beta2.240727.1 | 92 | 7/27/2024 |
| 1.0.0-beta2.240719.1 | 97 | 7/19/2024 |
| 1.0.0-beta2.240711.1 | 88 | 7/11/2024 |
| 1.0.0-beta1.240706.1 | 97 | 7/6/2024 |
| 1.0.0-beta1.240626.2 | 81 | 6/26/2024 |
| 1.0.0-beta1.240626.1 | 93 | 6/26/2024 |
| 1.0.0-beta1.240625.1 | 94 | 6/25/2024 |
| 1.0.0-beta1.240624.3 | 90 | 6/24/2024 |
| 1.0.0-beta1.240624.2 | 90 | 6/24/2024 |
| 1.0.0-beta1.240624.1 | 83 | 6/24/2024 |