New reactive core WIP

This commit is contained in:
2023-07-21 22:15:48 +02:00
parent 342fc0d047
commit b61c204e49
61 changed files with 1605 additions and 413 deletions

View File

@@ -0,0 +1,42 @@
using System.Collections.Specialized;
namespace DeclarativeProperty;
public class CollectionRepeaterProperty<TCollection, TItem> : DeclarativePropertyBase<TCollection>
where TCollection : IList<TItem>, INotifyCollectionChanged
{
private TCollection? _currentCollection;
public CollectionRepeaterProperty(IDeclarativeProperty<TCollection?> from)
{
_currentCollection = from.Value;
if (from.Value is { } value)
{
value.CollectionChanged += HandleCollectionChanged;
}
AddDisposable(from.Subscribe(Handle));
}
private void HandleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
var t = Task.Run(async () => await NotifySubscribersAsync(Value));
t.Wait();
}
private async Task Handle(TCollection? collection, CancellationToken cancellationToken = default)
{
if (_currentCollection is { } currentCollection)
{
currentCollection.CollectionChanged -= HandleCollectionChanged;
}
if (collection is {} newCollection)
{
//newCollection.CollectionChanged -= HandleCollectionChanged;
newCollection.CollectionChanged += HandleCollectionChanged;
}
_currentCollection = collection;
await SetNewValueAsync(collection, cancellationToken);
}
}

View File

@@ -0,0 +1,36 @@
namespace DeclarativeProperty;
public sealed class CombineLatestProperty<T1, T2, TResult> : DeclarativePropertyBase<TResult>
{
private readonly Func<T1?, T2?, Task<TResult>> _func;
private T1? _value1;
private T2? _value2;
public CombineLatestProperty(IDeclarativeProperty<T1> prop1, IDeclarativeProperty<T2> prop2, Func<T1?, T2?, Task<TResult>> func)
{
ArgumentNullException.ThrowIfNull(prop1);
ArgumentNullException.ThrowIfNull(prop2);
_func = func;
_value1 = prop1.Value is null ? default : prop1.Value;
_value2 = prop2.Value is null ? default : prop2.Value;
prop1.Subscribe(async (value1, token) =>
{
_value1 = value1;
await UpdateAsync(token);
});
prop2.Subscribe(async (value2, token) =>
{
_value2 = value2;
await UpdateAsync(token);
});
}
private async Task UpdateAsync(CancellationToken cancellationToken = default)
{
var result = await _func(_value1, _value2);
await SetNewValueAsync(result, cancellationToken);
}
}

View File

@@ -0,0 +1,52 @@
namespace DeclarativeProperty;
public sealed class DebounceProperty<T> : TimingPropertyBase<T>
{
private CancellationTokenSource? _debounceCts;
private bool _isActive;
private DateTime _startTime;
public bool ResetTimer { get; init; }
public TimeSpan WaitInterval { get; init; } = TimeSpan.FromMilliseconds(1);
public DebounceProperty(
IDeclarativeProperty<T> from,
TimeSpan interval,
Action<T?>? setValueHook = null) : base(from, interval, setValueHook)
{
}
protected override Task SetValue(T? next, CancellationToken cancellationToken = default)
{
_debounceCts?.Cancel();
var newTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_debounceCts = newTokenSource;
var newToken = newTokenSource.Token;
if (!_isActive || ResetTimer)
{
_isActive = true;
_startTime = DateTime.Now;
}
Task.Run(async () =>
{
try
{
while (DateTime.Now - _startTime < Interval)
{
await Task.Delay(WaitInterval, newToken);
}
WithLock(() => { _isActive = false; });
await FireAsync(next, cancellationToken);
}
catch (TaskCanceledException ex)
{
}
});
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,25 @@
namespace DeclarativeProperty;
public static class DeclarativePropertyHelpers
{
public static CombineLatestProperty<T1, T2, TResult> CombineLatest<T1, T2, TResult>(
IDeclarativeProperty<T1> prop1,
IDeclarativeProperty<T2> prop2,
Func<T1, T2, Task<TResult>> func)
=> new(prop1, prop2, func);
}
public sealed class DeclarativeProperty<T> : DeclarativePropertyBase<T>
{
public DeclarativeProperty(Action<T?>? setValueHook = null) : base(setValueHook)
{
}
public DeclarativeProperty(T initialValue, Action<T?>? setValueHook = null) : base(initialValue, setValueHook)
{
}
public async Task SetValue(T newValue, CancellationToken cancellationToken = default)
=> await SetNewValueAsync(newValue, cancellationToken);
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ObservableComputations" Version="2.3.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,143 @@
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 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)
{
var 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)
{
_subscribers.Add(onChange);
onChange(_value, default);
return new Unsubscriber<T>(this, onChange);
}
public void Unsubscribe(Func<T, CancellationToken, Task> onChange) => _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)
{
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();
if(cancellationToken.IsCancellationRequested) return;
}
_value = newValue;
if (_value != null)
{
foreach (var subscribeTrigger in _subscribeTriggers)
{
if(cancellationToken.IsCancellationRequested) return;
var disposable = subscribeTrigger(this, _value);
if (disposable != null) _triggerDisposables.Add(disposable);
}
}
}
OnPropertyChanged(nameof(Value));
}
await NotifySubscribersAsync(newValue, cancellationToken);
}
public async Task ReFireAsync()
=> await SetNewValueAsync(Value);
public void Dispose()
{
foreach (var disposable in _disposables)
{
disposable.Dispose();
}
_subscribers.Clear();
}
protected void AddDisposable(IDisposable disposable) => _disposables.Add(disposable);
}

View File

@@ -0,0 +1,63 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using ObservableComputations;
namespace DeclarativeProperty;
public static class DeclarativePropertyExtensions
{
public static IDeclarativeProperty<T> Debounce<T>(this IDeclarativeProperty<T> from, TimeSpan interval, bool resetTimer = false)
=> new DebounceProperty<T>(from, interval){ResetTimer = resetTimer};
public static IDeclarativeProperty<T> DistinctUntilChanged<T>(this IDeclarativeProperty<T> from)
=> new DistinctUntilChangedProperty<T>(from);
public static IDeclarativeProperty<TTo?> Map<TFrom, TTo>(this IDeclarativeProperty<TFrom?> from, Func<TFrom?, CancellationToken, Task<TTo?>> mapper)
=> new MapProperty<TFrom?, TTo?>(mapper, from);
public static IDeclarativeProperty<TTo?> Map<TFrom, TTo>(this IDeclarativeProperty<TFrom?> from, Func<TFrom?, TTo?> mapper)
=> new MapProperty<TFrom?, TTo?>((next, _) => Task.FromResult(mapper(next)), from);
public static async Task<IDeclarativeProperty<TTo?>> MapAsync<TFrom, TTo>(this IDeclarativeProperty<TFrom?> from, Func<TFrom?, CancellationToken, Task<TTo?>> mapper)
=> await MapProperty<TFrom?, TTo?>.CreateAsync(mapper, from);
public static async Task<IDeclarativeProperty<TTo?>> MapAsync<TFrom, TTo>(this IDeclarativeProperty<TFrom?> from, Func<TFrom?, TTo?> mapper)
=> await MapProperty<TFrom?, TTo?>.CreateAsync((next, _) => Task.FromResult(mapper(next)), from);
public static IDisposable Subscribe<T>(this IDeclarativeProperty<T> property, Action<T?, CancellationToken> onChange)
=> property.Subscribe((value, token) =>
{
onChange(value, token);
return Task.CompletedTask;
});
public static IDisposable Subscribe<T>(this IDeclarativeProperty<T> property, Action<T?> onChange)
=> property.Subscribe((value, _) =>
{
onChange(value);
return Task.CompletedTask;
});
public static IDeclarativeProperty<T> Extract<T>(
this IDeclarativeProperty<ReadOnlyObservableCollection<T>> from,
Func<IList<T>?, T?> extractor
)
=> new ExtractorProperty<T>(from, extractor);
public static IDeclarativeProperty<T> Extract<T>(
this IDeclarativeProperty<ObservableCollection<T>> from,
Func<IList<T>?, T?> extractor
)
=> new ExtractorProperty<T>(from, extractor);
public static IDeclarativeProperty<T> Extract<T>(
this IDeclarativeProperty<CollectionComputing<T>> from,
Func<IList<T>?, T?> extractor
)
=> new ExtractorProperty<T>(from, extractor);
public static IDeclarativeProperty<TCollection?> Watch<TCollection, TItem>(
this IDeclarativeProperty<TCollection?> collection)
where TCollection : IList<TItem>, INotifyCollectionChanged
=> new CollectionRepeaterProperty<TCollection?, TItem>(collection);
}

View File

@@ -0,0 +1,32 @@
namespace DeclarativeProperty;
public sealed class DistinctUntilChangedProperty<T> : DeclarativePropertyBase<T>
{
private readonly Func<T?, T?, bool>? _comparer;
private bool _firstFire = true;
public DistinctUntilChangedProperty(IDeclarativeProperty<T> from, Func<T?, T?, bool>? comparer = null) : base(from.Value)
{
_comparer = comparer;
AddDisposable(from.Subscribe(Handle));
}
async Task Handle(T? next, CancellationToken cancellationToken = default)
{
if (_comparer is { } comparer)
{
if (comparer(Value, next))
{
return;
}
}
else if ((next is null && Value is null && !_firstFire)
|| (Value?.Equals(next) ?? false))
{
return;
}
_firstFire = false;
await SetNewValueAsync(next, cancellationToken);
}
}

View File

@@ -0,0 +1,174 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using ObservableComputations;
namespace DeclarativeProperty;
public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T>
{
private interface ICollectionWrapper : IDisposable
{
IList<T> Collection { get; }
}
private interface ICollectionWrapper<in TCollection> : ICollectionWrapper
where TCollection : IList<T>, INotifyCollectionChanged
{
static abstract ICollectionWrapper<TCollection> Create(
TCollection collection,
Action<object?, NotifyCollectionChangedEventArgs> handler
);
}
private class ObservableCollectionWrapper : ICollectionWrapper<ObservableCollection<T>>
{
private readonly NotifyCollectionChangedEventHandler _handler;
private readonly ObservableCollection<T> _collection;
public ObservableCollectionWrapper(
ObservableCollection<T> collection,
Action<object?, NotifyCollectionChangedEventArgs> handler
)
{
_collection = collection;
_handler = new NotifyCollectionChangedEventHandler(handler);
_collection.CollectionChanged += _handler;
}
public IList<T> Collection => _collection;
public void Dispose() => _collection.CollectionChanged -= _handler;
public static ICollectionWrapper<ObservableCollection<T>> Create(
ObservableCollection<T> collection,
Action<object?, NotifyCollectionChangedEventArgs> handler
) => new ObservableCollectionWrapper(collection, handler);
}
private class ReadOnlyObservableCollectionWrapper : ICollectionWrapper<ReadOnlyObservableCollection<T>>
{
private readonly NotifyCollectionChangedEventHandler _handler;
private readonly ReadOnlyObservableCollection<T> _collection;
public ReadOnlyObservableCollectionWrapper(
ReadOnlyObservableCollection<T> collection,
Action<object?, NotifyCollectionChangedEventArgs> handler
)
{
_collection = collection;
_handler = new NotifyCollectionChangedEventHandler(handler);
((INotifyCollectionChanged) _collection).CollectionChanged += _handler;
}
public IList<T> Collection => _collection;
public void Dispose() => ((INotifyCollectionChanged) _collection).CollectionChanged -= _handler;
public static ICollectionWrapper<ReadOnlyObservableCollection<T>> Create(
ReadOnlyObservableCollection<T> collection,
Action<object?, NotifyCollectionChangedEventArgs> handler
) => new ReadOnlyObservableCollectionWrapper(collection, handler);
}
private class CollectionComputingWrapper : ICollectionWrapper<CollectionComputing<T>>
{
private readonly OcConsumer _consumer;
private readonly NotifyCollectionChangedEventHandler _handler;
private readonly CollectionComputing<T> _collection;
public CollectionComputingWrapper(
CollectionComputing<T> collection,
Action<object?, NotifyCollectionChangedEventArgs> handler
)
{
_collection = collection;
_handler = new NotifyCollectionChangedEventHandler(handler);
((INotifyCollectionChanged) _collection).CollectionChanged += _handler;
collection.For(_consumer = new OcConsumer());
}
public IList<T> Collection => _collection;
public void Dispose()
{
((INotifyCollectionChanged) _collection).CollectionChanged -= _handler;
_consumer.Dispose();
}
public static ICollectionWrapper<CollectionComputing<T>> Create(
CollectionComputing<T> collection,
Action<object?, NotifyCollectionChangedEventArgs> handler
) => new CollectionComputingWrapper(collection, handler);
}
private readonly Func<IList<T>?, T?> _extractor;
private ICollectionWrapper? _collectionWrapper;
public ExtractorProperty(
IDeclarativeProperty<ObservableCollection<T>> from,
Func<IList<T>?, T?> extractor) : base(extractor(from.Value))
{
_extractor = extractor;
_collectionWrapper = from.Value is null
? null
: new ObservableCollectionWrapper(from.Value, CollectionUpdated);
AddDisposable(from.Subscribe(SetValue<ObservableCollectionWrapper, ObservableCollection<T>>));
}
public ExtractorProperty(
IDeclarativeProperty<ReadOnlyObservableCollection<T>> from,
Func<IList<T>?, T?> extractor) : base(extractor(from.Value))
{
_extractor = extractor;
_collectionWrapper = from.Value is null
? null
: new ReadOnlyObservableCollectionWrapper(from.Value, CollectionUpdated);
AddDisposable(from.Subscribe(SetValue<ReadOnlyObservableCollectionWrapper, ReadOnlyObservableCollection<T>>));
}
public ExtractorProperty(
IDeclarativeProperty<CollectionComputing<T>> from,
Func<IList<T>?, T?> extractor) : base(extractor(from.Value))
{
_extractor = extractor;
_collectionWrapper = from.Value is null
? null
: new CollectionComputingWrapper(from.Value, CollectionUpdated);
AddDisposable(from.Subscribe(SetValue<CollectionComputingWrapper, CollectionComputing<T>>));
}
private void CollectionUpdated(object? sender, NotifyCollectionChangedEventArgs e)
=> Task.Run(async () => await Fire(_collectionWrapper?.Collection)).Wait();
private async Task SetValue<TWrapper, TCollection>(TCollection? next, CancellationToken cancellationToken = default)
where TCollection : IList<T>, INotifyCollectionChanged
where TWrapper : ICollectionWrapper<TCollection>
{
_collectionWrapper?.Dispose();
_collectionWrapper = next is null
? null
: TWrapper.Create(next, CollectionUpdated);
await Fire(next, cancellationToken);
}
private async Task Fire(IList<T>? items, CancellationToken cancellationToken = default)
{
var newValue = items is null
? default
: _extractor(items);
await SetNewValueAsync(newValue, cancellationToken);
}
}

View File

@@ -0,0 +1,34 @@
namespace DeclarativeProperty;
public sealed class FilterProperty<T> : DeclarativePropertyBase<T>
{
private readonly Func<T?, Task<bool>> _filter;
public FilterProperty(
Func<T?, Task<bool>> filter,
IDeclarativeProperty<T> from,
Action<T?>? setValueHook = null) : base(setValueHook)
{
_filter = filter;
AddDisposable(from.Subscribe(SetValue));
}
private async Task SetValue(T? next, CancellationToken cancellationToken = default)
{
if (await _filter(next))
{
await SetNewValueAsync(next, cancellationToken);
}
}
public static async Task<FilterProperty<T>> CreateAsync(
Func<T?, Task<bool>> filter,
IDeclarativeProperty<T> from,
Action<T?>? setValueHook = null)
{
var prop = new FilterProperty<T>(filter, from, setValueHook);
await prop.SetValue(from.Value);
return prop;
}
}

View File

@@ -0,0 +1,17 @@
using System.ComponentModel;
namespace DeclarativeProperty;
public interface IDeclarativeProperty<T> : INotifyPropertyChanged, IDisposable, IObservable<T>
{
T? Value { get; set; }
IDisposable Subscribe(Func<T?, CancellationToken, Task> onChange);
void Unsubscribe(Func<T?, CancellationToken, Task> onChange);
IDeclarativeProperty<T> RegisterTrigger(
Func<IDeclarativeProperty<T>, T, IDisposable?> triggerSubscribe,
Action<IDeclarativeProperty<T>, T>? triggerUnsubscribe = null
);
Task ReFireAsync();
}

View File

@@ -0,0 +1,32 @@
namespace DeclarativeProperty;
public sealed class MapProperty<TFrom, TTo> : DeclarativePropertyBase<TTo>
{
private readonly Func<TFrom?, CancellationToken, Task<TTo?>> _mapper;
public MapProperty(
Func<TFrom?, CancellationToken, Task<TTo?>> mapper,
IDeclarativeProperty<TFrom?> from,
Action<TTo?>? setValueHook = null) : base(setValueHook)
{
_mapper = mapper;
AddDisposable(from.Subscribe(SetValue));
}
private async Task SetValue(TFrom? next, CancellationToken cancellationToken = default)
{
var newValue = await _mapper(next, cancellationToken);
await SetNewValueAsync(newValue, cancellationToken);
}
public static async Task<MapProperty<TFrom?, TTo?>> CreateAsync(
Func<TFrom?, CancellationToken, Task<TTo?>> mapper,
IDeclarativeProperty<TFrom?> from,
Action<TTo?>? setValueHook = null)
{
var prop = new MapProperty<TFrom?, TTo?>(mapper, from, setValueHook);
await prop.SetValue(from.Value);
return prop;
}
}

View File

@@ -0,0 +1,24 @@
namespace DeclarativeProperty;
public sealed class SwitchProperty<TItem> : DeclarativePropertyBase<TItem>
{
private IDisposable? _innerSubscription;
public SwitchProperty(IDeclarativeProperty<IDeclarativeProperty<TItem?>?> from) : base(from.Value is null ? default : from.Value.Value)
{
AddDisposable(from.Subscribe(HandleStreamChange));
_innerSubscription = from.Value?.Subscribe(HandleInnerValueChange);
}
private async Task HandleStreamChange(IDeclarativeProperty<TItem?>? next, CancellationToken token)
{
_innerSubscription?.Dispose();
_innerSubscription = next?.Subscribe(HandleInnerValueChange);
await SetNewValueAsync(next is null ? default : next.Value, token);
}
private async Task HandleInnerValueChange(TItem? next, CancellationToken token)
=> await SetNewValueAsync(next, token);
}

View File

@@ -0,0 +1,58 @@
namespace DeclarativeProperty;
public class ThrottleProperty<T> : TimingPropertyBase<T>
{
private CancellationTokenSource? _debounceCts;
private DateTime _lastFired;
public ThrottleProperty(
IDeclarativeProperty<T> from,
TimeSpan interval,
Action<T?>? setValueHook = null) : base(from, interval, setValueHook)
{
}
protected override Task SetValue(T? next, CancellationToken cancellationToken = default)
{
_debounceCts?.Cancel();
if (DateTime.Now - _lastFired > Interval)
{
_lastFired = DateTime.Now;
// Note: Recursive chains can happen. Awaiting this can cause a deadlock.
Task.Run(async () => await FireAsync(next, cancellationToken));
}
else
{
_debounceCts = new();
Task.Run(async () =>
{
try
{
await Task.Delay(Interval, _debounceCts.Token);
await FireIfNeededAsync(
next,
() => { _lastFired = DateTime.Now; },
_debounceCts.Token, cancellationToken
);
/*var shouldFire = WithLock(() =>
{
if (_debounceCts.Token.IsCancellationRequested)
return false;
_lastFired = DateTime.Now;
return true;
});
if (!shouldFire) return;
await Fire(next, cancellationToken);*/
}
catch (TaskCanceledException ex)
{
}
});
}
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,85 @@
namespace DeclarativeProperty;
public abstract class TimingPropertyBase<T> : DeclarativePropertyBase<T>
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
protected TimeSpan Interval { get; }
protected TimingPropertyBase(
IDeclarativeProperty<T> from,
TimeSpan interval,
Action<T?>? setValueHook = null) : base(from.Value, setValueHook)
{
Interval = interval;
AddDisposable(from.Subscribe(SetValueInternal));
}
private async Task SetValueInternal(T? next, CancellationToken cancellationToken = default)
=> await WithLockAsync(async () => await SetValue(next, cancellationToken), cancellationToken);
protected void WithLock(Action action)
{
try
{
_semaphore.Wait();
action();
}
finally
{
_semaphore.Release();
}
}
protected TResult WithLock<TResult>(Func<TResult> func)
{
try
{
_semaphore.Wait();
return func();
}
finally
{
_semaphore.Release();
}
}
protected async Task WithLockAsync(Func<Task> action, CancellationToken cancellationToken = default)
{
try
{
await _semaphore.WaitAsync(cancellationToken);
await action();
}
finally
{
_semaphore.Release();
}
}
protected abstract Task SetValue(T? next, CancellationToken cancellationToken = default);
protected async Task FireIfNeededAsync(
T? next,
Action cleanup,
CancellationToken timingCancellationToken = default,
CancellationToken cancellationToken = default)
{
await Task.Delay(Interval, timingCancellationToken);
var shouldFire = WithLock(() =>
{
if (timingCancellationToken.IsCancellationRequested)
return false;
cleanup();
return true;
});
if (!shouldFire) return;
await FireAsync(next, cancellationToken);
}
protected async Task FireAsync(T? next, CancellationToken cancellationToken = default)
=> await SetNewValueAsync(next, cancellationToken);
}

View File

@@ -0,0 +1,14 @@
namespace DeclarativeProperty;
internal sealed class Unsubscriber<T> : IDisposable
{
private readonly IDeclarativeProperty<T> _owner;
private readonly Func<T?, CancellationToken, Task> _onChange;
public Unsubscriber(IDeclarativeProperty<T> owner, Func<T?, CancellationToken, Task> onChange)
{
_owner = owner;
_onChange = onChange;
}
public void Dispose() => _owner.Unsubscribe(_onChange);
}

View File

@@ -0,0 +1,13 @@
namespace System;
public static class DeferTools
{
public static IDisposable Defer(Action action) => new DeferDisposable(action);
}
internal readonly struct DeferDisposable : IDisposable
{
readonly Action _action;
public DeferDisposable(Action action) => _action = action;
public void Dispose() => _action();
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>System</RootNamespace>
</PropertyGroup>
</Project>