LightResults 9.0.0

Prefix Reserved
dotnet add package LightResults --version 9.0.0                
NuGet\Install-Package LightResults -Version 9.0.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="LightResults" Version="9.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add LightResults --version 9.0.0                
#r "nuget: LightResults, 9.0.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.
// Install LightResults as a Cake Addin
#addin nuget:?package=LightResults&version=9.0.0

// Install LightResults as a Cake Tool
#tool nuget:?package=LightResults&version=9.0.0                

Banner

LightResults - Operation Result Patterns for .NET

LightResults is an extremely light and modern .NET library that provides a simple and flexible implementation of the Result Pattern. The Result Pattern is a way of representing the outcome of an operation, whether it's successful or has encountered an error, in a more explicit and structured manner. This project is heavily inspired by Michael Altmann's excellent work with FluentResults.

main nuget downloads

References

This library targets .NET Standard 2.0, .NET 6.0, .NET 7.0, .NET 8.0, and .NET 9.0.

Dependencies

This library has no dependencies.

Advantages of this library

  • ๐Ÿชถ Lightweight โ€” Only contains what's necessary to implement the Result Pattern.
  • โš™๏ธ Extensible โ€” Simple interfaces and base classes make it easy to adapt.
  • ๐Ÿงฑ Immutable โ€” Results and errors are immutable and cannot be changed after being created.
  • ๐Ÿงต Thread-safe โ€” The Error list and Metadata dictionary use Immutable classes for thread-safety.
  • โœจ Modern โ€” Built against the latest version of .NET using the most recent best practices.
  • ๐Ÿงช Native โ€” Written, compiled, and tested against the latest versions of .NET.
  • โค๏ธ Compatible โ€” Available for dozens of versions of .NET as a .NET Standard 2.0 library.
  • ๐Ÿชš Trimmable โ€” Compatible with ahead-of-time compilation (AOT) as of .NET 7.0.
  • ๐Ÿš€ Performant โ€” Heavily optimized and benchmarked to aim for the highest possible performance.

Extensions

Several extensions are available to simplify implementation that use LightResults.

Documentation

Make sure to read the docs for the full API.

Getting Started

LightResults consists of only three classes Result, Result<TValue>, and Error.

  • The Result class represents a generic result indicating success or failure.
  • The Result<TValue> class represents a success or failure result with a value.
  • The Error class represents an error with a message and optional associated metadata.

Creating a successful result

Successful results can be created using the Success method.

var successResult = Result.Success();

var successResultWithValue = Result.Success(349.4);

Creating a failure result

Failed results can be created using the Failure method.

var failureResult = Result.Failure();

var failureResultWithMessage = Result.Failure("Operation failure!");

var failureResultWithMessageAndMetadata = Result.Failure("Operation failure!", ("UserId", userId));

var failureResultWithMessageAndException = Result.Failure("Operation failure!", ex);

Checking the state of a result

There are two methods used to check a result, IsSuccess() and IsFailed(). Both of which have several overloads to obtain the value and error.

if (result.IsSuccess())
{
    // The result is successful.
}

if (result.IsFailure(out var error))
{
    // The result is failure.
    if (error.Message.Length > 0)
        Console.WriteLine(error.Message);
    else
        Console.WriteLine("An unknown error occured!");
}

Getting the value

The value from a successful result can be retrieved through the out parameter of the Success() method.

if (result.IsSuccess(out var value))
{
    Console.WriteLine($"Value is {value}");
}

Creating errors

Errors can be created with or without a message.

var errorWithoutMessage = new Error();

var errorWithMessage = new Error("Something went wrong!");

Or with a message and metadata.

var errorWithMetadataTuple = new Error("Something went wrong!", ("Key", "Value"));

var metadata = new Dictionary<string, object> { { "Key", "Value" } };
var errorWithMetadataDictionary = new Error("Something went wrong!", metadata);

Custom errors

The best way to represent specific errors is to make custom error classes that inherit from Error and define the error message as a base constructor parameter.

public sealed class NotFoundError : Error
{
    public NotFoundError()
        : base("The resource cannot be found.")
    {
    }
}

var notFoundError = new NotFoundError();
var notFoundResult = Result.Failure(notFoundError);

Then the result can be checked against that error type.

if (result.IsFailure(out var error) && error is NotFoundError)
{
    // Looks like the resource was not found, we better do something about that!
}

Or checked to see if there are any errors of that type.

if (result.IsFailure() && result.HasError<NotFoundError>())
{
    // At least one of the errors was a NotFoundError.
}

This can be especially useful when combined with metadata that is related to a specific type of error.

public sealed class HttpError : Error
{
    public HttpError(HttpStatusCode statusCode)
        : base("An HTTP error occured.", ("StatusCode", statusCode))
    {
    }
}

We can further simplify creating errors by creating an error factory.

public static AppError
{
    public Result NotFound()
    {
        var notFoundError = new NotFoundError();
        return Result.Failure(notFoundError);
    }

    public Result HttpError(HttpStatusCode statusCode)
    {
        var httpError = new HttpError(statusCode)
        return Result.Failure(httpError);
    }
}

Which clearly and explicitly describes the results.

public Result GetPerson(int id)
{
    var person = _database.GetPerson(id);
    
    if (person is null)
        return AppError.NotFound();
    
    return Result.Success();
}

Handling Exceptions

Specific overloads have been added to Failure() and Failure<TValue>() to simplify using try-catch blocks and return from them with a result instead of throwing.

public Result DoSomeWork()
{
    try
    {
        // We must not throw an exception in this method!
    }
    catch(Exception ex)
    {
        return Result.Failure(ex);
    }
    
    return Result.Success();
}

Static abstract members in interfaces

Note: Applies to .NET 7.0 (C# 11.0) or higher only!

Thanks to the static abstract members in interfaces introduced in .NET 7.0 (C# 11.0), it is possible to use generics to obtain access to the methods of the generic variant of the result. As such the error factory can be enhanced to take advantage of that.

public static AppError
{
    public Result NotFound()
    {
        var notFoundError = new NotFoundError();
        return Result.Failure(notFoundError);
    }
    
    public TResult NotFound<TResult>()
    {
        var notFoundError = new NotFoundError(); 
        return TResult.Failure(notFoundError);
    }
}

What's new in v9.0

The API for LightResults was completely redesigned for v9.0 to improve performance and remove any potential pits of failure caused by the prior version's use of properties that could result in exceptions being thrown when values were accessed without checking the state of the result. Thus, there are several breaking changes, detailed below, that developers must be aware of when upgrading from v8.0 to v9.0.

Breaking changes between v8.0 and v9.0

  • Factory methods for creating generic results have changed names.
    • Result.Ok() has been renamed to Result.Success().
    • Result.Fail() has been renamed to Result.Failure().
  • Factory methods for creating results with values have changed names and type to allow omitting the type when it is known.
    • Result<TValue>.Ok() has been renamed and moved to Result.Success<TValue>().
    • Result<TValue>.Fail() has been renamed to Result.Failure<TValue>().
  • The Value and Error properties have been removed.
    • result.Value has been replaced by result.IsSuccess(out var value).
    • result.Error has been replaced by result.IsError(out var error).
  • Several constructors of the Error type have been removed or have changed.
    • Error((string Key, object Value) metadata) has been removed.
    • Error(IDictionary<string, object> metadata) has been removed.
    • Error(string message, IDictionary<string, object> metadata) has been changed to Error(string message, IReadOnlyDictionary<string, object> metadata).

Migrating

The following steps in the following order will reduce the amount of manual work required to migrate and refactor code to use the new API.

  1. Find and replace all instances of Result.Ok( with Result.Success(.
  2. Find and replace all instances of Result.Fail( with Result.Failure(.
  3. Regex find and replace all instances of Result(<[^>]+>)\.Ok\( with Result.Success$1(.
  4. Regex find and replace all instances of Result(<[^>]+>)\.Fail\( with Result.Failure$1(.
  5. Find and replace all instances of .IsSuccess with IsSuccess(out var value).
  6. Find and replace all instances of .IsFailed with IsFailure(out var error).
  7. Find instances of result.Value and refactor them to the use the value exposed by the IsSuccess() method.
  8. Find instances of result.Error and refactor them to the use the error exposed by the IsFailure() method.

New method overloads and property initializers

  • New overloads have been added for KeyValuePair<string, object> metadata.
    • Result.Failure(string errorMessage, KeyValuePair<string, object> metadata) has been added.
    • Result.Failure<TValue>(string errorMessage, KeyValuePair<string, object> metadata) has been added.
  • New overloads have been added to simplify handling exceptions.
    • Result.Failure(Exception ex) has been added.
    • Result.Failure(string errorMessage, Exception ex) has been added.
    • Result.Failure<TValue>(Exception ex) has been added.
    • Result.Failure<TValue>(string errorMessage, Exception ex) has been added.
  • New overloads where added to access the value.
    • result.IsSuccess(out TValue value) has been added.
    • result.IsFailure(out IError error, out TValue value) has been added.
  • New overloads where added to access the first error.
    • result.IsFailure(out IError error) has been added.
    • result.IsSuccess(out TValue value, out IError error) has been added.
    • result.HasError<TError>(out IError error) has been added.
  • New property initializers where added to Error.
    • Message { get; } has changed to Message { get; init; }.
    • Metadata { get; } has changed to Metadata { get; init; }.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  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 is compatible.  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 is compatible. 
.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 (7)

Showing the top 5 NuGet packages that depend on LightResults:

Package Downloads
LightResults.Extensions.ExceptionHandling

Exception handling for LightResults.

McdaToolkit

Package Description

LightResults.Extensions.Json

System.Text.Json converters for LightResults.

LightResults.Extensions.ValueObjects

Value Objects using LightResults.

LightResults.Extensions.EntityFrameworkCore

EntityFrameworkCore with LightResults.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on LightResults:

Repository Stars
jscarle/LightResults
An extremely light and modern Operation Result Pattern library for .NET.
Version Downloads Last updated
9.0.0 127 12/28/2024
9.0.0-preview.16 67 12/21/2024
9.0.0-preview.15 10,506 7/5/2024
9.0.0-preview.14 55 7/5/2024
9.0.0-preview.13 53 7/5/2024
9.0.0-preview.12 45 7/5/2024
9.0.0-preview.11 135 6/26/2024
9.0.0-preview.10 150 4/5/2024
9.0.0-preview.9 67 4/4/2024
9.0.0-preview.8 80 3/30/2024
9.0.0-preview.7 102 3/20/2024
9.0.0-preview.6 286 3/20/2024
9.0.0-preview.5 67 3/20/2024
9.0.0-preview.4 76 3/19/2024
9.0.0-preview.3 65 3/19/2024
9.0.0-preview.2 77 3/17/2024
9.0.0-preview.1 71 3/5/2024
8.0.11 2,453 9/14/2024
8.0.10 4,956 6/26/2024
8.0.9 9,540 2/27/2024
8.0.8 177 2/20/2024
8.0.7 127 2/20/2024
8.0.6 149 2/18/2024
8.0.5 128 2/15/2024
8.0.4 134 2/14/2024
8.0.3 272 1/19/2024
8.0.2 141 1/19/2024
8.0.1 136 1/17/2024
8.0.0 149 1/17/2024