RLC.TaskChaining
2.20.0
dotnet add package RLC.TaskChaining --version 2.20.0
NuGet\Install-Package RLC.TaskChaining -Version 2.20.0
<PackageReference Include="RLC.TaskChaining" Version="2.20.0" />
<PackageVersion Include="RLC.TaskChaining" Version="2.20.0" />
<PackageReference Include="RLC.TaskChaining" />
paket add RLC.TaskChaining --version 2.20.0
#r "nuget: RLC.TaskChaining, 2.20.0"
#:package RLC.TaskChaining@2.20.0
#addin nuget:?package=RLC.TaskChaining&version=2.20.0
#tool nuget:?package=RLC.TaskChaining&version=2.20.0
RLC.TaskChaining
Monadic-style chaining for C# Tasks.
Rationale
Asynchronous code (particularly in C#) typically relies on using the async/await feature introduced in C# 5.0. This has a lot of benefits, but it unfortunately tends to push code into an imperative style. This library aims to make writing asychronous functional code easier, cleaner, and less error-prone using extensions to System.Threading.Tasks.
Installation
Install RLC.TaskChaining as a NuGet package via an IDE package manager or using the command-line instructions at nuget.org.
API
Monadic Functions
Monads are usually portrayed as a lot more complicated than they really have to be. See MONADS.md for a brief tutorial. Note that this library contains both standard monadic functions (such as ResultMap(fmap), Bind, etc) and bluebird Promise-style functions (Then, Catch, Tap, etc). Some scenarios will lend themselves to one over the other.
Chaining
Then
Once a Task<T> has been created, successive operations can be chained using the Then method.
HttpClient client; // Assuming this is coming from an HttpClientFactory or injected or whatever
Task.FromResult("https://www.google.com") // Task<string>
.Then(client.GetAsync) // Task<HttpResponseMessage>
.Then(response => response.StatusCode); // Task<System.Net.HttpStatusCode>
Catch
When a Task<T> enters a faulted state, the Catch method can be used to return the Task<T> to a non-faulted state.
HttpClient client; // Assuming this is coming from an HttpClientFactory or injected or whatever
Task.FromResult("not-a-url") // Task<string>
.Then(client.GetAsync) // Task<HttpResponseMessage> but FAULTED
.Catch(exception => exception.Message) // Task<string> and NOT FAULTED
.Then(message => message.Length) // Task<int>
Note that it's entirely possible for a Catch method to cause the Task<T> to remain in a faulted state, e.g. if you only wanted to recover into a non-faulted state if a particular exception type occurred (although CatchWhen is better for this if you're only trying to recover from a single exception type).
HttpClient client; // Assuming this is coming from an HttpClientFactory or injected or whatever
Task.FromResult("not-a-url") // Task<string>
.Then(client.GetAsync) // Task<HttpResponseMessage> but FAULTED
.Catch(exception => exception is NullReferenceException
? exception.Message
: Task.FromException(exception)) // Task<string> but STILL FAULTED if anything other than NullReferenceException occurred
.Then(message => message.Length) // Task<int>
CatchWhen
When a Task<T> enters a faulted state, the CatchWhen method can be used to return the Task<T> to a non-faulted state if the contained exception matches the supplied type.
HttpClient client; // Assuming this is coming from an HttpClientFactory or injected or whatever
Task.FromResult("not-a-url") // Task<string>
.Then(client.GetAsync) // Task<HttpResponseMessage> but FAULTED
.CatchWhen<string, NullReferenceException>(exception => exception.Message) // Task<string> and NOT FAULTED if client.GetAsync threw a NullReferenceException
.Then(message => message.Length) // Task<int>
IfFulfilled/IfFaulted/Tap
The IfFulfilled and IfFaulted methods can be used to perform side effects such as logging when the Task<T> is in the fulfilled or faulted state, respectively.
NOTE: These functions do not trap errors. If the function passed to them throws an exception, the result is a faulted task with that exception.
HttpClient client; // Assuming this is coming from an HttpClientFactory or injected or whatever
Task.FromResult("https://www.google.com/")
.Then(client.GetAsync)
.IfFulfilled(response => _logger.LogDebug("Got response {Response}", response)
.Then(response => response.StatusCode);
HttpClient client; // Assuming this is coming from an HttpClientFactory or injected or whatever
Task.FromResult("not-a-url")
.Then(client.GetAsync)
.IfFaulted(exception => _logger.LogException(exception, "Failed to get URL")
.Catch(exception => exception.Message)
.Then(message => message.Length);
The Tap method takes both an onFulfilled and onRejected Action in the event that you want to perform some side effect on both sides of the Task at a single time.
HttpClient client; // Assuming this is coming from an HttpClientFactory or injected or whatever
Task.FromResult(someExternalUrl)
.Then(client.GetAsync)
.Tap(
response => _logger.LogDebug("Got response {Response}", response),
exception => _logger.LogException(exception, "Failed to get URL")
)
Retry
Task.Retry can be used to automatically retry a function. The RetryOptions type holds the retry interval, backoff rate, maximum attempt count, an optional Action to perform when a retry is about to happen, and a Predicate<Exception> that is used to decide whether or not a retry should be performed based on the Exception that occurred during the last execution.
HttpClient client; // Assuming this is coming from an HttpClientFactory or injected or whatever
ILogger logger;
RetryOptions options = new (
3,
TimeSpan.FromMilliseconds(1000),
2,
(attemptCount, duration, exception) => logger.LogError(exception, $"Starting retry {attemptCount} after {duration.TotalMilliseconds} milliseconds"),
exception => exception is NullReferenceException ? true : false // Only NullReferenceExceptions will trigger retries, other exceptions will fall through
);
Task.FromResult(someExternalUrl)
.Retry(client.GetAsync, options)
If the RetryOptions parameter is not passed, the default values (3 attempts, 1000ms duration, backoff rate of 2) are used.
Static Methods
There are some convenience methods on TaskExtras that are useful when transitioning between the fulfilled and faulted states.
RejectIf
RejectIf can be used to transition into a faulted state based on some Predicate<T>.
Task.FromResult(1)
.Then(TaskExtras.RejectIf(
value => value % 2 == 0,
value => new Exception($"{nameof(value)} was not even")
));
ResolveIf
ResolveIf can be used to transition from a faulted state to a fulfilled state based on some Predicate<Exception>.
Task.FromException<int>(new ArgumentException())
.Catch(TaskExtras.ResolveIf(
exception => exception is ArgumentException,
exception => exception.Message.Length
))
InvokeIf
InvokeIf can be used to conditionally invoke a function. This is most useful for side effects.
Task.FromResult(someUrl)
.Then(httpClient.GetAsync)
.IfFulfilled(TaskExtras.InvokeIf(
httpResponse => !httpResponse.IsSuccessStatusCode,
httpResponse => _logger.LogWarning("Got '{StatusCode}' response from server", httpResponse.StatusCode)
);
However, it can be used with .Then as well:
Task.FromResult(4)
.Then(InvokeIf(
value => value % 2 == 0,
value => value + 1
);
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.20.0 | 885 | 7/8/2025 |
| 2.19.2 | 2,074 | 3/27/2025 |
| 2.19.0 | 308 | 3/6/2025 |
| 2.18.5 | 1,435 | 11/25/2024 |
| 2.18.4 | 189 | 11/22/2024 |
| 2.18.3 | 184 | 11/22/2024 |
| 2.18.2 | 195 | 11/22/2024 |
| 2.18.1 | 1,127 | 7/26/2024 |
| 2.16.1 | 2,106 | 4/20/2024 |
| 2.16.0 | 973 | 12/18/2023 |
| 2.15.0 | 2,312 | 11/1/2023 |
| 2.14.0 | 2,166 | 5/14/2023 |
| 2.13.0 | 590 | 5/12/2023 |
| 2.12.0 | 554 | 5/12/2023 |
| 2.11.0 | 9,414 | 3/12/2023 |
| 2.10.0 | 872 | 12/29/2022 |
| 2.9.0 | 916 | 9/22/2022 |
| 2.8.0 | 848 | 9/21/2022 |
| 2.7.0 | 1,137 | 6/18/2022 |
| 2.6.0 | 895 | 6/17/2022 |
| 2.5.0 | 879 | 6/16/2022 |
| 2.4.0 | 947 | 6/10/2022 |
| 2.3.0 | 891 | 6/9/2022 |
| 2.2.0 | 865 | 5/20/2022 |
| 2.1.2 | 890 | 5/17/2022 |
| 2.1.1 | 915 | 5/13/2022 |
| 2.1.0 | 854 | 5/13/2022 |
| 2.0.0 | 866 | 5/1/2022 |
| 1.0.0 | 879 | 4/26/2022 |
| 0.3.1 | 836 | 4/26/2022 |
| 0.3.0 | 852 | 4/26/2022 |
| 0.2.0 | 903 | 4/24/2022 |
| 0.1.1 | 879 | 4/18/2022 |
| 0.1.0 | 941 | 4/18/2022 |