Spirantis.Result
10.0.0
dotnet add package Spirantis.Result --version 10.0.0
NuGet\Install-Package Spirantis.Result -Version 10.0.0
<PackageReference Include="Spirantis.Result" Version="10.0.0" />
<PackageVersion Include="Spirantis.Result" Version="10.0.0" />
<PackageReference Include="Spirantis.Result" />
paket add Spirantis.Result --version 10.0.0
#r "nuget: Spirantis.Result, 10.0.0"
#:package Spirantis.Result@10.0.0
#addin nuget:?package=Spirantis.Result&version=10.0.0
#tool nuget:?package=Spirantis.Result&version=10.0.0
Spirantis.Result
A small, dependency-free result type for C#. Model the outcome of an operation as an explicit
success or failure instead of throwing, with a lightweight functional API (Match, Map,
Bind, Tap) for composing the happy path.
Result— success/failure with an optionalError.Result<T>— success carries a value of typeT; failure carries anError.Error— a failure reason: aMessageand an optional wrappedException.
Targets .NET 10. No runtime dependencies. MIT licensed.
Install
dotnet add package Spirantis.Result
Quick start
using Spirantis.Result;
Result<int> Parse(string input) =>
int.TryParse(input, out var n)
? n // implicit: value -> success
: new Error("not a number"); // implicit: error -> failure
string message = Parse("41")
.Map(n => n + 1) // 42, still success
.Match(
onSuccess: n => $"got {n}",
onFailure: e => $"failed: {e?.Message}");
// "got 42"
Creating results
Use the implicit conversions or the static factories — whichever reads better.
// Result (no value)
Result ok = Result.Success();
Result bad = Result.Fail("disk full");
Result wrap = new InvalidOperationException("boom"); // implicit -> failure
// Result<T>
Result<int> value = 42; // implicit -> success
Result<int> error = new Error("nope"); // implicit -> failure
Result<int> viaFactory = Result.Fail<int>("nope");
Result<User> user = Result.Success(new User());
Note: on
Result<T>, onlyT(success) andError(failure) convert implicitly. Unlike the non-genericResult, there is intentionally no implicitstring/Exceptionconversion — it would be ambiguous forResult<string>/Result<Exception>. UseResult.Fail<T>(...)for those.
Inspecting results
if (result.IsSuccess) { /* ... */ }
if (result.IsFailure) { /* ... */ }
// Result<T>: TryGetValue narrows nullability for you
if (result.TryGetValue(out var value))
Console.WriteLine(value); // value is non-null here
if (!result.TryGetValue(out var value, out var error))
Console.WriteLine(error?.Message);
int safe = result.GetValueOr(-1);
int? maybe = result.GetValueOrDefault();
IsSuccess on Result<T> is annotated with [MemberNotNullWhen(true, nameof(Value))], so the
compiler knows Value is non-null inside an if (result.IsSuccess) branch.
Composing
| Method | On | Behaviour |
|---|---|---|
Match |
Result, Result<T> |
Fold to a single value via onSuccess / onFailure. |
Map |
Result<T> |
Transform the value on success; propagate the failure otherwise. |
Bind |
Result<T> |
Chain another Result<T>-producing step; short-circuits on failure. |
Tap |
Result<T> |
Run a side effect on the value when successful; returns the same result. |
OnSuccess / OnFailure |
Result |
Run a side effect for the matching branch; returns the same result. |
Result<string> outcome = LoadUser(id) // Result<User>
.Tap(u => logger.LogInformation("loaded {Id}", u.Id))
.Bind(u => Authorize(u)) // Result<Session>
.Map(s => s.Token); // Result<string>
The
onFailuredelegate receivesError?(nullable): a failure produced byResult.Fail()has no associated error.
Errors
var fromMessage = new Error("validation failed");
var fromException = new Error(ex); // Message defaults to ex.Message
var withBoth = new Error(ex, "friendly text");
Error implicitMsg = "shorthand"; // implicit
Error is immutable and has value equality: two errors are equal when their Message matches and
they reference the same Exception instance (exceptions compare by reference).
Equality
Result, Result<T>, and Error all implement value equality (Equals / GetHashCode), which
makes them easy to assert in tests:
Assert.Equal(Result.Success(42), Result.Success(42));
Assert.Equal(Result.Fail<int>("x"), Result.Fail<int>("x"));
Results of different runtime types are never equal (e.g. Result<int> ≠ Result<long>).
Extending
Result / Result<T> are open for inheritance — derive when you need extra state. Override the
protected EqualsCore hook to fold that state into equality:
public class FunctionResult : Result<object>
{
public FunctionResult(FunctionResultType type, object value) : base(value) => Type = type;
public FunctionResultType Type { get; init; }
protected override bool EqualsCore(Result other) =>
base.EqualsCore(other) && Type == ((FunctionResult)other).Type;
}
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Spirantis.Result:
| Package | Downloads |
|---|---|
|
Spirantis.Sequencing.Abstraction
Abstractions (ISequenceFunction, FunctionResult, ISequenceContext) for Spirantis.Sequencing — a fluent engine for composing asynchronous, branching workflows. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0 | 135 | 6/8/2026 |