ResultExtensions 3.1.0

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

// Install ResultExtensions as a Cake Tool
#tool nuget:?package=ResultExtensions&version=3.1.0

Build Workflow Status ResultExtensions Nuget Package ResultExtensions.AspNetCore Nuget Package ResultExtensions.FluentAssertions Nuget Package

Result Extensions

A extremely simple library providing a discriminated union type powered with fluent extensions for .NET.

Introduction

The Result pattern is a functional programming approach used to represent the outcome of a computation that can either succeed with a value or fail with one or more errors. This can help make error handling more explicit and prevent unexpected runtime errors. This project offers a simple discriminated union type for .NET, powered with fluent extension functions and support for ASP.NET Core's IResult and IActionResult response objects.

What issue is this project attempting to address?

When talking about error handling in .NET, the first thing that comes to mind is exceptions. Exceptions are a powerful mechanism for handling errors. When an exception is thrown, the method's execution is interrupted, and the runtime searches for a catch block that can handle the exception. This approach, known as the fast-fail principle, proves beneficial in numerous scenarios. However, exceptions have some drawbacks:

  • Performance: Throwing and catching exceptions can incur a significant performance cost, as it involves stack unwinding, context switching, and memory allocation. This can be problematic in performance-critical scenarios.

  • Complexity: Exceptions can disrupt the normal flow of control in your application, making the code harder to read and understand. They can lead to non-linear code execution paths, which may complicate debugging and maintenance.

  • Overuse: Over-reliance on exceptions for control flow or error handling can lead to poorly structured code. This can make it harder to reason about the code and can result in unexpected behavior.

  • Implicit: Exceptions are not part of a method's signature, meaning that the caller must be aware of the exceptions that a method can throw.

  • Testing: Testing code that throws exceptions can be challenging, as you need to set up the test environment to trigger or bypass the exception. This can make it harder to write unit tests and verify the behavior of the code.

The Result pattern is an alternative approach to error handling that addresses some of these issues. Instead of throwing exceptions, a method returns a result object that encapsulates the outcome of the operation. This result object can represent either a successful result or an error, along with additional information about the error. By making the error explicit, the Result pattern can help improve code performance, readability, maintainability, and testability.

This doesn't mean we should replace exceptions entirely. Exceptions should be used for exceptional cases, not as a regular part of program execution.

Getting Started

Just add the package to begin using it:

dotnet add package ResultExtensions

You might want to consider adding the ASP.NET Core extensions package as well:

dotnet add package ResultExtensions.AspNetCore

If you are using FluentAssertions to write tests, we offer a package with specialized assertions:

dotnet add package ResultExtensions.FluentAssertions

Now that the necessary packages have been added, let's explore the capabilities of the ResultExtensions.

Features

Let's explore some of the features provided by the ResultExtensions library.

Basic usage

Returns either a value or an error
public Result<int> Divide(int dividend, int divisor)
{
    if (divisor is 0)
    {
        return Error.Failure("Cannot divide by zero.");
    }

    return dividend / divisor;
}
Returns either a value or multiple errors
public Result<int> Validate(int value)
{
    var errors = new List<Error>();

    if (value < 0)
    {
        errors.Add(Error.Validation("Value must be greater than or equal to zero."));
    }

    if (value % 2 is not 0)
    {
        errors.Add(Error.Validation("Value must be an even number."));
    }

    return errors.Length is 0 ? value : errors;
}

Creating a Result<T>

From implicit conversions
To a successful result
Result<string> r1 = "Hello, World!";
Result<Point> r2 = new Point(10, 20);
To a failed result
Result<string> r1 = Error.Validation("Must start with a letter.");
Result<string> r2 = new[] 
{ 
    Error.Validation("Must start with a letter."),
    Error.Validation("Must be at least 8 characters long.")
};
From static factory methods
To a successful result
var r1 = Result<int>.Success(42);
var r2 = Result<Point>.Success(new Point(10, 20));
To a failed result
var r1 = Result<string>.Failure(Error.Validation("Must start with a letter."));

var r2 = Result<string>.Failure(
    Error.Validation("Must start with a letter."),
    Error.Validation("Must be at least 8 characters long."));

var r3 = Result<string>.Failure(new[]
{
    Error.Validation("Must start with a letter."),
    Error.Validation("Must be at least 8 characters long.")
});

var r4 = Result<string>.Failure(new List<Error>
{
    Error.Validation("Must start with a letter."),
    Error.Validation("Must be at least 8 characters long.")
});

var r5 = Result<string>.Failure(ImmutableArray.Create(
    Error.Validation("Must start with a letter."),
    Error.Validation("Must be at least 8 characters long.")));

Handling the result

Using the Switch method

The Switch method allows you to specify separate actions for the success and failure cases.

Result<int> Process(int x)
{ ... }

Process(5).Switch(
    onSuccess: value => Console.WriteLine($"Success: {value}"),
    onFailure: error => Console.WriteLine($"Failure: {error}"));
Using the Match method

The Match method allows you to specify separate actions for the success and failure cases and return a new value.

Result<int> Process(int x)
{ ... }

var str = Process(5).Match<string>(
    onSuccess: value => value.ToString(),
    onFailure: error => error.Message);

Both Switch and Match methods have a *All variant that allows you to capture all errors in the failure case.

🚧 Work in progress...

Documentation

All public types and members are documented. The documentation can be found in the source code.

Inspired By

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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen 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 (2)

Showing the top 2 NuGet packages that depend on ResultExtensions:

Package Downloads
ResultExtensions.AspNetCore

Provides a set of extensions methods to produce responses based on the Result<T> type.

ResultExtensions.FluentAssertions

Provides a set of custom FluentAssertions extensions for the Result<T> type.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.1.0 180 4/15/2024
3.0.0 132 4/13/2024
2.1.0 112 4/13/2024