ForgeSharp.Results
2.2.1
dotnet add package ForgeSharp.Results --version 2.2.1
NuGet\Install-Package ForgeSharp.Results -Version 2.2.1
<PackageReference Include="ForgeSharp.Results" Version="2.2.1" />
<PackageVersion Include="ForgeSharp.Results" Version="2.2.1" />
<PackageReference Include="ForgeSharp.Results" />
paket add ForgeSharp.Results --version 2.2.1
#r "nuget: ForgeSharp.Results, 2.2.1"
#:package ForgeSharp.Results@2.2.1
#addin nuget:?package=ForgeSharp.Results&version=2.2.1
#tool nuget:?package=ForgeSharp.Results&version=2.2.1
ForgeSharp.Results
A monadic Result type for .NET that replaces exceptions as flow control with explicit, composable success/failure values. All core types are readonly struct — zero heap allocations, zero GC pressure.
| Target | Supported |
|---|---|
| .NET 10 | Yes |
| .NET 8 | Yes |
| .NET Standard 2.1 | Yes |
| .NET Standard 2.0 | Yes |
Install
dotnet add package ForgeSharp.Results
Get started
Return a Result from any operation. Check IsSuccess to branch on the outcome — no try/catch required.
Result<User> GetUser(int id)
{
var user = db.Find(id);
return user is not null
? Result.Ok(user)
: Result.Fail<User>("User not found.");
}
var result = GetUser(42);
if (result.IsSuccess)
Console.WriteLine(result.Value.Name);
else
Console.WriteLine(result.Message);
Chain operations with Map and Bind. Failures propagate automatically — no manual error checking between steps.
var result = GetUser(42)
.Bind(user => Validate(user))
.Map(user => user.Profile);
Wrap code that throws into a Result with Capture:
Result result = Result.Capture(() => RiskyOperation());
Result<int> parsed = Result.Capture(() => int.Parse(input));
// Async
Result<Data> data = await Result.CaptureAsync(() => httpClient.GetFromJsonAsync<Data>(url));
Core types
| Type | Description |
|---|---|
Result |
Non-generic success/failure. |
Result<T> |
Carries a T value on success. |
Result<TValue, TError> |
Discriminated result with a custom error type. |
Options<T> |
Optional value wrapper (Some / None). |
EnumerableResult / EnumerableResult<T> |
Aggregated batch results with counts, ratios, and filtering. |
Results distinguish three states: success, validation fault (with a message), and exception (with a captured Exception). The IsSuccess, IsValidationFault, and IsException properties let you branch accordingly.
Pipelines
Pipelines compose result-producing operations into discrete, reusable units. They come in sync (IPipeline<T>) and async (IAsyncPipeline<T>) variants and support LINQ query syntax.
var pipeline =
from user in GetUserPipeline()
where user.IsActive
from profile in GetProfilePipeline(user.Id)
select (user, profile);
Result<(User user, Profile profile)> result = pipeline.Execute();
Pipelines also support retry and timeout policies:
var resilient = Pipeline.Create(() => CallExternalService())
.Retry(3, TimeSpan.FromSeconds(1))
.Timeout(TimeSpan.FromSeconds(10));
Result result = resilient.Execute();
Use Repeat to execute a pipeline multiple times and collect the results:
EnumerableResult batch = pipeline.Repeat(100).ToEnumerableResult();
Console.WriteLine($"{batch.SuccessPercentage}% succeeded");
Extension methods
Every extension ships with an async counterpart (e.g. Map / MapAsync).
Chaining
| Method | Purpose |
|---|---|
Map |
Transform the value if successful. |
Bind |
FlatMap — chain an operation that itself returns a Result. |
Select |
Project the value to a new type. |
Flatten |
Unwrap a nested Result<Result<T>>. |
var name = GetUser(42)
.Bind(u => LoadSettings(u.Id))
.Map(s => s.DisplayName)
.GetOrDefault("Unknown");
Side effects
| Method | Purpose |
|---|---|
Tap |
Execute an action on success without changing the result. |
TapError |
Execute an action on failure without changing the result — great for logging. |
Restore |
Recover from a failure with a fallback function. |
var result = GetUser(42)
.Tap(user => Log.Info($"Found {user.Name}"))
.TapError(err => Log.Warn($"Lookup failed: {err.Message}"));
Error transformation
| Method | Purpose |
|---|---|
MapError |
Transform the error of a Result<TValue, TError> while preserving the value. |
Result<User, ApiError> apiResult = CallApi();
Result<User, string> friendly = apiResult.MapError(e => e.UserMessage);
Value extraction
| Method | Purpose |
|---|---|
GetOrDefault |
Returns the value, or a fallback if failed. |
GetOrThrow |
Returns the value, or re-throws the captured exception. |
EnsureNotNull |
Converts a Result<T?> to Result<T>, failing on null. |
AsResult |
Drops the value, converting Result<T> to Result. |
TryGetValue |
Try-pattern extraction (bool + out T). |
ToOption |
Converts a result to Options<T> — Some on success, None on failure. |
Collections
| Method | Purpose |
|---|---|
TryGetValueResult |
Dictionary lookup that returns Result<T> instead of bool. |
FirstOrResult |
First matching element, or a failure with a message. |
ResolveAsync |
Awaits a collection of Task<Result<T>> into aggregated results. |
FilterValues / FilterExceptions |
Extract successes or failures from an EnumerableResult<T>. |
AggregateValidation |
Combines all validation messages into a single result. |
ExtractValuesOrDefault |
All values from a batch, substituting a default for failures. |
Options interop
Convert freely between Options<T> and Result<T>:
// Result → Option
Options<User> maybeUser = GetUser(42).ToOption();
// Option → Result
Result<User> result = maybeUser.ToResult("No user found.");
// With a discriminated error type
Result<User, MyError> typed = maybeUser.ToResult(new MyError("missing"));
I/O integration
File, directory, and HTTP operations are wrapped as Result-returning extension methods so you never need a try/catch around I/O.
File & directory
Result<FileStream> stream = new FileInfo("data.bin").OpenReadAsResult();
Result deleted = File.DeleteAsResult("temp.log");
Result<DirectoryInfo> dir = Directory.CreateAsResult(@"C:\App\Logs");
Result removed = new DirectoryInfo(@"C:\App\Old").DeleteAsResult();
HttpClient
Every HTTP verb has an AsResult variant:
Result<HttpResponseMessage> response = await httpClient.GetAsResultAsync("https://api.example.com/items");
Result<Item[]> items = await httpClient
.GetAsResultAsync("https://api.example.com/items")
.ThenEnsureSuccessStatusCodeAsync(
success: r => r.Content.ReadFromJsonAsync<Item[]>(),
failure: r => $"API returned {r.StatusCode}");
API reference
| Area | Key types / methods |
|---|---|
| Core | Result, Result<T>, Result<TValue, TError>, Options<T> |
| Pipelines | IPipeline<T>, IAsyncPipeline<T>, Pipeline.Create(...) |
| Monad | Map, MapError, Bind, Select, Flatten, Tap, TapError, Restore, Retry, Timeout, Repeat |
| Extraction | GetOrDefault, GetOrThrow, EnsureNotNull, AsResult, TryGetValue, ToOption, ToResult |
| Collections | EnumerableResult<T>, TryGetValueResult, FirstOrResult, ResolveAsync, AggregateValidation |
| Integration | OpenReadAsResult, DeleteAsResult, CreateAsResult, SendAsResultAsync, ThenEnsureSuccessStatusCode |
| LINQ | from/where/select query syntax on pipelines |
Contributing
Contributions are welcome. Open an issue or submit a pull request on GitHub.
License
| 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 is compatible. 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 is compatible. 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 is compatible. |
| .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.
-
.NETStandard 2.1
- No dependencies.
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on ForgeSharp.Results:
| Package | Downloads |
|---|---|
|
ForgeSharp.Results.AspNetCore
AspNetCore Integration for ForgeSharp.Results. |
GitHub repositories
This package is not used by any popular GitHub repositories.