Skip to content

Commit 56b5fe4

Browse files
committed
Add ArgumentException-s for invalid command arguments
1 parent a2c9313 commit 56b5fe4

File tree

3 files changed

+92
-9
lines changed

3 files changed

+92
-9
lines changed

CommunityToolkit.Mvvm/Input/AsyncRelayCommand{T}.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -262,13 +262,18 @@ public bool CanExecute(T? parameter)
262262
[MethodImpl(MethodImplOptions.AggressiveInlining)]
263263
public bool CanExecute(object? parameter)
264264
{
265-
if (default(T) is not null &&
266-
parameter is null)
265+
// Special case, see RelayCommand<T>.CanExecute(object?) for more info
266+
if (parameter is null && default(T) is not null)
267267
{
268268
return false;
269269
}
270270

271-
return CanExecute((T?)parameter);
271+
if (!RelayCommand<T>.TryGetCommandArgument(parameter, out T? result))
272+
{
273+
RelayCommand<T>.ThrowArgumentExceptionForInvalidCommandArgument(parameter);
274+
}
275+
276+
return CanExecute(result);
272277
}
273278

274279
/// <inheritdoc/>
@@ -286,7 +291,12 @@ public void Execute(T? parameter)
286291
/// <inheritdoc/>
287292
public void Execute(object? parameter)
288293
{
289-
Execute((T?)parameter);
294+
if (!RelayCommand<T>.TryGetCommandArgument(parameter, out T? result))
295+
{
296+
RelayCommand<T>.ThrowArgumentExceptionForInvalidCommandArgument(parameter);
297+
}
298+
299+
Execute(result);
290300
}
291301

292302
/// <inheritdoc/>
@@ -322,7 +332,12 @@ public Task ExecuteAsync(T? parameter)
322332
/// <inheritdoc/>
323333
public Task ExecuteAsync(object? parameter)
324334
{
325-
return ExecuteAsync((T?)parameter);
335+
if (!RelayCommand<T>.TryGetCommandArgument(parameter, out T? result))
336+
{
337+
RelayCommand<T>.ThrowArgumentExceptionForInvalidCommandArgument(parameter);
338+
}
339+
340+
return ExecuteAsync(result);
326341
}
327342

328343
/// <inheritdoc/>

CommunityToolkit.Mvvm/Input/Interfaces/IAsyncRelayCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public interface IAsyncRelayCommand : IRelayCommand, INotifyPropertyChanged
6363
/// </summary>
6464
/// <param name="parameter">The input parameter.</param>
6565
/// <returns>The <see cref="Task"/> representing the async operation being executed.</returns>
66+
/// <exception cref="System.ArgumentException">Thrown if <paramref name="parameter"/> is incompatible with the underlying command implementation.</exception>
6667
Task ExecuteAsync(object? parameter);
6768

6869
/// <summary>

CommunityToolkit.Mvvm/Input/RelayCommand{T}.cs

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// more info in ThirdPartyNotices.txt in the root of the project.
77

88
using System;
9+
using System.Diagnostics.CodeAnalysis;
910
using System.Runtime.CompilerServices;
1011

1112
namespace CommunityToolkit.Mvvm.Input;
@@ -81,13 +82,19 @@ public bool CanExecute(T? parameter)
8182
/// <inheritdoc/>
8283
public bool CanExecute(object? parameter)
8384
{
84-
if (default(T) is not null &&
85-
parameter is null)
85+
// Special case a null value for a value type argument type.
86+
// This ensures that no exceptions are thrown during initialization.
87+
if (parameter is null && default(T) is not null)
8688
{
8789
return false;
8890
}
8991

90-
return CanExecute((T?)parameter);
92+
if (!TryGetCommandArgument(parameter, out T? result))
93+
{
94+
ThrowArgumentExceptionForInvalidCommandArgument(parameter);
95+
}
96+
97+
return CanExecute(result);
9198
}
9299

93100
/// <inheritdoc/>
@@ -100,6 +107,66 @@ public void Execute(T? parameter)
100107
/// <inheritdoc/>
101108
public void Execute(object? parameter)
102109
{
103-
Execute((T?)parameter);
110+
if (!TryGetCommandArgument(parameter, out T? result))
111+
{
112+
ThrowArgumentExceptionForInvalidCommandArgument(parameter);
113+
}
114+
115+
Execute(result);
116+
}
117+
118+
/// <summary>
119+
/// Tries to get a command argument of compatible type <typeparamref name="T"/> from an input <see cref="object"/>.
120+
/// </summary>
121+
/// <param name="parameter">The input parameter.</param>
122+
/// <param name="result">The resulting <typeparamref name="T"/> value, if any.</param>
123+
/// <returns>Whether or not a compatible command argument could be retrieved.</returns>
124+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
125+
internal static bool TryGetCommandArgument(object? parameter, out T? result)
126+
{
127+
// If the argument is null and the default value of T is also null, then the
128+
// argument is valid. T might be a reference type or a nullable value type.
129+
if (parameter is null && default(T) is null)
130+
{
131+
result = default;
132+
133+
return true;
134+
}
135+
136+
// Check if the argument is a T value, so either an instance of a type or a derived
137+
// type of T is a reference type, an interface implementation if T is an interface,
138+
// or a boxed value type in case T was a value type.
139+
if (parameter is T argument)
140+
{
141+
result = argument;
142+
143+
return true;
144+
}
145+
146+
result = default;
147+
148+
return false;
149+
}
150+
151+
/// <summary>
152+
/// Throws an <see cref="ArgumentException"/> if an invalid command argument is used.
153+
/// </summary>
154+
/// <param name="parameter">The input parameter.</param>
155+
/// <exception cref="ArgumentException">Thrown with an error message to give info on the invalid parameter.</exception>
156+
[DoesNotReturn]
157+
internal static void ThrowArgumentExceptionForInvalidCommandArgument(object? parameter)
158+
{
159+
[MethodImpl(MethodImplOptions.NoInlining)]
160+
static Exception GetException(object? parameter)
161+
{
162+
if (parameter is null)
163+
{
164+
return new ArgumentException($"Parameter \"{nameof(parameter)}\" (object) must not be null, as the command type requires an argument of type {typeof(T)}.", nameof(parameter));
165+
}
166+
167+
return new ArgumentException($"Parameter \"{nameof(parameter)}\" (object) cannot be of type {parameter.GetType()}, as the command type requires an argument of type {typeof(T)}.", nameof(parameter));
168+
}
169+
170+
throw GetException(parameter);
104171
}
105172
}

0 commit comments

Comments
 (0)