QuickResult 2.2.0

dotnet add package QuickResult --version 2.2.0
                    
NuGet\Install-Package QuickResult -Version 2.2.0
                    
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="QuickResult" Version="2.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="QuickResult" Version="2.2.0" />
                    
Directory.Packages.props
<PackageReference Include="QuickResult" />
                    
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 QuickResult --version 2.2.0
                    
#r "nuget: QuickResult, 2.2.0"
                    
#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 QuickResult@2.2.0
                    
#: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=QuickResult&version=2.2.0
                    
Install as a Cake Addin
#tool nuget:?package=QuickResult&version=2.2.0
                    
Install as a Cake Tool

QuickResult

Lightweight Result<T> for C# with full LINQ query syntax support for sync, async, and mixed sync/async flows.

NuGet NuGet Downloads License: MIT


Why QuickResult?

Result<T> makes expected failures explicit and composable.

  • ✅ Avoid exceptions for normal control flow
  • ✅ Keep happy-path and error-path close together
  • ✅ Compose operations with Map, Bind, and LINQ queries
  • ✅ Works naturally with async workflows
  • ✅ Extensible error types via IError — use pattern matching (is, as, switch) on failures

Install

dotnet add package QuickResult

Quick Start

using QuickResult;

var result = Result.Success(10).Map(x => x * 2);
var message = result.Match(
    onSuccess: value => $"OK: {value}",
    onFailure: error => $"FAIL: {error}");

Custom Error Types

Implement IError to create domain-specific errors:

public class HttpResponseError : IError
{
    public string Message { get; }
    public int StatusCode { get; }

    public HttpResponseError(int statusCode, string message)
    {
        StatusCode = statusCode;
        Message    = message;
    }
}

// Use it directly:
var result = Result.Failure(new HttpResponseError(503, "Service unavailable"));

// Transform failures within a pipeline
var mapped = result.MapFailure(e => e switch
{
    HttpResponseError { StatusCode: 503 } => new Error("Try again later"),
    _                                     => e
});

// Or pattern match when consuming the error
if (result.IsFailure && result.Error is HttpResponseError http)
    Console.WriteLine($"HTTP {http.StatusCode}: {http.Message}");

Plain strings still work — they're implicitly wrapped in the built-in Error class:

var fail = Result<int>.Failure("Something went wrong"); // just works

Core API

Member Purpose
Result<T>.Success(value) Create a successful result
Result.Success() Create a successful Result<Unit>
Result<T>.Failure(error) Create a failed result (accepts IError or string)
Result.FromNullable(value, error) Convert nullable reference/value types into Result<T>
IsSuccess / IsFailure Check result state
Value Get success value (throws on failure)
Error Get IError (throws on success)
Match(...) Convert both branches to one value
MatchAsync(...) Async version of match (supports async branches)
ValueOr(...) Get value or fallback
Map(...) Transform success value
MapFailure(...) Transform failure error
Bind(...) Chain result-returning functions
Result.Try(...) Wrap sync Func<T> or Action in try/catch and return result
Result.TryAsync(...) Wrap async Func<Task<T>> or Func<Task> in try/catch and return result
left \| right Fallback to right if left failed
Result.From(...) / Result.FromAsync(...) Start a composable pipeline from sync/async value factories
pipeline.Try() Execute pipeline and convert thrown exceptions into Failure(...)
result.WhenNull(error) Convert successful nullable result into Failure(error) when null

Examples

1) Create success/failure

var ok = Result.Success(42);
var fail = Result.Failure("Something went wrong");

2) From nullable

string? name = GetNameOrNull();
var nameResult = Result.FromNullable(name, "Name was missing");

int? port = GetPortOrNull();
var portResult = Result.FromNullable(port, "Port was missing");

3) Try / TryAsync

var parsed = Result.Try(() => int.Parse("123")); // Success(123)
var failed = Result.Try(() => int.Parse("abc")); // Failure("...")

var sideEffect = Result.Try(() => Console.WriteLine("done")); // Result

var loaded = await Result.TryAsync(async () => { await Task.Delay(10); return "done"; }); // Success("done")

var ping = await Result.TryAsync(async () => { await Task.Delay(10); }); // Result

4.1) Match both paths

string text = ok.Match(
    onSuccess: v => $"Value: {v}",
    onFailure: e => $"Error: {e.Message}");

4.2) Match async branches (Result<T> source)

var text = await ok.MatchAsync(
    onSuccess: v => Task.FromResult("Value: {v}"),
    onFailure: e => Task.FromResult("Error: {e.Message}"));

4.3) Match directly on Task<Result<T>> (fluent)

var text = await GetSuccessAsync(11).MatchAsync(
    onSuccess: v => "success: {v}",
    onFailure: e => "failure: {e.Message}");

5.1) Map

var length = Result.Success("hello").Map(s => s.Length); // Success(5)

5.2) Map failure

var mappedError = Result.Failure("boom").MapFailure(e => new Error( $"wrapped: {e.Message}")); // Failure("wrapped: boom")

6) ValueOr

var value1 = Result.Success(10).ValueOr(0); // 10
var value2 = Result.Failure("bad").ValueOr(0); // 0
var value3 = Result.Failure("bad").ValueOr(e => e.Message.Length); // 3

7) Bind

Result ParsePositiveInt(string input) =>
    int.TryParse(input, out var n) && n > 0
        ? Result.Success(n)
        : Result.Failure("Input must be a positive integer");

var parsed = Result.Success("25").Bind(ParsePositiveInt);

8) Fallback with |

var chosen = Result.Failure("primary failed") | Result.Success(10); // Success(10)

9.1) Pipeline start + Try + WhenNull

var userResult = await Result.FromAsync(() => repository.GetUserByIdAsync(userId, ct))
                             .Try()
                             .WhenNull("User not found");

// Works with Task<T>
var p1 = Result.FromAsync(() => SomeCallAsync());

// Also works with ConfigureAwait(...)
var p2 = Result.FromAsync(() => SomeCallAsync().ConfigureAwait(false));

9.2) LINQ query with pipelines

var query = from val1 in Result.FromAsync(() => service.GetValue1Async(ct))
                               .Try()
                               .WhenNull("Value1 was null")
            from val2 in Result.FromAsync(() => service.GetValue2Async(ct))
                               .Try()
                               .WhenNull("Value2 was null")
            select val1 + val2;

var result = await query;

Boolean guards in LINQ queries

When your query includes a boolean step (for example, "does user own documents?"), you can short-circuit with domain-specific errors using:

  • FailIfTrue(error)
  • FailIfFalse(error)

These methods work on Result<bool> and Task<Result<bool>>, so they compose cleanly in LINQ query syntax.

Task<Result> CheckAsync() =>
    Task.FromResult(Result.Success(true));

Task NextAsync() =>
    Task.FromResult(Result.Success(42));

var query = from ok in CheckAsync().FailIfFalse("Precondition failed")
            from value in NextAsync()
            select value * 2;

var result = await query; // Success(84)

Why not use where?

C# query where only accepts a boolean predicate and cannot carry a domain error message. For Result pipelines, explicit boolean guards (FailIfTrue / FailIfFalse) keep error handling clear and intentional.


LINQ Support

Sync query

var query = from a in Result.Success(10)
            from b in Result.Success(5)
            select a + b; // Success(15)

Async query

static Task<Result> GetAsync(int n) =>
    Task.FromResult(Result.Success(n));

var query = from a in GetAsync(4)
            from b in GetAsync(5)
            select a * b;

var result = await query; // Success(20)

Async query + fluent MatchAsync

var text = await (from left in GetAsync(11)
                  from right in GetAsync(31)
                  select left + right)
                 .MatchAsync(
                      onSuccess: v => "success: {v}",
                      onFailure: e => "failure: {e.Message}");

Mixed sync/async query

var query = from a in Result.Success(4) // sync
            from b in GetAsync(6)       // async
            from c in Result.Success(2) // sync
            select a + b + c;

var result = await query; // Success(12)

Behavior Notes

  • Value throws InvalidOperationException when result is failure.
  • Error throws InvalidOperationException when result is success.
  • Failure(error) throws ArgumentException if error message is null/empty/whitespace.
  • Failure(string) implicitly wraps the string in the default Error type.
  • Result.FromNullable(value, error) returns Failure(error) when the nullable input is null.
  • Result.Try(...) and Result.TryAsync(...) convert thrown exceptions into Failure(...) using the exception message.
  • Try/TryAsync support both value-returning and no-value (Unit) operations.
  • If an exception message is null/whitespace, Try/TryAsync use the exception type name as the failure message.
  • Failure short-circuits through Map, Bind, and LINQ query chains.
  • Result.From/FromAsync create deferred pipelines. Pair with .Try() to convert thrown exceptions into failures.
  • WhenNull(error) converts a successful nullable result into Failure(error) when the value is null.

Target Frameworks

  • .NET 8+
  • C# 12+

License

MIT

Product Compatible and additional computed target framework versions.
.NET 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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.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.2.0 93 7/24/2026
2.1.0 96 7/24/2026
2.0.0 157 3/29/2026
1.8.0 133 3/13/2026
1.7.0 127 3/13/2026
1.6.0 131 3/13/2026
1.5.0 130 3/13/2026
1.4.0 130 3/12/2026
1.3.0 136 3/12/2026
1.2.0 129 3/12/2026
1.1.0 140 3/12/2026
1.0.0 130 3/12/2026