Skip to content

Commit 5660681

Browse files
author
msftbot[bot]
authored
[Feature] Microsoft.Toolkit.Mvvm package (Preview 5) (#3562)
## 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
2 parents d437373 + 4792ea2 commit 5660681

20 files changed

+758
-160
lines changed

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier,
340340
// instance. This will result in no further allocations after the first time this method is called for a given
341341
// generic type. We only pay the cost of the virtual call to the delegate, but this is not performance critical
342342
// code and that overhead would still be much lower than the rest of the method anyway, so that's fine.
343-
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, _ => { }, propertyName);
343+
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new(), newValue, static _ => { }, propertyName);
344344
}
345345

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

368368
/// <summary>
@@ -401,7 +401,7 @@ protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier,
401401
/// </remarks>
402402
protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>? taskNotifier, Task<T>? newValue, [CallerMemberName] string? propertyName = null)
403403
{
404-
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, _ => { }, propertyName);
404+
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new(), newValue, static _ => { }, propertyName);
405405
}
406406

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

430430
/// <summary>

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableRecipient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ protected virtual void OnDeactivated()
116116
/// </remarks>
117117
protected virtual void Broadcast<T>(T oldValue, T newValue, string? propertyName)
118118
{
119-
var message = new PropertyChangedMessage<T>(this, propertyName, oldValue, newValue);
119+
PropertyChangedMessage<T> message = new(this, propertyName, oldValue, newValue);
120120

121121
Messenger.Send(message);
122122
}

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs

Lines changed: 220 additions & 25 deletions
Large diffs are not rendered by default.

Microsoft.Toolkit.Mvvm/DependencyInjection/Ioc.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public sealed class Ioc : IServiceProvider
4747
/// <summary>
4848
/// Gets the default <see cref="Ioc"/> instance.
4949
/// </summary>
50-
public static Ioc Default { get; } = new Ioc();
50+
public static Ioc Default { get; } = new();
5151

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

137-
if (!(oldServices is null))
137+
if (oldServices is not null)
138138
{
139139
ThrowInvalidOperationExceptionForRepeatedConfiguration();
140140
}

Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@ public sealed class AsyncRelayCommand : ObservableObject, IAsyncRelayCommand
2222
/// <summary>
2323
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="CanBeCanceled"/>.
2424
/// </summary>
25-
internal static readonly PropertyChangedEventArgs CanBeCanceledChangedEventArgs = new PropertyChangedEventArgs(nameof(CanBeCanceled));
25+
internal static readonly PropertyChangedEventArgs CanBeCanceledChangedEventArgs = new(nameof(CanBeCanceled));
2626

2727
/// <summary>
2828
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IsCancellationRequested"/>.
2929
/// </summary>
30-
internal static readonly PropertyChangedEventArgs IsCancellationRequestedChangedEventArgs = new PropertyChangedEventArgs(nameof(IsCancellationRequested));
30+
internal static readonly PropertyChangedEventArgs IsCancellationRequestedChangedEventArgs = new(nameof(IsCancellationRequested));
3131

3232
/// <summary>
3333
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IsRunning"/>.
3434
/// </summary>
35-
internal static readonly PropertyChangedEventArgs IsRunningChangedEventArgs = new PropertyChangedEventArgs(nameof(IsRunning));
35+
internal static readonly PropertyChangedEventArgs IsRunningChangedEventArgs = new(nameof(IsRunning));
3636

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

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

127127
/// <inheritdoc/>
128128
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
@@ -155,15 +155,15 @@ public Task ExecuteAsync(object? parameter)
155155
if (CanExecute(parameter))
156156
{
157157
// Non cancelable command delegate
158-
if (!(this.execute is null))
158+
if (this.execute is not null)
159159
{
160160
return ExecutionTask = this.execute();
161161
}
162162

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

166-
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
166+
CancellationTokenSource cancellationTokenSource = this.cancellationTokenSource = new();
167167

168168
OnPropertyChanged(IsCancellationRequestedChangedEventArgs);
169169

Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand{T}.cs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,17 @@ public sealed class AsyncRelayCommand<T> : ObservableObject, IAsyncRelayCommand<
1919
/// <summary>
2020
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute(T)"/> is used.
2121
/// </summary>
22-
private readonly Func<T, Task>? execute;
22+
private readonly Func<T?, Task>? execute;
2323

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

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

3434
/// <summary>
3535
/// The <see cref="CancellationTokenSource"/> instance to use to cancel <see cref="cancelableExecute"/>.
@@ -44,7 +44,7 @@ public sealed class AsyncRelayCommand<T> : ObservableObject, IAsyncRelayCommand<
4444
/// </summary>
4545
/// <param name="execute">The execution logic.</param>
4646
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
47-
public AsyncRelayCommand(Func<T, Task> execute)
47+
public AsyncRelayCommand(Func<T?, Task> execute)
4848
{
4949
this.execute = execute;
5050
}
@@ -54,7 +54,7 @@ public AsyncRelayCommand(Func<T, Task> execute)
5454
/// </summary>
5555
/// <param name="cancelableExecute">The cancelable execution logic.</param>
5656
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
57-
public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute)
57+
public AsyncRelayCommand(Func<T?, CancellationToken, Task> cancelableExecute)
5858
{
5959
this.cancelableExecute = cancelableExecute;
6060
}
@@ -65,7 +65,7 @@ public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute)
6565
/// <param name="execute">The execution logic.</param>
6666
/// <param name="canExecute">The execution status logic.</param>
6767
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
68-
public AsyncRelayCommand(Func<T, Task> execute, Func<T, bool> canExecute)
68+
public AsyncRelayCommand(Func<T?, Task> execute, Predicate<T?> canExecute)
6969
{
7070
this.execute = execute;
7171
this.canExecute = canExecute;
@@ -77,7 +77,7 @@ public AsyncRelayCommand(Func<T, Task> execute, Func<T, bool> canExecute)
7777
/// <param name="cancelableExecute">The cancelable execution logic.</param>
7878
/// <param name="canExecute">The execution status logic.</param>
7979
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
80-
public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute, Func<T, bool> canExecute)
80+
public AsyncRelayCommand(Func<T?, CancellationToken, Task> cancelableExecute, Predicate<T?> canExecute)
8181
{
8282
this.cancelableExecute = cancelableExecute;
8383
this.canExecute = canExecute;
@@ -106,7 +106,7 @@ private set
106106
}
107107

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

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

123123
/// <inheritdoc/>
124124
[MethodImpl(MethodImplOptions.AggressiveInlining)]
125-
public bool CanExecute(T parameter)
125+
public bool CanExecute(T? parameter)
126126
{
127127
return this.canExecute?.Invoke(parameter) != false;
128128
}
@@ -131,44 +131,43 @@ public bool CanExecute(T parameter)
131131
[MethodImpl(MethodImplOptions.AggressiveInlining)]
132132
public bool CanExecute(object? parameter)
133133
{
134-
if (typeof(T).IsValueType &&
135-
parameter is null &&
136-
this.canExecute is null)
134+
if (default(T) is not null &&
135+
parameter is null)
137136
{
138-
return true;
137+
return false;
139138
}
140139

141-
return CanExecute((T)parameter!);
140+
return CanExecute((T?)parameter);
142141
}
143142

144143
/// <inheritdoc/>
145144
[MethodImpl(MethodImplOptions.AggressiveInlining)]
146-
public void Execute(T parameter)
145+
public void Execute(T? parameter)
147146
{
148147
ExecuteAsync(parameter);
149148
}
150149

151150
/// <inheritdoc/>
152151
public void Execute(object? parameter)
153152
{
154-
ExecuteAsync((T)parameter!);
153+
ExecuteAsync((T?)parameter);
155154
}
156155

157156
/// <inheritdoc/>
158-
public Task ExecuteAsync(T parameter)
157+
public Task ExecuteAsync(T? parameter)
159158
{
160159
if (CanExecute(parameter))
161160
{
162161
// Non cancelable command delegate
163-
if (!(this.execute is null))
162+
if (this.execute is not null)
164163
{
165164
return ExecutionTask = this.execute(parameter);
166165
}
167166

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

171-
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
170+
CancellationTokenSource cancellationTokenSource = this.cancellationTokenSource = new();
172171

173172
OnPropertyChanged(AsyncRelayCommand.IsCancellationRequestedChangedEventArgs);
174173

@@ -182,7 +181,7 @@ public Task ExecuteAsync(T parameter)
182181
/// <inheritdoc/>
183182
public Task ExecuteAsync(object? parameter)
184183
{
185-
return ExecuteAsync((T)parameter!);
184+
return ExecuteAsync((T?)parameter);
186185
}
187186

188187
/// <inheritdoc/>

Microsoft.Toolkit.Mvvm/Input/Interfaces/IAsyncRelayCommand{T}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ public interface IAsyncRelayCommand<in T> : IAsyncRelayCommand, IRelayCommand<T>
1818
/// </summary>
1919
/// <param name="parameter">The input parameter.</param>
2020
/// <returns>The <see cref="Task"/> representing the async operation being executed.</returns>
21-
Task ExecuteAsync(T parameter);
21+
Task ExecuteAsync(T? parameter);
2222
}
2323
}

Microsoft.Toolkit.Mvvm/Input/Interfaces/IRelayCommand{T}.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ public interface IRelayCommand<in T> : IRelayCommand
1818
/// <param name="parameter">The input parameter.</param>
1919
/// <returns>Whether or not the current command can be executed.</returns>
2020
/// <remarks>Use this overload to avoid boxing, if <typeparamref name="T"/> is a value type.</remarks>
21-
bool CanExecute(T parameter);
21+
bool CanExecute(T? parameter);
2222

2323
/// <summary>
2424
/// Provides a strongly-typed variant of <see cref="ICommand.Execute(object)"/>.
2525
/// </summary>
2626
/// <param name="parameter">The input parameter.</param>
2727
/// <remarks>Use this overload to avoid boxing, if <typeparamref name="T"/> is a value type.</remarks>
28-
void Execute(T parameter);
28+
void Execute(T? parameter);
2929
}
3030
}

Microsoft.Toolkit.Mvvm/Input/RelayCommand{T}.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ public sealed class RelayCommand<T> : IRelayCommand<T>
2424
/// <summary>
2525
/// The <see cref="Action"/> to invoke when <see cref="Execute(T)"/> is used.
2626
/// </summary>
27-
private readonly Action<T> execute;
27+
private readonly Action<T?> execute;
2828

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

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

6969
/// <inheritdoc/>
7070
[MethodImpl(MethodImplOptions.AggressiveInlining)]
71-
public bool CanExecute(T parameter)
71+
public bool CanExecute(T? parameter)
7272
{
7373
return this.canExecute?.Invoke(parameter) != false;
7474
}
7575

7676
/// <inheritdoc/>
7777
public bool CanExecute(object? parameter)
7878
{
79-
if (typeof(T).IsValueType &&
80-
parameter is null &&
81-
this.canExecute is null)
79+
if (default(T) is not null &&
80+
parameter is null)
8281
{
83-
return true;
82+
return false;
8483
}
8584

86-
return CanExecute((T)parameter!);
85+
return CanExecute((T?)parameter);
8786
}
8887

8988
/// <inheritdoc/>
9089
[MethodImpl(MethodImplOptions.AggressiveInlining)]
91-
public void Execute(T parameter)
90+
public void Execute(T? parameter)
9291
{
9392
if (CanExecute(parameter))
9493
{
@@ -99,7 +98,7 @@ public void Execute(T parameter)
9998
/// <inheritdoc/>
10099
public void Execute(object? parameter)
101100
{
102-
Execute((T)parameter!);
101+
Execute((T?)parameter);
103102
}
104103
}
105104
}

Microsoft.Toolkit.Mvvm/Messaging/IMessengerExtensions.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ private static class DiscoveredRecipients<TToken>
4747
/// <summary>
4848
/// The <see cref="ConditionalWeakTable{TKey,TValue}"/> instance used to track the preloaded registration actions for each recipient.
4949
/// </summary>
50-
public static readonly ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]> RegistrationMethods
51-
= new ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]>();
50+
public static readonly ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]> RegistrationMethods = new();
5251
}
5352

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

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

164163
/// <summary>
@@ -175,7 +174,7 @@ public static void Register<TMessage, TToken>(this IMessenger messenger, IRecipi
175174
where TMessage : class
176175
where TToken : IEquatable<TToken>
177176
{
178-
messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, (r, m) => r.Receive(m));
177+
messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, static (r, m) => r.Receive(m));
179178
}
180179

181180
/// <summary>

0 commit comments

Comments
 (0)