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
                    
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="Wolfgang.TryPattern" Version="0.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Wolfgang.TryPattern" Version="0.5.0" />
                    
Directory.Packages.props
<PackageReference Include="Wolfgang.TryPattern" />
                    
Project file
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 Wolfgang.TryPattern --version 0.5.0
                    
#r "nuget: Wolfgang.TryPattern, 0.5.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 Wolfgang.TryPattern@0.5.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=Wolfgang.TryPattern&version=0.5.0
                    
Install as a Cake Addin
#tool nuget:?package=Wolfgang.TryPattern&version=0.5.0
                    
Install as a Cake Tool

Wolfgang.TryPattern

NuGet NuGet downloads PR build Release OSSF Scorecard License: MIT .NET GitHub

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


🚀 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

  1. Microsoft.CodeAnalysis.NetAnalyzers — Built-in .NET analyzers for correctness and performance
  2. Roslynator.Analyzers — Advanced refactoring and code quality rules
  3. AsyncFixer — Async/await best practices and anti-pattern detection
  4. Microsoft.VisualStudio.Threading.Analyzers — Thread safety and async patterns
  5. Microsoft.CodeAnalysis.BannedApiAnalyzers — Prevents usage of banned synchronous APIs (see BannedSymbols.txt)
  6. Meziantou.Analyzer — Comprehensive code quality rules
  7. SonarAnalyzer.CSharp — Industry-standard code analysis
  8. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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.

Version Downloads Last Updated
0.5.0 345 9/27/2026
0.4.1 6,198 8/20/2026
0.4.0 733 7/18/2026
0.3.4 1,004 6/30/2026
0.3.3 147 6/30/2026
0.3.2 129 6/27/2026
0.3.1 665 5/10/2026
0.3.0 161 5/3/2026
0.2.0 852 1/18/2026
0.1.0 132 1/17/2026
0.1.0-rc1 133 1/17/2026