DeclarativeProperty nullability fixes

This commit is contained in:
2023-09-27 15:58:59 +02:00
parent 30638c6c97
commit 373847b1ad
25 changed files with 163 additions and 151 deletions

View File

@@ -88,7 +88,7 @@ public record SizeScanContainer : ISizeScanContainer
return Task.CompletedTask;
}
public required IDeclarativeProperty<string> Status { get; init; } = new DeclarativeProperty<string>();
public required IDeclarativeProperty<string> Status { get; init; } = new DeclarativeProperty<string>(string.Empty);
public required SizeScanTask SizeScanTask { get; init; }

View File

@@ -17,7 +17,7 @@ public class SizeScanTask : ISizeScanTask
private readonly ILogger<SizeScanTask> _logger;
private Thread? _sizeScanThread;
private static int _searchId = 1;
private readonly DeclarativeProperty<string> _containerStatus = new();
private readonly DeclarativeProperty<string> _containerStatus = new(string.Empty);
private readonly IDeclarativeProperty<string> _containerStatusDebounced;
public ISizeScanContainer SizeSizeScanContainer { get; private set; } = null!;
public bool IsRunning { get; private set; }

View File

@@ -13,7 +13,7 @@ namespace FileTime.App.Core.ViewModels;
public abstract partial class AppStateBase : IAppState
{
private readonly BehaviorSubject<string?> _searchText = new(null);
private readonly DeclarativeProperty<ITabViewModel?> _selectedTab = new();
private readonly DeclarativeProperty<ITabViewModel?> _selectedTab = new(null);
private readonly DeclarativeProperty<ViewMode> _viewMode = new(Models.Enums.ViewMode.Default);
private readonly ObservableCollection<ITabViewModel> _tabs = new();
private readonly DeclarativeProperty<string?> _rapidTravelText;

View File

@@ -15,11 +15,11 @@ public class CopyCommand : CommandBase, ITransportationCommand
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly List<OperationProgress> _operationProgresses = new();
private readonly DeclarativeProperty<OperationProgress?> _currentOperationProgress = new();
private readonly DeclarativeProperty<OperationProgress?> _currentOperationProgress = new(null);
private long _recentTotalSum;
private readonly DeclarativeProperty<long> _recentTotalProcessed = new();
private readonly DeclarativeProperty<DateTime> _recentStartTime = new();
private readonly DeclarativeProperty<long> _recentTotalProcessed = new(0);
private readonly DeclarativeProperty<DateTime> _recentStartTime = new(DateTime.Now);
public IReadOnlyList<FullName> Sources { get; }

View File

@@ -19,9 +19,9 @@ public class Tab : ITab
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ITabEvents _tabEvents;
private readonly DeclarativeProperty<IContainer?> _currentLocation = new();
private readonly DeclarativeProperty<IContainer?> _currentLocationForced = new();
private readonly DeclarativeProperty<AbsolutePath?> _currentRequestItem = new();
private readonly DeclarativeProperty<IContainer?> _currentLocation = new(null);
private readonly DeclarativeProperty<IContainer?> _currentLocationForced = new(null);
private readonly DeclarativeProperty<AbsolutePath?> _currentRequestItem = new(null);
private readonly ObservableCollection<ItemFilter> _itemFilters = new();
private readonly CircularBuffer<FullName> _history = new(20);
private readonly CircularBuffer<FullName> _future = new(20);
@@ -64,28 +64,26 @@ public class Tab : ITab
return Task.CompletedTask;
});
CurrentItems = DeclarativePropertyHelpers.CombineLatest(
IDeclarativeProperty<ObservableCollection<IItem>?> asd1 = DeclarativePropertyHelpers.CombineLatest(
CurrentLocation,
itemFiltersProperty,
(container, filters) =>
Task<ObservableCollection<IItem>?> (container, filters) =>
{
ObservableCollection<IItem>? items = null;
if (container is not null)
{
items = container
.Items
.Selecting<AbsolutePath, IItem>(i => MapItem(i));
if (container is null) return Task.FromResult(items);
if (filters is not null)
{
items = items.Filtering(i => filters.All(f => f.Filter(i)));
}
}
items = container
.Items
.Selecting<AbsolutePath, IItem>(i => MapItem(i));
return Task.FromResult(items);
items = items.Filtering(i => filters.All(f => f.Filter(i)));
return Task.FromResult(items)!;
}
).CombineLatest(
);
var asd2 = asd1.CombineLatest(
Ordering.Map(ordering =>
{
var (itemComparer, order) = ordering switch
@@ -103,7 +101,7 @@ public class Tab : ITab
return (itemComparer, order, ordering is not ItemOrdering.Name and not ItemOrdering.NameDesc);
}),
(items, ordering) =>
Task<ObservableCollection<IItem>?> (items, ordering) =>
{
if (items is null) return Task.FromResult<ObservableCollection<IItem>?>(null);
@@ -117,14 +115,16 @@ public class Tab : ITab
? primaryOrderedItems.ThenOrdering(i => i.DisplayName)
: primaryOrderedItems;
return Task.FromResult(orderedItems);
return Task.FromResult<ObservableCollection<IItem>?>(orderedItems);
}
);
CurrentItems = asd2;
CurrentSelectedItem = DeclarativePropertyHelpers.CombineLatest(
CurrentItems.Watch<ObservableCollection<IItem>, IItem>(),
CurrentItems.Watch<ObservableCollection<IItem>?, IItem>(),
_currentRequestItem.DistinctUntilChanged(),
(items, selected) =>
Task<AbsolutePath?> (items, selected) =>
{
var itemSelectingContext = new LastItemSelectingContext(CurrentLocation.Value);
var lastItemSelectingContext = _lastItemSelectingContext;
@@ -143,7 +143,7 @@ public class Tab : ITab
var candidate = _selectedItemCandidates.FirstOrDefault(c => items.Any(i => i.FullName?.Path == c.Path.Path));
if (candidate != null)
{
return Task.FromResult(candidate);
return Task.FromResult<AbsolutePath?>(candidate);
}
}
@@ -160,7 +160,7 @@ public class Tab : ITab
DeclarativePropertyHelpers.CombineLatest(
CurrentItems,
CurrentSelectedItem,
(items, selected) =>
Task<IEnumerable<AbsolutePath>?> (items, selected) =>
{
if (items is null || selected is null) return Task.FromResult<IEnumerable<AbsolutePath>?>(null);
var primaryCandidates = items.SkipWhile(i => i.FullName is {Path: var p} && p != selected.Path.Path).Skip(1);
@@ -169,7 +169,7 @@ public class Tab : ITab
.Concat(secondaryCandidates)
.Select(c => new AbsolutePath(_timelessContentProvider, c));
return Task.FromResult(candidates);
return Task.FromResult<IEnumerable<AbsolutePath>?>(candidates);
})
.Subscribe(candidates =>
{

View File

@@ -8,6 +8,6 @@ public interface IMainWindowViewModelBase
DeclarativeProperty<string> Title { get; }
bool Loading { get; }
IObservable<string?> MainFont { get; }
DeclarativeProperty<string> FatalError { get; }
DeclarativeProperty<string?> FatalError { get; }
IReadOnlyList<WindowTransparencyLevel> TransparencyLevelHint { get; }
}

View File

@@ -9,6 +9,6 @@ public class MainWindowLoadingViewModel : IMainWindowViewModelBase
public bool Loading => true;
public IObservable<string?> MainFont { get; } = new BehaviorSubject<string?>("");
public DeclarativeProperty<string> Title { get; } = new("Loading...");
public DeclarativeProperty<string> FatalError { get; } = new();
public DeclarativeProperty<string?> FatalError { get; } = new(null);
public IReadOnlyList<WindowTransparencyLevel> TransparencyLevelHint { get; } = new[] {WindowTransparencyLevel.AcrylicBlur};
}

View File

@@ -53,10 +53,10 @@ public partial class MainWindowViewModel : IMainWindowViewModel
private readonly OcConsumer _rootDriveInfosConsumer = new();
public bool Loading => false;
public IObservable<string?> MainFont => _fontService.MainFont.Select(x => x ?? "");
public DeclarativeProperty<string> FatalError { get; } = new();
public DeclarativeProperty<string?> FatalError { get; } = new(null);
public IReadOnlyList<WindowTransparencyLevel> TransparencyLevelHint { get; } = new[] {WindowTransparencyLevel.Blur};
public IGuiAppState AppState => _appState;
public DeclarativeProperty<string> Title { get; } = new();
public DeclarativeProperty<string> Title { get; } = new(string.Empty);
public Thickness IconStatusPanelMargin { get; private set; } = new(20, 10, 10, 10);
public Action? FocusDefaultElement { get; set; }
public Action? ShowWindow { get; set; }

View File

@@ -3,11 +3,11 @@
namespace DeclarativeProperty;
public class CollectionRepeaterProperty<TCollection, TItem> : DeclarativePropertyBase<TCollection>
where TCollection : IList<TItem>, INotifyCollectionChanged
where TCollection : IList<TItem>?, INotifyCollectionChanged?
{
private TCollection? _currentCollection;
public CollectionRepeaterProperty(IDeclarativeProperty<TCollection?> from) : base(from.Value)
public CollectionRepeaterProperty(IDeclarativeProperty<TCollection> from) : base(from.Value)
{
_currentCollection = from.Value;
if (from.Value is { } value)
@@ -18,7 +18,7 @@ public class CollectionRepeaterProperty<TCollection, TItem> : DeclarativePropert
AddDisposable(from.Subscribe(Handle));
}
public CollectionRepeaterProperty(TCollection? collection) : base(collection)
public CollectionRepeaterProperty(TCollection collection) : base(collection)
{
ArgumentNullException.ThrowIfNull(collection);
@@ -32,7 +32,7 @@ public class CollectionRepeaterProperty<TCollection, TItem> : DeclarativePropert
t.Wait();
}
private async Task Handle(TCollection? collection, CancellationToken cancellationToken = default)
private async Task Handle(TCollection collection, CancellationToken cancellationToken = default)
{
if (_currentCollection is { } currentCollection)
{

View File

@@ -3,18 +3,18 @@
public class CombineAllProperty<T, TResult> : DeclarativePropertyBase<TResult>
{
private readonly List<IDeclarativeProperty<T>> _sources;
private readonly Func<IEnumerable<T>, Task<TResult?>> _combiner;
private readonly Func<IEnumerable<T>, Task<TResult>> _combiner;
public CombineAllProperty(
IEnumerable<IDeclarativeProperty<T>> sources,
Func<IEnumerable<T>, Task<TResult?>> combiner,
Action<TResult?>? setValueHook = null) : base(setValueHook)
Func<IEnumerable<T>, Task<TResult>> combiner,
Action<TResult>? setValueHook = null) : base(default!, setValueHook)
{
var sourcesList = sources.ToList();
_sources = sourcesList;
_combiner = combiner;
var initialValueTask = _combiner(sourcesList.Select(p => p.Value)!);
var initialValueTask = Task.Run(async () => await _combiner(sourcesList.Select(p => p.Value)));
initialValueTask.Wait();
SetNewValueSync(initialValueTask.Result);
@@ -24,12 +24,12 @@ public class CombineAllProperty<T, TResult> : DeclarativePropertyBase<TResult>
}
}
private async Task OnSourceChanged(T? arg1, CancellationToken arg2) => await Update();
private async Task OnSourceChanged(T arg1, CancellationToken arg2) => await Update();
private async Task Update()
{
var values = _sources.Select(p => p.Value);
var result = await _combiner(values!);
var result = await _combiner(values);
await SetNewValueAsync(result);
}

View File

@@ -2,23 +2,27 @@
public sealed class CombineLatestProperty<T1, T2, TResult> : DeclarativePropertyBase<TResult>
{
private readonly Func<T1?, T2?, Task<TResult>> _func;
private T1? _value1;
private T2? _value2;
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,
Action<TResult?>? setValueHook = null) : base(setValueHook)
Func<T1, T2, Task<TResult>> func,
Action<TResult>? setValueHook = null) : base(default!, setValueHook)
{
ArgumentNullException.ThrowIfNull(prop1);
ArgumentNullException.ThrowIfNull(prop2);
_func = func;
_value1 = prop1.Value is null ? default : prop1.Value;
_value2 = prop2.Value is null ? default : prop2.Value;
_value1 = prop1.Value;
_value2 = prop2.Value;
var initialValueTask = Task.Run(async () => await _func(_value1, _value2));
initialValueTask.Wait();
SetNewValueSync(initialValueTask.Result);
prop1.Subscribe(async (value1, token) =>
{
@@ -51,7 +55,7 @@ public sealed class CombineLatestProperty<T1, T2, T3, TResult> : DeclarativeProp
IDeclarativeProperty<T2> prop2,
IDeclarativeProperty<T3> prop3,
Func<T1?, T2?, T3?,Task<TResult>> func,
Action<TResult?>? setValueHook = null) : base(setValueHook)
Action<TResult>? setValueHook = null) : base(default!, setValueHook)
{
ArgumentNullException.ThrowIfNull(prop1);
ArgumentNullException.ThrowIfNull(prop2);
@@ -59,9 +63,13 @@ public sealed class CombineLatestProperty<T1, T2, T3, TResult> : DeclarativeProp
_func = func;
_value1 = prop1.Value is null ? default : prop1.Value;
_value2 = prop2.Value is null ? default : prop2.Value;
_value3 = prop3.Value is null ? default : prop3.Value;
_value1 = prop1.Value;
_value2 = prop2.Value;
_value3 = prop3.Value;
var initialValueTask = Task.Run(async () => await _func(_value1, _value2, _value3));
initialValueTask.Wait();
SetNewValueSync(initialValueTask.Result);
prop1.Subscribe(async (value1, token) =>
{

View File

@@ -5,7 +5,7 @@ public sealed class CombineProperty<TFrom, TTo> : DeclarativePropertyBase<TTo>
private readonly Func<IReadOnlyList<TFrom?>, Task<TTo>> _combiner;
private readonly List<IDeclarativeProperty<TFrom>> _sourceProperties = new();
public CombineProperty(Func<IReadOnlyList<TFrom?>, Task<TTo>> combiner)
public CombineProperty(Func<IReadOnlyList<TFrom?>, Task<TTo>> combiner) : base(default!)
{
_combiner = combiner;
}

View File

@@ -3,9 +3,9 @@
public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
{
private readonly object _lock = new();
private readonly Func<T?, TimeSpan> _interval;
private readonly Func<T, TimeSpan> _interval;
private DateTime _startTime = DateTime.MinValue;
private T? _nextValue;
private T _nextValue = default!;
private CancellationToken _nextCancellationToken;
private bool _isThrottleTaskRunning;
public bool ResetTimer { get; init; }
@@ -13,14 +13,14 @@ public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
public DebounceProperty(
IDeclarativeProperty<T> from,
Func<T?, TimeSpan> interval,
Action<T?>? setValueHook = null) : base(from.Value, setValueHook)
Func<T, TimeSpan> interval,
Action<T>? setValueHook = null) : base(from.Value, setValueHook)
{
_interval = interval;
AddDisposable(from.Subscribe(SetValue));
}
private Task SetValue(T? next, CancellationToken cancellationToken = default)
private Task SetValue(T next, CancellationToken cancellationToken = default)
{
lock (_lock)
{
@@ -38,7 +38,7 @@ public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
_startTime = DateTime.Now;
_isThrottleTaskRunning = true;
Task.Run(async () => await StartDebounceTask());
Task.Run(StartDebounceTask);
}
return Task.CompletedTask;
@@ -51,7 +51,7 @@ public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
await Task.Delay(WaitInterval);
}
T? next;
T next;
CancellationToken cancellationToken;
lock (_lock)
{

View File

@@ -6,15 +6,15 @@ public static class DeclarativePropertyHelpers
IDeclarativeProperty<T1> prop1,
IDeclarativeProperty<T2> prop2,
Func<T1, T2, Task<TResult>> func,
Action<TResult?>? setValueHook = null)
=> new(prop1, prop2, func!, setValueHook);
Action<TResult>? setValueHook = null)
=> new(prop1, prop2, func, setValueHook);
public static CombineLatestProperty<T1, T2, T3, TResult> CombineLatest<T1, T2, T3, TResult>(
IDeclarativeProperty<T1> prop1,
IDeclarativeProperty<T2> prop2,
IDeclarativeProperty<T3> prop3,
Func<T1, T2, T3, Task<TResult>> func,
Action<TResult?>? setValueHook = null)
Action<TResult>? setValueHook = null)
=> new(prop1, prop2, prop3, func!, setValueHook);
public static MergeProperty<T> Merge<T>(params IDeclarativeProperty<T>[] props)
@@ -23,11 +23,7 @@ public static class DeclarativePropertyHelpers
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 DeclarativeProperty(T initialValue, Action<T>? setValueHook = null) : base(initialValue, setValueHook)
{
}

View File

@@ -5,8 +5,8 @@ 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<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();
@@ -14,29 +14,24 @@ public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T>
private readonly object _triggerLock = new();
private readonly object _subscriberLock = new();
private T? _value;
private T _value;
public 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)
protected DeclarativePropertyBase(T initialValue, Action<T>? setValueHook = null)
{
_setValueHook = setValueHook;
_value = initialValue;
}
protected async Task NotifySubscribersAsync(T? newValue, CancellationToken cancellationToken = default)
protected async Task NotifySubscribersAsync(T newValue, CancellationToken cancellationToken = default)
{
List<Func<T?, CancellationToken, Task>> subscribers;
List<Func<T, CancellationToken, Task>> subscribers;
lock (_subscriberLock)
{
subscribers = _subscribers.ToList();
@@ -54,7 +49,7 @@ public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public IDisposable Subscribe(Func<T?, CancellationToken, Task> onChange)
public IDisposable Subscribe(Func<T, CancellationToken, Task> onChange)
{
lock (_subscriberLock)
{
@@ -70,7 +65,7 @@ public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T>
{
lock (_subscriberLock)
{
_subscribers.Remove(onChange!);
_subscribers.Remove(onChange);
}
}
@@ -99,14 +94,14 @@ public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T>
return Task.CompletedTask;
});
protected async Task SetNewValueAsync(T? newValue, CancellationToken cancellationToken = default)
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)
protected void SetNewValueSync(T newValue, CancellationToken cancellationToken = default)
{
if (!(Value?.Equals(newValue) ?? false))
{

View File

@@ -21,16 +21,16 @@ public static class DeclarativePropertyExtensions
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?, Task<TTo?>> mapper)
public static IDeclarativeProperty<TTo> Map<TFrom, TTo>(this IDeclarativeProperty<TFrom> from, Func<TFrom, Task<TTo>> mapper)
=> Map(from, async (v, _) => await mapper(v));
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, 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 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)
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)
@@ -43,39 +43,39 @@ public static class DeclarativePropertyExtensions
return Task.CompletedTask;
});
public static IDisposable Subscribe<T>(this IDeclarativeProperty<T> property, Action<T?> onChange)
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
public static IDeclarativeProperty<TResult> Extract<T, TResult>(
this IDeclarativeProperty<ReadOnlyObservableCollection<T>?> from,
Func<IList<T>?, TResult> extractor
)
=> new ExtractorProperty<T>(from, extractor);
=> new ExtractorProperty<T, TResult>(from, extractor);
public static IDeclarativeProperty<T> Extract<T>(
this IDeclarativeProperty<ObservableCollection<T>> from,
Func<IList<T>?, T?> extractor
public static IDeclarativeProperty<TResult> Extract<T, TResult>(
this IDeclarativeProperty<ObservableCollection<T>?> from,
Func<IList<T>?, TResult> extractor
)
=> new ExtractorProperty<T>(from, extractor);
=> new ExtractorProperty<T, TResult>(from, extractor);
public static IDeclarativeProperty<T> Extract<T>(
this IDeclarativeProperty<CollectionComputing<T>> from,
Func<IList<T>?, T?> extractor
public static IDeclarativeProperty<TResult> Extract<T, TResult>(
this IDeclarativeProperty<CollectionComputing<T>?> from,
Func<IList<T>?, TResult> extractor
)
=> new ExtractorProperty<T>(from, extractor);
=> new ExtractorProperty<T, TResult>(from, extractor);
public static IDeclarativeProperty<TCollection> Watch<TCollection, TItem>(
this IDeclarativeProperty<TCollection?> collection)
where TCollection : IList<TItem>, INotifyCollectionChanged
this IDeclarativeProperty<TCollection> collection)
where TCollection : IList<TItem>?, INotifyCollectionChanged?
=> new CollectionRepeaterProperty<TCollection, TItem>(collection);
public static IDeclarativeProperty<TCollection> Watch<TCollection, TItem>(
this TCollection collection)
where TCollection : IList<TItem>, INotifyCollectionChanged
where TCollection : IList<TItem>?, INotifyCollectionChanged?
=> new CollectionRepeaterProperty<TCollection, TItem>(collection);
public static IDeclarativeProperty<ObservableCollection<TItem>> Watch<TItem>(
@@ -90,16 +90,16 @@ public static class DeclarativePropertyExtensions
public static IDeclarativeProperty<TResult> CombineLatest<T1, T2, TResult>(
this IDeclarativeProperty<T1> prop1,
IDeclarativeProperty<T2> prop2,
Func<T1, T2, Task<TResult?>> func,
Func<T1, T2, Task<TResult>> func,
Action<TResult?>? setValueHook = null)
=> new CombineLatestProperty<T1,T2,TResult>(prop1, prop2, func!, setValueHook);
=> new CombineLatestProperty<T1,T2,TResult>(prop1, prop2, func, setValueHook);
public static IDeclarativeProperty<T?> Switch<T>(this IDeclarativeProperty<IDeclarativeProperty<T?>?> from)
=> new SwitchProperty<T?>(from);
public static IDeclarativeProperty<T> Switch<T>(this IDeclarativeProperty<IDeclarativeProperty<T>?> from)
=> new SwitchProperty<T>(from);
public static IDeclarativeProperty<TResult> CombineAll<T, TResult>(
this IEnumerable<IDeclarativeProperty<T>> sources,
Func<IEnumerable<T>, Task<TResult?>> combiner,
Func<IEnumerable<T>, Task<TResult>> combiner,
Action<TResult?>? setValueHook = null)
=> new CombineAllProperty<T,TResult>(sources, combiner, setValueHook);
}

View File

@@ -11,7 +11,7 @@ public sealed class DistinctUntilChangedProperty<T> : DeclarativePropertyBase<T>
AddDisposable(from.Subscribe(Handle));
}
async Task Handle(T? next, CancellationToken cancellationToken = default)
async Task Handle(T next, CancellationToken cancellationToken = default)
{
if (_comparer is { } comparer)
{
@@ -20,8 +20,10 @@ public sealed class DistinctUntilChangedProperty<T> : DeclarativePropertyBase<T>
return;
}
}
else if ((next is null && Value is null && !_firstFire)
|| (Value?.Equals(next) ?? false))
else if (
(next is null && Value is null && !_firstFire)
|| (Value?.Equals(next) ?? false)
)
{
return;
}

View File

@@ -4,7 +4,7 @@ using ObservableComputations;
namespace DeclarativeProperty;
public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T>
public sealed class ExtractorProperty<T, TResult> : DeclarativePropertyBase<TResult>
{
private interface ICollectionWrapper : IDisposable
{
@@ -108,12 +108,12 @@ public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T>
) => new CollectionComputingWrapper(collection, handler);
}
private readonly Func<IList<T>?, T?> _extractor;
private readonly Func<IList<T>?, TResult> _extractor;
private ICollectionWrapper? _collectionWrapper;
public ExtractorProperty(
IDeclarativeProperty<ObservableCollection<T>> from,
Func<IList<T>?, T?> extractor) : base(extractor(from.Value))
IDeclarativeProperty<ObservableCollection<T>?> from,
Func<IList<T>?, TResult> extractor) : base(extractor(from.Value))
{
_extractor = extractor;
_collectionWrapper = from.Value is null
@@ -124,8 +124,8 @@ public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T>
}
public ExtractorProperty(
IDeclarativeProperty<ReadOnlyObservableCollection<T>> from,
Func<IList<T>?, T?> extractor) : base(extractor(from.Value))
IDeclarativeProperty<ReadOnlyObservableCollection<T>?> from,
Func<IList<T>?, TResult> extractor) : base(extractor(from.Value))
{
_extractor = extractor;
_collectionWrapper = from.Value is null
@@ -136,8 +136,8 @@ public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T>
}
public ExtractorProperty(
IDeclarativeProperty<CollectionComputing<T>> from,
Func<IList<T>?, T?> extractor) : base(extractor(from.Value))
IDeclarativeProperty<CollectionComputing<T>?> from,
Func<IList<T>?, TResult> extractor) : base(extractor(from.Value))
{
_extractor = extractor;
_collectionWrapper = from.Value is null
@@ -165,9 +165,7 @@ public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T>
private async Task Fire(IList<T>? items, CancellationToken cancellationToken = default)
{
var newValue = items is null
? default
: _extractor(items);
var newValue = _extractor(items);
await SetNewValueAsync(newValue, cancellationToken);
}

View File

@@ -7,13 +7,22 @@ public sealed class FilterProperty<T> : DeclarativePropertyBase<T>
public FilterProperty(
Func<T?, Task<bool>> filter,
IDeclarativeProperty<T> from,
Action<T?>? setValueHook = null) : base(setValueHook)
Action<T>? setValueHook = null) : base(default!, setValueHook)
{
_filter = filter;
var initialValueTask = Task.Run(async () => await _filter(from.Value));
initialValueTask.Wait();
if (initialValueTask.Result)
{
SetNewValueSync(from.Value);
}
// Unfortunately we can't set a default value if the parent current value can not passed by the filter.
AddDisposable(from.Subscribe(SetValue));
}
private async Task SetValue(T? next, CancellationToken cancellationToken = default)
private async Task SetValue(T next, CancellationToken cancellationToken = default)
{
if (await _filter(next))
{
@@ -24,7 +33,7 @@ public sealed class FilterProperty<T> : DeclarativePropertyBase<T>
public static async Task<FilterProperty<T>> CreateAsync(
Func<T?, Task<bool>> filter,
IDeclarativeProperty<T> from,
Action<T?>? setValueHook = null)
Action<T>? setValueHook = null)
{
var prop = new FilterProperty<T>(filter, from, setValueHook);
await prop.SetValue(from.Value);

View File

@@ -4,9 +4,9 @@ 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);
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,

View File

@@ -6,14 +6,19 @@ public sealed class MapProperty<TFrom, TTo> : DeclarativePropertyBase<TTo>
public MapProperty(
Func<TFrom, CancellationToken, Task<TTo>> mapper,
IDeclarativeProperty<TFrom?> from,
Action<TTo?>? setValueHook = null) : base(setValueHook)
IDeclarativeProperty<TFrom> from,
Action<TTo>? setValueHook = null) : base(default!, setValueHook)
{
_mapper = mapper;
var initialValueTask = Task.Run(async () => await _mapper(from.Value, CancellationToken.None));
initialValueTask.Wait();
SetNewValueSync(initialValueTask.Result);
AddDisposable(from.Subscribe(SetValue));
}
private async Task SetValue(TFrom? next, CancellationToken cancellationToken = default)
private async Task SetValue(TFrom next, CancellationToken cancellationToken = default)
{
var newValue = await _mapper(next!, cancellationToken);
await SetNewValueAsync(newValue, cancellationToken);
@@ -21,8 +26,8 @@ public sealed class MapProperty<TFrom, TTo> : DeclarativePropertyBase<TTo>
public static async Task<MapProperty<TFrom, TTo>> CreateAsync(
Func<TFrom, CancellationToken, Task<TTo>> mapper,
IDeclarativeProperty<TFrom?> from,
Action<TTo?>? setValueHook = null)
IDeclarativeProperty<TFrom> from,
Action<TTo>? setValueHook = null)
{
var prop = new MapProperty<TFrom, TTo>(mapper, from, setValueHook);
await prop.SetValue(from.Value);

View File

@@ -2,7 +2,7 @@
public class MergeProperty<T> : DeclarativePropertyBase<T>
{
public MergeProperty(params IDeclarativeProperty<T>[] props)
public MergeProperty(params IDeclarativeProperty<T>[] props) : base(default!)
{
ArgumentNullException.ThrowIfNull(props);
@@ -12,6 +12,6 @@ public class MergeProperty<T> : DeclarativePropertyBase<T>
}
}
private async Task UpdateAsync(T? newValue, CancellationToken token)
private async Task UpdateAsync(T newValue, CancellationToken token)
=> await SetNewValueAsync(newValue, token);
}

View File

@@ -4,7 +4,7 @@ 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)
public SwitchProperty(IDeclarativeProperty<IDeclarativeProperty<TItem>?> from) : base(from.Value is null ? default! : from.Value.Value)
{
AddDisposable(from.Subscribe(HandleStreamChange));
_innerSubscription = from.Value?.Subscribe(HandleInnerValueChange);
@@ -15,10 +15,9 @@ public sealed class SwitchProperty<TItem> : DeclarativePropertyBase<TItem>
_innerSubscription?.Dispose();
_innerSubscription = next?.Subscribe(HandleInnerValueChange);
await SetNewValueAsync(next is null ? default : next.Value, token);
await SetNewValueAsync(next is null ? default! : next.Value, token);
}
private async Task HandleInnerValueChange(TItem? next, CancellationToken token)
private async Task HandleInnerValueChange(TItem next, CancellationToken token)
=> await SetNewValueAsync(next, token);
}

View File

@@ -9,13 +9,13 @@ public class ThrottleProperty<T> : DeclarativePropertyBase<T>
public ThrottleProperty(
IDeclarativeProperty<T> from,
Func<TimeSpan> interval,
Action<T?>? setValueHook = null) : base(from.Value, setValueHook)
Action<T>? setValueHook = null) : base(from.Value, setValueHook)
{
_interval = interval;
AddDisposable(from.Subscribe(SetValue));
}
private async Task SetValue(T? next, CancellationToken cancellationToken = default)
private async Task SetValue(T next, CancellationToken cancellationToken = default)
{
lock (_lock)
{

View File

@@ -3,9 +3,9 @@
internal sealed class Unsubscriber<T> : IDisposable
{
private readonly IDeclarativeProperty<T> _owner;
private readonly Func<T?, CancellationToken, Task> _onChange;
private readonly Func<T, CancellationToken, Task> _onChange;
public Unsubscriber(IDeclarativeProperty<T> owner, Func<T?, CancellationToken, Task> onChange)
public Unsubscriber(IDeclarativeProperty<T> owner, Func<T, CancellationToken, Task> onChange)
{
_owner = owner;
_onChange = onChange;