Utilities
Shiny.Core includes a few general-purpose types that its modules use internally. They are public, so you can use them too.
NotifyPropertyChanged
Section titled “NotifyPropertyChanged”A minimal INotifyPropertyChanged base class:
public class TripViewModel : NotifyPropertyChanged{ string? status; public string? Status { get => this.status; set => this.Set(ref this.status, value); // raises only when the value changes }
protected override void OnNpcHookChanged(bool hasSubscribers) { // start/stop expensive work when the first binding attaches or the last one leaves }}HasSubscribers and OnNpcHookChanged let a class stay idle until something actually binds to it. RaisePropertyChanged() raises the event manually, using the caller’s member name by default.
Collections
Section titled “Collections”| Type | Description |
|---|---|
INotifyReadOnlyCollection<T> |
IReadOnlyList<T> + INotifyCollectionChanged. Lets a service expose a live list without letting callers change it |
INotifyCollectionChanged<T> |
A mutable version with AddRange, RemoveRange and ReplaceAll, each raising a single change notification |
BindingList<T> |
An ObservableCollection<T> implementation of the above. Writes take a reader/writer lock, enumeration returns a snapshot, and bulk operations raise one Reset instead of one event per item |
readonly Shiny.Collections.BindingList<Peripheral> found = new();public INotifyReadOnlyCollection<Peripheral> Found => this.found;
void OnScanBatch(IEnumerable<Peripheral> batch) => this.found.AddRange(batch);DisposableCollection
Section titled “DisposableCollection”A thread-safe ICollection<IDisposable> that disposes everything in it at once. Adding is thread-safe, and disposal takes a snapshot first, so a disposable that adds another disposable during Dispose doesn’t break it:
readonly DisposableCollection subscriptions = new();
subscriptions.Add(someSubscription);subscriptions.Add(anotherOne);
// latersubscriptions.Dispose(); // disposes all of them and empties the collectionRunning Delegates
Section titled “Running Delegates”RunDelegates is how Shiny calls every registered delegate for an event, such as all your IGpsDelegate implementations. It runs them concurrently, logs each failure against the delegate’s type, and never lets one failing delegate stop the others:
await services.RunDelegates<IMyDelegate>(x => x.OnSomething(args), logger);Use it if you write your own delegate-style extension point.
Small Extensions
Section titled “Small Extensions”| Member | Description |
|---|---|
string.IsEmpty() |
String.IsNullOrWhiteSpace as an extension |
Type.GetDefaultValue() |
The default value of a primitive or common BCL struct without reflection (AOT-safe). Throws NotSupportedException for other structs |


