SourceGeneration.ChangeTracking
1.0.0-beta4.250310.1
dotnet add package SourceGeneration.ChangeTracking --version 1.0.0-beta4.250310.1
NuGet\Install-Package SourceGeneration.ChangeTracking -Version 1.0.0-beta4.250310.1
<PackageReference Include="SourceGeneration.ChangeTracking" Version="1.0.0-beta4.250310.1" />
paket add SourceGeneration.ChangeTracking --version 1.0.0-beta4.250310.1
#r "nuget: SourceGeneration.ChangeTracking, 1.0.0-beta4.250310.1"
// Install SourceGeneration.ChangeTracking as a Cake Addin #addin nuget:?package=SourceGeneration.ChangeTracking&version=1.0.0-beta4.250310.1&prerelease // Install SourceGeneration.ChangeTracking as a Cake Tool #tool nuget:?package=SourceGeneration.ChangeTracking&version=1.0.0-beta4.250310.1&prerelease
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-beta4.250107.1
dotnet add package SourceGeneration.ChangeTracking --version 1.0.0-beta4.250107.1
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 Goods()
{
Price = 1.0;
}
public partial int Number { get; set; }
public partial double Price { get; set; }
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.
State
Based on ChangeTracking, we can build a state that subscribes to changes.
[ChangeTracking]
public partial class Goods : State<Goods>
{
public Goods()
{
Price = 1.0;
}
public partial int Number { get; set; }
public partial double Price { get; set; }
public partial int Count { get; set; }
}
The State class can create a IChangeTracker.
Goods state = new Goods();
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.Root
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 theUpdate
method is called, regardless of whether the value has changed or not.
[ChangeTracking]
public partial class Goods : State<Goods>
{
public Goods()
{
Tags = [];
}
public partial ChangeTrackingList<SubState> Tags { get; set; }
}
[ChangeTracking]
public partial class SubState
{
public partial string? Tag { get; set; }
}
// 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
var disposable = tracker.Watch(
selector: x => x.Tags,
subscriber: x => Console.WriteLine($"Tags count has changed: {x.Count}"),
scope: ChangeTrackingScope.Root);
// 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();
disposable.Dispose();
// Watch Tags with scope `ChangeTrackingScope.Cascading`
// The state will push last value when you subscribed
// ouput: Tags value has changed: first tag has modified
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();
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 SourceGeneration.Blazor.Statity 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. |
.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
- SourceGeneration.ChangeTracking.SourceGenerator (>= 1.0.0-beta4.250310.1)
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.0-beta4.250310.1 | 125 | 12 days ago |
1.0.0-beta4.250303.1 | 55 | 20 days ago |
1.0.0-beta4.250302.1 | 55 | 20 days ago |
1.0.0-beta4.250107.1 | 54 | 3 months ago |
1.0.0-beta3.241113.1 | 59 | 4 months ago |
1.0.0-beta3.241108.1 | 53 | 4 months ago |
1.0.0-beta3.240831.1 | 78 | 7 months ago |
1.0.0-beta2.240822.1 | 79 | 7 months ago |
1.0.0-beta2.240812.2 | 72 | 7 months ago |
1.0.0-beta2.240812.1 | 79 | 7 months ago |
1.0.0-beta2.240804.1 | 44 | 8 months ago |
1.0.0-beta2.240727.1 | 66 | 8 months ago |
1.0.0-beta2.240719.1 | 70 | 8 months ago |
1.0.0-beta2.240711.1 | 63 | 8 months ago |
1.0.0-beta1.240706.1 | 65 | 9 months ago |
1.0.0-beta1.240626.2 | 61 | 9 months ago |
1.0.0-beta1.240626.1 | 67 | 9 months ago |
1.0.0-beta1.240625.1 | 71 | 9 months ago |
1.0.0-beta1.240624.3 | 61 | 9 months ago |
1.0.0-beta1.240624.2 | 64 | 9 months ago |
1.0.0-beta1.240624.1 | 63 | 9 months ago |