TripleG3.Cis
0.0.3
dotnet add package TripleG3.Cis --version 0.0.3
NuGet\Install-Package TripleG3.Cis -Version 0.0.3
<PackageReference Include="TripleG3.Cis" Version="0.0.3" />
<PackageVersion Include="TripleG3.Cis" Version="0.0.3" />
<PackageReference Include="TripleG3.Cis" />
paket add TripleG3.Cis --version 0.0.3
#r "nuget: TripleG3.Cis, 0.0.3"
#:package TripleG3.Cis@0.0.3
#addin nuget:?package=TripleG3.Cis&version=0.0.3
#tool nuget:?package=TripleG3.Cis&version=0.0.3
TripleG3.Cis
Asynchronously Request Change, Read Immutable State, Listen For State Changes - Clean, Simple, Safe
Command Immutable State Pattern: a tiny .NET library for services that need one clear state snapshot, one update pipeline, and simple change notifications.
Think of it as a calm little command center for async work: call a method, the service becomes Busy, it produces a value, then it becomes Ready. If something goes sideways, the state lands on Error with a message.
Install Or Reference
This repo currently contains the library project directly. Reference it from another project with a normal project reference:
<ItemGroup>
<ProjectReference Include="..\TripleG3.Cis\TripleG3.Cis.csproj" />
</ItemGroup>
Build everything from the repository root:
dotnet build TripleG3.Cis.slnx
The Simple Idea
TripleG3.Cis gives you a pattern for command-driven state:
- A public method can represent the command, such as
RefreshAsyncorSetNextStepAsync. - The command calls
SetAsync(...), or simple consumers can useStateService<T>directly. StateService<T>serializes updates so one transition runs at a time.- Consumers read
Stateor subscribe toStateChanged.
No mystery ceremony. Just immutable snapshots and predictable transitions.
API Tour
| Type | What It Does | When To Use It |
|---|---|---|
State<T> |
Immutable snapshot with nullable Value, Status, and ErrorMessage. |
Return or inspect the latest service state. |
StateStatus |
Status enum: None, Busy, Ready, Error. |
Decide what the UI, caller, or workflow should do next. |
StateValueFactory<T> |
Async delegate that creates the next state value. | Wrap the actual work used by SetAsync. |
IStateService<T> |
Contract for observable state services. | Depend on state behavior without tying callers to a concrete class. |
StateService<T> |
Concrete service that implements state transitions and notifications. | Use directly for simple state or derive from it for named command methods. |
IStateService<T>.Empty |
No-op state service that always returns State<T>.Empty. |
Use as a safe default or placeholder. |
State Statuses
StateStatus.None means nothing has run yet.
StateStatus.Busy means a transition is running.
StateStatus.Ready means the latest value was produced successfully. State<T>.Value is nullable because empty and failed states may not have a reference-type value.
StateStatus.Error means the latest transition threw an exception. Check State<T>.ErrorMessage for the message.
Use StateService Directly
For one-command state, use StateService<T> directly and pass the value factory to SetAsync.
using TripleG3.Cis;
public sealed record DownloadInfo(string FileName, int PercentComplete);
var service = new StateService<DownloadInfo>();
service.StateChanged += (_, state) =>
{
Console.WriteLine($"{state.Status}: {state.Value?.FileName} {state.Value?.PercentComplete}%");
};
State<DownloadInfo> result = await service.SetAsync(GetDownloadInfoAsync, CancellationToken.None);
if (result is { Status: StateStatus.Ready, Value: { } downloadInfo })
{
Console.WriteLine($"Done: {downloadInfo.FileName}");
}
static async ValueTask<DownloadInfo> GetDownloadInfoAsync(CancellationToken cancellationToken)
{
await Task.Delay(250, cancellationToken);
return new DownloadInfo("Guide.pdf", 100);
}
Create A Named State Service
Derive from StateService<T> and expose command methods that call SetAsync.
using TripleG3.Cis;
public sealed record DownloadInfo(string FileName, int PercentComplete);
public sealed class DownloadStateService : StateService<DownloadInfo>
{
public ValueTask<State<DownloadInfo>> RefreshAsync(CancellationToken cancellationToken)
{
return SetAsync(GetDownloadInfoAsync, cancellationToken);
}
private static async ValueTask<DownloadInfo> GetDownloadInfoAsync(CancellationToken cancellationToken)
{
await Task.Delay(250, cancellationToken);
return new DownloadInfo("Guide.pdf", 100);
}
}
Use a named service like this:
var service = new DownloadStateService();
service.StateChanged += (_, state) =>
{
Console.WriteLine($"{state.Status}: {state.Value?.FileName} {state.Value?.PercentComplete}%");
};
State<DownloadInfo> result = await service.RefreshAsync(CancellationToken.None);
if (result is { Status: StateStatus.Ready, Value: { } downloadInfo })
{
Console.WriteLine($"Done: {downloadInfo.FileName}");
}
Examples Details
The src\TripleG3.Cis\Examples path contains a small console-friendly walkthrough. These types are compiled by the console sample and tests, not by the packaged library assembly.
| Example Type | What It Shows | Where It Fits |
|---|---|---|
ExampleServiceSteps |
A workflow value that moves from None to Complete. |
The state value type used by the example service. |
IExampleApi |
An external step lookup contract. | The dependency ExampleService calls to get the next step. |
ExampleApi |
A simulated API implementation with a small delay. | The sample external dependency used by the console app. |
IExampleService |
A service contract that extends IStateService<ExampleServiceSteps>. |
The abstraction callers can depend on. |
ExampleService |
A concrete service built on StateService<ExampleServiceSteps>. |
The command implementation that asks IExampleApi for the next step. |
ExampleServiceWatcher |
A simple console observer for StateChanged. |
The listener that prints every state transition. |
Use The Example Service
The Examples folder contains a tiny workflow that walks through these values:
None -> Step1 -> Step2 -> Step3 -> Complete
using TripleG3.Cis.Examples;
var exampleApi = new ExampleApi();
var service = new ExampleService(exampleApi);
var watcher = new ExampleServiceWatcher(service);
await service.SetNextStepAsync(CancellationToken.None);
await service.SetNextStepAsync(CancellationToken.None);
await service.SetNextStepAsync(CancellationToken.None);
await service.SetNextStepAsync(CancellationToken.None);
Console.WriteLine(service.State.Status);
Console.WriteLine(service.State.Value);
ExampleService delegates step calculation to IExampleApi with the state-aware SetAsync overload, then lets StateService<ExampleServiceSteps> handle the Busy, Ready, and Error transitions. ExampleServiceWatcher subscribes to StateChanged and writes each update to the console. It is intentionally simple so the state pattern is easy to see.
Console output from running src\TripleG3.Cis.ConsoleTest\TripleG3.Cis.ConsoleTest.csproj:
State changed: Busy - None
-----------------------------------------------------
State changed: Ready - Step1
-----------------------------------------------------
State changed: Busy - Step1
-----------------------------------------------------
State changed: Ready - Step2
-----------------------------------------------------
State changed: Busy - Step2
-----------------------------------------------------
State changed: Ready - Step3
-----------------------------------------------------
State changed: Busy - Step3
-----------------------------------------------------
State changed: Ready - Complete
-----------------------------------------------------
Use The Empty Service
IStateService<T>.Empty is handy when a caller needs an IStateService<T> but there is no real implementation yet.
IStateService<string> service = IStateService<string>.Empty;
State<string> state = await service.SetAsync(
cancellationToken => new ValueTask<string>("This value is ignored."),
CancellationToken.None);
Console.WriteLine(state.Status); // None
Console.WriteLine(state.Value is null); // True
The empty service never invokes the factory and always returns State<T>.Empty, whose Value is null for reference-type state values.
Tips
- Keep state values small and meaningful. Records work nicely.
- Put business actions in command methods such as
LoadAsync,SaveAsync, orSetNextStepAsync. - Let
SetAsynchandle the transition state instead of setting status manually. - Subscribe to
StateChangedwhen a UI, console, or workflow needs to react. - Inspect
StateStatus.ErrorandErrorMessagewhen a command fails.
Project Layout
src/
TripleG3.Cis/
Delegates.cs StateValueFactory<T>
IStateService.cs IStateService<T>
State.cs State<T>
StateService.cs StateService<T>
StateStatus.cs StateStatus
Examples/
ExampleApi.cs ExampleApi
ExampleService.cs ExampleService
ExampleServiceSteps.cs ExampleServiceSteps
ExampleServiceWatcher.cs
IExampleApi.cs IExampleApi
IExampleService.cs
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.