Skip to content

[Feature] Microsoft.Toolkit.Mvvm package (Preview 5) #3562

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
24 commits merged into from
Feb 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cfc7865
Enabled C# 9, switched to target typed new()
Sergio0694 Nov 12, 2020
b4f66bd
Memory improvements for weak messenger on NS2.0
Sergio0694 Nov 12, 2020
820e078
Added generic ObservableValidator.GetErrors return
Sergio0694 Nov 12, 2020
77b0ae2
Changed command CanExecute args to Predicate<T>
Sergio0694 Nov 18, 2020
47f495c
Improved nullability annotations in commands
Sergio0694 Nov 18, 2020
905307f
Minor codegen improvements
Sergio0694 Nov 19, 2020
09ed2f2
Minor code tweaks
Sergio0694 Nov 20, 2020
ff82101
Updated NuGet packages
Sergio0694 Nov 21, 2020
a28668d
Minor code style tweaks
Sergio0694 Nov 21, 2020
446a93b
Fixed NRE when calling CanExecute(null) over value types
Sergio0694 Dec 11, 2020
504c3e2
Switched ObservableValidator.ValidateProperty to protected
Sergio0694 Jan 14, 2021
5da7ece
Fixed unit tests with RelayCommand and value types
Sergio0694 Jan 15, 2021
54ab0f9
Added ObservableValidator.ClearErrors(string) API
Sergio0694 Jan 22, 2021
02843c6
Added unit tests for ClearErrors
Sergio0694 Jan 22, 2021
0437ee4
Added ObservableValidator.ValidateAllProperties() API
Sergio0694 Jan 22, 2021
7351c53
Added caching for reflection setup in ValidateAllProperties
Sergio0694 Jan 22, 2021
48157f0
Added missing nullability annotations
Sergio0694 Jan 26, 2021
e8b624a
Added .NET 5 target (and removed built-in package)
Sergio0694 Jan 26, 2021
d70e6d9
Added missing types to NuGet description
Sergio0694 Jan 26, 2021
fefb499
Added custom ValidationContext to ObservableValidator
Sergio0694 Feb 8, 2021
69b133c
Tweaked ObservableValidator constructors, added tests
Sergio0694 Feb 8, 2021
ae01304
Added missing nullability annotations
Sergio0694 Feb 8, 2021
2e10189
Switched to LINQ expressions for ValidateAllProperties
Sergio0694 Feb 9, 2021
4792ea2
Added unit test for validation with an injected service
Sergio0694 Feb 10, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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