165 lines
4.9 KiB
C#
165 lines
4.9 KiB
C#
using System.ComponentModel;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace DeclarativeProperty;
|
|
|
|
public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T>
|
|
{
|
|
private readonly List<Func<T?, CancellationToken, Task>> _subscribers = new();
|
|
private readonly Action<T?>? _setValueHook;
|
|
private readonly List<IDisposable> _disposables = new();
|
|
private readonly List<Func<IDeclarativeProperty<T>, T, IDisposable?>> _subscribeTriggers = new();
|
|
private readonly List<Action<IDeclarativeProperty<T>, T>> _unsubscribeTriggers = new();
|
|
private readonly List<IDisposable> _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<T?>? setValueHook = null)
|
|
{
|
|
_setValueHook = setValueHook;
|
|
}
|
|
|
|
protected DeclarativePropertyBase(T? initialValue, Action<T?>? setValueHook = null)
|
|
{
|
|
_setValueHook = setValueHook;
|
|
_value = initialValue;
|
|
}
|
|
|
|
|
|
protected async Task NotifySubscribersAsync(T? newValue, CancellationToken cancellationToken = default)
|
|
{
|
|
List<Func<T?, CancellationToken, Task>> 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<T?, CancellationToken, Task> onChange)
|
|
{
|
|
lock (_subscriberLock)
|
|
{
|
|
_subscribers.Add(onChange);
|
|
}
|
|
|
|
onChange(_value, default);
|
|
|
|
return new Unsubscriber<T>(this, onChange);
|
|
}
|
|
|
|
public void Unsubscribe(Func<T, CancellationToken, Task> onChange)
|
|
{
|
|
lock (_subscriberLock)
|
|
{
|
|
_subscribers.Remove(onChange!);
|
|
}
|
|
}
|
|
|
|
public IDeclarativeProperty<T> RegisterTrigger(
|
|
Func<IDeclarativeProperty<T>, T, IDisposable?> triggerSubscribe,
|
|
Action<IDeclarativeProperty<T>, 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<T>.Subscribe(IObserver<T> 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);
|
|
} |