Working with SharpAndRusty Part 3: Asynchronous Operations and Integrating with Exceptions

Introduction

In Part 1 and Part 2, we established the core principles of using Result<T, E> for type-safe error handling and demonstrated how to compose operations using Map, Bind, and the LINQ query syntax.

Modern C# development is dominated by asynchronous operations (using async/await) for I/O-bound tasks. Furthermore, most applications must interface with older or third-party code that still relies on throwing exceptions for error reporting.

This final part addresses these critical challenges, showing how Esox.SharpAndRusty provides full async integration and utility methods to safely bridge the gap between Result-based code and exception-throwing methods.


⚡ Full Asynchronous Support

A key feature of a production-ready library is its seamless integration with the asynchronous programming model. SharpAndRusty offers async-specific versions of its core composition methods, ensuring that you can maintain a clean, result-based pipeline even when dealing with Task<T> returns.

Async Composition Methods

The library provides Async suffixes for the core functional methods:

  • MapAsync: Transforms the success value using an asynchronous mapper function.
  • BindAsync: Chains a fallible operation that returns a Task<Result<U, E>>.
  • TapAsync / InspectAsync: Executes an asynchronous side effect (like logging or metrics) without altering the result.
  • OrElseAsync: Provides an asynchronous fallback operation in case of failure.

This complete suite ensures you never have to break out of the Result pipeline to handle a Task.

Example: Asynchronous Order Processing

Consider an order processing workflow that involves asynchronous steps like fetching user data, processing payment, and sending an email—all of which can fail:

C#

public async Task<Result<OrderConfirmation, string>> ProcessOrderAsync(OrderRequest request)
{
    // Validate is synchronous, returns Result<Order, string>
    return await ValidateOrder(request)
        // Bind to an async operation that fetches user data
        .BindAsync(order => GetUserAsync(order.UserId))
        // Bind to an async operation that processes payment
        .BindAsync(async user => await ProcessPaymentAsync(user, request))
        // InspectAsync executes an async side effect (logging)
        .InspectAsync(payment => Logger.Log($"Payment done for {payment.TransactionId}"))
        // Bind to the final async step (sends email)
        .BindAsync(payment => SendConfirmationEmailAsync(payment.Order))
        // OrElse for a final fallback if everything above failed
        .OrElse(error =>
        {
            // Fallback: create pending order for manual review
            return CreatePendingOrder(request, error);
        });
}

The resulting code is a clear, sequential pipeline where errors are explicitly handled and automatically propagated across asynchronous boundaries.


🛑 Bridging the Gap: Exception Handling Helpers

While Esox.SharpAndRusty advocates for avoiding exceptions for control flow, the reality is that you often need to call existing methods that throw exceptions. To safely integrate this legacy code into your Result pipeline, the library provides the static Try and TryAsync factory methods.

The Result<T, E>.Try Method

The synchronous Try method executes an operation within a hidden try...catch block. If an exception is caught, it converts that exception into your specified error type (E).

C#

// Synchronous operation that might throw a FormatException
var result = Result<int, string>.Try(
    operation: () => int.Parse("42"), // If input was "abc", it throws
    errorHandler: ex => $"Parse failed: {ex.Message}" // Converts Exception to string error
);
// Result: Ok(42)

// File I/O example
var fileContent = Result<string, string>.Try(
    operation: () => File.ReadAllText("config.json"),
    errorHandler: ex => $"Failed to read config: {ex.Message}"
);

The Result<T, E>.TryAsync Method

Similarly, TryAsync wraps an asynchronous operation (Task<T>) that might throw an exception.

C#

// Async operation that might throw a HttpRequestException
var asyncResult = await Result<User, string>.TryAsync(
    operation: async () => await httpClient.GetUserAsync(userId),
    errorHandler: ex => $"HTTP request failed: {ex.Message}"
);

These helpers allow you to quickly and safely wrap external dependencies, converting unpredictable exceptions into predictable, type-safe Result errors, which can then be seamlessly used in the rest of your functional pipeline.


⚖️ Final Conclusion: The Production-Ready Choice

Throughout this series, we have demonstrated that Esox.SharpAndRusty is more than just a conceptual implementation of Result<T, E>; it is a production-ready framework for modern C# development.

By adopting this library, you gain:

  1. Explicitness and Type Safety: Method signatures communicate potential failures, enforced by the compiler.
  2. Composability: Functional combinators (Map, Bind) and LINQ integration allow for clear, chained logic.
  3. Performance: Built as a readonly struct, it avoids exception overhead for expected failure cases.
  4. Modern Compatibility: Full async integration and exception handling helpers (Try/TryAsync) ensure it works flawlessly with both new and legacy C# code.

The choice to use result types is a commitment to reliability. By choosing Esox.SharpAndRusty, you equip your C# projects with a powerful, idiomatic toolset that eliminates ambiguity, increases testability, and transforms error handling from an afterthought into an integral, and predictable, part of your application’s logic.

Ready to start? Clone the repository and run the comprehensive test suite to see its robustness in action!

Bash

# Clone the repository
git clone https://github.com/snoekiede/Esox.SharpAndRusty.git

# Run tests (123 unit tests)
dotnet test
The Code Nomad
The Code Nomad
Articles: 162

Leave a Reply

Your email address will not be published. Required fields are marked *