Linger.Results
1.6.2
See the version list below for details.
dotnet add package Linger.Results --version 1.6.2
NuGet\Install-Package Linger.Results -Version 1.6.2
<PackageReference Include="Linger.Results" Version="1.6.2" />
<PackageVersion Include="Linger.Results" Version="1.6.2" />
<PackageReference Include="Linger.Results" />
paket add Linger.Results --version 1.6.2
#r "nuget: Linger.Results, 1.6.2"
#:package Linger.Results@1.6.2
#addin nuget:?package=Linger.Results&version=1.6.2
#tool nuget:?package=Linger.Results&version=1.6.2
Linger.Results
A modern operation result handling library designed with a functional style approach, helping developers handle various operation results more elegantly. By using the Result pattern instead of exceptions, it enables more controllable and predictable error handling processes.
Upgrading from 1.6 to 2.0? See Preparing for Linger 2.0.
Features
- Provides clear success/failure result representation
- Supports generic results that can carry return values of any type
- Offers rich functional operations (mapping, binding, combining, etc.)
- Strong typing for error handling, more controllable than exceptions
- Supports multiple frameworks including .Net10.0, .NET 9.0, .NET 8.0, .NET Standard 2.0, and .NET Framework 4.7.2
Installation
dotnet add package Linger.Results
Basic Usage
Creating Results
// Create success results
var success = Result.Success();
var successWithValue = Result.Success(42);
// Create failure results
var failure = Result.Failure("Operation failed");
var failureWithError = Result.Failure(new Error("ErrorCode", "Detailed error message"));
// Create not found results
var notFound = Result.NotFound();
Using Generic Results
// Define a method that returns user information
public Result<User> GetUser(int id)
{
var user = _repository.FindById(id);
if (user == null)
return Result<User>.NotFound($"User with ID {id} not found");
return Result<User>.Success(user);
}
// Elegant syntax with implicit conversion
public Result<User> GetUserWithValidation(string email)
{
// Validate email
if (string.IsNullOrEmpty(email))
{
// Directly return Result.Failure, automatically converts to Result<User>
return Result.Failure(new Error("ValidationError", "Email cannot be empty"));
}
var user = _repository.FindByEmail(email);
if (user == null)
{
// This also converts automatically
return Result.NotFound("User not found");
}
return Result<User>.Success(user);
}
// Usage example
var result = GetUser(123);
if (result.IsSuccess)
{
var user = result.Value; // Value is only accessible when result is successful
Console.WriteLine($"Found user: {user.Name}");
}
else
{
Console.WriteLine($"Error: {string.Join(", ", result.Errors.Select(e => e.Message))}");
}
Using Match Method
// Use Match method to handle different result states
string displayName = result.Match(
user => $"User: {user.Name}",
errors => $"Error: {string.Join(", ", errors.Select(e => e.Message))}"
);
Safe Value Access
// Use TryGetValue to safely access result value
if (result.TryGetValue(out var user))
{
// Successfully obtained user
Console.WriteLine($"User: {user.Name}");
}
// Use ValueOrDefault to get value or default value
var safeUser = result.ValueOrDefault; // null when failed
// Specify default value
var userOrGuest = result.GetValueOrDefault(new User { Name = "Guest" });
Method Chaining
// Use extension methods for method chaining
var finalResult = GetUser(123)
.Ensure(user => user.IsActive, new Error("User.Inactive", "User is not active"))
.Map(user => user.Email)
.Bind(email => SendEmail(email));
Async Support
All async extension methods require a CancellationToken parameter for proper cancellation support:
// Async operations with CancellationToken
var result = await GetUserAsync(123)
.MapAsync(async (user, token) => await GetUserPreferencesAsync(user, token), cancellationToken)
.BindAsync(async (prefs, token) => await UpdatePreferencesAsync(prefs, token), cancellationToken);
Async Support with CancellationToken
// All async extension methods support CancellationToken for cancellable operations
public async Task<Result<OrderSummary>> ProcessOrderAsync(int orderId, CancellationToken cancellationToken)
{
return await GetOrderAsync(orderId)
// MapAsync with CancellationToken
.MapAsync(async (order, token) =>
{
// Perform async transformation with cancellation support
return await CalculateTotalAsync(order, token);
}, cancellationToken)
// BindAsync with CancellationToken
.BindAsync(async (total, token) =>
{
// Chain another Result-returning async operation
return await ValidatePaymentAsync(total, token);
}, cancellationToken)
// EnsureAsync with CancellationToken
.EnsureAsync(
async (payment, token) => await CheckInventoryAsync(payment, token),
new Error("Inventory", "Insufficient inventory"),
cancellationToken)
// MatchAsync with CancellationToken
.MatchAsync(
async (payment, token) =>
{
await SendConfirmationEmailAsync(payment, token);
return Result<OrderSummary>.Success(new OrderSummary(payment));
},
async (errors, token) =>
{
await LogErrorsAsync(errors, token);
return Result<OrderSummary>.Failure(errors);
},
cancellationToken);
}
// Example: Using with HttpClient or database operations
public async Task<Result<User>> UpdateUserWithCancellationAsync(
User user,
CancellationToken cancellationToken)
{
return await ValidateUser(user)
.MapAsync(async (validUser, token) =>
{
// Database operation with cancellation
await _dbContext.Users.AddAsync(validUser, token);
await _dbContext.SaveChangesAsync(token);
return validUser;
}, cancellationToken)
.EnsureAsync(
async (savedUser, token) =>
{
// Verify the save operation
var exists = await _dbContext.Users
.AnyAsync(u => u.Id == savedUser.Id, token);
return exists;
},
new Error("Database", "User save verification failed"),
cancellationToken);
}
Using Result.Create for Condition Checking
// Create results based on boolean conditions
public Result ValidatePassword(string password)
{
var results = new[]
{
Result.Create(password.Length >= 8),
Result.Create(password.Any(char.IsUpper)),
Result.Create(password.Any(char.IsDigit))
};
return results.Combine();
}
// Or use Ensure with Result<T> for chained validation
public Result<string> ValidateAndReturnPassword(string password)
{
return Result.Success(password)
.Ensure(p => p.Length >= 8, new Error("Password", "Password must be at least 8 characters"))
.Ensure(p => p.Any(char.IsUpper), new Error("Password", "Password must contain uppercase letters"))
.Ensure(p => p.Any(char.IsDigit), new Error("Password", "Password must contain digits"));
}
Implicit Conversion and Elegant Syntax
Linger.Results provides powerful implicit conversion features to make your code more concise and elegant. It supports three types of implicit conversions:
1. Implicit Conversion from Result to Result<T>
public Result<User> CreateUser(CreateUserRequest request)
{
// Validate username
if (string.IsNullOrEmpty(request.Username))
{
// Directly return Result.Failure, automatically converts to Result<User>
return Result.Failure("Username cannot be empty");
}
// Check email format
if (!IsValidEmail(request.Email))
{
// Using custom error object, also converts automatically
return Result.Failure(new Error("Email.Invalid", "Invalid email format"));
}
// Check if user already exists
if (UserExists(request.Username))
{
// NotFound also supports implicit conversion
return Result.NotFound("Username is already taken");
}
// Success case
var user = new User { Username = request.Username, Email = request.Email };
return Result<User>.Success(user);
}
2. Implicit Conversion from Result<T> to Result
public Result ProcessUser(int userId)
{
// Get user (returns Result<User>)
Result<User> userResult = GetUser(userId);
// Automatically converts to Result, loses specific value but preserves status and error info
Result processResult = userResult;
if (processResult.IsSuccess)
{
// Execute processing logic
return Result.Success();
}
// Error information is preserved
return processResult;
}
3. Implicit Conversion from T to Result<T>
public Result<User> GetDefaultUser()
{
var defaultUser = new User { Name = "Default User", Email = "default@example.com" };
// Object automatically converts to successful Result<User>
return defaultUser;
}
public Result<string> GetConfigValue(string key)
{
string value = _configuration[key];
// If value is null, automatically creates failure result
// If value is not null, automatically creates success result
return value; // Equivalent to Result<string>.Create(value)
}
Chained Conversion Examples
// Demonstrates different return types that can implicitly convert to Result<User>
private Result<User> GetUserById(int id)
{
return id switch
{
1 => _testUser, // User → Result<User>
0 => Result.Success(), // Result → Result<User>
_ => Result.Failure("User not found") // Result → Result<User>
};
}
// Can freely convert between different result types
private Result ProcessUserData(Result<User> userResult)
{
// Result<User> → Result
return userResult;
}
### API Design Principles
After optimization, Linger.Results follows these design principles:
1. **Clear API Boundaries**: `Result` class focuses on non-generic operations, `Result<T>` class handles operations with return values
2. **Implicit Conversion Support**: Supports natural conversion from `Result` to `Result<T>`, but avoids unexpected value conversions
3. **Type Safety**: Ensures type correctness at compile time, avoiding runtime errors
4. **Concise Syntax**: Reduces boilerplate code and improves development efficiency
### Important Notes on Implicit Conversion
⚠️ **Important Notes**:
- `Result<T>` → `Result` conversion will **lose value information**, as non-generic Result doesn't store specific values
- In `T` → `Result<T>` conversion, if value is `null`, it automatically creates a failure result
- Accessing `.Value` property on a failed `Result<T>` will throw `InvalidOperationException`
- Recommended to use `.ValueOrDefault` or `.TryGetValue()` for safe value access
```csharp
// Correct usage examples
Result<User> userResult = GetUser(123);
// ✅ Safe value access
if (userResult.TryGetValue(out var user))
{
Console.WriteLine($"User: {user.Name}");
}
// ✅ Using default value
var safeUser = userResult.ValueOrDefault;
// ❌ Dangerous: Will throw exception if result is failed
var user = userResult.Value; // May throw InvalidOperationException
Error Handling
// Use Try method to catch exceptions and convert to results
var result = ResultExtensions.Try(
() => SomeOperationThatMightThrow(),
ex => ex.ToError()
);
Advanced Usage
Combining Multiple Results
// Combine multiple results, succeeds only when all results succeed
var combinedResult = Result.Combine(
ValidateUsername(request.Username),
ValidateEmail(request.Email),
ValidatePassword(request.Password)
);
if (combinedResult.IsSuccess)
{
// All validations passed
return Result.Success(new User { /* ... */ });
}
Custom Error Types
// Define domain-specific error types
public static class UserErrors
{
public static readonly Error NotFound = new("User.NotFound", "User not found");
public static readonly Error InvalidCredentials = new("User.InvalidCredentials", "Invalid username or password");
public static readonly Error DuplicateEmail = new("User.DuplicateEmail", "Email is already in use");
}
// Use custom errors
public Result<User> Authenticate(string username, string password)
{
var user = _repository.FindByUsername(username);
if (user == null)
return Result.Failure(UserErrors.NotFound); // Implicit conversion to Result<User>
if (!ValidatePassword(password, user.PasswordHash))
return Result.Failure(UserErrors.InvalidCredentials); // Implicit conversion to Result<User>
return Result<User>.Success(user);
}
Conditional Branch Processing
public async Task<Result<OrderConfirmation>> ProcessOrder(Order order, CancellationToken cancellationToken)
{
// Chain process order workflow
return await ValidateOrder(order)
.BindAsync(async (validOrder, token) =>
{
// Choose different processing paths based on payment method
if (validOrder.PaymentMethod == PaymentMethod.CreditCard)
return await ProcessCreditCardPayment(validOrder, token);
else if (validOrder.PaymentMethod == PaymentMethod.BankTransfer)
return await ProcessBankTransfer(validOrder, token);
else
return Result<OrderConfirmation>.Failure("Unsupported payment method");
}, cancellationToken);
}
ResultStatus Enum
The ResultStatus enum includes three states:
Ok- Operation succeededNotFound- Resource not foundError- Operation failed
For additional status codes, you can use Error with specific error codes:
// Define domain-specific error codes for different scenarios
public static class StatusErrors
{
public static readonly Error Unauthorized = new("Status.Unauthorized", "Authentication required");
public static readonly Error Forbidden = new("Status.Forbidden", "Access denied");
public static readonly Error Conflict = new("Status.Conflict", "Resource conflict");
public static readonly Error ValidationError = new("Status.ValidationError", "Validation failed");
}
// Usage
public Result<User> GetUser(int id, string token)
{
if (!IsValidToken(token))
return Result.Failure(StatusErrors.Unauthorized);
if (!HasPermission(token, "read:users"))
return Result.Failure(StatusErrors.Forbidden);
// ...
}
Best Practices
Prefer Result and Result<T> over exceptions:
- Return Result for expected errors (validation errors, resource not found, etc.)
- Use exceptions only for truly exceptional situations (program errors, unexpected system failures)
Maintain consistency in return values:
- Service methods should consistently return Result or Result<T>, not mix results and exceptions
- Maintain unified error handling patterns for easier handling by callers
Use meaningful error codes:
- Define domain-specific error constants
- Use structured error codes (like "Category.SubCategory.Error")
Leverage implicit conversion to simplify code:
- In methods returning
Result<T>, you can directly returnResult.Failure()orResult.NotFound() - This makes code more concise and elegant while maintaining type safety
- In methods returning
Use API correctly:
- For generic results, use
Result<T>.Success(),Result<T>.Failure()methods directly - Leverage implicit conversion from
ResulttoResult<T>instead of relying on removed forwarding methods
- For generic results, use
Leverage method chaining:
- Use functional composition instead of traditional conditional statements
- Map, Bind, Tap methods can greatly improve code readability
For Web APIs:
- Combine with Linger.Results.AspNetCore package to convert to HTTP responses
- Use ProblemDetails format to return standardized error responses
Comparison with Exception Handling
| Aspect | Result Pattern | Exception Mechanism |
|---|---|---|
| Visibility | Explicit return type, visible at compile time | Implicit throwing, known only at runtime |
| Performance | Better, no stack capture overhead | Worse, especially in high-frequency call scenarios |
| Composability | Excellent, supports chaining and composition operations | Weak, requires multiple try-catch layers |
| Type Safety | Strong typing, compiler assistance | Weak typing, based on string matching |
| Use Cases | Business logic, expected errors | Program errors, unexpected exceptions |
License
MIT
Migration from ExecuteResult
⚠️ Note:
ExecuteResult,ExecuteResult<T>, andErrorObjare now obsolete and will be removed in a future version. Please migrate toResultandResult<T>.
// Old code (deprecated)
var result = new ExecuteResult(true, "Success");
var resultWithValue = new ExecuteResult<User>(user);
// New code (recommended)
var result = Result.Success();
var resultWithValue = Result.Success(user);
// Accessing errors
// Old: result.Message
// New: result.FirstError.Message or result.Errors
FirstError Property
Both Result and Result<T> now have a FirstError property for convenient access to the first error:
// Old way
var message = result.Errors.FirstOrDefault()?.Message ?? "";
// New way
var message = result.FirstError.Message; // Returns Error.None if no errors
| 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 is compatible. 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 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. |
| .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 is compatible. 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. |
-
.NETFramework 4.7.2
- No dependencies.
-
.NETStandard 2.0
- No dependencies.
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Linger.Results:
| Package | Downloads |
|---|---|
|
Linger.Results.AspNetCore
ASP.NET Core integration for Linger.Results library, providing extension methods to seamlessly convert Result objects to ActionResults with appropriate HTTP status codes. Simplifies API response handling with RFC 7807 support and automatic status mapping. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.6.4 | 58 | 8/16/2026 |
| 1.6.3 | 109 | 8/5/2026 |
| 1.6.2 | 124 | 8/2/2026 |
| 1.6.0 | 126 | 7/25/2026 |
| 1.5.5 | 125 | 7/23/2026 |
| 1.5.4-preview | 106 | 7/21/2026 |
| 1.5.3-preview | 111 | 7/20/2026 |
| 1.5.2-preview | 117 | 7/19/2026 |
| 1.5.1-preview | 110 | 7/15/2026 |
| 1.5.0-preview | 109 | 7/14/2026 |
| 1.4.4-preview | 126 | 6/16/2026 |
| 1.4.3-preview | 113 | 6/15/2026 |
| 1.4.2 | 128 | 5/20/2026 |
| 1.4.1-preview | 126 | 5/12/2026 |
| 1.4.0 | 134 | 5/6/2026 |
| 1.3.3-preview | 116 | 5/5/2026 |
| 1.3.2-preview | 122 | 4/29/2026 |
| 1.3.1-preview | 120 | 4/28/2026 |
| 1.3.0-preview | 112 | 4/27/2026 |
| 1.2.0-preview | 130 | 3/29/2026 |