using System.Reactive.Disposables; namespace System.Reactive.Linq; public static class ObservableSwitch { public static IObservable Switch(this IObservable> observable, T defaultValue) { return new SwitchObservable(observable, defaultValue); } private class SwitchObservable(IObservable> sources, T defaultValue) : ObservableBase { protected override IDisposable SubscribeCore(IObserver observer) { _ _ = new(defaultValue, observer); _.Run(sources); return _; } private class _(T defaultValue, IObserver observer) : ObserverBase> { private readonly SerialDisposable _innerSerialDisposable = new(); private readonly SingleAssignmentDisposable _upstream = new(); private bool _hasLatest; private int _latest; private bool _stopped = false; public void Run(IObservable> sources) { _upstream.Disposable = sources.Subscribe(this); if (_innerSerialDisposable.Disposable is null) { observer.OnNext(defaultValue); } } protected override void Dispose(bool disposing) { if (disposing) { _innerSerialDisposable?.Dispose(); } base.Dispose(disposing); } protected void ForwardOnCompleted() => observer.OnCompleted(); protected void ForwardOnError(Exception error) => observer.OnError(error); protected void ForwardOnNext(T value) => observer.OnNext(value); protected override void OnCompletedCore() { _upstream.Dispose(); _stopped = true; if (!_hasLatest) { observer.OnCompleted(); } } protected override void OnErrorCore(Exception error) => ForwardOnError(error); protected override void OnNextCore(IObservable value) { uint id = unchecked((uint)Interlocked.Increment(ref _latest)); _hasLatest = true; var innerObserver = new InnerObserver(this, id, defaultValue); _innerSerialDisposable.Disposable = innerObserver; innerObserver.Subscribe(value); } private class InnerObserver(_ parent, uint id, T defaultValue) : ObserverBase { private readonly SingleAssignmentDisposable _upstream = new(); public bool Found { get; set; } = false; public void Subscribe(IObservable upstream) { _upstream.Disposable = upstream.SubscribeSafe(this); if (!Found) { OnNext(defaultValue); } } protected override void Dispose(bool disposing) { if (disposing) { _upstream.Dispose(); } base.Dispose(disposing); } protected override void OnCompletedCore() { Dispose(); if (parent._latest == id) { parent._hasLatest = false; if (!Found) { OnNextCore(defaultValue); } if (parent._stopped) { parent.ForwardOnCompleted(); } } } protected override void OnErrorCore(Exception error) { Dispose(); if (parent._latest == id) { if (!Found) { OnNextCore(defaultValue); } parent.ForwardOnError(error); } } protected override void OnNextCore(T value) { Found = true; if (parent._latest == id) { parent.ForwardOnNext(value); } } } } } }