Skip to content

Commit

Permalink
[Feature] Microsoft.Toolkit.Mvvm package (Preview 5) (#3562)
Browse files Browse the repository at this point in the history
## Follow up to #3428 
<!-- Add the relevant issue number after the "#" mentioned above (for ex: Fixes #1234) which will automatically close the issue once the PR is merged. -->
This PR is for tracking all changes/fixes/improvements to the `Microsoft.Toolkit.Mvvm` package following the Preview 4.

<!-- Add a brief overview here of the feature/bug & fix. -->

## PR Type
What kind of change does this PR introduce?
<!-- Please uncomment one or more that apply to this PR. -->

- Feature
- Improvements
<!-- - Code style update (formatting) -->
<!-- - Refactoring (no functional changes, no api changes) -->
<!-- - Build or CI related changes -->
<!-- - Documentation content changes -->
<!-- - Sample app changes -->
<!-- - Other... Please describe: -->

## Overview

This PR is used to track and implement new features and tweaks for the `Microsoft.Toolkit.Mvvm` package.
See the linked issue for more info, and for a full list of changes included in this PR.


## PR Checklist

Please check if your PR fulfills the following requirements:

- [X] Tested code with current [supported SDKs](../readme.md#supported)
- [ ] ~~Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). Link: <!-- docs PR link -->~~
- [ ] ~~Sample in sample app has been added / updated (for bug fixes / features)~~
    - [ ] ~~Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets)~~
- [X] Tests for the changes have been added (for bug fixes / features) (if applicable)
- [X] Header has been added to all new source files (run *build/UpdateHeaders.bat*)
- [X] Contains **NO** breaking changes
  • Loading branch information
msftbot[bot] authored Feb 13, 2021
2 parents d437373 + 4792ea2 commit 5660681
Show file tree
Hide file tree
Showing 20 changed files with 758 additions and 160 deletions.
8 changes: 4 additions & 4 deletions Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier,
// instance. This will result in no further allocations after the first time this method is called for a given
// generic type. We only pay the cost of the virtual call to the delegate, but this is not performance critical
// code and that overhead would still be much lower than the rest of the method anyway, so that's fine.
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, _ => { }, propertyName);
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new(), newValue, static _ => { }, propertyName);
}

/// <summary>
Expand All @@ -362,7 +362,7 @@ protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier,
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier, Task? newValue, Action<Task?> callback, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, callback, propertyName);
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new(), newValue, callback, propertyName);
}

/// <summary>
Expand Down Expand Up @@ -401,7 +401,7 @@ protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier,
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>? taskNotifier, Task<T>? newValue, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, _ => { }, propertyName);
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new(), newValue, static _ => { }, propertyName);
}

/// <summary>
Expand All @@ -424,7 +424,7 @@ protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>? taskNoti
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>? taskNotifier, Task<T>? newValue, Action<Task<T>?> callback, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, callback, propertyName);
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new(), newValue, callback, propertyName);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ protected virtual void OnDeactivated()
/// </remarks>
protected virtual void Broadcast<T>(T oldValue, T newValue, string? propertyName)
{
var message = new PropertyChangedMessage<T>(this, propertyName, oldValue, newValue);
PropertyChangedMessage<T> message = new(this, propertyName, oldValue, newValue);

Messenger.Send(message);
}
Expand Down
245 changes: 220 additions & 25 deletions Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Microsoft.Toolkit.Mvvm/DependencyInjection/Ioc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public sealed class Ioc : IServiceProvider
/// <summary>
/// Gets the default <see cref="Ioc"/> instance.
/// </summary>
public static Ioc Default { get; } = new Ioc();
public static Ioc Default { get; } = new();

/// <summary>
/// The <see cref="IServiceProvider"/> instance to use, if initialized.
Expand Down Expand Up @@ -134,7 +134,7 @@ public void ConfigureServices(IServiceProvider serviceProvider)
{
IServiceProvider? oldServices = Interlocked.CompareExchange(ref this.serviceProvider, serviceProvider, null);

if (!(oldServices is null))
if (oldServices is not null)
{
ThrowInvalidOperationExceptionForRepeatedConfiguration();
}
Expand Down
12 changes: 6 additions & 6 deletions Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ public sealed class AsyncRelayCommand : ObservableObject, IAsyncRelayCommand
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="CanBeCanceled"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs CanBeCanceledChangedEventArgs = new PropertyChangedEventArgs(nameof(CanBeCanceled));
internal static readonly PropertyChangedEventArgs CanBeCanceledChangedEventArgs = new(nameof(CanBeCanceled));

/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IsCancellationRequested"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs IsCancellationRequestedChangedEventArgs = new PropertyChangedEventArgs(nameof(IsCancellationRequested));
internal static readonly PropertyChangedEventArgs IsCancellationRequestedChangedEventArgs = new(nameof(IsCancellationRequested));

/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IsRunning"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs IsRunningChangedEventArgs = new PropertyChangedEventArgs(nameof(IsRunning));
internal static readonly PropertyChangedEventArgs IsRunningChangedEventArgs = new(nameof(IsRunning));

/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute"/> is used.
Expand Down Expand Up @@ -122,7 +122,7 @@ private set
}

/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null) && IsRunning;
public bool CanBeCanceled => this.cancelableExecute is not null && IsRunning;

/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
Expand Down Expand Up @@ -155,15 +155,15 @@ public Task ExecuteAsync(object? parameter)
if (CanExecute(parameter))
{
// Non cancelable command delegate
if (!(this.execute is null))
if (this.execute is not null)
{
return ExecutionTask = this.execute();
}

// Cancel the previous operation, if one is pending
this.cancellationTokenSource?.Cancel();

var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
CancellationTokenSource cancellationTokenSource = this.cancellationTokenSource = new();

OnPropertyChanged(IsCancellationRequestedChangedEventArgs);

Expand Down
39 changes: 19 additions & 20 deletions Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ public sealed class AsyncRelayCommand<T> : ObservableObject, IAsyncRelayCommand<
/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute(T)"/> is used.
/// </summary>
private readonly Func<T, Task>? execute;
private readonly Func<T?, Task>? execute;

/// <summary>
/// The cancelable <see cref="Func{T1,T2,TResult}"/> to invoke when <see cref="Execute(object?)"/> is used.
/// </summary>
private readonly Func<T, CancellationToken, Task>? cancelableExecute;
private readonly Func<T?, CancellationToken, Task>? cancelableExecute;

/// <summary>
/// The optional action to invoke when <see cref="CanExecute(T)"/> is used.
/// </summary>
private readonly Func<T, bool>? canExecute;
private readonly Predicate<T?>? canExecute;

/// <summary>
/// The <see cref="CancellationTokenSource"/> instance to use to cancel <see cref="cancelableExecute"/>.
Expand All @@ -44,7 +44,7 @@ public sealed class AsyncRelayCommand<T> : ObservableObject, IAsyncRelayCommand<
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, Task> execute)
public AsyncRelayCommand(Func<T?, Task> execute)
{
this.execute = execute;
}
Expand All @@ -54,7 +54,7 @@ public AsyncRelayCommand(Func<T, Task> execute)
/// </summary>
/// <param name="cancelableExecute">The cancelable execution logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute)
public AsyncRelayCommand(Func<T?, CancellationToken, Task> cancelableExecute)
{
this.cancelableExecute = cancelableExecute;
}
Expand All @@ -65,7 +65,7 @@ public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute)
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, Task> execute, Func<T, bool> canExecute)
public AsyncRelayCommand(Func<T?, Task> execute, Predicate<T?> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
Expand All @@ -77,7 +77,7 @@ public AsyncRelayCommand(Func<T, Task> execute, Func<T, bool> canExecute)
/// <param name="cancelableExecute">The cancelable execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute, Func<T, bool> canExecute)
public AsyncRelayCommand(Func<T?, CancellationToken, Task> cancelableExecute, Predicate<T?> canExecute)
{
this.cancelableExecute = cancelableExecute;
this.canExecute = canExecute;
Expand Down Expand Up @@ -106,7 +106,7 @@ private set
}

/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null) && IsRunning;
public bool CanBeCanceled => this.cancelableExecute is not null && IsRunning;

/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
Expand All @@ -122,7 +122,7 @@ public void NotifyCanExecuteChanged()

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanExecute(T parameter)
public bool CanExecute(T? parameter)
{
return this.canExecute?.Invoke(parameter) != false;
}
Expand All @@ -131,44 +131,43 @@ public bool CanExecute(T parameter)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanExecute(object? parameter)
{
if (typeof(T).IsValueType &&
parameter is null &&
this.canExecute is null)
if (default(T) is not null &&
parameter is null)
{
return true;
return false;
}

return CanExecute((T)parameter!);
return CanExecute((T?)parameter);
}

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute(T parameter)
public void Execute(T? parameter)
{
ExecuteAsync(parameter);
}

/// <inheritdoc/>
public void Execute(object? parameter)
{
ExecuteAsync((T)parameter!);
ExecuteAsync((T?)parameter);
}

/// <inheritdoc/>
public Task ExecuteAsync(T parameter)
public Task ExecuteAsync(T? parameter)
{
if (CanExecute(parameter))
{
// Non cancelable command delegate
if (!(this.execute is null))
if (this.execute is not null)
{
return ExecutionTask = this.execute(parameter);
}

// Cancel the previous operation, if one is pending
this.cancellationTokenSource?.Cancel();

var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
CancellationTokenSource cancellationTokenSource = this.cancellationTokenSource = new();

OnPropertyChanged(AsyncRelayCommand.IsCancellationRequestedChangedEventArgs);

Expand All @@ -182,7 +181,7 @@ public Task ExecuteAsync(T parameter)
/// <inheritdoc/>
public Task ExecuteAsync(object? parameter)
{
return ExecuteAsync((T)parameter!);
return ExecuteAsync((T?)parameter);
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public interface IAsyncRelayCommand<in T> : IAsyncRelayCommand, IRelayCommand<T>
/// </summary>
/// <param name="parameter">The input parameter.</param>
/// <returns>The <see cref="Task"/> representing the async operation being executed.</returns>
Task ExecuteAsync(T parameter);
Task ExecuteAsync(T? parameter);
}
}
4 changes: 2 additions & 2 deletions Microsoft.Toolkit.Mvvm/Input/Interfaces/IRelayCommand{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ public interface IRelayCommand<in T> : IRelayCommand
/// <param name="parameter">The input parameter.</param>
/// <returns>Whether or not the current command can be executed.</returns>
/// <remarks>Use this overload to avoid boxing, if <typeparamref name="T"/> is a value type.</remarks>
bool CanExecute(T parameter);
bool CanExecute(T? parameter);

/// <summary>
/// Provides a strongly-typed variant of <see cref="ICommand.Execute(object)"/>.
/// </summary>
/// <param name="parameter">The input parameter.</param>
/// <remarks>Use this overload to avoid boxing, if <typeparamref name="T"/> is a value type.</remarks>
void Execute(T parameter);
void Execute(T? parameter);
}
}
23 changes: 11 additions & 12 deletions Microsoft.Toolkit.Mvvm/Input/RelayCommand{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ public sealed class RelayCommand<T> : IRelayCommand<T>
/// <summary>
/// The <see cref="Action"/> to invoke when <see cref="Execute(T)"/> is used.
/// </summary>
private readonly Action<T> execute;
private readonly Action<T?> execute;

/// <summary>
/// The optional action to invoke when <see cref="CanExecute(T)"/> is used.
/// </summary>
private readonly Func<T, bool>? canExecute;
private readonly Predicate<T?>? canExecute;

/// <inheritdoc/>
public event EventHandler? CanExecuteChanged;
Expand All @@ -43,7 +43,7 @@ public sealed class RelayCommand<T> : IRelayCommand<T>
/// nullable <see cref="object"/> parameter, it is recommended that if <typeparamref name="T"/> is a reference type,
/// you should always declare it as nullable, and to always perform checks within <paramref name="execute"/>.
/// </remarks>
public RelayCommand(Action<T> execute)
public RelayCommand(Action<T?> execute)
{
this.execute = execute;
}
Expand All @@ -54,7 +54,7 @@ public RelayCommand(Action<T> execute)
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public RelayCommand(Action<T> execute, Func<T, bool> canExecute)
public RelayCommand(Action<T?> execute, Predicate<T?> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
Expand All @@ -68,27 +68,26 @@ public void NotifyCanExecuteChanged()

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanExecute(T parameter)
public bool CanExecute(T? parameter)
{
return this.canExecute?.Invoke(parameter) != false;
}

/// <inheritdoc/>
public bool CanExecute(object? parameter)
{
if (typeof(T).IsValueType &&
parameter is null &&
this.canExecute is null)
if (default(T) is not null &&
parameter is null)
{
return true;
return false;
}

return CanExecute((T)parameter!);
return CanExecute((T?)parameter);
}

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute(T parameter)
public void Execute(T? parameter)
{
if (CanExecute(parameter))
{
Expand All @@ -99,7 +98,7 @@ public void Execute(T parameter)
/// <inheritdoc/>
public void Execute(object? parameter)
{
Execute((T)parameter!);
Execute((T?)parameter);
}
}
}
9 changes: 4 additions & 5 deletions Microsoft.Toolkit.Mvvm/Messaging/IMessengerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ private static class DiscoveredRecipients<TToken>
/// <summary>
/// The <see cref="ConditionalWeakTable{TKey,TValue}"/> instance used to track the preloaded registration actions for each recipient.
/// </summary>
public static readonly ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]> RegistrationMethods
= new ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]>();
public static readonly ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]> RegistrationMethods = new();
}

/// <summary>
Expand Down Expand Up @@ -139,7 +138,7 @@ static Action<IMessenger, object, TToken> GetRegistrationAction(Type type, Metho
// For more info on this, see the related issue at https://github.com/dotnet/roslyn/issues/5835.
Action<IMessenger, object, TToken>[] registrationActions = DiscoveredRecipients<TToken>.RegistrationMethods.GetValue(
recipient.GetType(),
t => LoadRegistrationMethodsForType(t));
static t => LoadRegistrationMethodsForType(t));

foreach (Action<IMessenger, object, TToken> registrationAction in registrationActions)
{
Expand All @@ -158,7 +157,7 @@ static Action<IMessenger, object, TToken> GetRegistrationAction(Type type, Metho
public static void Register<TMessage>(this IMessenger messenger, IRecipient<TMessage> recipient)
where TMessage : class
{
messenger.Register<IRecipient<TMessage>, TMessage, Unit>(recipient, default, (r, m) => r.Receive(m));
messenger.Register<IRecipient<TMessage>, TMessage, Unit>(recipient, default, static (r, m) => r.Receive(m));
}

/// <summary>
Expand All @@ -175,7 +174,7 @@ public static void Register<TMessage, TToken>(this IMessenger messenger, IRecipi
where TMessage : class
where TToken : IEquatable<TToken>
{
messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, (r, m) => r.Receive(m));
messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, static (r, m) => r.Receive(m));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ private Entry[] Resize()
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this);
public Enumerator GetEnumerator() => new(this);

/// <summary>
/// Enumerator for <see cref="DictionarySlim{TKey,TValue}"/>.
Expand Down
Loading

0 comments on commit 5660681

Please sign in to comment.