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" />
<PackageReference Include="Tethys.Results" />
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
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#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
#tool nuget:?package=Tethys.Results&version=1.1.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
Tethys.Results
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
andWhen
- ✅ 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 resultResult.Ok(string message)
- Creates a successful result with a messageResult.Fail(string message)
- Creates a failed result with an error messageResult.Fail(string message, Exception exception)
- Creates a failed result with message and exceptionResult.Fail(Exception exception)
- Creates a failed result from an exceptionResult.Combine(IEnumerable<Result> results)
- Combines multiple results into one
Result<T> Class
Result<T>.Ok(T value)
- Creates a successful result with a valueResult<T>.Ok(T value, string message)
- Creates a successful result with a value and messageResult<T>.Fail(string message)
- Creates a failed result with an error messageResult<T>.Fail(string message, Exception exception)
- Creates a failed result with message and exceptionResult<T>.Fail(Exception exception)
- Creates a failed result from an exceptionResult<T>.Combine(IEnumerable<Result<T>> results)
- Combines multiple results with values
Extension Methods
Then(Func<Result> operation)
- Chains operations on successful resultsThen<T>(Func<Result<T>> operation)
- Chains operations that return valuesThenAsync(Func<Task<Result>> operation)
- Chains async operationsThenAsync<T>(Func<Task<Result<T>>> operation)
- Chains async operations that return valuesWhen(bool condition, Func<Result> operation)
- Conditionally executes operationsGetValueOrDefault(T defaultValue = default)
- Gets the value or a defaultTryGetValue(out T value)
- Tries to get the value using the Try patternGetValueOrThrow()
- 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
- Use Result for Expected Failures - Reserve exceptions for truly exceptional cases
- Chain Operations - Leverage
Then
andThenAsync
for clean, readable code - Avoid Nested Results - Use
Then
instead of manual checking - Consistent Error Messages - Provide clear, actionable error messages
- Leverage Implicit Conversions - Simplify code with implicit conversions where appropriate
- Prefer TryGetValue - Use
TryGetValue
overGetValueOrThrow
for safer value extraction - 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
- 📧 Email: support@tethys.dev
- 🐛 Issues: GitHub Issues
- 📖 Documentation: Full Documentation
Acknowledgments
Inspired by functional programming patterns and the Railway Oriented Programming approach.
Product | Versions 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.