7.2. C#¶
7.2.1. Snapshot¶
- Install:
dotnet add package VMx - Publication status: package name is documented, but the core NuGet package is
not published yet; use a project or local source reference until a
csharp-v*release publishes it. - Reactive primitive:
System.Reactive - Naming idiom: PascalCase
- Hub concurrency: ordinary producers retain synchronous calling-thread delivery while nested cross-hub callbacks enqueue without a wait cycle
7.2.2. What To Reach For¶
C# is the most direct fit when you want .NET host integration, ICommand-style
UI binding, or desktop MVVM with WPF or Avalonia. The README also documents
companion assemblies for DI and notifications.
7.2.3. Serviced Collections¶
ServicedObservableCollection<T> is an ObservableCollection<T> with normal
local CollectionChanged delivery plus equivalent messages on an optional
hub. Its complete surface includes inherited Add, Remove, RemoveAt, Move,
Clear, the indexer, and named Replace / ReplaceAll:
var notes = new ServicedObservableCollection<Note>(hub);
notes.Add(first);
notes.Add(second);
notes.Replace(0, revised);
notes.Move(0, notes.Count - 1); // one Move locally, then on the hub
notes.ReplaceAll(serverSnapshot); // one Reset
Invalid indices throw before mutation; equal-index Move and empty Clear are
no-ops. Removal targets the first equal value and returns false when absent.
The collection never disposes or reparents its items.
Add KeyedServicedObservableCollection<TKey,TItem> when that same ordered,
caller-owned list also needs stable-key access:
var notesById = new KeyedServicedObservableCollection<Guid, Note>(
note => note.Id,
hub);
notesById.Add(first);
bool found = notesById.TryGetValue(first.Id, out Note? note);
bool added = notesById.Upsert(revised); // false: Replace at the same position
bool removed = notesById.RemoveKey(first.Id);
ContainsKey tests membership; an optional comparer follows the hub argument.
The projected key is captured until indexed replacement or delete-then-add, so
mutating Id does not silently rekey the membership. Duplicate projection and
projector failure occur before mutation. Key lookup and target discovery are
expected O(1), append is amortized O(1), and ordered middle shifts remain O(n).
Local delivery is immediate and precedes optional hub publication; an existing
hub transaction defers only the hub message. The keyed type still has no batch,
Count notification channel, VM lifecycle interface, or item ownership.
7.2.4. Imperative Engine Bridge¶
SubscribeValue returns IDisposable and uses
EqualityComparer<TValue>.Default unless an IEqualityComparer<TValue> is
supplied:
using VMx.Messages;
IDisposable exposureSubscription = cameraVm.SubscribeValue(
vm => vm.Model.Exposure,
(exposure, _) => material.Uniforms.Exposure.Value = exposure,
fireImmediately: true);
// Host adapter disposal:
exposureSubscription.Dispose();
The host adapter owns the handle. The callback receives (current, previous);
immediate delivery uses the initial value for both. The selector reevaluates
after every property message from this fixed VM, not on every render frame.
7.2.5. Pointers¶
- Flavor README: langs/csharp/README.md
- Getting started guide: Getting Started with VMx — C#
- Example portfolio: Examples overview
- Flagship Notes Workspace: Notes Workspace
- Avalonia recipe: Avalonia Integration
7.2.6. Current Example Coverage¶
- Console:
examples/csharp/console/HelloVMx/ - WPF Todo app:
examples/csharp/wpf/TodoApp/ - Avalonia flagship:
examples/csharp/avalonia/NotesShowcase/
Use the site's Smaller Examples page for the short demos and Integration Recipes for host wiring routes.