Exception handling is a critical aspect of software development, and C# provides powerful mechanisms to handle and propagate exceptions. However, what if there was an alternative approach that allows us to return exceptions instead of throwing them? Is there a better way than try, catch, throw, handle, maybe rethrow, etc… Can we find an alternative way?
I’ve always found myself dealing with unstructured data as input in the applications I’ve had to build. Whether that’s handling user input or writing complex digital forensics software to recover deleted information for law enforcement. I wanted to take a crack a solving this with some of the things I had dabbled with regarding implicit conversion operators.
I created a class called TriedEx<T>
, that can represent either a value or an exception, providing more control over error handling and control flow. In this blog post, we’ll explore how I use TriedEx<T>
and how it helps me with cleaning up my code while offering more failure details than a simple boolean return value for pass or fail. It might not be your cup of tea, but that’s okay! It’s just another perspective that you can include in your travels.
Before diving into the specifics of TriedEx<T>
, let’s recap TriedEx<T>
. By defining implicit operators, we can effortlessly convert between TriedEx<T>
, T
, and Exception
types. This flexibility allows us to work with TriedEx<T>
instances as if they were the underlying values or exceptions themselves, simplifying our code and improving readability.
And the best part? If we wanted to deal with exceptional cases, we could avoid throwing exceptions altogether.
You can check out this video for more details about implicit operators as well:
A key feature of TriedEx<T>
is the ability to perform pattern matching using the Match
and MatchAsync
methods. These methods enable concise and expressive handling of success and failure cases based on the status of the TriedEx<T>
instance. Let’s look at some examples to see how this works.
In the following example, we’ll assume that we have a method called Divide
that will be able to handle exceptional cases for us. And before you say, “Wait, I thought we wanted to avoid throwing exceptions!” I’m just illustrating some of the functionality of TriedEx<T>
to start:
TriedEx<int> result = Divide(10, 0);
result.Match(
success => Console.WriteLine($"Result: {success}"),
failure => Console.WriteLine($"Error: {failure.Message}")
);
In the above example, the Match
method takes two lambda expressions: one for the success case (where the value is available) and one for the failure case (where the exception is present).
Depending on the state of the TriedEx<int>
instance, the corresponding lambda expression will be executed, allowing us to handle the outcome of the operation gracefully.
If you’d like to be able to return a value after handling the success or error case, there are overrides for that as well:
TriedEx<int> result = Divide(10, 0);
var messageToPrint = result.Match(
success => $"Result: {success}"),
failure => $"Error: {failure.Message}")
);
Console.WriteLine(messageToPrint);
Similarly, the MatchAsync
method provides the same functionality but allows us to work with asynchronous operations. This is particularly useful when dealing with I/O operations or remote calls that may take some time to complete.
Another feature of TriedEx<T>
is its TriedEx<T>
instance in a convenient and readable way. Let’s take a look at an example:
TriedEx<string> result = ProcessInput(userInput);
var (success, value, error) = result;
if (success)
{
Console.WriteLine($"Processed value: {value}");
}
else
{
Console.WriteLine($"Error occurred: {error.Message}");
}
In this example, the deconstruction pattern is used to unpack the success status, value, and error from the TriedEx<string>
instance. By leveraging the deconstruction syntax, we can access these components and perform the appropriate actions based on the outcome of the operation.
If you’re interested in seeing practical examples of how this class can be used, check out the full blog post! More code examples are included to walk you through how this design has helped me clean up my exception-handling code.
Remember to subscribe to my newsletter for a quick 5-minute read every weekend! This includes content summaries, other learning resources, and community spotlights as well. Thank you for your support!
Also published here as a full article.