Sviatoslav93.Result 3.0.0

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

Result

Build Status NuGet License: MIT

A robust, functional approach to error handling in C#. The Result pattern is an alternative to exception-based error handling that makes error states explicit and composable.

Features

  • ๐Ÿš€ Zero exceptions - Explicit error handling without throwing exceptions
  • ๐Ÿ”— Fluent chaining - Chain operations with Bind, BindAsync, and more
  • ๐ŸŽฏ Type safe - Compile-time safety for error handling
  • โšก High performance - No exception overhead
  • ๐Ÿงช Composable - Combine, validate, and transform results easily
  • ๐Ÿ”„ Async support - Full async/await integration
  • ๐Ÿ“ฆ Lightweight - Minimal dependencies

Installation

dotnet add package Sviatoslav93.Result

Quick Start

using Result;
using Result.Extensions;

// Basic usage
Result<int> Divide(int a, int b)
{
    if (b == 0)
    {
        return new Error("Division by zero");
    }

    return a / b;
}

// Chain operations
var result = Divide(10, 2)
    .Bind(x => x * 2)
    .Bind(x => x.ToString())
    .Match(
        onSuccess: value => $"Result: {value}",
        onFailure: errors => $"Error: {string.Join(", ", errors)}");

Console.WriteLine(result); // Output: Result: 10

Core Concepts

Result<T>

The Result<T> type represents either a successful value of type T or a collection of errors:

// Success
Result<string> success = Result.Success("Hello World");
Result<string> implicit = "Hello World"; // Implicit conversion

// Failure
Result<string> failure = Result.Failure<string>(new Error("Something went wrong"));
Result<string> implicitError = new Error("Something went wrong"); // Implicit conversion

Error

The Error record represents an error with contextual information:

// Simple error
var error = new Error("Invalid input");

// Error with code
var codedError = new Error("Invalid email format", "EMAIL_INVALID");

// Error with exception can be created in the Try method
var result = Result.Try(() => int.Parse("invalid"), ex => new Error(ex.Message));

Usage Examples

Basic Operations

// Creating results
Result<int> success = 42;
Result<int> failure = new Error("Invalid number");

// Checking success/failure
if (result.IsSuccess)
{
    Console.WriteLine($"Value: {result.Value}");
}
else
{
    Console.WriteLine($"Errors: {string.Join(", ", result.Errors)}");
}

// Pattern matching
var message = result.Match(
    onSuccess: value => $"Got: {value}",
    onFailure: errors => $"Failed: {string.Join(", ", errors)}");

Chaining Operations

// Synchronous chaining
var result = ParseInt("123")
    .Bind(x => x * 2)
    .Bind(x => x > 100 ? x : new Error("Value too small"))
    .Bind(x => x.ToString());

// Async chaining
var asyncResult = await GetUserAsync(userId)
    .BindAsync(user => GetProfileAsync(user.Id))
    .BindAsync(profile => UpdateLastAccessAsync(profile))
    .MatchAsync(
        onSuccess: profile => $"Updated: {profile.Name}",
        onFailure: errors => $"Failed: {string.Join(", ", errors)}");

Error Handling and Validation

// Validation pipeline
Result<string> ValidateEmail(string email)
{
    if (string.IsNullOrWhiteSpace(email))
        return new Error("Email is required");

    if (!email.Contains("@"))
        return new Error("Invalid email format");

    if (email.Length > 100)
        return new Error("Email too long");

    return email;
}

// Chain validations
var userResult = ValidateName(name)
    .Bind(n => ValidateEmail(email)
        .Bind(e => ValidateAge(age)
            .Bind(a => new User(n, e, a))));

Combining Results

// Combine multiple results
var nameResult = GetName();
var emailResult = GetEmail();
var ageResult = GetAge();

var userResult = nameResult.Combine(emailResult, ageResult)
    .Bind(values => new User(values[0], values[1], int.Parse(values[2])));

// All must succeed, or you get all errors
if (!userResult.IsSuccess)
{
    foreach (var error in userResult.Errors)
    {
        Console.WriteLine($"Validation error: {error.Message}");
    }
}

Exception Safety

// Safely execute code that might throw
var result = Result.Try(() =>
{
    return int.Parse("not-a-number"); // This will throw
});

// result.IsSuccess == false
// result.Errors contains the exception details

// Custom error handling
var customResult = Result.Try(
    () => RiskyOperation(),
    ex => new Error($"Operation failed: {ex.Message}", "RISKY_OP_FAILED"));

Working with None Type

For operations that don't return values:

// Use None for void operations
Result<None> SaveData(string data)
{
    try
    {
        File.WriteAllText("data.txt", data);
        return None.Value;
    }
    catch (Exception ex)
    {
        return new Error("Failed to save data");
    }
}

// Chain void operations
var saveResult = await ValidateData(data)
    .BindAsync(d => SaveDataAsync(d))
    .BindAsync(_ => LogSuccessAsync())
    .BindAsync(_ => NotifyUsersAsync());

Advanced Patterns

Recovery and Fallbacks
// Provide fallback values
var result = GetPrimaryData()
    .Recover("default-value")
    .Recover(errors => $"Fallback for: {errors.Count()} errors");

// Conditional recovery
var recovered = GetUserPreferences()
    .Recover(errors =>
        errors.Any(e => e.Code == "NOT_FOUND")
            ? "default-preferences"
            : throw new InvalidOperationException());
Side Effects with Do
// Perform side effects without changing the result
var result = await ProcessDataAsync(input)
    .DoAsync(data => LogProcessedAsync(data))
    .DoAsync(data => CacheResultAsync(data))
    .DoErrorAsync(errors => LogErrorsAsync(errors))
    .BindAsync(data => TransformAsync(data));

Async Patterns

Task<Result<T>> Integration

// Working with Task<Result<T>>
Task<Result<User>> GetUserAsync(int id);

var result = await GetUserAsync(123)
    .BindAsync(user => GetProfileAsync(user.Id))
    .BindAsync(profile => EnrichProfileAsync(profile))
    .MatchAsync(
        onSuccess: profile => $"Welcome {profile.DisplayName}",
        onFailure: errors => "Failed to load profile");

Best Practices

1. Use Implicit Conversions

// Good: Clean and readable
Result<string> GetUserName() => "John Doe";
Result<string> GetError() => new Error("User not found");

// Avoid: Verbose
Result<string> GetUserNameVerbose() => Result.Success("John Doe");

2. Chain Operations for Readability

// Good: Fluent pipeline
var result = input
    .AsResult()
    .Bind(Validate)
    .Bind(Transform)
    .Bind(Save)
    .Match(
        onSuccess: data => $"Saved: {data.Id}",
        onFailure: errors => LogAndReturnError(errors));

3. Use Meaningful Error Messages and Codes

// Good: Descriptive errors with codes
if (age < 0)
    return new Error("Age cannot be negative", "INVALID_AGE");

// Avoid: Generic errors
if (age < 0)
    return new Error("Invalid input");

4. Combine Multiple Results

// Combine multiple results into one
var nameResult = GetName();
var emailResult = GetEmail();
var ageResult = GetAge();

var userResult = nameResult.Combine(emailResult, ageResult)
    .Bind(values => new User(values[0], values[1], int.Parse(values[2])));

Performance

The Result pattern eliminates exception overhead:

// Traditional exception-based (slow for expected failures)
try
{
    var result = int.Parse(userInput);
    return result * 2;
}
catch (FormatException)
{
    return -1; // Default value
}

// Result-based (fast)
return Result.Try(() => int.Parse(userInput))
    .Recover(-1)
    .Match(
        x => x * 2,
        _ => -1);

Testing

Results make testing error conditions straightforward:

[Fact]
public void Should_Return_Error_When_Division_By_Zero()
{
    var result = Calculator.Divide(10, 0);

    Assert.False(result.IsSuccess);
    Assert.Single(result.Errors);
    Assert.Contains("division by zero", result.Errors[0].Message);
}

[Fact]
public void Should_Chain_Successful_Operations()
{
    var result = Calculator.Divide(10, 2)
        .Bind(x => x * 3)
        .Bind(x => x.ToString());

    Assert.True(result.IsSuccess);
    Assert.Equal("15", result.Value);
}

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Product Compatible and additional computed target framework versions.
.NET 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.
  • 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
3.0.0 171 2/10/2026 3.0.0 is deprecated because it is no longer maintained.
2.1.0 557 3/26/2025
2.0.1 197 10/31/2024
2.0.0 171 10/25/2024
1.2.0 213 8/19/2024
1.1.0 206 8/12/2024
1.0.0 201 8/11/2024