Tethys.Results 1.1.0

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

Tethys.Results

NuGet Build Status License: MIT

A lightweight, thread-safe Result pattern implementation for .NET that provides a clean, functional approach to error handling without exceptions.

Features

  • Simple and Intuitive API - Easy to understand and use
  • Thread-Safe - Immutable design ensures thread safety
  • No Dependencies - Lightweight with zero external dependencies
  • Async Support - First-class support for async/await patterns
  • Functional Composition - Chain operations with Then and When
  • Type-Safe - Generic Result<T> for operations that return values
  • Error Aggregation - Combine multiple results and aggregate errors
  • Implicit Conversions - Seamless conversion between values and Results

Installation

dotnet add package Tethys.Results

Quick Start

Basic Usage

using Tethys.Results;

// Simple success/failure results
Result successResult = Result.Ok("Success message");
Result failureResult = Result.Fail("Something went wrong");

// Results with values
Result<string> valueResult = Result<string>.Ok("Hello World", "Operation completed");
Result<int> errorResult = Result<int>.Fail("Not found");

// Results with exceptions
var exception = new Exception("Test exception");
Result failWithException = Result.Fail("Error message", exception);

Chaining Operations

// Chain multiple operations that depend on each other
var result = Result.Ok("Start")
    .Then(() => 
    {
        // Do some work
        return Result.Ok("First operation completed");
    })
    .Then(() => 
    {
        // Do more work
        return Result.Ok("Second operation completed");
    })
    .Then(() => 
    {
        // Final operation
        return Result.Ok("All operations completed");
    });

if (result.Success)
{
    Console.WriteLine($"Success: {result.Message}");
}
else
{
    Console.WriteLine($"Error: {result.Message}");
}

Working with Data

// Transform data through a pipeline
var result = Result<int>.Ok(42, "Initial value")
    .Then(value => 
    {
        // Transform the value
        return Result<string>.Ok(value.ToString(), "Converted to string");
    })
    .Then(str => 
    {
        // Further transformation
        return Result<string>.Ok($"The answer is: {str}", "Formatted result");
    });

// Extract the final value
string finalValue = result.GetValueOrDefault("No answer available");

Async Support

// Chain async operations seamlessly
var result = await Result.Ok("Start")
    .ThenAsync(async () =>
    {
        await Task.Delay(100); // Simulate async work
        return Result.Ok("Async operation completed");
    })
    .ThenAsync(async () =>
    {
        await Task.Delay(100); // More async work
        return Result<string>.Ok("Final result", "All async operations done");
    });

// Work with Task<Result> directly
Task<Result<int>> asyncResult = Task.FromResult(Result<int>.Ok(42, "From async"));
var processed = await asyncResult.ThenAsync(async value =>
{
    await Task.Delay(100);
    return Result<string>.Ok($"Processed: {value}", "Transformation complete");
});

Conditional Execution

// Execute operations conditionally
var result = Result.Ok("Initial state")
    .When(true, () => Result.Ok("Condition was true"))
    .When(false, () => Result.Ok("This won't execute"));

// Conditional execution with data
var dataResult = Result<int>.Ok(10)
    .When(true, () => Result<int>.Ok(20))
    .Then(value => Result<int>.Ok(value * 2)); // Results in 40

Error Aggregation

// Combine multiple validation results
var results = new List<Result>
{
    ValidateEmail("user@example.com"),
    ValidatePassword("SecurePass123!"),
    ValidateUsername("johndoe")
};

var combined = Result.Combine(results);
if (!combined.Success)
{
    // Access aggregated errors
    var aggregateError = combined.Exception as AggregateError;
    foreach (var error in aggregateError.ErrorMessages)
    {
        Console.WriteLine($"Validation error: {error}");
    }
}

// Combine results with data
var dataResults = new List<Result<int>>
{
    Result<int>.Ok(1),
    Result<int>.Ok(2),
    Result<int>.Ok(3)
};

var combinedData = Result<int>.Combine(dataResults);
if (combinedData.Success)
{
    var sum = combinedData.Data.Sum(); // Sum is 6
}

Value Extraction

Result<User> userResult = GetUser(userId);

// Get value or default
User user = userResult.GetValueOrDefault(new User { Name = "Guest" });

// Try pattern
if (userResult.TryGetValue(out User foundUser))
{
    Console.WriteLine($"Found user: {foundUser.Name}");
}
else
{
    Console.WriteLine("User not found");
}

// Get value or throw (use sparingly)
try
{
    User user = userResult.GetValueOrThrow();
    // Use the user
}
catch (InvalidOperationException ex)
{
    // Handle the error
    Console.WriteLine($"Failed to get user: {ex.Message}");
}

Implicit Conversions

// Implicitly convert values to Results
Result<int> implicitResult = 42; // Creates Result<int>.Ok(42)
Result<string> stringResult = "Hello"; // Creates Result<string>.Ok("Hello")

// Implicitly convert successful Results to values (throws if failed)
Result<int> successResult = Result<int>.Ok(42);
int value = successResult; // Gets 42

// Use in expressions
Result<int> result1 = Result<int>.Ok(10);
Result<int> result2 = Result<int>.Ok(20);
int sum = result1 + result2; // Implicit conversion, sum is 30

API Reference

Result Class

  • Result.Ok() - Creates a successful result
  • Result.Ok(string message) - Creates a successful result with a message
  • Result.Fail(string message) - Creates a failed result with an error message
  • Result.Fail(string message, Exception exception) - Creates a failed result with message and exception
  • Result.Fail(Exception exception) - Creates a failed result from an exception
  • Result.Combine(IEnumerable<Result> results) - Combines multiple results into one

Result<T> Class

  • Result<T>.Ok(T value) - Creates a successful result with a value
  • Result<T>.Ok(T value, string message) - Creates a successful result with a value and message
  • Result<T>.Fail(string message) - Creates a failed result with an error message
  • Result<T>.Fail(string message, Exception exception) - Creates a failed result with message and exception
  • Result<T>.Fail(Exception exception) - Creates a failed result from an exception
  • Result<T>.Combine(IEnumerable<Result<T>> results) - Combines multiple results with values

Extension Methods

  • Then(Func<Result> operation) - Chains operations on successful results
  • Then<T>(Func<Result<T>> operation) - Chains operations that return values
  • ThenAsync(Func<Task<Result>> operation) - Chains async operations
  • ThenAsync<T>(Func<Task<Result<T>>> operation) - Chains async operations that return values
  • When(bool condition, Func<Result> operation) - Conditionally executes operations
  • GetValueOrDefault(T defaultValue = default) - Gets the value or a default
  • TryGetValue(out T value) - Tries to get the value using the Try pattern
  • GetValueOrThrow() - Gets the value or throws an exception

Advanced Usage

Real-World Example: Order Processing

public async Task<Result<Order>> ProcessOrderAsync(OrderRequest request)
{
    return await ValidateOrderRequest(request)
        .ThenAsync(async validRequest => await CreateOrder(validRequest))
        .ThenAsync(async order => await ApplyDiscounts(order))
        .ThenAsync(async order => await CalculateTaxes(order))
        .ThenAsync(async order => await ChargePayment(order))
        .ThenAsync(async order => await SendConfirmationEmail(order))
        .ThenAsync(async order =>
        {
            await LogOrderProcessed(order);
            return Result<Order>.Ok(order, "Order processed successfully");
        });
}

// Usage
var result = await ProcessOrderAsync(orderRequest);
if (result.Success)
{
    return Ok(result.Value);
}
else
{
    _logger.LogError(result.Exception, "Order processing failed: {Message}", result.Message);
    return BadRequest(result.Message);
}

Integration with ASP.NET Core

[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
    var result = await _userService.GetUserAsync(id);
    
    return result.Success
        ? Ok(result.Value)
        : NotFound(result.Message);
}

[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] CreateUserRequest request)
{
    var validationResult = ValidateRequest(request);
    if (!validationResult.Success)
    {
        return BadRequest(validationResult.Message);
    }

    var result = await _userService.CreateUserAsync(request);
    
    return result.Success
        ? CreatedAtAction(nameof(GetUser), new { id = result.Value.Id }, result.Value)
        : BadRequest(result.Message);
}

Error Handling Patterns

// Centralized error handling
public Result<T> ExecuteWithErrorHandling<T>(Func<T> operation, string operationName)
{
    try
    {
        var result = operation();
        return Result<T>.Ok(result, $"{operationName} completed successfully");
    }
    catch (ValidationException ex)
    {
        return Result<T>.Fail($"Validation failed in {operationName}: {ex.Message}", ex);
    }
    catch (NotFoundException ex)
    {
        return Result<T>.Fail($"Resource not found in {operationName}: {ex.Message}", ex);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Unexpected error in {OperationName}", operationName);
        return Result<T>.Fail($"An unexpected error occurred in {operationName}", ex);
    }
}

Best Practices

  1. Use Result for Expected Failures - Reserve exceptions for truly exceptional cases
  2. Chain Operations - Leverage Then and ThenAsync for clean, readable code
  3. Avoid Nested Results - Use Then instead of manual checking
  4. Consistent Error Messages - Provide clear, actionable error messages
  5. Leverage Implicit Conversions - Simplify code with implicit conversions where appropriate
  6. Prefer TryGetValue - Use TryGetValue over GetValueOrThrow for safer value extraction
  7. Aggregate Validation Errors - Use Result.Combine for multiple validation checks

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

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

Support

Acknowledgments

Inspired by functional programming patterns and the Railway Oriented Programming approach.

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.  net9.0 was computed.  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 was computed.  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. 
.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.
  • .NETStandard 2.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
1.1.0 125 7/6/2025
1.0.0 974 4/2/2025