https://github.com/safwa1/sharpresults
A lightweight, zero-dependency C# library that implements the Result and Option types for more explicit and type-safe error handling. SharpResults helps you avoid exceptions for control flow and makes success/failure and presence/absence states explicit in your code.
https://github.com/safwa1/sharpresults
csharp dotnet error-handling extension-methods fluent-api functional-programming library monads nuget optianal result-type sharpresults
Last synced: 8 months ago
JSON representation
A lightweight, zero-dependency C# library that implements the Result and Option types for more explicit and type-safe error handling. SharpResults helps you avoid exceptions for control flow and makes success/failure and presence/absence states explicit in your code.
- Host: GitHub
- URL: https://github.com/safwa1/sharpresults
- Owner: safwa1
- Created: 2025-05-12T21:14:17.000Z (9 months ago)
- Default Branch: master
- Last Pushed: 2025-06-09T19:20:50.000Z (8 months ago)
- Last Synced: 2025-06-09T20:26:57.946Z (8 months ago)
- Topics: csharp, dotnet, error-handling, extension-methods, fluent-api, functional-programming, library, monads, nuget, optianal, result-type, sharpresults
- Language: C#
- Homepage: https://www.nuget.org/packages/SharpResults
- Size: 43.9 KB
- Stars: 2
- Watchers: 1
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# SharpResults
A lightweight, zero-dependency C# library that implements the Result and Option types for more explicit and type-safe error handling. SharpResults helps you avoid exceptions for control flow and makes success/failure and presence/absence states explicit in your code.

[](https://www.nuget.org/packages/SharpResults/)
[](https://opensource.org/licenses/MIT)
[](https://www.nuget.org/packages/SharpResults/)
## ð Features
- ðĄïļ **Type-safe error handling** - No more unexpected exceptions
- ð **Chainable operations** - Fluent API for transforming and combining results
- ð§Đ **LINQ integration** - Works with C# query syntax
- ⥠**Async support** - First-class support for async/await operations
- ð **Comprehensive API** - Rich set of methods for working with results
- ðŠķ **Lightweight** - Zero dependencies
- ðŊ **Multi-targeting** - Supports .NET 8.0 and .NET 9.0
- ð **Performance optimized** - Minimal overhead compared to traditional exception handling
## ð Table of Contents
- [Installation](#-installation)
- [Basic Usage](#basic-usage)
- [Advanced Usage](#advanced-usage)
- [Asynchronous Operations](#asynchronous-operations)
- [Real-World Examples](#real-world-examples)
- [Performance Considerations](#performance-considerations)
- [Comparison with Other Libraries](#comparison-with-other-libraries)
- [Contributing](#contributing)
- [License](#license)
## ðĶ Installation
### Package Manager
```
Install-Package SharpResults
```
### .NET CLI
```
dotnet add package SharpResults
```
## Basic Usage
### Creating Results
```csharp
using SharpResults;
using SharpResults.Types;
// Create a successful result
Result success = Result.Ok(42);
// Create a failed result with an exception
Result failure = Result.Err(new Exception("Something went wrong"));
// Create a failed result with just a message
Result failureWithMessage = Result.Err("Invalid input");
// Implicit conversion from value to successful result
Result implicitSuccess = 42;
```
### Checking Result State
```csharp
Result result = GetSomeResult();
// Using properties
if (result.IsOk)
{
// Use result.Value safely here
Console.WriteLine($"Got value: {result.Value}");
}
else
{
// Handle the error
Console.WriteLine($"Error: {result.Exception.Message}");
}
// Using pattern matching (C# 8.0+)
switch (result)
{
case { IsOk: true } ok:
Console.WriteLine($"Success: {ok.Value}");
break;
case { IsErr: true } err:
Console.WriteLine($"Error: {err.Exception.Message}");
break;
}
// Using deconstruction syntax
var (isOk, value, exception) = result;
if (isOk)
{
Console.WriteLine($"Value: {value}");
}
```
### Transforming Results
```csharp
// Map transforms the value inside a successful result
Result result = Result.Ok(42);
Result mapped = result.Map(x => x.ToString());
// AndThen (or FlatMap) chains result-returning operations
Result parsed = Result.Ok("42")
.AndThen(str => {
if (int.TryParse(str, out int value))
return Result.Ok(value);
return Result.Err("Parsing failed");
});
// LINQ syntax support
Result computation =
from x in Result.Ok(10)
from y in Result.Ok(5)
select x + y; // Result.Ok(15)
```
### Handling Errors
```csharp
// Recover from errors
Result recovered = Result.Err("Original error")
.OrElse(ex => Result.Ok(42));
// Provide default values
int value = Result.Err("Error")
.UnwrapOr(42); // 42
// Use a fallback function
int computed = Result.Err(new Exception("Failed"))
.UnwrapOrElse(ex => 42); // 42
// Map errors to different error types
Result mappedError = Result.Err("Database error")
.MapErr(msg => new CustomError(ErrorType.Database, msg));
```
### Pattern Matching with Match
```csharp
// Transform both success and error cases
string message = result.Match(
ok: value => $"Success: {value}",
err: ex => $"Error: {ex.Message}"
);
// Execute actions based on result state
result.Match(
ok: value => SaveToDatabase(value),
err: ex => LogError(ex)
);
```
## Advanced Usage
### Custom Error Types
Use `Result` when you want to use custom error types instead of exceptions:
```csharp
// Define a custom error type
public enum ApiError
{
NotFound,
Unauthorized,
ServerError
}
// Create results with custom error types
Result GetUser(int id)
{
if (id <= 0)
return ApiError.NotFound; // Implicit conversion
if (!IsAuthenticated())
return Result.Err(ApiError.Unauthorized);
try
{
var user = _repository.GetUser(id);
return user != null
? Result.Ok(user)
: ApiError.NotFound;
}
catch
{
return ApiError.ServerError;
}
}
// Usage
var result = GetUser(42);
// Pattern matching with custom errors
string message = result.Match(
ok: user => $"Found user: {user.Name}",
err: error => error switch
{
ApiError.NotFound => "User not found",
ApiError.Unauthorized => "Please login first",
ApiError.ServerError => "Server error occurred",
_ => "Unknown error"
}
);
```
### Working with Void Returns
Use `Unit` as a return type for operations that don't return a value:
```csharp
// For methods that don't return a value
Result SaveData(string data)
{
try
{
// Save data to database
File.WriteAllText("data.txt", data);
return Result.Ok(Unit.Value);
}
catch (Exception ex)
{
return Result.Err(ex);
}
}
// Usage
Result saveResult = SaveData("important data");
if (saveResult.IsOk)
{
Console.WriteLine("Data saved successfully");
}
```
### Try-Catch Alternative
Use `Result.From` to automatically catch exceptions:
```csharp
// Automatically catches exceptions
Result divideResult = Result.From(() => 10 / 0);
// divideResult will be an Err with DivideByZeroException
// For void-returning methods
Result writeResult = Result.From(() => File.WriteAllText("file.txt", "content"));
// With custom error mapping
Result customErrorResult = Result.From(
() => ParseComplexData(),
ex => $"Parsing error: {ex.Message}"
);
```
### Chaining Multiple Operations
```csharp
Result GetProcessedData(string input)
{
return Result.Ok(input)
.AndThen(ValidateInput)
.Map(data => data.ToUpper())
.AndThen(ProcessData)
.Inspect(data => Console.WriteLine($"Processing complete: {data}"))
.InspectErr(ex => Console.WriteLine($"Error occurred: {ex.Message}"));
}
Result ValidateInput(string input)
{
return string.IsNullOrEmpty(input)
? Result.Err("Input cannot be empty")
: Result.Ok(input);
}
Result ProcessData(string data)
{
// Process the data
return Result.Ok($"Processed: {data}");
}
```
### Combining Multiple Results
```csharp
// Combine multiple results into one
Result<(int, string, bool)> combined = Result.Combine(
Result.Ok(42),
Result.Ok("hello"),
Result.Ok(true)
);
// If any result is an error, the combined result will be an error
Result<(int, string)> partialError = Result.Combine(
Result.Ok(42),
Result.Err("Something went wrong")
); // Will be Err with "Something went wrong"
// Combine a collection of results
IEnumerable> results = new[] { Result.Ok(1), Result.Ok(2), Result.Ok(3) };
Result> collectionResult = Result.Combine(results);
// Result.Ok([1, 2, 3])
```
## Asynchronous Operations
SharpResults provides first-class support for asynchronous operations:
```csharp
// Async methods returning Result
public async Task> GetUserAsync(int id)
{
try
{
var user = await _userRepository.GetByIdAsync(id);
return user != null
? Result.Ok(user)
: Result.Err("User not found");
}
catch (Exception ex)
{
return Result.Err(ex);
}
}
// Chaining async operations
public async Task> PlaceOrderAsync(int userId, int productId)
{
return await GetUserAsync(userId)
.MapAsync(async user => {
// Validate user can place orders
if (!user.CanPlaceOrders)
return Result.Err("User cannot place orders");
return Result.Ok(user);
})
.AndThenAsync(async user => await GetProductAsync(productId))
.AndThenAsync(async product => {
if (!product.InStock)
return Result.Err("Product out of stock");
var confirmation = await _orderService.CreateOrderAsync(userId, productId);
return Result.Ok(confirmation);
});
}
// Using LINQ with async results
public async Task> GetOrderSummaryAsync(int orderId)
{
var orderResult = await GetOrderAsync(orderId);
return await (from order in orderResult
from customer in await GetCustomerAsync(order.CustomerId)
from items in await GetOrderItemsAsync(orderId)
select new OrderSummary
{
OrderId = order.Id,
CustomerName = customer.Name,
Items = items,
Total = items.Sum(i => i.Price)
});
}
```
## Real-World Examples
### Web API Controller
```csharp
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpGet("{id}")]
public async Task GetUser(int id)
{
var result = await _userService.GetUserAsync(id);
return result.Match(
ok: user => Ok(user),
err: ex => ex.Message == "User not found"
? NotFound(ex.Message)
: StatusCode(500, "An error occurred while processing your request")
);
}
[HttpPost]
public async Task CreateUser(CreateUserRequest request)
{
var result = await _userService.CreateUserAsync(request);
return result.Match(
ok: user => CreatedAtAction(nameof(GetUser), new { id = user.Id }, user),
err: ex => BadRequest(ex.Message)
);
}
}
```
### Domain Logic with Validation
```csharp
public class OrderService
{
private readonly IOrderRepository _orderRepository;
private readonly IProductRepository _productRepository;
private readonly ICustomerRepository _customerRepository;
// Constructor with DI
public async Task> PlaceOrderAsync(OrderRequest request)
{
// Validate request
var validationResult = ValidateOrderRequest(request);
if (validationResult.IsErr)
return Result.Err(validationResult.Exception);
// Check if customer exists
var customerResult = await _customerRepository.GetByIdAsync(request.CustomerId);
if (customerResult.IsErr)
return Result.Err($"Customer not found: {request.CustomerId}");
// Check if all products exist and are in stock
var productsResult = await CheckProductsAvailabilityAsync(request.Items);
if (productsResult.IsErr)
return productsResult.MapErr();
// Create order
var order = new Order
{
CustomerId = request.CustomerId,
Items = request.Items.Select(i => new OrderItem { ProductId = i.ProductId, Quantity = i.Quantity }).ToList(),
OrderDate = DateTime.UtcNow,
Status = OrderStatus.Pending
};
// Save order
try
{
await _orderRepository.CreateAsync(order);
return Result.Ok(order);
}
catch (Exception ex)
{
return Result.Err(ex);
}
}
private Result ValidateOrderRequest(OrderRequest request)
{
if (request == null)
return Result.Err("Order request cannot be null");
if (request.CustomerId <= 0)
return Result.Err("Invalid customer ID");
if (request.Items == null || !request.Items.Any())
return Result.Err("Order must contain at least one item");
if (request.Items.Any(i => i.Quantity <= 0))
return Result.Err("All items must have a quantity greater than zero");
return Result.Ok(Unit.Value);
}
private async Task> CheckProductsAvailabilityAsync(IEnumerable items)
{
foreach (var item in items)
{
var productResult = await _productRepository.GetByIdAsync(item.ProductId);
if (productResult.IsErr)
return Result.Err($"Product not found: {item.ProductId}");
var product = productResult.Value;
if (product.StockQuantity < item.Quantity)
return Result.Err($"Insufficient stock for product {product.Name}. Available: {product.StockQuantity}, Requested: {item.Quantity}");
}
return Result.Ok(Unit.Value);
}
}
```
## Performance Considerations
SharpResults is designed to be lightweight and efficient. Here are some performance considerations:
- **Avoid exceptions for control flow**: Using SharpResults instead of throwing exceptions can significantly improve performance in error-prone code paths.
- **Lazy error handling**: Error messages and exceptions are only created when needed.
- **Minimal allocations**: The library minimizes heap allocations where possible.
- **Struct-based implementation**: For performance-critical code, consider using the struct-based variants of Result types.
## Comparison with Other Libraries
### âïļ TL;DR â Summary
| Feature / Library | SharpResults | FluentResults | OneOf | CSharpFunctionalExtensions |
|------------------|----------------------|---------------|-------|-----------------------------|
| â
Strong Result/Error typing | âïļ Yes | âïļ Yes | â No | âïļ Yes |
| â
Rich extension methods | âïļ Yes | âïļ Partial | â Minimal | âïļ Yes |
| â
Exception capturing | âïļ Yes | âïļ Yes | â No | âïļ Yes |
| â
Pattern matching support | âïļ With Match | âïļ With ResultType | âïļ Native | â Manual |
| â
Null safety / value handling | âïļ Yes | âïļ Yes | â ïļ Riskier | âïļ Yes |
| â
Simplicity & minimalism | âïļ Lean and clean | â Heavy | âïļ Minimal | â Verbose |
| â
Control over error types | âïļ Generic errors (TError) | âïļ Message objects | â Not applicable | âïļ Generic |
| ð ïļ IDE-friendliness (C# tooling) | âïļ Yes | âïļ Yes | â ïļ Limited | âïļ Yes |
### Detailed Feature Comparison
| Feature | SharpResults | OneOf | CSharpFunctionalExtensions | FluentResults |
|---------|-------------|-------|----------------------------|---------------|
| Custom Error Types | â
| â
| â
| â
|
| LINQ Support | â
| â | â
| â |
| Async Support | â
| â | â
| â
|
| Pattern Matching | â
| â
| â | â |
| Implicit Conversions | â
| â
| â
| â |
| Deconstruction | â
| â
| â
| â |
| Multiple Error Collection | â
| â | â | â
|
| Zero Dependencies | â
| â
| â
| â
|
| .NET Standard 2.0+ | â
| â
| â
| â
|
## Contributing
Contributions are welcome! Here's how you can contribute:
1. **Fork the repository**
2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
3. **Commit your changes**: `git commit -m 'Add some amazing feature'`
4. **Push to the branch**: `git push origin feature/amazing-feature`
5. **Open a Pull Request**
### Development Guidelines
- Follow the existing code style and conventions
- Add unit tests for new features
- Update documentation for any changes
- Ensure all tests pass before submitting a PR
## License
This project is licensed under the MIT License - see the LICENSE file for details.