Ranvier.CSharp
0.1.1-preview.1
dotnet add package Ranvier.CSharp --version 0.1.1-preview.1
NuGet\Install-Package Ranvier.CSharp -Version 0.1.1-preview.1
<PackageReference Include="Ranvier.CSharp" Version="0.1.1-preview.1" />
<PackageVersion Include="Ranvier.CSharp" Version="0.1.1-preview.1" />
<PackageReference Include="Ranvier.CSharp" />
paket add Ranvier.CSharp --version 0.1.1-preview.1
#r "nuget: Ranvier.CSharp, 0.1.1-preview.1"
#:package Ranvier.CSharp@0.1.1-preview.1
#addin nuget:?package=Ranvier.CSharp&version=0.1.1-preview.1&prerelease
#tool nuget:?package=Ranvier.CSharp&version=0.1.1-preview.1&prerelease
Ranvier.CSharp
C# entry points for Ranvier: the Reactive factories take
delegates, and extension methods cover Graph.Run, Signal.Update and the projection operators.
Per-node equality
Signals, memos (including owning and seeded forms), split effects and boundaries accept an optional typed comparer through additional overloads:
var name = Reactive.Signal("Ada", StringComparer.OrdinalIgnoreCase);
var label = Reactive.Memo(() => name.Value.Trim(), StringComparer.OrdinalIgnoreCase);
Reactive.EffectOn(() => label.Value, Console.WriteLine, StringComparer.OrdinalIgnoreCase);
Call these factories inside graph.Run or an active graph scope. Omit the comparer to use the
graph policy. A supplied comparer applies only to that node; pending/error state transitions
and ownership rules still apply. Null comparers throw ArgumentNullException.
Basic use
using Ranvier;
using Ranvier.CSharp;
using static Ranvier.CSharp.Reactive;
var graph = new Graph();
graph.Run(() =>
{
var count = Signal(1);
var doubled = Memo(() => count.Value * 2);
Effect(() => Console.WriteLine($"doubled {doubled.Value}"));
count.Value = 2;
});
Async values
A derived value over an async source, with loading and failure surfaced by a boundary instead of a hand-written
busy flag and try/catch:
public sealed class SearchViewModel
{
public SearchViewModel(ISearchService service)
{
Query = Signal("");
Results = Async(token => service.SearchAsync(Query.Value, token));
Summary = Boundary(
() => Results.Value.Count == 0 ? "No matches" : string.Join(", ", Results.Value),
() => "Searching…",
error => $"Search failed: {error.Message}");
}
public Signal<string> Query { get; }
public AsyncMemo<IReadOnlyList<string>> Results { get; }
public Boundary<string> Summary { get; }
public bool IsSearching => Summary.IsWaiting;
public Exception? Error => Summary.Caught;
}
ISearchService declares one method, Task<IReadOnlyList<string>> SearchAsync(string query, CancellationToken token).
Construct the view model inside graph.Run. The first read of Summary after a write to Query starts a new search
and cancels the token of the one in progress; IsSearching and Error follow the boundary.
Binding to XAML
The core's fixed-dependency bindings
copy one source to a target. The Reactive facade has no BindSignal or BindMemo factories;
the XAML notification adapter below serves view-model properties.
ReactiveBindings raises INotifyPropertyChanged and INotifyDataErrorInfo for view-model properties backed by
memos and signals, inside any existing view model; ReactiveObject is a base class over it. See the
C# guide.
ReactiveCommand is an ICommand whose CanExecute and busy state come from graph nodes; see
Commands.
Editable collections
Create an editable source inside graph.Run, then derive views from its Rows:
var items = Reactive.KeyedCollection<(int Id, string Title), int>(item => item.Id);
items.Edit(edit =>
{
edit.AddOrUpdate((1, "Write"));
edit.AddOrUpdate((2, "Test"));
});
var titles = items.Rows.Select(item => item.Title);
using var reader = titles.NewValueReader();
reader.Read();
items.AddOrUpdate((2, "Retest"));
var delta = reader.Read();
An existing-key write preserves its position and updates its row directly. New keys append;
Remove and Clear change membership. Edit batches synchronous writes and retains applied
edits if the callback throws.
NewKeyReader reports membership and order; NewValueReader also reports KeyChange.Changed
for unequal settled values under the graph policy. First reads and overflow report a reset.
Deltas contain coalesced key hints: read current values through the projection and handle its
pending/error states there. Dispose readers to release shared observation. See the
C# collection guide.
Tracing
Tracing.Named and Tracing.Label compile in every build. The queries (Origin, Why, WhyDepth, WhyNot,
History, WaitingOn, Snapshot, Resolve, Reconcile, Events, DumpText, Dump) return text, and exist only
in the traced package, Ranvier.CSharp.Traced. It holds the same assembly, namespaces and version as
Ranvier.CSharp, and depends on Ranvier.Traced. Switch between the two and define RANVIER_TRACE with them:
<PropertyGroup Condition="'$(RanvierTrace)' == 'true'">
<DefineConstants>$(DefineConstants);RANVIER_TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ranvier.CSharp" Condition="'$(RanvierTrace)' != 'true'" />
<PackageReference Include="Ranvier.CSharp.Traced" Condition="'$(RanvierTrace)' == 'true'" />
</ItemGroup>
var graph = new Graph();
graph.Run(() =>
{
var count = Tracing.Named("count", () => Signal(1));
var log = Effect(() => Console.WriteLine(count.Value));
Tracing.Named("changes", () => EffectOn(() => count.Value, value => { }));
Flush();
count.Value = 2;
Flush();
#if RANVIER_TRACE
Console.WriteLine(Tracing.Why(graph, log));
Console.WriteLine(Tracing.Why(graph, "/changes"));
#endif
});
Each per-node query takes the node, or its identity path. Tracing.Label is [Conditional("RANVIER_TRACE")]: the
call stays only in a project that defines RANVIER_TRACE itself.
| 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 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. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | 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.1
- FSharp.Core (>= 8.0.100)
- Ranvier (>= 0.1.1-preview.1)
-
net10.0
- FSharp.Core (>= 8.0.100)
- Ranvier (>= 0.1.1-preview.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.1-preview.1 | 74 | 10/28/2026 |
| 0.1.0-preview.7 | 56 | 10/26/2026 |
| 0.1.0-preview.6 | 57 | 10/21/2026 |
| 0.1.0-preview.5 | 69 | 10/4/2026 |
| 0.1.0-preview.4 | 70 | 10/3/2026 |
| 0.1.0-preview.3 | 64 | 9/30/2026 |
| 0.1.0-preview.2 | 66 | 9/30/2026 |
| 0.1.0-preview.1 | 59 | 9/29/2026 |
Preview release. The API may change before 1.0.