using System.ComponentModel; using System.Runtime.CompilerServices; namespace DeclarativeProperty; public abstract class DeclarativePropertyBase : IDeclarativeProperty { private readonly List> _subscribers = new(); private readonly Action? _setValueHook; private readonly List _disposables = new(); private readonly List, T, IDisposable?>> _subscribeTriggers = new(); private readonly List, T>> _unsubscribeTriggers = new(); private readonly List _triggerDisposables = new(); private readonly object _triggerLock = new(); private readonly object _subscriberLock = new(); private T? _value; public T? Value { get => _value; set => _setValueHook?.Invoke(value); } protected DeclarativePropertyBase(Action? setValueHook = null) { _setValueHook = setValueHook; } protected DeclarativePropertyBase(T? initialValue, Action? setValueHook = null) { _setValueHook = setValueHook; _value = initialValue; } protected async Task NotifySubscribersAsync(T? newValue, CancellationToken cancellationToken = default) { List> subscribers; lock (_subscriberLock) { subscribers = _subscribers.ToList(); } foreach (var handler in subscribers) { await handler(newValue, cancellationToken); } } public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); public IDisposable Subscribe(Func onChange) { lock (_subscriberLock) { _subscribers.Add(onChange); } onChange(_value, default); return new Unsubscriber(this, onChange); } public void Unsubscribe(Func onChange) { lock (_subscriberLock) { _subscribers.Remove(onChange!); } } public IDeclarativeProperty RegisterTrigger( Func, T, IDisposable?> triggerSubscribe, Action, T>? triggerUnsubscribe = null) { lock (_triggerLock) { if (Value != null) { var disposable = triggerSubscribe(this, Value); if (disposable != null) _triggerDisposables.Add(disposable); } _subscribeTriggers.Add(triggerSubscribe); if (triggerUnsubscribe != null) _unsubscribeTriggers.Add(triggerUnsubscribe); return this; } } IDisposable IObservable.Subscribe(IObserver observer) => Subscribe((v, _) => { observer.OnNext(v!); return Task.CompletedTask; }); protected async Task SetNewValueAsync(T? newValue, CancellationToken cancellationToken = default) { SetNewValueSync(newValue, cancellationToken); if (cancellationToken.IsCancellationRequested) return; await NotifySubscribersAsync(newValue, cancellationToken); } protected void SetNewValueSync(T? newValue, CancellationToken cancellationToken = default) { if (!(Value?.Equals(newValue) ?? false)) { lock (_triggerLock) { if (_value != null) { foreach (var unsubscribeTrigger in _unsubscribeTriggers) { unsubscribeTrigger(this, _value); } foreach (var triggerDisposable in _triggerDisposables) { triggerDisposable.Dispose(); } _triggerDisposables.Clear(); } _value = newValue; if (_value != null) { foreach (var subscribeTrigger in _subscribeTriggers) { var disposable = subscribeTrigger(this, _value); if (disposable != null) _triggerDisposables.Add(disposable); } } } if (cancellationToken.IsCancellationRequested) return; OnPropertyChanged(nameof(Value)); } } public async Task ReFireAsync() => await SetNewValueAsync(Value); public void Dispose() { foreach (var disposable in _disposables) { disposable.Dispose(); } lock (_subscriberLock) { _subscribers.Clear(); } } protected void AddDisposable(IDisposable disposable) => _disposables.Add(disposable); }