RandomSkunk.Results 1.4.0

dotnet add package RandomSkunk.Results --version 1.4.0                
NuGet\Install-Package RandomSkunk.Results -Version 1.4.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.4.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add RandomSkunk.Results --version 1.4.0                
#r "nuget: RandomSkunk.Results, 1.4.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.4.0

// Install RandomSkunk.Results as a Cake Tool
#tool nuget:?package=RandomSkunk.Results&version=1.4.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 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 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 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 (3)

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

Package Downloads
RandomSkunk.Results.AspNetCore

Using RandomSkunk.Results from ASP.NET Core applications.

RandomSkunk.Results.Http

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

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.4.0 642 6/28/2023
1.3.1 315 5/15/2023
1.3.0 331 5/3/2023
1.2.0 391 4/11/2023
1.1.0 458 3/28/2023
1.0.0 692 1/12/2023