Wolfgang.TryPattern
0.5.0
Prefix Reserved
dotnet add package Wolfgang.TryPattern --version 0.5.0
NuGet\Install-Package Wolfgang.TryPattern -Version 0.5.0
<PackageReference Include="Wolfgang.TryPattern" Version="0.5.0" />
<PackageVersion Include="Wolfgang.TryPattern" Version="0.5.0" />
<PackageReference Include="Wolfgang.TryPattern" />
paket add Wolfgang.TryPattern --version 0.5.0
#r "nuget: Wolfgang.TryPattern, 0.5.0"
#:package Wolfgang.TryPattern@0.5.0
#addin nuget:?package=Wolfgang.TryPattern&version=0.5.0
#tool nuget:?package=Wolfgang.TryPattern&version=0.5.0
Wolfgang.TryPattern
A lightweight .NET library that provides a Try/Result pattern for executing actions and functions with automatic exception handling. Instead of try/catch blocks scattered throughout your code, wrap operations in Try.Run() and get back a Result indicating success or failure.
📦 Installation
Via .NET CLI
dotnet add package Wolfgang.TryPattern
Via Package Manager Console
Install-Package Wolfgang.TryPattern
📄 License
This project is licensed under the MIT License.
📚 Documentation
- GitHub Repository: https://github.com/Chris-Wolfgang/Try-Pattern
- API Documentation: https://chris-wolfgang.github.io/Try-Pattern/
- API Reference: https://chris-wolfgang.github.io/Try-Pattern/api/
- Contributing Guide: CONTRIBUTING.md
- Changelog: CHANGELOG.md
🚀 Quick Start
Execute an action safely
using Wolfgang.TryPattern;
var result = Try.Run(() => File.Delete("temp.txt"));
if (result.Succeeded)
{
Console.WriteLine("File deleted.");
}
else
{
Console.WriteLine($"Failed: {result.ErrorMessage}");
}
Execute a function and get the return value
var result = Try.Run(() => int.Parse("42"));
if (result.Succeeded)
{
Console.WriteLine($"Parsed value: {result.Value}");
}
else
{
Console.WriteLine($"Parse failed: {result.ErrorMessage}");
}
Async support
var result = await Try.RunAsync(async () =>
{
var response = await httpClient.GetStringAsync("https://example.com");
return response;
});
if (result.Failed)
{
Console.WriteLine($"Request failed: {result.ErrorMessage}");
}
Cancellation support
Try.RunAsync(Action, CancellationToken) runs a synchronous action on the thread pool via Task.Run. The token cancels the task before it starts; once running, cancellation is cooperative (your code must check the token). OperationCanceledException is always rethrown, never captured as a Result.
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var result = await Try.RunAsync(() =>
{
foreach (var item in largeDataSet)
{
cts.Token.ThrowIfCancellationRequested();
Process(item);
}
}, cts.Token);
✨ Features
| Feature | Description |
|---|---|
Try.Run(Action) |
Execute an action, return Result |
Try.Run<T>(Func<T>) |
Execute a function, return Result<T> with the value |
Try.RunAsync(Action, CancellationToken) |
Run a synchronous action on the thread pool with cooperative cancellation |
Try.RunAsync<T>(Func<Task<T>>, CancellationToken) |
Async function execution with cancellation support |
Result.Success() |
Create a successful result |
Result.Failure(message) |
Create a failed result with an error message |
Result<T>.Success(value) |
Create a successful result with a value |
Result<T>.Failure(message) |
Create a failed result |
Result.Flatten(results) |
Combine multiple results into one |
Result.AnyFailed(results) |
Check if any results failed |
Result.AllSucceeded(results) |
Check if all results succeeded |
Result Properties
| Property | Description |
|---|---|
Succeeded |
true if the operation completed successfully |
Failed |
true if the operation failed (inverse of Succeeded) |
ErrorMessage |
The error message if failed, null if succeeded |
Value |
(Generic only) The return value if succeeded, throws InvalidOperationException if failed |
🔗 Combining Results
var r1 = Try.Run(() => ValidateName(name));
var r2 = Try.Run(() => ValidateEmail(email));
var r3 = Try.Run(() => ValidateAge(age));
// Flatten into a single result
var combined = Result.Flatten(r1, r2, r3);
if (combined.Failed)
{
// ErrorMessage contains all failures separated by newlines
Console.WriteLine(combined.ErrorMessage);
}
// Or check individually
if (Result.AnyFailed(r1, r2, r3))
{
Console.WriteLine("At least one validation failed.");
}
📖 Real-World Examples
Database access with Try.RunAsync
Wrap database calls to get a clean Result instead of scattered try/catch:
// On .NET 5+ Try.RunAsync<T> returns Result<T?> (the nullable-context signature),
// so this method's return type is Result<Customer?>. On net462 / netstandard2.0
// it's Result<Customer> instead. Callers use `result.Value!` after checking
// `result.Succeeded` — the Value is non-null on the success path by contract.
public async Task<Result<Customer?>> GetCustomerByIdAsync(int id, CancellationToken token = default)
{
return await Try.RunAsync(async () =>
{
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(token);
await using var command = new SqlCommand("SELECT Id, Name, Email FROM Customers WHERE Id = @Id", connection);
command.Parameters.AddWithValue("@Id", id);
await using var reader = await command.ExecuteReaderAsync(token);
if (!await reader.ReadAsync(token))
throw new InvalidOperationException($"Customer {id} not found.");
return new Customer
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Email = reader.GetString(2)
};
}, token);
}
// Usage
var result = await GetCustomerByIdAsync(42);
if (result.Succeeded)
{
Console.WriteLine($"Found: {result.Value!.Name}");
}
else
{
Console.WriteLine($"Lookup failed: {result.ErrorMessage}");
}
Async query returning a list
// Same nullable-context signature — Result<List<Order>?> on .NET 5+.
public async Task<Result<List<Order>?>> GetRecentOrdersAsync(int customerId, CancellationToken token = default)
{
return await Try.RunAsync(async () =>
{
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(token);
// ... query and return orders
return orders;
}, token);
}
Using Result as a repository return type
Result and Result<T> work well as return types from repositories and service layers. You don't need Try.Run() to create them -- use the static factory methods directly:
// On .NET 5+ Result<T>.Success / Failure return Result<T?>; declare
// the method return type accordingly. On net462 / netstandard2.0
// the same code compiles with Result<Customer>.
public class CustomerRepository
{
public Result<Customer?> GetById(int id)
{
var customer = dbContext.Customers.Find(id);
return customer is not null
? Result<Customer>.Success(customer)
: Result<Customer>.Failure($"Customer with ID {id} not found.");
}
public Result Save(Customer customer)
{
if (string.IsNullOrWhiteSpace(customer.Name))
return Result.Failure("Customer name is required.");
if (string.IsNullOrWhiteSpace(customer.Email))
return Result.Failure("Customer email is required.");
dbContext.Customers.Update(customer);
dbContext.SaveChanges();
return Result.Success();
}
public Result Delete(int id)
{
var customer = dbContext.Customers.Find(id);
if (customer is null)
return Result.Failure($"Customer with ID {id} not found.");
dbContext.Customers.Remove(customer);
dbContext.SaveChanges();
return Result.Success();
}
}
Using Result in a Web API controller
Return Result from your service layer and map it to HTTP responses:
[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
private readonly CustomerRepository _repository;
public CustomersController(CustomerRepository repository) => _repository = repository;
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
var result = _repository.GetById(id);
return result.Succeeded
? Ok(result.Value!)
: NotFound(new { error = result.ErrorMessage });
}
[HttpPut("{id}")]
public IActionResult Update(int id, CustomerDto dto)
{
var lookup = _repository.GetById(id);
if (lookup.Failed)
return NotFound(new { error = lookup.ErrorMessage });
lookup.Value!.Name = dto.Name;
lookup.Value!.Email = dto.Email;
var saveResult = _repository.Save(lookup.Value!);
return saveResult.Succeeded
? NoContent()
: BadRequest(new { error = saveResult.ErrorMessage });
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
var result = _repository.Delete(id);
return result.Succeeded
? NoContent()
: NotFound(new { error = result.ErrorMessage });
}
}
Chaining operations with validation
// Result<Order?> on .NET 5+, Result<Order> on legacy TFMs — see the
// database example above for the nullable-context rationale.
public Result<Order?> PlaceOrder(OrderRequest request)
{
// Validate
var validation = Result.Flatten(
ValidateCustomer(request.CustomerId),
ValidateItems(request.Items),
ValidatePayment(request.PaymentMethod)
);
if (validation.Failed)
return Result<Order>.Failure(validation.ErrorMessage!);
// Execute
return Try.Run(() =>
{
var order = orderService.Create(request);
emailService.SendConfirmation(order);
return order;
});
}
🎯 Supported Frameworks
This library targets:
- .NET Framework: 4.6.2
- .NET Standard: 2.0
- .NET: 8.0, 10.0
See the NuGet package page for the authoritative per-TFM compatibility matrix.
🔍 Code Quality & Static Analysis
This project enforces strict code quality standards through 8 specialized analyzers, a <TreatWarningsAsErrors>true</TreatWarningsAsErrors> Release gate, and SourceLink-embedded provenance for debugger step-into.
Analyzers in Use
- Microsoft.CodeAnalysis.NetAnalyzers — Built-in .NET analyzers for correctness and performance
- Roslynator.Analyzers — Advanced refactoring and code quality rules
- AsyncFixer — Async/await best practices and anti-pattern detection
- Microsoft.VisualStudio.Threading.Analyzers — Thread safety and async patterns
- Microsoft.CodeAnalysis.BannedApiAnalyzers — Prevents usage of banned synchronous APIs (see
BannedSymbols.txt) - Meziantou.Analyzer — Comprehensive code quality rules
- SonarAnalyzer.CSharp — Industry-standard code analysis
- Microsoft.CodeAnalysis.PublicApiAnalyzers — Tracks the public API surface via
PublicAPI.Shipped.txt/PublicAPI.Unshipped.txt; surfaces additions/removals at compile time as a breaking-change review gate
🛠️ Building from Source
# Clone the repository
git clone https://github.com/Chris-Wolfgang/Try-Pattern.git
cd Try-Pattern
# Restore and build
dotnet restore
dotnet build --configuration Release
# Run tests
dotnet test --configuration Release
# Format code
dotnet format
# Verify formatting
dotnet format --verify-no-changes
🔐 Verify the build
Every release attaches a reproducible-build-manifest.json listing the SHA-256 of every shipped .nupkg, .snupkg, and the lib/<tfm>/*.dll files inside them, plus the SDK version and commit SHA. Any third party can rebuild from source at the release tag and confirm the produced binaries match what NuGet.org served.
See docs/REPRODUCIBLE-BUILD.md for the guarantee we make, the step-by-step verification procedure, and how to file a reproducibility-discrepancy report.
🤝 Contributing
Contributions are welcome — see CONTRIBUTING.md for the development workflow, coding conventions, and PR checklist.
| 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 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 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 is compatible. 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. |
-
.NETFramework 4.6.2
- No dependencies.
-
.NETStandard 2.0
- No dependencies.
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Wolfgang.TryPattern:
| Package | Downloads |
|---|---|
|
Wolfgang.D20.Dice
Contains class for generating random numbers by simulating rolling of dice with various number of sides |
GitHub repositories
This package is not used by any popular GitHub repositories.