Skip to content

Commit 3a9522e

Browse files
Merge pull request #21 from brminnick/Release-v3.1.0
Release v3.1.0
2 parents 587496f + eac68e1 commit 3a9522e

38 files changed

+438
-342
lines changed

README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices/
4343
### AsyncAwaitBestPractices.MVVM
4444

4545
- Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices.MVVM/
46-
- Add to any project supporting .NET Standard 2.0
46+
- Add to any project supporting .NET Standard 1.0
4747

4848
## Why Do I Need This?
4949

@@ -152,12 +152,16 @@ void InitializeSafeFireAndForget()
152152
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => Console.WriteLine(ex));
153153
}
154154

155+
void UninitializeSafeFireAndForget()
156+
{
157+
// Remove default exception handling
158+
SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling()
159+
}
160+
155161
void HandleButtonTapped(object sender, EventArgs e)
156162
{
157163
// Allows the async Task method to safely run on a different thread while not awaiting its completion
158-
159164
// onException: If a WebException is thrown, print its StatusCode to the Console. **Note**: If a non-WebException is thrown, it will not be handled by `onException`
160-
161165
// Because we set `SetDefaultExceptionHandling` in `void InitializeSafeFireAndForget()`, the entire exception will also be printed to the Console
162166
ExampleAsyncMethod().SafeFireAndForget<WebException>(onException: ex =>
163167
{
@@ -166,7 +170,6 @@ void HandleButtonTapped(object sender, EventArgs e)
166170
});
167171

168172
// HandleButtonTapped continues execution here while `ExampleAsyncMethod()` is running on a different thread
169-
// ...
170173
}
171174

172175
async Task ExampleAsyncMethod()
@@ -193,7 +196,7 @@ public event EventHandler CanExecuteChanged
193196
remove => _weakEventManager.RemoveEventHandler(value);
194197
}
195198

196-
public void OnCanExecuteChanged() => _canExecuteChangedEventManager.HandleEvent(this, EventArgs.Empty, nameof(CanExecuteChanged));
199+
void OnCanExecuteChanged() => _canExecuteChangedEventManager.HandleEvent(this, EventArgs.Empty, nameof(CanExecuteChanged));
197200
```
198201

199202
#### Using `Delegate`
@@ -207,7 +210,7 @@ public event PropertyChangedEventHandler PropertyChanged
207210
remove => _propertyChangedEventManager.RemoveEventHandler(value);
208211
}
209212

210-
public void OnPropertyChanged([CallerMemberName]string propertyName = "") => _propertyChangedEventManager.HandleEvent(this, new PropertyChangedEventArgs(propertyName), nameof(PropertyChanged));
213+
void OnPropertyChanged([CallerMemberName]string propertyName = "") => _propertyChangedEventManager.HandleEvent(this, new PropertyChangedEventArgs(propertyName), nameof(PropertyChanged));
211214
```
212215

213216
#### Using `Action`
@@ -221,7 +224,7 @@ public event Action ActionEvent
221224
remove => _weakActionEventManager.RemoveEventHandler(value);
222225
}
223226

224-
public void OnActionEvent(string message) => _weakActionEventManager.HandleEvent(message, nameof(ActionEvent));
227+
void OnActionEvent(string message) => _weakActionEventManager.HandleEvent(message, nameof(ActionEvent));
225228
```
226229

227230
### `WeakEventManager<T>`
@@ -238,7 +241,7 @@ public event EventHandler<string> ErrorOcurred
238241
remove => _errorOcurredEventManager.RemoveEventHandler(value);
239242
}
240243

241-
public void OnErrorOcurred(string message) => _errorOcurredEventManager.HandleEvent(this, message, nameof(ErrorOcurred));
244+
void OnErrorOcurred(string message) => _errorOcurredEventManager.HandleEvent(this, message, nameof(ErrorOcurred));
242245
```
243246

244247
#### Using `Action<T>`
@@ -252,7 +255,7 @@ public event Action<string> ActionEvent
252255
remove => _weakActionEventManager.RemoveEventHandler(value);
253256
}
254257

255-
public void OnActionEvent(string message) => _weakActionEventManager.HandleEvent(message, nameof(ActionEvent));
258+
void OnActionEvent(string message) => _weakActionEventManager.HandleEvent(message, nameof(ActionEvent));
256259
```
257260

258261
## AsyncAwaitBestPractices.MVVM

Src/AsyncAwaitBestPractices.MVVM.nuspec

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
33
<metadata minClientVersion="2.5">
44
<id>AsyncAwaitBestPractices.MVVM</id>
5-
<version>3.0.0</version>
5+
<version>3.1.0</version>
66
<title>Task Extensions for MVVM</title>
77
<authors>Brandon Minnick, John Thiriet</authors>
88
<owners>Brandon Minnick</owners>
@@ -14,23 +14,21 @@
1414
<summary>Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.</summary>
1515
<tags>task,fire and forget, threading, extensions, system.threading.tasks,async,await</tags>
1616
<dependencies>
17-
<dependency id="AsyncAwaitBestPractices" version="3.0.0" />
17+
<dependency id="AsyncAwaitBestPractices" version="3.1.0" />
1818
</dependencies>
1919
<releaseNotes>
2020
New In This Release:
21-
- Added support for `event Action` and `event Action&lt;T&gt;`
22-
- Added support for `.SafeFireAndForget&lt;TException&gt;()`
23-
- Added `SafeFireAndForgetExtensions.SetDefaultExceptionHandling(Action&lt;Exception&gt; onException)` to set a default action for every call to `SafeFireAndForget`
24-
- Added `SafeFireAndForgetExtensions.Initialize(bool shouldAlwaysRethrowException = false)`. When set to `true` will rethrow every exception caught by `SafeFireAndForget`. Warning: `SafeFireAndForgetExtensions.Initialize(true)` is only recommended for DEBUG environments.
25-
- Added support for `Exception innerException` to `InvalidCommandParameterException`
26-
- Breaking Change: Changed default value to `continueOnCapturedContext = false`. This improves performance by not requiring a context switch when `.SafeFireAndForget()` and `IAsyncCommand` have completed.
21+
- Added `SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling`
22+
- Fixed InvalidHandleEventException bug when using the incorrect overload for `WeakEventManager&lt;T&gt;.HandleEvent`
23+
- Fixed `NullReferenceException` when calling `ICommand.Execute`
24+
- Improved .NET Standard Dependency to .NET Standard 1.0
2725
</releaseNotes>
28-
<repository type="git" url="https://github.com/brminnick/AsyncAwaitBestPractices.git" branch="master" commit="e768a96e2ceddc7644eee54d60cbd9343ca857b7" />
26+
<repository type="git" url="https://github.com/brminnick/AsyncAwaitBestPractices.git" branch="Release-v3.1.0" commit="c13174438950d3e1adc54d2911dc900df877a4de" />
2927
<copyright>Copyright (c) 2018 Brandon Minnick</copyright>
3028
</metadata>
3129
<files>
32-
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.pdb" target="lib\netstandard2.0\AsyncAwaitBestPractices.MVVM.pdb" />
33-
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.dll" target="lib\netstandard2.0\AsyncAwaitBestPractices.MVVM.dll" />
34-
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml" target="lib\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml" />
30+
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" />
31+
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" />
32+
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" />
3533
</files>
3634
</package>

Src/AsyncAwaitBestPractices.MVVM/AsyncAwaitBestPractices.MVVM.csproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project Sdk="Microsoft.NET.Sdk">
33
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
5-
<LangVersion>latest</LangVersion>
4+
<TargetFramework>netstandard1.0</TargetFramework>
5+
<LangVersion>8.0</LangVersion>
6+
<Nullable>enable</Nullable>
7+
<NullableContextOptions>enable</NullableContextOptions>
68
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
79
</PropertyGroup>
810
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Reflection;
23
using System.Threading.Tasks;
34
using System.Windows.Input;
45

@@ -9,15 +10,12 @@ namespace AsyncAwaitBestPractices.MVVM
910
/// </summary>
1011
public sealed class AsyncCommand<T> : IAsyncCommand<T>
1112
{
12-
#region Constant Fields
1313
readonly Func<T, Task> _execute;
14-
readonly Func<object, bool> _canExecute;
15-
readonly Action<Exception> _onException;
14+
readonly Func<object?, bool> _canExecute;
15+
readonly Action<Exception>? _onException;
1616
readonly bool _continueOnCapturedContext;
1717
readonly WeakEventManager _weakEventManager = new WeakEventManager();
18-
#endregion
1918

20-
#region Constructors
2119
/// <summary>
2220
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.AsyncCommand`1"/> class.
2321
/// </summary>
@@ -26,18 +24,16 @@ public sealed class AsyncCommand<T> : IAsyncCommand<T>
2624
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
2725
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
2826
public AsyncCommand(Func<T, Task> execute,
29-
Func<object, bool> canExecute = null,
30-
Action<Exception> onException = null,
27+
Func<object?, bool>? canExecute = null,
28+
Action<Exception>? onException = null,
3129
bool continueOnCapturedContext = false)
3230
{
3331
_execute = execute ?? throw new ArgumentNullException(nameof(execute), $"{nameof(execute)} cannot be null");
3432
_canExecute = canExecute ?? (_ => true);
3533
_onException = onException;
3634
_continueOnCapturedContext = continueOnCapturedContext;
3735
}
38-
#endregion
3936

40-
#region Events
4137
/// <summary>
4238
/// Occurs when changes occur that affect whether or not the command should execute
4339
/// </summary>
@@ -46,15 +42,13 @@ public event EventHandler CanExecuteChanged
4642
add => _weakEventManager.AddEventHandler(value);
4743
remove => _weakEventManager.RemoveEventHandler(value);
4844
}
49-
#endregion
5045

51-
#region Methods
5246
/// <summary>
5347
/// Determines whether the command can execute in its current state
5448
/// </summary>
5549
/// <returns><c>true</c>, if this command can be executed; otherwise, <c>false</c>.</returns>
5650
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
57-
public bool CanExecute(object parameter) => _canExecute(parameter);
51+
public bool CanExecute(object? parameter) => _canExecute(parameter);
5852

5953
/// <summary>
6054
/// Raises the CanExecuteChanged event.
@@ -70,30 +64,38 @@ public event EventHandler CanExecuteChanged
7064

7165
void ICommand.Execute(object parameter)
7266
{
73-
if (parameter is T validParameter)
74-
ExecuteAsync(validParameter).SafeFireAndForget(_continueOnCapturedContext, _onException);
75-
else if (parameter is null && !typeof(T).IsValueType)
76-
ExecuteAsync((T)parameter).SafeFireAndForget(_continueOnCapturedContext, _onException);
77-
else
78-
throw new InvalidCommandParameterException(typeof(T), parameter.GetType());
67+
switch (parameter)
68+
{
69+
case T validParameter:
70+
ExecuteAsync(validParameter).SafeFireAndForget(_continueOnCapturedContext, _onException);
71+
break;
72+
73+
#pragma warning disable CS8601 //Possible null reference assignment
74+
case null when !typeof(T).GetTypeInfo().IsValueType:
75+
ExecuteAsync((T)parameter).SafeFireAndForget(_continueOnCapturedContext, _onException);
76+
break;
77+
#pragma warning restore CS8601
78+
79+
case null:
80+
throw new InvalidCommandParameterException(typeof(T));
81+
82+
default:
83+
throw new InvalidCommandParameterException(typeof(T), parameter.GetType());
84+
}
7985
}
80-
#endregion
8186
}
8287

8388
/// <summary>
8489
/// An implmentation of IAsyncCommand. Allows Commands to safely be used asynchronously with Task.
8590
/// </summary>
8691
public sealed class AsyncCommand : IAsyncCommand
8792
{
88-
#region Constant Fields
8993
readonly Func<Task> _execute;
90-
readonly Func<object, bool> _canExecute;
91-
readonly Action<Exception> _onException;
94+
readonly Func<object?, bool> _canExecute;
95+
readonly Action<Exception>? _onException;
9296
readonly bool _continueOnCapturedContext;
9397
readonly WeakEventManager _weakEventManager = new WeakEventManager();
94-
#endregion
9598

96-
#region Constructors
9799
/// <summary>
98100
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.AsyncCommand`1"/> class.
99101
/// </summary>
@@ -102,18 +104,16 @@ public sealed class AsyncCommand : IAsyncCommand
102104
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
103105
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
104106
public AsyncCommand(Func<Task> execute,
105-
Func<object, bool> canExecute = null,
106-
Action<Exception> onException = null,
107+
Func<object?, bool>? canExecute = null,
108+
Action<Exception>? onException = null,
107109
bool continueOnCapturedContext = false)
108110
{
109111
_execute = execute ?? throw new ArgumentNullException(nameof(execute), $"{nameof(execute)} cannot be null");
110112
_canExecute = canExecute ?? (_ => true);
111113
_onException = onException;
112114
_continueOnCapturedContext = continueOnCapturedContext;
113115
}
114-
#endregion
115116

116-
#region Events
117117
/// <summary>
118118
/// Occurs when changes occur that affect whether or not the command should execute
119119
/// </summary>
@@ -122,15 +122,13 @@ public event EventHandler CanExecuteChanged
122122
add => _weakEventManager.AddEventHandler(value);
123123
remove => _weakEventManager.RemoveEventHandler(value);
124124
}
125-
#endregion
126125

127-
#region Methods
128126
/// <summary>
129127
/// Determines whether the command can execute in its current state
130128
/// </summary>
131129
/// <returns><c>true</c>, if this command can be executed; otherwise, <c>false</c>.</returns>
132130
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
133-
public bool CanExecute(object parameter) => _canExecute(parameter);
131+
public bool CanExecute(object? parameter) => _canExecute(parameter);
134132

135133
/// <summary>
136134
/// Raises the CanExecuteChanged event.
@@ -144,6 +142,5 @@ public event EventHandler CanExecuteChanged
144142
public Task ExecuteAsync() => _execute();
145143

146144
void ICommand.Execute(object parameter) => _execute().SafeFireAndForget(_continueOnCapturedContext, _onException);
147-
#endregion
148145
}
149146
}

Src/AsyncAwaitBestPractices.MVVM/InvalidCommandParameterException.cs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ namespace AsyncAwaitBestPractices.MVVM
55
/// <summary>
66
/// Represents errors that occur during IAsyncCommand execution.
77
/// </summary>
8-
class InvalidCommandParameterException : Exception
8+
public class InvalidCommandParameterException : Exception
99
{
1010
/// <summary>
1111
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
1212
/// </summary>
1313
/// <param name="excpectedType">Excpected parameter type for AsyncCommand.Execute.</param>
1414
/// <param name="actualType">Actual parameter type for AsyncCommand.Execute.</param>
1515
/// <param name="innerException">Inner Exception</param>
16-
public InvalidCommandParameterException(Type excpectedType, Type actualType, Exception innerException) : this(CreateErrorMessage(excpectedType, actualType), innerException)
16+
public InvalidCommandParameterException(Type excpectedType, Type actualType, Exception innerException) : base(CreateErrorMessage(excpectedType, actualType), innerException)
1717
{
1818

1919
}
@@ -23,39 +23,32 @@ public InvalidCommandParameterException(Type excpectedType, Type actualType, Exc
2323
/// </summary>
2424
/// <param name="excpectedType">Excpected parameter type for AsyncCommand.Execute.</param>
2525
/// <param name="actualType">Actual parameter type for AsyncCommand.Execute.</param>
26-
public InvalidCommandParameterException(Type excpectedType, Type actualType) : this(CreateErrorMessage(excpectedType, actualType))
26+
public InvalidCommandParameterException(Type excpectedType, Type actualType) : base(CreateErrorMessage(excpectedType, actualType))
2727
{
2828

2929
}
3030

3131
/// <summary>
3232
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
3333
/// </summary>
34-
/// <param name="message">Exception Message</param>
34+
/// <param name="excpectedType">Excpected parameter type for AsyncCommand.Execute.</param>
3535
/// <param name="innerException">Inner Exception</param>
36-
public InvalidCommandParameterException(string message, Exception innerException) : base(message, innerException)
36+
public InvalidCommandParameterException(Type excpectedType, Exception innerException) : base(CreateErrorMessage(excpectedType), innerException)
3737
{
3838

3939
}
4040

4141
/// <summary>
4242
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
4343
/// </summary>
44-
/// <param name="message">Exception Message</param>
45-
public InvalidCommandParameterException(string message) : base(message)
44+
/// <param name="excpectedType">Excpected parameter type for AsyncCommand.Execute.</param>
45+
public InvalidCommandParameterException(Type excpectedType) : base(CreateErrorMessage(excpectedType))
4646
{
4747

4848
}
4949

50-
/// <summary>
51-
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
52-
/// </summary>
53-
public InvalidCommandParameterException()
54-
{
55-
56-
}
50+
static string CreateErrorMessage(Type excpectedType) => $"Invalid type for parameter. Expected Type {excpectedType}";
5751

58-
static string CreateErrorMessage(Type excpectedType, Type actualType) =>
59-
$"Invalid type for parameter. Expected Type {excpectedType}, but received Type {actualType}";
52+
static string CreateErrorMessage(Type excpectedType, Type actualType) => $"Invalid type for parameter. Expected Type {excpectedType}, but received Type {actualType}";
6053
}
6154
}

Src/AsyncAwaitBestPractices.UnitTests/AsyncAwaitBestPractices.UnitTests.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project Sdk="Microsoft.NET.Sdk">
33
<PropertyGroup>
4-
<TargetFramework>netcoreapp2.1</TargetFramework>
5-
<LangVersion>latest</LangVersion>
4+
<TargetFramework>netcoreapp3.0</TargetFramework>
5+
<LangVersion>8.0</LangVersion>
6+
<Nullable>enable</Nullable>
7+
<NullableContextOptions>enable</NullableContextOptions>
68
<IsPackable>false</IsPackable>
79
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
810
</PropertyGroup>
911
<ItemGroup>
1012
<PackageReference Include="nunit" Version="3.12.0" />
11-
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
13+
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
1214
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
1315
</ItemGroup>
1416
<ItemGroup>

0 commit comments

Comments
 (0)