// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT License. // See the LICENSE file in the project root for more information. using System.Collections.Generic; namespace System.Reactive { /// /// Base class for classes that expose an observable sequence as a well-known event pattern (sender, event arguments). /// Contains functionality to maintain a map of event handler delegates to observable sequence subscriptions. Subclasses /// should only add an event with custom add and remove methods calling into the base class's operations. /// /// The type of the sender that raises the event. /// The type of the event data generated by the event. public abstract class EventPatternSourceBase { private sealed class Observer : ObserverBase>, ISafeObserver> { private bool _isDone; private bool _isAdded; private readonly Delegate _handler; private readonly object _gate = new(); private readonly Action _invoke; private readonly EventPatternSourceBase _sourceBase; public Observer(EventPatternSourceBase sourceBase, Delegate handler, Action invoke) { _handler = handler; _invoke = invoke; _sourceBase = sourceBase; } protected override void OnNextCore(EventPattern value) { _sourceBase._invokeHandler(_invoke, value); } protected override void OnErrorCore(Exception error) { Remove(); error.Throw(); } protected override void OnCompletedCore() { Remove(); } private void Remove() { lock (_gate) { if (_isAdded) { _sourceBase.Remove(_handler); } else { _isDone = true; } } } public void SetResource(IDisposable resource) { lock (_gate) { if (!_isDone) { _sourceBase.Add(_handler, resource); _isAdded = true; } } } } private readonly IObservable> _source; private readonly Dictionary> _subscriptions; private readonly Action, /*object,*/ EventPattern> _invokeHandler; /// /// Creates a new event pattern source. /// /// Source sequence to expose as an event. /// Delegate used to invoke the event for each element of the sequence. /// or is null. protected EventPatternSourceBase(IObservable> source, Action, /*object,*/ EventPattern> invokeHandler) { _source = source ?? throw new ArgumentNullException(nameof(source)); _invokeHandler = invokeHandler ?? throw new ArgumentNullException(nameof(invokeHandler)); _subscriptions = new Dictionary>(); } /// /// Adds the specified event handler, causing a subscription to the underlying source. /// /// Event handler to add. The same delegate should be passed to the operation in order to remove the event handler. /// Invocation delegate to raise the event in the derived class. /// or is null. protected void Add(Delegate handler, Action invoke) { if (handler == null) { throw new ArgumentNullException(nameof(handler)); } if (invoke == null) { throw new ArgumentNullException(nameof(invoke)); } var observer = new Observer(this, handler, invoke); // // [OK] Use of unsafe Subscribe: non-pretentious wrapper of an observable in an event; exceptions can occur during +=. // observer.SetResource(_source.Subscribe(observer)); } private void Add(Delegate handler, IDisposable disposable) { lock (_subscriptions) { if (!_subscriptions.TryGetValue(handler, out var l)) { _subscriptions[handler] = l = new Stack(); } l.Push(disposable); } } /// /// Removes the specified event handler, causing a disposal of the corresponding subscription to the underlying source that was created during the operation. /// /// Event handler to remove. This should be the same delegate as one that was passed to the operation. /// is null. protected void Remove(Delegate handler) { if (handler == null) { throw new ArgumentNullException(nameof(handler)); } IDisposable? d = null; lock (_subscriptions) { if (_subscriptions.TryGetValue(handler, out var l)) { d = l.Pop(); if (l.Count == 0) { _subscriptions.Remove(handler); } } } d?.Dispose(); } } }