Aigamo.Results 0.0.0-preview005

Prefix Reserved
This is a prerelease version of Aigamo.Results.
dotnet add package Aigamo.Results --version 0.0.0-preview005
                    
NuGet\Install-Package Aigamo.Results -Version 0.0.0-preview005
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Aigamo.Results" Version="0.0.0-preview005" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Aigamo.Results" Version="0.0.0-preview005" />
                    
Directory.Packages.props
<PackageReference Include="Aigamo.Results" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Aigamo.Results --version 0.0.0-preview005
                    
#r "nuget: Aigamo.Results, 0.0.0-preview005"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Aigamo.Results@0.0.0-preview005
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Aigamo.Results&version=0.0.0-preview005&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Aigamo.Results&version=0.0.0-preview005&prerelease
                    
Install as a Cake Tool

Aigamo.Results

A fully-serializable, F#-like result pattern for C#.

NuGet

Aigamo.Results models success and failure as a single Result<T, TError> value — a closed record with exactly two cases, Ok and Error — and gives you a full set of functional operations (Map, FlatMap, Fold, Combine, …) to transform and compose results without ever throwing. Unlike many result types, it round-trips cleanly through System.Text.Json, so a Result can cross a serialization boundary — an API response, a message queue, a cache — and come back intact.

Features

  • Result<T, TError> with a strongly-typed error channel (not just a message or an exception)
  • A closed hierarchy (Ok / Error) — the compiler knows there are no other cases
  • Fully serializable with System.Text.Json (polymorphic, round-trips by value)
  • Rich functional API: Map, MapError, FlatMap, FlatMapError, Fold, Tap, GetOr, Combine, Merge, MapEach, Flatten, Validate, Contains, and more
  • Async all the way: every operation has Task-lifted overloads, so sync and async steps chain seamlessly
  • Unit for value-less results (Result<Unit, TError>)
  • Implicit conversions: return a value or an error directly and let it become a Result<T, TError>
  • An optional analyzer to enforce implicit- or explicit-construction style across a project

Getting Started

1. Install the package

dotnet add package Aigamo.Results

Tip: To share the reference and drop the per-file using, add it to Directory.Build.props with a global Using:

<Project>

  <ItemGroup>
    <PackageReference Include="Aigamo.Results" />
    <Using Include="Aigamo.Results" />
  </ItemGroup>

</Project>

2. Create results

using Aigamo.Results;

Result<int, string> ok = Result.Ok<int, string>(42);
Result<int, string> error = Result.Error<int, string>("something went wrong");

A value or an error also converts implicitly, so you can often drop the factory call:

Result<int, string> ok = 42;                    // -> Ok(42)
Result<int, string> error = "something went wrong"; // -> Error("something went wrong")

A function that can fail returns its outcome instead of throwing:

static Result<int, string> Parse(string s) =>
	int.TryParse(s, out var value)
		? value                                 // implicit -> Ok(value)
		: $"'{s}' is not a number";             // implicit -> Error(...)

Note: implicit conversion is unavailable when T and TError are the same type (Result<string, string>) — the two conversions are ambiguous (CS0457). Use the explicit Result.Ok / Result.Error factories there.

3. Inspect the outcome

Fold collapses both cases into a single value:

string message = Parse("42").Fold(
	onOk: value => $"Parsed {value}",
	onError: error => $"Failed: {error}"
);

Match gives you the case objects directly, and helpers like GetOr, Contains, and Validate cover common checks:

int value = Parse("nope").GetOr(ifError: _ => 0); // 0
bool isFortyTwo = Parse("42").Contains(42);         // true
bool isPositive = Parse("42").Validate(x => x > 0); // true

Transforming and composing

// Map the success value; Error passes through untouched.
Result<string, string> label = Parse("42").Map(x => $"#{x}");

// FlatMap chains another fallible step, short-circuiting on the first Error.
Result<int, string> checked = Parse("42").FlatMap(x =>
	x >= 0 ? Result.Ok<int, string>(x) : Result.Error<int, string>("negative"));

// MapError transforms the error channel.
Result<int, MyError> typed = Parse("42").MapError(msg => new MyError(msg));

// Tap runs a side effect on success without changing the result.
Parse("42").Tap(x => Console.WriteLine(x));

Combine several results into a tuple (the first Error wins), or collapse a sequence:

// Combine: accumulate success values into a growing tuple.
Result<(int, int, int), string> combined = Parse("1")
	.Combine(_ => Parse("2"))
	.Combine(_ => Parse("3"));

// Merge: IEnumerable<Result<T, E>> -> Result<T[], E>
Result<int[], string> merged = new[] { "1", "2", "3" }.Select(Parse).Merge();

Async

Every operation ships Task-lifted overloads, so you can freely mix synchronous and asynchronous steps in one chain — the Task is threaded through for you:

Result<User, string> result = await LoadUserAsync(id)   // Task<Result<User, string>>
	.FlatMap(user => ValidateAsync(user))               // async step
	.Map(user => user.Name)                             // sync step
	.TapError(error => logger.LogWarning(error));       // async source, sync action

For each operation you get the overloads for a Task source, an async selector, and both at once.

Serialization

Result<T, TError> is annotated for System.Text.Json polymorphic serialization and round-trips by value:

using System.Text.Json;

var ok = Result.Ok<int, string>(42);
string json = JsonSerializer.Serialize(ok);
// {"$type":"Ok","ResultValue":42}

var back = JsonSerializer.Deserialize<Result<int, string>>(json);
// back == ok  (true)

An Error serializes as {"$type":"Error","ErrorValue":...}. No custom converter or configuration required.

Enforcing a conversion style

Whether to write Result.Ok(value) explicitly or lean on the implicit conversion is a matter of taste — so the package ships an analyzer that lets a project pick one and enforce it. Set the policy in .editorconfig:

[*.cs]
# choose one:
aigamo_results_conversion_style = implicit   # or: explicit
  • implicit → ARS001 flags an explicit Result.Ok / Result.Error call where the value could convert implicitly, and the code fix removes the wrapper.
  • explicit → ARS002 flags reliance on the implicit conversion, and the code fix wraps the value in Result.Ok / Result.Error.

When the key is unset, neither rule fires. Both default to warning severity and can be tuned per rule:

dotnet_diagnostic.ARS001.severity = suggestion

ARS001 is never reported for Result<T, T>, where implicit conversion is ambiguous.

Under the hood

Result<T, TError> is a closed record whose Ok/Error cases are nested with a private constructor, so the hierarchy cannot be extended elsewhere. The exhaustive Match method is generated by Aigamo.MatchGenerator, and the Task-lifted overloads are emitted by an internal [GenerateAsyncOverloads] source generator — the hand-written code only defines the synchronous core of each operation.

Inspiration

Aigamo.Results is inspired by Nut.Results. The goal here is a result type that keeps a strongly-typed error channel and serializes cleanly, with the functional surface generated rather than hand-maintained.

License

MIT

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Aigamo.Results:

Package Downloads
Aigamo.Otsuki

DirectPlay 8 peer-to-peer implementation built on actors.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.0-preview005 150 9/23/2026
0.0.0-preview004 74 9/22/2026
0.0.0-preview003 87 9/17/2026
0.0.0-preview002 67 9/16/2026
0.0.0-preview001 67 9/16/2026