using System; using System.Collections.Generic; using System.Threading; public delegate void ObservablePropertySubscriber(T oldValue, T newValue); public interface IObservableProperty { /// /// The current value of the observable property /// T Value { get; } /// /// Subscribe to changes from this property /// /// /// will be called on a captured /// void Subscribe(ObservablePropertySubscriber subscriber, CancellationToken token); /// /// Subscribe to changes from this property /// /// /// will NOT be called on a captured /// void SubscribeSynchronously(ObservablePropertySubscriber subscriber, CancellationToken token); /// /// Subscribe to changes from this property. will be called immediately with the current value /// /// /// will be called on a captured /// void SubscribeAndNotify(ObservablePropertySubscriber subscriber, CancellationToken token); } public class ObservableProperty : PublisherBase>, IObservableProperty { private readonly object _valueLock = new(); private readonly IEqualityComparer _comparer; private readonly T _initialValue; private T _value; public T Value { get { lock (_valueLock) { return _value; } } } public ObservableProperty(T initialValue, IEqualityComparer? comparer = null) { _comparer = comparer ?? EqualityComparer.Default; _initialValue = initialValue; _value = initialValue; } public void SubscribeAndNotify(ObservablePropertySubscriber subscriber, CancellationToken token) { AddSubscriber(subscriber, SynchronizationContext.Current ?? new SynchronizationContext(), token); if (!token.IsCancellationRequested) { T value; lock (_valueLock) { value = _value; } subscriber(_initialValue, value); } } public void Publish(T newValue) { T oldValue; lock (_valueLock) { oldValue = _value; if (_comparer.Equals(oldValue, newValue)) { return; } _value = newValue; } Publish(x => x.Invoke(oldValue, newValue)); } public void UpdateValue(Func updater) { // We're calling upwards within a lock! The caller must be careful to limit the scope of this T oldValue; T newValue; lock (_valueLock) { oldValue = _value; newValue = updater(_value); if (_comparer.Equals(oldValue, newValue)) { return; } _value = newValue; } Publish(x => x.Invoke(oldValue, newValue)); } private class Subscriber { public ObservablePropertySubscriber Delegate { get; init; } = null!; public SynchronizationContext SynchronizationContext { get; init; } = null!; } }