diff --git a/Src/AsyncAwaitBestPractices.MVVM.nuspec b/Src/AsyncAwaitBestPractices.MVVM.nuspec index d7c1b70..7f3fd13 100644 --- a/Src/AsyncAwaitBestPractices.MVVM.nuspec +++ b/Src/AsyncAwaitBestPractices.MVVM.nuspec @@ -2,7 +2,7 @@ AsyncAwaitBestPractices.MVVM - 4.1.0 + 4.1.1 Async Extensions for ICommand Brandon Minnick, John Thiriet Brandon Minnick @@ -24,18 +24,14 @@ task, valuetask, fire and forget, threading, extensions, system.threading.tasks, async, await - + New In This Release: - - Unseal AsyncCommand - - Unseal AsyncCommand<T> - - Unseal AsyncValueCommand - - Unseal AsyncValueCommand<T> - - Include Assembly Signing + - Add Extension Methods Without `in` Keyword (provides support for older versions of Visual Studio) - Copyright (c) 2018 Brandon Minnick + Copyright (c) 2020 Brandon Minnick diff --git a/Src/AsyncAwaitBestPractices.MVVM/AsyncCommand.cs b/Src/AsyncAwaitBestPractices.MVVM/AsyncCommand.cs index 9306f7e..efb3607 100644 --- a/Src/AsyncAwaitBestPractices.MVVM/AsyncCommand.cs +++ b/Src/AsyncAwaitBestPractices.MVVM/AsyncCommand.cs @@ -67,12 +67,12 @@ void ICommand.Execute(object parameter) switch (parameter) { case T validParameter: - ExecuteAsync(validParameter).SafeFireAndForget(_onException, _continueOnCapturedContext); + ExecuteAsync(validParameter).SafeFireAndForget(_onException, in _continueOnCapturedContext); break; #pragma warning disable CS8604 // Possible null reference argument. case null when !typeof(T).GetTypeInfo().IsValueType: - ExecuteAsync((T)parameter).SafeFireAndForget(_onException, _continueOnCapturedContext); + ExecuteAsync((T)parameter).SafeFireAndForget(_onException, in _continueOnCapturedContext); break; #pragma warning restore CS8604 // Possible null reference argument. @@ -141,6 +141,6 @@ public event EventHandler CanExecuteChanged /// The executed Task public Task ExecuteAsync() => _execute(); - void ICommand.Execute(object parameter) => _execute().SafeFireAndForget(_onException, _continueOnCapturedContext); + void ICommand.Execute(object parameter) => _execute().SafeFireAndForget(_onException, in _continueOnCapturedContext); } } diff --git a/Src/AsyncAwaitBestPractices.MVVM/AsyncValueCommand.cs b/Src/AsyncAwaitBestPractices.MVVM/AsyncValueCommand.cs index d62f091..996e94c 100644 --- a/Src/AsyncAwaitBestPractices.MVVM/AsyncValueCommand.cs +++ b/Src/AsyncAwaitBestPractices.MVVM/AsyncValueCommand.cs @@ -24,9 +24,9 @@ public class AsyncValueCommand : IAsyncValueCommand /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown /// If set to true continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false continue on a different context; this will allow the Synchronization Context to continue on a different thread public AsyncValueCommand(Func execute, - Func? canExecute = null, - Action? onException = null, - bool continueOnCapturedContext = false) + Func? canExecute = null, + Action? onException = null, + bool continueOnCapturedContext = false) { _execute = execute ?? throw new ArgumentNullException(nameof(execute), $"{nameof(execute)} cannot be null"); _canExecute = canExecute ?? (_ => true); @@ -67,11 +67,11 @@ void ICommand.Execute(object parameter) switch (parameter) { case T validParameter: - ExecuteAsync(validParameter).SafeFireAndForget(_onException, _continueOnCapturedContext); + ExecuteAsync(validParameter).SafeFireAndForget(_onException, in _continueOnCapturedContext); break; #pragma warning disable CS8604 // Possible null reference argument. case null when !typeof(T).GetTypeInfo().IsValueType: - ExecuteAsync((T)parameter).SafeFireAndForget(_onException, _continueOnCapturedContext); + ExecuteAsync((T)parameter).SafeFireAndForget(_onException, in _continueOnCapturedContext); break; #pragma warning restore CS8604 // Possible null reference argument. @@ -103,9 +103,9 @@ public class AsyncValueCommand : IAsyncValueCommand /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown /// If set to true continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false continue on a different context; this will allow the Synchronization Context to continue on a different thread public AsyncValueCommand(Func execute, - Func? canExecute = null, - Action? onException = null, - bool continueOnCapturedContext = false) + Func? canExecute = null, + Action? onException = null, + bool continueOnCapturedContext = false) { _execute = execute ?? throw new ArgumentNullException(nameof(execute), $"{nameof(execute)} cannot be null"); _canExecute = canExecute ?? (_ => true); @@ -140,6 +140,6 @@ public event EventHandler CanExecuteChanged /// The executed Task public ValueTask ExecuteAsync() => _execute(); - void ICommand.Execute(object parameter) => _execute().SafeFireAndForget(_onException, _continueOnCapturedContext); + void ICommand.Execute(object parameter) => _execute().SafeFireAndForget(_onException, in _continueOnCapturedContext); } } diff --git a/Src/AsyncAwaitBestPractices.nuspec b/Src/AsyncAwaitBestPractices.nuspec index ebbd61f..73c4f9b 100644 --- a/Src/AsyncAwaitBestPractices.nuspec +++ b/Src/AsyncAwaitBestPractices.nuspec @@ -2,7 +2,7 @@ AsyncAwaitBestPractices - 4.1.0 + 4.1.1 Task Extensions for System.Threading.Tasks Brandon Minnick, John Thiriet Brandon Minnick @@ -25,42 +25,38 @@ task,fire and forget, threading, extensions, system.threading.tasks,async,await - + - + - + - + - + - + - + - + New In This Release: - - Add SafeFireAndForget(Action<Exception> onException, bool continueOnCapturedContext) - - Add SafeFireAndForget<TException>(Action<Exception> onException, bool continueOnCapturedContext) - - Obsolete: SafeFireAndForget(bool continueOnCapturedContext, Action<Exception> onException) - - Obsolete: SafeFireAndForget<TException>(bool continueOnCapturedContext, Action<TException> onException) - - Include Assembly Signing + - Add Extension Methods Without `in` Keyword (provides support for older versions of Visual Studio) - Copyright (c) 2018 Brandon Minnick + Copyright (c) 2020 Brandon Minnick diff --git a/Src/AsyncAwaitBestPractices/AsyncAwaitBestPractices.csproj b/Src/AsyncAwaitBestPractices/AsyncAwaitBestPractices.csproj index cf75c26..e7e1037 100644 --- a/Src/AsyncAwaitBestPractices/AsyncAwaitBestPractices.csproj +++ b/Src/AsyncAwaitBestPractices/AsyncAwaitBestPractices.csproj @@ -14,7 +14,7 @@ bin\Release\netstandard1.0\AsyncAwaitBestPractices.xml - + diff --git a/Src/AsyncAwaitBestPractices/SafeFireAndForgetExtensions.cs b/Src/AsyncAwaitBestPractices/SafeFireAndForgetExtensions.cs index 95ee580..3b9b917 100644 --- a/Src/AsyncAwaitBestPractices/SafeFireAndForgetExtensions.cs +++ b/Src/AsyncAwaitBestPractices/SafeFireAndForgetExtensions.cs @@ -6,7 +6,7 @@ namespace AsyncAwaitBestPractices /// /// Extension methods for System.Threading.Tasks.Task and System.Threading.Tasks.ValueTask /// - public static class SafeFireAndForgetExtensions + public static partial class SafeFireAndForgetExtensions { static Action? _onException; static bool _shouldAlwaysRethrowException; @@ -47,48 +47,8 @@ public static class SafeFireAndForgetExtensions /// Exception type. If an exception is thrown of a different type, it will not be handled public static void SafeFireAndForget(this Task task, in Action? onException = null, in bool continueOnCapturedContext = false) where TException : Exception => HandleSafeFireAndForget(task, continueOnCapturedContext, onException); - #region Obsolete Methods - /// - /// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. - /// - /// ValueTask. - /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread - /// If an exception is thrown in the ValueTask, onException will execute. If onException is null, the exception will be re-thrown - [Obsolete("Use SafeFireAndForget(Action onException, bool continueOnCapturedContext)")] - public static void SafeFireAndForget(this ValueTask task, in bool continueOnCapturedContext, in Action? onException) => HandleSafeFireAndForget(task, continueOnCapturedContext, onException); - - - /// - /// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. - /// - /// ValueTask. - /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread - /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown - /// Exception type. If an exception is thrown of a different type, it will not be handled - [Obsolete("Use SafeFireAndForget(Action onException, bool continueOnCapturedContext)")] - public static void SafeFireAndForget(this ValueTask task, in bool continueOnCapturedContext, in Action? onException) where TException : Exception => HandleSafeFireAndForget(task, continueOnCapturedContext, onException); - /// - /// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. - /// - /// Task. - /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread - /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown - [Obsolete("Use SafeFireAndForget(Action onException, bool continueOnCapturedContext)")] - public static void SafeFireAndForget(this Task task, in bool continueOnCapturedContext, in Action? onException) => HandleSafeFireAndForget(task, continueOnCapturedContext, onException); - - /// - /// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. - /// - /// Task. - /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread - /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown - /// Exception type. If an exception is thrown of a different type, it will not be handled - [Obsolete("Use SafeFireAndForget(Action onException, bool continueOnCapturedContext)")] - public static void SafeFireAndForget(this Task task, in bool continueOnCapturedContext, in Action? onException) where TException : Exception => HandleSafeFireAndForget(task, continueOnCapturedContext, onException); - #endregion - /// /// Initialize SafeFireAndForget /// @@ -114,7 +74,6 @@ public static void SetDefaultExceptionHandling(in Action onException) _onException = onException; } -#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void static async void HandleSafeFireAndForget(ValueTask valueTask, bool continueOnCapturedContext, Action? onException) where TException : Exception { try @@ -144,7 +103,6 @@ static async void HandleSafeFireAndForget(Task task, bool continueOn throw; } } -#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void static void HandleException(in TException exception, in Action? onException) where TException : Exception { diff --git a/Src/AsyncAwaitBestPractices/SafeFireAndForgetExtensions.extensions.cs b/Src/AsyncAwaitBestPractices/SafeFireAndForgetExtensions.extensions.cs new file mode 100644 index 0000000..be5ed6c --- /dev/null +++ b/Src/AsyncAwaitBestPractices/SafeFireAndForgetExtensions.extensions.cs @@ -0,0 +1,103 @@ +using System; +using System.Threading.Tasks; + +namespace AsyncAwaitBestPractices +{ + /// + /// Extension methods for System.Threading.Tasks.Task and System.Threading.Tasks.ValueTask + /// + public static partial class SafeFireAndForgetExtensions + { + /// + /// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. + /// + /// ValueTask. + /// If an exception is thrown in the ValueTask, onException will execute. If onException is null, the exception will be re-thrown + /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread + public static void SafeFireAndForget(this ValueTask task, Action? onException, bool continueOnCapturedContext = false) => task.SafeFireAndForget(in onException, in continueOnCapturedContext); + + + /// + /// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. + /// + /// ValueTask. + /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown + /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread + /// Exception type. If an exception is thrown of a different type, it will not be handled + public static void SafeFireAndForget(this ValueTask task, Action? onException, bool continueOnCapturedContext = false) where TException : Exception => task.SafeFireAndForget(in onException, in continueOnCapturedContext); + + + /// + /// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. + /// + /// Task. + /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown + /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread + public static void SafeFireAndForget(this Task task, Action? onException, bool continueOnCapturedContext = false) => task.SafeFireAndForget(in onException, in continueOnCapturedContext); + + /// + /// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. + /// + /// Task. + /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown + /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread + /// Exception type. If an exception is thrown of a different type, it will not be handled + public static void SafeFireAndForget(this Task task, Action? onException, bool continueOnCapturedContext = false) where TException : Exception => task.SafeFireAndForget(in onException, in continueOnCapturedContext); + + /// + /// Initialize SafeFireAndForget + /// + /// Warning: When true, there is no way to catch this exception and it will always result in a crash. Recommended only for debugging purposes. + /// + /// If set to true, after the exception has been caught and handled, the exception will always be rethrown. + public static void Initialize(bool shouldAlwaysRethrowException) => Initialize(in shouldAlwaysRethrowException); + + /// + /// Set the default action for SafeFireAndForget to handle every exception + /// + /// If an exception is thrown in the Task using SafeFireAndForget, onException will execute + public static void SetDefaultExceptionHandling(Action onException) => SetDefaultExceptionHandling(in onException); + + #region Obsolete Methods + /// + /// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. + /// + /// ValueTask. + /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread + /// If an exception is thrown in the ValueTask, onException will execute. If onException is null, the exception will be re-thrown + [Obsolete("Use SafeFireAndForget(Action onException, bool continueOnCapturedContext)")] + public static void SafeFireAndForget(this ValueTask task, in bool continueOnCapturedContext, in Action? onException) => task.SafeFireAndForget(in onException, in continueOnCapturedContext); + + + /// + /// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. + /// + /// ValueTask. + /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread + /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown + /// Exception type. If an exception is thrown of a different type, it will not be handled + [Obsolete("Use SafeFireAndForget(Action onException, bool continueOnCapturedContext)")] + public static void SafeFireAndForget(this ValueTask task, in bool continueOnCapturedContext, in Action? onException) where TException : Exception => task.SafeFireAndForget(in onException, in continueOnCapturedContext); + + + /// + /// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. + /// + /// Task. + /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread + /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown + [Obsolete("Use SafeFireAndForget(Action onException, bool continueOnCapturedContext)")] + public static void SafeFireAndForget(this Task task, in bool continueOnCapturedContext, in Action? onException) => task.SafeFireAndForget(in onException, in continueOnCapturedContext); + + /// + /// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/. + /// + /// Task. + /// If set to true, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to false, continue on a different context; this will allow the Synchronization Context to continue on a different thread + /// If an exception is thrown in the Task, onException will execute. If onException is null, the exception will be re-thrown + /// Exception type. If an exception is thrown of a different type, it will not be handled + [Obsolete("Use SafeFireAndForget(Action onException, bool continueOnCapturedContext)")] + public static void SafeFireAndForget(this Task task, in bool continueOnCapturedContext, in Action? onException) where TException : Exception => task.SafeFireAndForget(in onException, in continueOnCapturedContext); + #endregion + } +} diff --git a/Src/AsyncAwaitBestPractices/WeakEventManager/WeakEventManager.cs b/Src/AsyncAwaitBestPractices/WeakEventManager/WeakEventManager.cs index c65e5d1..0764b34 100644 --- a/Src/AsyncAwaitBestPractices/WeakEventManager/WeakEventManager.cs +++ b/Src/AsyncAwaitBestPractices/WeakEventManager/WeakEventManager.cs @@ -11,7 +11,7 @@ namespace AsyncAwaitBestPractices /// Weak event manager that allows for garbage collection when the EventHandler is still subscribed /// /// Event args type. - public class WeakEventManager + public partial class WeakEventManager { readonly Dictionary> _eventHandlers = new Dictionary>(); @@ -100,7 +100,7 @@ public void HandleEvent(in TEventArgs eventArgs, in string eventName) => /// /// Weak event manager that allows for garbage collection when the EventHandler is still subscribed /// - public class WeakEventManager + public partial class WeakEventManager { readonly Dictionary> _eventHandlers = new Dictionary>(); diff --git a/Src/AsyncAwaitBestPractices/WeakEventManager/WeakEventManager.extensions.cs b/Src/AsyncAwaitBestPractices/WeakEventManager/WeakEventManager.extensions.cs new file mode 100644 index 0000000..850f11e --- /dev/null +++ b/Src/AsyncAwaitBestPractices/WeakEventManager/WeakEventManager.extensions.cs @@ -0,0 +1,89 @@ +using System; +using System.Runtime.CompilerServices; + +namespace AsyncAwaitBestPractices +{ + /// + /// Weak event manager that allows for garbage collection when the EventHandler is still subscribed + /// + /// Event args type. + public partial class WeakEventManager + { + /// + /// Adds the event handler + /// + /// Handler + /// Event name + public void AddEventHandler(EventHandler handler, [CallerMemberName] string eventName = "") => AddEventHandler(in handler, in eventName); + + /// + /// Adds the event handler + /// + /// Handler + /// Event name + public void AddEventHandler(Action action, [CallerMemberName] string eventName = "") => AddEventHandler(in action, in eventName); + + /// + /// Removes the event handler + /// + /// Handler + /// Event name + public void RemoveEventHandler(EventHandler handler, [CallerMemberName] string eventName = "") => RemoveEventHandler(in handler, in eventName); + + /// + /// Removes the event handler + /// + /// Handler + /// Event name + public void RemoveEventHandler(Action action, [CallerMemberName] string eventName = "") => RemoveEventHandler(in action, in eventName); + + /// + /// Executes the event EventHandler + /// + /// Sender + /// Event arguments + /// Event name + public void HandleEvent(object? sender, TEventArgs eventArgs, string eventName) => HandleEvent(in sender, in eventArgs, in eventName); + + /// + /// Executes the event Action + /// + /// Event arguments + /// Event name + public void HandleEvent(TEventArgs eventArgs, string eventName) => HandleEvent(in eventArgs, in eventName); + } + + /// + /// Weak event manager that allows for garbage collection when the EventHandler is still subscribed + /// + public partial class WeakEventManager + { + /// + /// Adds the event handler + /// + /// Handler + /// Event name + public void AddEventHandler(Delegate handler, [CallerMemberName] string eventName = "") => AddEventHandler(in handler, in eventName); + + /// + /// Removes the event handler. + /// + /// Handler + /// Event name + public void RemoveEventHandler(Delegate handler, [CallerMemberName] string eventName = "") => RemoveEventHandler(in handler, in eventName); + + /// + /// Executes the event EventHandler + /// + /// Sender + /// Event arguments + /// Event name + public void HandleEvent(object? sender, object? eventArgs, string eventName) => HandleEvent(in sender, in eventArgs, in eventName); + + /// + /// Executes the event Action + /// + /// Event name + public void HandleEvent(string eventName) => HandleEvent(in eventName); + } +} \ No newline at end of file diff --git a/Src/HackerNews.Droid/HackerNews.Droid.csproj b/Src/HackerNews.Droid/HackerNews.Droid.csproj index 9a27edf..5a02203 100644 --- a/Src/HackerNews.Droid/HackerNews.Droid.csproj +++ b/Src/HackerNews.Droid/HackerNews.Droid.csproj @@ -64,8 +64,8 @@ - - + + diff --git a/Src/HackerNews.iOS/HackerNews.iOS.csproj b/Src/HackerNews.iOS/HackerNews.iOS.csproj index 087b301..74820a7 100644 --- a/Src/HackerNews.iOS/HackerNews.iOS.csproj +++ b/Src/HackerNews.iOS/HackerNews.iOS.csproj @@ -93,9 +93,9 @@ - + - + diff --git a/Src/HackerNews/HackerNews.csproj b/Src/HackerNews/HackerNews.csproj index 979b8e0..f7e20e3 100644 --- a/Src/HackerNews/HackerNews.csproj +++ b/Src/HackerNews/HackerNews.csproj @@ -8,8 +8,8 @@ - - + +