Skip to content

Commit 009ceeb

Browse files
Add Support for Nullable ValueTypes
1 parent f22f864 commit 009ceeb

File tree

11 files changed

+123
-149
lines changed

11 files changed

+123
-149
lines changed

Src/AsyncAwaitBestPractices.MVVM/AsyncAwaitBestPractices.MVVM.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
</Description>
2626
<PackageReleaseNotes>
2727
New in this release:
28-
- Improve Nullability
28+
- Add AsyncCommand support for nullable value types
2929
</PackageReleaseNotes>
30-
<Version>5.1.0</Version>
30+
<Version>6.0.0-pre1</Version>
3131
<RepositoryUrl>https://github.com/brminnick/AsyncAwaitBestPractices</RepositoryUrl>
3232
<Product>$(AssemblyName) ($(TargetFramework))</Product>
3333
<AssemblyVersion>1.0.0.0</AssemblyVersion>

Src/AsyncAwaitBestPractices.MVVM/AsyncCommand/BaseAsyncCommand.shared.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.ComponentModel;
3-
using System.Reflection;
43
using System.Threading.Tasks;
54
using System.Windows.Input;
65

@@ -43,7 +42,7 @@ protected private BaseAsyncCommand(Func<TExecute?, Task>? execute,
4342
bool ICommand.CanExecute(object parameter) => parameter switch
4443
{
4544
TCanExecute validParameter => CanExecute(validParameter),
46-
null when !typeof(TCanExecute).GetTypeInfo().IsValueType => CanExecute((TCanExecute?)parameter),
45+
null when IsNullable<TCanExecute>() => CanExecute((TCanExecute?)parameter),
4746
null => throw new InvalidCommandParameterException(typeof(TCanExecute)),
4847
_ => throw new InvalidCommandParameterException(typeof(TCanExecute), parameter.GetType()),
4948
};
@@ -56,7 +55,7 @@ void ICommand.Execute(object? parameter)
5655
ExecuteAsync(validParameter).SafeFireAndForget(_onException, _continueOnCapturedContext);
5756
break;
5857

59-
case null when !typeof(TExecute).GetTypeInfo().IsValueType:
58+
case null when IsNullable<TExecute>():
6059
ExecuteAsync((TExecute?)parameter).SafeFireAndForget(_onException, _continueOnCapturedContext);
6160
break;
6261

Src/AsyncAwaitBestPractices.MVVM/AsyncValueCommand/BaseAsyncValueCommand.shared.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.ComponentModel;
3-
using System.Reflection;
43
using System.Threading.Tasks;
54
using System.Windows.Input;
65

@@ -43,7 +42,7 @@ protected private BaseAsyncValueCommand(Func<TExecute?, ValueTask>? execute,
4342
bool ICommand.CanExecute(object? parameter) => parameter switch
4443
{
4544
TCanExecute validParameter => CanExecute(validParameter),
46-
null when !typeof(TCanExecute).GetTypeInfo().IsValueType => CanExecute((TCanExecute?)parameter),
45+
null when IsNullable<TCanExecute>() => CanExecute((TCanExecute?)parameter),
4746
null => throw new InvalidCommandParameterException(typeof(TCanExecute)),
4847
_ => throw new InvalidCommandParameterException(typeof(TCanExecute), parameter.GetType()),
4948
};
@@ -56,7 +55,7 @@ void ICommand.Execute(object? parameter)
5655
ExecuteAsync(validParameter).SafeFireAndForget(_onException, _continueOnCapturedContext);
5756
break;
5857

59-
case null when !typeof(TExecute).GetTypeInfo().IsValueType:
58+
case null when IsNullable<TExecute>():
6059
ExecuteAsync((TExecute?)parameter).SafeFireAndForget(_onException, _continueOnCapturedContext);
6160
break;
6261

Src/AsyncAwaitBestPractices.MVVM/BaseCommand.shared.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.ComponentModel;
3+
using System.Reflection;
34

45
namespace AsyncAwaitBestPractices.MVVM
56
{
@@ -38,5 +39,18 @@ public event EventHandler CanExecuteChanged
3839
/// Raises the CanExecuteChanged event.
3940
/// </summary>
4041
public void RaiseCanExecuteChanged() => _weakEventManager.RaiseEvent(this, EventArgs.Empty, nameof(CanExecuteChanged));
42+
43+
protected static bool IsNullable<T>()
44+
{
45+
var type = typeof(T);
46+
47+
if (!type.GetTypeInfo().IsValueType)
48+
return true; // ref-type
49+
50+
if (Nullable.GetUnderlyingType(type) != null)
51+
return true; // Nullable<T>
52+
53+
return false; // value-type
54+
}
4155
}
4256
}

Src/AsyncAwaitBestPractices.UnitTests/BaseTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ protected event EventHandler<string> TestStringEvent
2323

2424
protected Task NoParameterTask() => Task.Delay(Delay);
2525
protected Task IntParameterTask(int delay) => Task.Delay(delay);
26+
protected Task NullableIntParameterTask(int? delay) => Task.Delay(delay ?? Delay);
2627
protected Task StringParameterTask(string? text) => Task.Delay(Delay);
2728
protected Task NoParameterImmediateNullReferenceExceptionTask() => throw new NullReferenceException();
2829
protected Task ParameterImmediateNullReferenceExceptionTask(int delay) => throw new NullReferenceException();

Src/AsyncAwaitBestPractices.UnitTests/CommandTests/AsyncCommand/Tests_IAsyncCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public async Task AsyncCommand_ExecuteAsync_StringParameter_Test(string paramete
9090
public void IAsyncCommand_Parameter_CanExecuteTrue_Test()
9191
{
9292
//Arrange
93-
IAsyncCommand<int> command = new AsyncCommand<int>(IntParameterTask, CanExecuteTrue);
93+
IAsyncCommand<int?> command = new AsyncCommand<int?>(NullableIntParameterTask, CanExecuteTrue);
9494
IAsyncCommand<int, int> command2 = new AsyncCommand<int, int>(IntParameterTask, CanExecuteTrue);
9595

9696
//Act
@@ -104,7 +104,7 @@ public void IAsyncCommand_Parameter_CanExecuteTrue_Test()
104104
public void IAsyncCommand_Parameter_CanExecuteFalse_Test()
105105
{
106106
//Arrange
107-
IAsyncCommand<int> command = new AsyncCommand<int>(IntParameterTask, CanExecuteFalse);
107+
IAsyncCommand<int?> command = new AsyncCommand<int?>(NullableIntParameterTask, CanExecuteFalse);
108108
IAsyncCommand<int, int> command2 = new AsyncCommand<int, int>(IntParameterTask, CanExecuteFalse);
109109

110110
//Act

Src/AsyncAwaitBestPractices.UnitTests/CommandTests/AsyncCommand/Tests_ICommand_AsyncCommand.cs

Lines changed: 48 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public async Task ICommand_Execute_StringParameter_Test(string parameter)
4545
}
4646

4747
[Test]
48-
public async Task ICommand_TwoParameters_ExecuteAsync_InvalidValueTypeParameter_Test()
48+
public void ICommand_TwoParameters_ExecuteAsync_InvalidValueTypeParameter_Test()
4949
{
5050
//Arrange
5151
InvalidCommandParameterException? actualInvalidCommandParameterException = null;
@@ -54,24 +54,15 @@ public async Task ICommand_TwoParameters_ExecuteAsync_InvalidValueTypeParameter_
5454
ICommand command = new AsyncCommand<string, string>(StringParameterTask);
5555

5656
//Act
57-
try
58-
{
59-
command.Execute(Delay);
60-
await NoParameterTask();
61-
await NoParameterTask();
62-
}
63-
catch (InvalidCommandParameterException e)
64-
{
65-
actualInvalidCommandParameterException = e;
66-
}
57+
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.Execute(Delay));
6758

6859
//Assert
6960
Assert.IsNotNull(actualInvalidCommandParameterException);
7061
Assert.AreEqual(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
7162
}
7263

7364
[Test]
74-
public async Task ICommand_ExecuteAsync_InvalidValueTypeParameter_Test()
65+
public void ICommand_ExecuteAsync_InvalidValueTypeParameter_Test()
7566
{
7667
//Arrange
7768
InvalidCommandParameterException? actualInvalidCommandParameterException = null;
@@ -80,24 +71,15 @@ public async Task ICommand_ExecuteAsync_InvalidValueTypeParameter_Test()
8071
ICommand command = new AsyncCommand<string>(StringParameterTask);
8172

8273
//Act
83-
try
84-
{
85-
command.Execute(Delay);
86-
await NoParameterTask();
87-
await NoParameterTask();
88-
}
89-
catch (InvalidCommandParameterException e)
90-
{
91-
actualInvalidCommandParameterException = e;
92-
}
74+
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.Execute(Delay));
9375

9476
//Assert
9577
Assert.IsNotNull(actualInvalidCommandParameterException);
9678
Assert.AreEqual(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
9779
}
9880

9981
[Test]
100-
public async Task ICommand_TwoParameters_ExecuteAsync_InvalidReferenceTypeParameter_Test()
82+
public void ICommand_TwoParameters_ExecuteAsync_InvalidReferenceTypeParameter_Test()
10183
{
10284
//Arrange
10385
InvalidCommandParameterException? actualInvalidCommandParameterException = null;
@@ -107,24 +89,15 @@ public async Task ICommand_TwoParameters_ExecuteAsync_InvalidReferenceTypeParame
10789
ICommand command = new AsyncCommand<int, int>(IntParameterTask);
10890

10991
//Act
110-
try
111-
{
112-
command.Execute("Hello World");
113-
await NoParameterTask();
114-
await NoParameterTask();
115-
}
116-
catch (InvalidCommandParameterException e)
117-
{
118-
actualInvalidCommandParameterException = e;
119-
}
92+
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.Execute("Hello World"));
12093

12194
//Assert
12295
Assert.IsNotNull(actualInvalidCommandParameterException);
12396
Assert.AreEqual(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
12497
}
12598

12699
[Test]
127-
public async Task ICommand_ExecuteAsync_InvalidReferenceTypeParameter_Test()
100+
public void ICommand_ExecuteAsync_InvalidReferenceTypeParameter_Test()
128101
{
129102
//Arrange
130103
InvalidCommandParameterException? actualInvalidCommandParameterException = null;
@@ -134,24 +107,15 @@ public async Task ICommand_ExecuteAsync_InvalidReferenceTypeParameter_Test()
134107
ICommand command = new AsyncCommand<int>(IntParameterTask);
135108

136109
//Act
137-
try
138-
{
139-
command.Execute("Hello World");
140-
await NoParameterTask();
141-
await NoParameterTask();
142-
}
143-
catch (InvalidCommandParameterException e)
144-
{
145-
actualInvalidCommandParameterException = e;
146-
}
110+
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.Execute("Hello World"));
147111

148112
//Assert
149113
Assert.IsNotNull(actualInvalidCommandParameterException);
150114
Assert.AreEqual(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
151115
}
152116

153117
[Test]
154-
public async Task ICommand_TwoParameters_ExecuteAsync_ValueTypeParameter_Test()
118+
public void ICommand_TwoParameters_ExecuteAsync_ValueTypeParameter_Test()
155119
{
156120
//Arrange
157121
InvalidCommandParameterException? actualInvalidCommandParameterException = null;
@@ -161,24 +125,15 @@ public async Task ICommand_TwoParameters_ExecuteAsync_ValueTypeParameter_Test()
161125
ICommand command = new AsyncCommand<int, int>(IntParameterTask);
162126

163127
//Act
164-
try
165-
{
166-
command.Execute(null);
167-
await NoParameterTask();
168-
await NoParameterTask();
169-
}
170-
catch (InvalidCommandParameterException e)
171-
{
172-
actualInvalidCommandParameterException = e;
173-
}
128+
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.Execute(null));
174129

175130
//Assert
176131
Assert.IsNotNull(actualInvalidCommandParameterException);
177132
Assert.AreEqual(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
178133
}
179134

180135
[Test]
181-
public async Task ICommand_ExecuteAsync_ValueTypeParameter_Test()
136+
public void ICommand_ExecuteAsync_ValueTypeParameter_Test()
182137
{
183138
//Arrange
184139
InvalidCommandParameterException? actualInvalidCommandParameterException = null;
@@ -188,16 +143,7 @@ public async Task ICommand_ExecuteAsync_ValueTypeParameter_Test()
188143
ICommand command = new AsyncCommand<int>(IntParameterTask);
189144

190145
//Act
191-
try
192-
{
193-
command.Execute(null);
194-
await NoParameterTask();
195-
await NoParameterTask();
196-
}
197-
catch (InvalidCommandParameterException e)
198-
{
199-
actualInvalidCommandParameterException = e;
200-
}
146+
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.Execute(null));
201147

202148
//Assert
203149
Assert.IsNotNull(actualInvalidCommandParameterException);
@@ -208,7 +154,7 @@ public async Task ICommand_ExecuteAsync_ValueTypeParameter_Test()
208154
public void ICommand_Parameter_CanExecuteTrue_Test()
209155
{
210156
//Arrange
211-
ICommand command = new AsyncCommand<int>(IntParameterTask, CanExecuteTrue);
157+
ICommand command = new AsyncCommand<int?>(NullableIntParameterTask, CanExecuteTrue);
212158
ICommand command2 = new AsyncCommand<int, int>(IntParameterTask, CanExecuteTrue);
213159

214160
//Act
@@ -222,7 +168,7 @@ public void ICommand_Parameter_CanExecuteTrue_Test()
222168
public void ICommand_Parameter_CanExecuteFalse_Test()
223169
{
224170
//Arrange
225-
ICommand command = new AsyncCommand<int>(IntParameterTask, CanExecuteFalse);
171+
ICommand command = new AsyncCommand<int?>(NullableIntParameterTask, CanExecuteFalse);
226172
ICommand command2 = new AsyncCommand<int, int>(IntParameterTask, CanExecuteFalse);
227173

228174
//Act
@@ -232,6 +178,40 @@ public void ICommand_Parameter_CanExecuteFalse_Test()
232178
Assert.False(command2.CanExecute(0));
233179
}
234180

181+
[Test]
182+
public void ICommand_Parameter_CanExecuteNull_Test()
183+
{
184+
//Arrange
185+
InvalidCommandParameterException? actualInvalidCommandParameterException = null;
186+
InvalidCommandParameterException expectedInvalidCommandParameterException = new InvalidCommandParameterException(typeof(int));
187+
188+
ICommand command = new AsyncCommand<int, int>(IntParameterTask, CanExecuteFalse);
189+
190+
//Act
191+
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.CanExecute(null));
192+
193+
//Assert
194+
Assert.IsNotNull(actualInvalidCommandParameterException);
195+
Assert.AreEqual(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
196+
}
197+
198+
[Test]
199+
public void ICommand_Parameter_ExecuteNull_Test()
200+
{
201+
//Arrange
202+
InvalidCommandParameterException? actualInvalidCommandParameterException = null;
203+
InvalidCommandParameterException expectedInvalidCommandParameterException = new InvalidCommandParameterException(typeof(int));
204+
205+
ICommand command = new AsyncCommand<int, int>(IntParameterTask, CanExecuteFalse);
206+
207+
//Act
208+
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.Execute(null));
209+
210+
//Assert
211+
Assert.IsNotNull(actualInvalidCommandParameterException);
212+
Assert.AreEqual(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
213+
}
214+
235215
[Test]
236216
public void ICommand_NoParameter_CanExecuteFalse_Test()
237217
{

Src/AsyncAwaitBestPractices.UnitTests/CommandTests/AsyncValueCommand/BaseAsyncValueCommandTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ abstract class BaseAsyncValueCommandTest : BaseTest
77
{
88
protected new ValueTask NoParameterTask() => ValueTaskDelay(Delay);
99
protected new ValueTask IntParameterTask(int delay) => ValueTaskDelay(delay);
10+
protected new ValueTask NullableIntParameterTask(int? delay) => ValueTaskDelay(delay ?? Delay);
1011
protected new ValueTask StringParameterTask(string? text) => ValueTaskDelay(Delay);
1112
protected new ValueTask NoParameterImmediateNullReferenceExceptionTask() => throw new NullReferenceException();
1213
protected new ValueTask ParameterImmediateNullReferenceExceptionTask(int delay) => throw new NullReferenceException();

Src/AsyncAwaitBestPractices.UnitTests/CommandTests/AsyncValueCommand/Tests_IAsyncValueCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public async Task AsyncValueCommand_ExecuteAsync_StringParameter_Test(string par
9090
public void IAsyncValueCommand_Parameter_CanExecuteTrue_Test()
9191
{
9292
//Arrange
93-
IAsyncValueCommand<int> command = new AsyncValueCommand<int>(IntParameterTask, CanExecuteTrue);
93+
IAsyncValueCommand<int?> command = new AsyncValueCommand<int?>(NullableIntParameterTask, CanExecuteTrue);
9494
IAsyncValueCommand<int, int> command2 = new AsyncValueCommand<int, int>(IntParameterTask, CanExecuteTrue);
9595

9696
//Act
@@ -104,7 +104,7 @@ public void IAsyncValueCommand_Parameter_CanExecuteTrue_Test()
104104
public void IAsyncValueCommand_Parameter_CanExecuteFalse_Test()
105105
{
106106
//Arrange
107-
IAsyncValueCommand<int> command = new AsyncValueCommand<int>(IntParameterTask, CanExecuteFalse);
107+
IAsyncValueCommand<int?> command = new AsyncValueCommand<int?>(NullableIntParameterTask, CanExecuteFalse);
108108
IAsyncValueCommand<int, int> command2 = new AsyncValueCommand<int, int>(IntParameterTask, CanExecuteFalse);
109109

110110
//Act

0 commit comments

Comments
 (0)