RandomSkunk.Results 1.0.0

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

// Install RandomSkunk.Results as a Cake Tool
#tool nuget:?package=RandomSkunk.Results&version=1.0.0

RandomSkunk.Results NuGet

This library contains three result types: Result<T>, which represents a result that has a required value; Maybe<T>, which represents a result that has an optional value; and Result, which represents a result that does not have a value.

Usage

Creation

To create a result, use one of the static factory methods.

int i = 123;
string s = "abc";
string n = null!;

// Results that have a required value:
Result<int> result1 = Result<int>.Success(i);
Result<int> result2 = Result<int>.Fail();

Result<string> result3 = Result<string>.FromValue(s); // Success("abc")
Result<string> result4 = Result<string>.FromValue(n); // Fail("Result value cannot be null.")

// using RandomSkunk.Result.FactoryExtensions;
Result<string> result5 = s.ToResult(); // Success("abc")
Result<string> result6 = n.ToResult(); // Fail("Result value cannot be null.")

// Results that have an optional value:
Maybe<int> result7 = Maybe<int>.Success(123);
Maybe<int> result8 = Maybe<int>.None;
Maybe<int> result9 = Maybe<int>.Fail();

Maybe<string> resultA = Maybe<string>.FromValue(s); // Success("abc")
Maybe<string> resultB = Maybe<string>.FromValue(n); // None

// using RandomSkunk.Result.FactoryExtensions;
Maybe<string> resultC = s.ToMaybe(); // Success(123)
Maybe<string> resultD = n.ToMaybe(); // None

// Results that do not have a value:
Result resultE = Result.Success();
Result resultF = Result.Fail();
From Exceptions

Fail results can be created directly from an exception. The caught exception is represented by the Error.InnerError property of the fail result's error.

Result<int> Divide(int x, int y)
{
    try
    {
        return Result<int>.Success(x / y);
    }
    catch (DivideByZeroException ex)
    {
        return Result<int>.Fail(ex);
    }
}
TryCatch classes

An entire try/catch statement can be replaced with a call to of the TryCatch class's methods.

Result<int> Divide(int x, int y) =>
    TryCatch<DivideByZeroException>.AsResult(() => x / y);

There are additional classes named "TryCatch" in, but with different number of generic arguments. The generic arguments allow you to specify exactly what type of exceptions to catch and in what order they should be caught. Then non-generic version catches the base Exception type.

Handling Results

Direct Access

To access the value and error of results directly, then access the Value and Error properties of the result. Note that accessing these properties will throw an InvalidStateException if not in the proper state: IsSuccess must be true in order to successfully access Value, and IsFail must be true in order to successfully access Error.

void Example(Result<int> result1, Maybe<int> result2, Result result3)
{
    if (result1.IsFail)
        Console.WriteLine($"Error: {result1.Error}");
    else
        Console.WriteLine($"Success: {result1.Value}");

    if (result2.IsSuccess)
        Console.WriteLine($"Success: {result2.Value}");
    else if (result2.IsNone)
        Console.WriteLine("None");
    else
        Console.WriteLine($"Error: {result2.Error}");

    if (result3.IsSuccess)
        Console.WriteLine("Success");
    else
        Console.WriteLine($"Error: {result3.Error}");
}
Match methods

The match methods map a result to a value using a series of function parameters for each of the possible outcomes of the result (Success, Fail, or None).

// Synchronous functions:
Result result1 = default;
string message1 = result1.Match(
    onSuccess: () => "Success",
    onFail: error => $"Fail: {error}");

// Asynchronous functions:
Maybe<Guid> result2 = default;
string message2 = await result2.Match(
    onSuccess: async userId =>
    {
        string userName = await GetUserName(userId);
        return $"Hello, {userName}!";
    },
    onNone: () => Task.FromResult("Unknown user"),
    onFail: error => Task.FromResult("Error"));
GetValueOr

Applicable to Result<T> and Maybe<T> only.

Gets the value of the Success result, or the specified fallback value if it is a Fail result.

Result<int>.Success(123).GetValueOr(456); // 123
Result<int>.Fail().GetValueOr(456); // 456

Maybe<int>.Success(123).GetValueOr(456); // 123
Maybe<int>.None.GetValueOr(456); // 456
Maybe<int>.Fail().GetValueOr(456); // 456
Or

Applicable to Result<T> and Maybe<T> only.

Returns the current result if it is a Success result; otherwise, returns a new Success result with the specified fallback value.

Result<int>.Success(123).Or(456); // Success(123)
Result<int>.Fail().Or(456); // Success(456)

Maybe<int>.Success(123).Or(456); // Success(123)
Maybe<int>.None.Or(456); // Success(456)
Maybe<int>.Fail().Or(456); // Success(456)
Else

Applicable to all three result types.

Returns the current result if it is a Success result, else returns the specified fallback result.

Result<int>.Success(123).Else(Result<int>.Success(456)); // Success(123)
Result<int>.Success(123).Else(Result<int>.Fail()); // Success(123)
Result<int>.Fail("A").Else(Result<int>.Success(456)); // Success(456)
Result<int>.Fail("A").Else(Result<int>.Fail("B")); // Fail("B")

Maybe<int>.Success(123).Else(Maybe<int>.Success(456)); // Success(123)
Maybe<int>.Success(123).Else(Maybe<int>.None); // Success(123)
Maybe<int>.Success(123).Else(Maybe<int>.Fail("B")); // Success(123)
Maybe<int>.None.Else(Maybe<int>.Success(456)); // Success(456)
Maybe<int>.None.Else(Maybe<int>.None); // None
Maybe<int>.None.Else(Maybe<int>.Fail("B")); // Fail("B")
Maybe<int>.Fail("A").Else(Maybe<int>.Success(456)); // Success(456)
Maybe<int>.Fail("A").Else(Maybe<int>.None); // None
Maybe<int>.Fail("A").Else(Maybe<int>.Fail("B")); // Fail("B")
Select / SelectAsync

Applicable to Result<T> and Maybe<T> only.

Transforms the current result - if Success - into a new Success result using the specified onSuccessSelector function. Otherwise, if the current result is Fail, it is transformed into a new Fail result with the same error.

The difference between Select and SelectMany is in the return value of their onSuccessSelector function. The selector for Select returns a regular (non-result) value, which is the value of the returned Success result. The selector for SelectMany returns a result value, which is itself the returned result (and might not be Success).

Result<int>.Success(123).Select(value => value.ToString()); // Success("123")
Result<int>.Fail("A").Select(value => value.ToString()); // Fail("A")

Maybe<int>.Success(123).Select(value => value.ToString()); // Success("123")
Maybe<int>.None.Select(value => value.ToString()); // None
Maybe<int>.Fail("A").Select(value => value.ToString()); // Fail("A")
SelectMany

Applicable to all three result types.

Transforms the current result - if Success - into a new result using the specified onSuccessSelector function. Otherwise, if the current result is Fail, it is transformed into a new Fail result with the same error.

The difference between Select and SelectMany is in the return value of their onSuccessSelector function. The selector for Select returns a regular (non-result) value, which is the value of the returned Success result. The selector for SelectMany returns a result value, which is itself the returned result (and might not be Success).

Result<int>.Success(123).SelectMany(GetSuccessResult); // Success("123")
Result<int>.Success(123).SelectMany(GetFailResult); // Fail
Result<int>.Fail("A").SelectMany(GetSuccessResult); // Fail
Result<int>.Fail("A").SelectMany(GetFailResult); // Fail

Maybe<bool>.Success(true).SelectMany(GetSuccessMaybe); // Success("true")
Maybe<bool>.Success(true).SelectMany(GetNoneMaybe); // None
Maybe<bool>.Success(true).SelectMany(GetFailMaybe); // Fail("B")
Maybe<bool>.None.SelectMany(GetSuccessMaybe); // None
Maybe<bool>.None.SelectMany(GetNoneMaybe); // None
Maybe<bool>.None.SelectMany(GetFailMaybe); // None
Maybe<bool>.Fail("A").SelectMany(GetSuccessMaybe); // Fail("A")
Maybe<bool>.Fail("A").SelectMany(GetNoneMaybe); // Fail("A")
Maybe<bool>.Fail("A").SelectMany(GetFailMaybe); // Fail("A")

Result<string> GetSuccessResult(int value) => Result<string>.Success(value.ToString());
Result<string> GetFailResult(int value) => Result<string>.Fail("B");

Maybe<string> GetSuccessMaybe(bool value) => Maybe<string>.Success(value.ToString());
Maybe<string> GetNoneMaybe(bool value) => Maybe<string>.None;
Maybe<string> GetFailMaybe(bool value) => Maybe<string>.Fail("B");

Flatten

Flattens a Result<Result<T>> into a Result<T> or a Maybe<Maybe<T>> into a Maybe<T>.

void Example(
    Result<Result<int>> nestedResult,
    Maybe<Maybe<int>> nestedMaybe)
{
    Result<int> flattenedResult = nestedResult.Flatten();
    Maybe<int> flattenedMaybe = nestedMaybe.Flatten();
}
Truncate

Truncates the value from a Result<T> or Maybe<T>, resulting in a Result.

void Example(Result<int> result, Maybe<int> maybe)
{
    Result truncatedFromResult = result.Truncate();
    Result truncatedFromMaybe = maybe.Truncate();
}
Where

Applicable to Maybe<T> and Result<T> only.

Filters a Success result to None unless the specified filter function evaluates to true. None and Fail results are not affected.

Maybe<int>.Success(123).Where(value => value < 150); // Success(123)
Maybe<int>.Success(456).Where(value => value < 150); // None
Maybe<int>.None.Where(value => value < 150); // None
Maybe<int>.Fail("A").Where(value => value < 150); // Fail("A")
WithError

Applicable to all three result types.

Returns a new result with a different error if the source is a Fail result. Success and None results are not affected.

Result failResult = Result.Fail("Inner error");
Result successResult = Result.Success();

// Fail("Outer error"("Inner error"))
failResult.WithError(error => new Error
    {
        Message = "Outer error",
        InnerError = error,
    });

// Success
successResult.WithError(error => new Error
    {
        Message = "Outer error",
        InnerError = error,
    });

Custom Errors

Custom errors can be created by inheriting from the Error record class.

public record class NotFoundError : Error
{
    public NotFoundError(int id, string resourceType = "record")
    {
        Message = $"A {resourceType} with the ID {id} could not be found.";
        ErrorCode = 404;
    }
}

// Create a fail result with our custom error.
Result<int> result = Result<int>.Fail(new NotFoundError(123));

// errorTitle: "Not Found Error"
string errorTitle = result.Error.Title;

// errorMessage: "A record with the ID 123 could not be found."
string errorMessage = result.Error.Message;

// errorCode: 404
int? errorCode = result.Error.ErrorCode;

Handling multiple results

If you have multiple results and need to do something based on whether they all succeeded or any did not succeed, wrap up the results in a value tuple and call one of the result tuple extensions: Match, OnAllSuccess, OnAnyNonSuccess, Select, or SelectMany.

void Example(
    Result<int> result1,
    Maybe<int> result2,
    Result<string> result3)
{
    (result1, result2, result3)
        .OnAllSuccess((r1, r2, r3) => Console.WriteLine($"Success: {r1}, {r2}, {r3}"))
        .OnAnyNonSuccess(error => Console.WriteLine($"Fail: {error}"));
}

LINQ Extension Methods

The easiest way to perform a sequence of result operations is by using LINQ-to-Results. This uses regular .NET LINQ syntax to compose the sequence of operations. The main advantage to this is that any non-success result will short-circuits the entire sequence - later operations are only evaluated if all earlier operations succeed.

using RandomSkunk.Results.Linq;

// Given methods with the following signatures:
Maybe<Person> GetPerson(Guid id)
Maybe<Department> GetDepartment(Guid id)

void Example(Guid personId)
{
    // Chain the results together:
    Maybe<Department> result =
        from person in GetPerson(personId)
        where person.IsActive
        from department in GetDepartment(person.DepartmentId)
        select department;
}
Product Versions
.NET net5.0 net5.0-windows net6.0 net6.0-android net6.0-ios net6.0-maccatalyst net6.0-macos net6.0-tvos net6.0-windows net7.0 net7.0-android net7.0-ios net7.0-maccatalyst net7.0-macos net7.0-tvos net7.0-windows
.NET Core netcoreapp2.0 netcoreapp2.1 netcoreapp2.2 netcoreapp3.0 netcoreapp3.1
.NET Standard netstandard2.0 netstandard2.1
.NET Framework net461 net462 net463 net47 net471 net472 net48 net481
MonoAndroid monoandroid
MonoMac monomac
MonoTouch monotouch
Tizen tizen40 tizen60
Xamarin.iOS xamarinios
Xamarin.Mac xamarinmac
Xamarin.TVOS xamarintvos
Xamarin.WatchOS xamarinwatchos
Compatible target framework(s)
Additional computed target framework(s)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on RandomSkunk.Results:

Package Downloads
RandomSkunk.Results.Http

Using RandomSkunk.Results with System.Net.Http and System.Net.Http.Json.

RandomSkunk.Results.AspNetCore

Using RandomSkunk.Results from ASP.NET Core applications.

RandomSkunk.Results.Dapper

Using RandomSkunk.Results with Dapper.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.0 307 1/12/2023
1.0.0-alpha23 58 1/3/2023
1.0.0-alpha22 52 12/9/2022
1.0.0-alpha21 66 11/10/2022
1.0.0-alpha20 69 10/26/2022
1.0.0-alpha19 57 10/18/2022
1.0.0-alpha18 60 10/13/2022
1.0.0-alpha17 60 10/3/2022
1.0.0-alpha16 74 9/22/2022
1.0.0-alpha15 79 7/29/2022
1.0.0-alpha14 80 7/1/2022
1.0.0-alpha13 86 6/3/2022
1.0.0-alpha12 78 6/1/2022
1.0.0-alpha11 80 5/26/2022
1.0.0-alpha10 79 5/24/2022
1.0.0-alpha09 74 5/19/2022
1.0.0-alpha08 86 5/12/2022
1.0.0-alpha07 83 5/10/2022
1.0.0-alpha06 79 5/7/2022
1.0.0-alpha05 82 5/5/2022
1.0.0-alpha04 78 5/3/2022
1.0.0-alpha02 84 4/29/2022
1.0.0-alpha01 84 4/28/2022