Skip to content
Shiny.NET
Shiny MAUI Shell v7 - App Links, App Shortcuts, & Navigation Interception!Shortcut me to it

Utilities

Shiny.Core includes a few general-purpose types that its modules use internally. They are public, so you can use them too.

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.

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);

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);
// later
subscriptions.Dispose(); // disposes all of them and empties the collection

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.

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