From f5e2226fc36cb2c9c42f27ee024d9cb793ca6507 Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:28:11 -0400 Subject: [PATCH 1/3] Add README with usage examples, API reference links, and feature table Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..d52642b --- /dev/null +++ b/README.md @@ -0,0 +1,179 @@ +# Wolfgang.TryPattern + +[![NuGet](https://img.shields.io/nuget/v/Wolfgang.TryPattern.svg)](https://www.nuget.org/packages/Wolfgang.TryPattern) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) + +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. + +- **GitHub Repository:** [https://github.com/Chris-Wolfgang/Try-Pattern](https://github.com/Chris-Wolfgang/Try-Pattern) +- **API Documentation:** [https://chris-wolfgang.github.io/Try-Pattern/](https://chris-wolfgang.github.io/Try-Pattern/) +- **API Reference:** [https://chris-wolfgang.github.io/Try-Pattern/api/](https://chris-wolfgang.github.io/Try-Pattern/api/) +- **Formatting Guide:** [docs/README-FORMATTING.md](docs/README-FORMATTING.md) +- **Contributing Guide:** [CONTRIBUTING.md](CONTRIBUTING.md) + +--- + +## Installation + +### Via .NET CLI + +```bash +dotnet add package Wolfgang.TryPattern +``` + +### Via Package Manager Console + +```powershell +Install-Package Wolfgang.TryPattern +``` + +--- + +## Quick Start + +### Execute an action safely + +```csharp +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 + +```csharp +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 + +```csharp +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 + +```csharp +var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + +// OperationCanceledException is rethrown, not captured +var result = await Try.RunAsync(() => LongRunningWork(), cts.Token); +``` + +--- + +## Features + +| Feature | Description | +|---------|-------------| +| `Try.Run(Action)` | Execute an action, return `Result` | +| `Try.Run(Func)` | Execute a function, return `Result` with the value | +| `Try.RunAsync(Action, CancellationToken)` | Async action execution with cancellation support | +| `Try.RunAsync(Func>, 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.Success(value)` | Create a successful result with a value | +| `Result.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, empty string if succeeded | +| `Value` | (Generic only) The return value if succeeded, throws `InvalidOperationException` if failed | + +--- + +## Combining Results + +```csharp +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."); +} +``` + +--- + +## Target Frameworks + +| Framework | Version | +|-----------|---------| +| .NET Framework | 4.6.2+ | +| .NET Standard | 2.0 | +| .NET | 8.0, 10.0 | + +--- + +## Building from Source + +```bash +# 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 +``` + +--- + +## License + +This project is licensed under the [MIT License](LICENSE). From 1e9889628d038f5c03e388a5109b418b7fc51a3c Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:58:52 -0400 Subject: [PATCH 2/3] Add real-world examples: database access, repository, Web API, chaining - Database access with Try.Run (sync and async) - Result as a repository return type (CRUD without Try.Run) - Web API controller mapping Result to HTTP responses - Chaining validation with Result.Flatten before execution Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/README.md b/README.md index d52642b..e32908f 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,183 @@ if (Result.AnyFailed(r1, r2, r3)) --- +## Real-World Examples + +### Database access with Try.Run + +Wrap database calls to get a clean `Result` instead of scattered try/catch: + +```csharp +public Result GetCustomerById(int id) +{ + return Try.Run(() => + { + using var connection = new SqlConnection(connectionString); + connection.Open(); + using var command = new SqlCommand("SELECT Id, Name, Email FROM Customers WHERE Id = @Id", connection); + command.Parameters.AddWithValue("@Id", id); + + using var reader = command.ExecuteReader(); + if (!reader.Read()) + throw new InvalidOperationException($"Customer {id} not found."); + + return new Customer + { + Id = reader.GetInt32(0), + Name = reader.GetString(1), + Email = reader.GetString(2) + }; + }); +} + +// Usage +var result = GetCustomerById(42); +if (result.Succeeded) +{ + Console.WriteLine($"Found: {result.Value.Name}"); +} +else +{ + Console.WriteLine($"Lookup failed: {result.ErrorMessage}"); +} +``` + +### Async database access + +```csharp +public async Task>> GetRecentOrdersAsync(int customerId) +{ + return await Try.RunAsync(async () => + { + await using var connection = new SqlConnection(connectionString); + await connection.OpenAsync(); + // ... query and return orders + return orders; + }); +} +``` + +### Using Result as a repository return type + +`Result` and `Result` 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: + +```csharp +public class CustomerRepository +{ + public Result GetById(int id) + { + var customer = dbContext.Customers.Find(id); + + return customer is not null + ? Result.Success(customer) + : Result.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: + +```csharp +[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 + +```csharp +public Result PlaceOrder(OrderRequest request) +{ + // Validate + var validation = Result.Flatten( + ValidateCustomer(request.CustomerId), + ValidateItems(request.Items), + ValidatePayment(request.PaymentMethod) + ); + + if (validation.Failed) + return Result.Failure(validation.ErrorMessage); + + // Execute + return Try.Run(() => + { + var order = orderService.Create(request); + emailService.SendConfirmation(order); + return order; + }); +} +``` + +--- + ## Target Frameworks | Framework | Version | From 3c7c555499bf15e8f058756b4ba34ef6540826df Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Mon, 13 Apr 2026 21:09:10 -0400 Subject: [PATCH 3/3] Address Copilot review: remove stale link, clarify RunAsync cancellation - Remove broken docs/README-FORMATTING.md link (file doesn't exist) - Clarify RunAsync runs sync action on thread pool, not async - Update cancellation example to show cooperative token checking Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e32908f..185eb17 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ A lightweight .NET library that provides a Try/Result pattern for executing acti - **GitHub Repository:** [https://github.com/Chris-Wolfgang/Try-Pattern](https://github.com/Chris-Wolfgang/Try-Pattern) - **API Documentation:** [https://chris-wolfgang.github.io/Try-Pattern/](https://chris-wolfgang.github.io/Try-Pattern/) - **API Reference:** [https://chris-wolfgang.github.io/Try-Pattern/api/](https://chris-wolfgang.github.io/Try-Pattern/api/) -- **Formatting Guide:** [docs/README-FORMATTING.md](docs/README-FORMATTING.md) - **Contributing Guide:** [CONTRIBUTING.md](CONTRIBUTING.md) --- @@ -80,11 +79,19 @@ if (result.Failed) ### 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`. + ```csharp var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); -// OperationCanceledException is rethrown, not captured -var result = await Try.RunAsync(() => LongRunningWork(), cts.Token); +var result = await Try.RunAsync(() => +{ + foreach (var item in largeDataSet) + { + cts.Token.ThrowIfCancellationRequested(); + Process(item); + } +}, cts.Token); ``` --- @@ -95,7 +102,7 @@ var result = await Try.RunAsync(() => LongRunningWork(), cts.Token); |---------|-------------| | `Try.Run(Action)` | Execute an action, return `Result` | | `Try.Run(Func)` | Execute a function, return `Result` with the value | -| `Try.RunAsync(Action, CancellationToken)` | Async action execution with cancellation support | +| `Try.RunAsync(Action, CancellationToken)` | Run a synchronous action on the thread pool with cooperative cancellation | | `Try.RunAsync(Func>, CancellationToken)` | Async function execution with cancellation support | | `Result.Success()` | Create a successful result | | `Result.Failure(message)` | Create a failed result with an error message |