Take advantage of the Result pattern, the Try-Parse pattern, and other techniques to make error handling more explicit and to avoid writing exception handling code in your .NET applications. Credit: Tee11 / Shutterstock Exception handling has been used in programming languages for decades to handle run-time errors in applications. However, throwing exceptions is costly in terms of performance, so we should avoid them in our code. This article discusses a few strategies we can use to avoid exceptions in C#. To use the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here. Create a .NET Core console application project in Visual Studio First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2022 is installed in your system, follow the steps outlined below to create a new .NET Core console application project. Launch the Visual Studio IDE. Click on “Create new project.” In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed. Click Next. In the “Configure your new project” window, specify the name and location for the new project. Click Next. In the “Additional information” window shown next, choose “.NET 8.0” as the framework version you would like to use. Click Create. We’ll use this .NET Core console application project to examine several ways we can avoid exceptions in the subsequent sections of this article. Why should we avoid exceptions? In an application, throwing, re-throwing, and catching exceptions is a costly affair in terms of application performance because of the high processing overhead. Moreover, if you overuse exceptions in your application’s source code, it will make your source code more difficult to read and maintain. When an exception is thrown in .NET, the normal running of your application is interrupted by a three-step process the runtime uses to handle the exception. Here is what happens: An exception object is createdThe runtime creates an exception object that contains the details of the exception such as the stack trace, error message, and the type of the exception that has been thrown (iArithmeticException, DivideByZeroException, IndexOutOfRangeException, StackOverflowException, etc.). The stack is unwoundIn this phase, the runtime searches for a matching try-catch block that can handle the exception that has occurred. This process starts from the point where the exception has been thrown and moves up the call stack until the runtime determines the right exception-handling code. The exception is handledIf the runtime finds the right code to handle the exception, the source code inside the try-catch block will be executed. If the try-catch block contains a finally block, it will be executed after the try-catch block has executed. If the right exception-handling code is not available, the exception is considered to be unhandled. In this case, the control is propagated up the call stack until it reaches the entry point of the application. At this point the runtime terminates the application and displays the appropriate error message. As you can see, handling exceptions involves a large amount of overhead and could significantly impact the performance of your application. Let’s us now examine some handy strategies we can use to avoid exceptions in C#. Avoid exceptions using the Result pattern The Result pattern is a good general strategy we can use to avoid exception handling code in your applications. You can implement this pattern in your C# code using a generic class that encapsulates the outcome (i.e., success or failure) of a particular operation. When an error has occurred in your application, you can use this pattern to return a result object in lieu of throwing an exception. This helps write code that is simple, clean, and easy to maintain. Consider the following class named Result that represents the result of an operation. public class Result { public bool IsSuccess { get; } public T Value { get; } public string ErrorMessage { get; } private Result(T value, string errorMessage, bool isSuccess) { Value = value; ErrorMessage = errorMessage; IsSuccess = isSuccess; } public static Result Success(T value) => new Result(value, null, true); public static Result Failure(string errorMessage) => new Result(default(T), errorMessage, false); } The Result class contains two methods, namely Success and Failure. While the former returns a value, the latter returns an error message. The following method shows how you can use the Result class to avoid exceptions in your code. public Result DivideNumbers(int x, int y) { if (x == 0) { return Result.Failure("Division by zero is not allowed."); } int result = x / y; return Result.Success(result); } The following code snippet shows how you might use the Result pattern with the DivideNumbers method. var result = Test.DivideNumbers(15, 5); if (result.IsSuccess) Console.WriteLine($"The result is: {result.Value}"); else Console.WriteLine($"Error occurred: {result.ErrorMessage}"); Avoid exceptions using the Try-Parse pattern The Try-Parse pattern is another great way to avoid exceptions in your application. In C#, the Try-Parse pattern is represented using the TryParse method, which converts a data type into another and returns a Boolean value. If the parsing process succeeds, then the output is true, false otherwise. You can take advantage of this pattern to avoid exceptions in your code while converting data types as shown in the code snippet given below. String str = "1000"; Boolean result = Int32.TryParse(str, out int n); if (result == true) Console.WriteLine($"{n}"); else Console.WriteLine("Error in conversion"); Avoid exceptions by calling Try* methods When converting a data type to another, you should take advantage of the Try-Parse pattern as shown above. Further, note that there are other Try methods such as TryGetValue. These methods return false if unsuccessful and return the result of a successful operation using an out parameter. The following code snippet shows how this can be accomplished. int myValue; if (dictionary.TryGetValue("MyKey", out myValue)) { //TryGetValue is successful so you can proceed as usual } else { //TryGetValue is unsuccessful so display //or return an appropriate error message } Avoid exceptions by handling common conditions You can also avoid exceptions by handling conditions that might trigger an exception at runtime. For example, you should check if a connection object is null or already closed before closing a database connection. This technique is shown in the code snippet given below. if (connection!= null && connection.State != ConnectionState.Closed) { connection.Close(); } If you don’t check your database connection instances for null, or if you explicitly close an already-closed connection using a call to the Close method, you might encounter an InvalidOperationException. The following code snippet shows how you should handle InvalidOperationException in your code. try { connection.Close(); } catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); } Excess usage of exceptions can degrade your application’s performance. Therefore you should take measures to avoid handling exceptions in your code when they can easily be replaced by logic. A rule of thumb is to check for common error conditions in your code so that you can avoid exceptions. In all other cases, you can take advantage of the Result pattern that provides a structured method for handling errors. Finally, remember to use exceptions only in exceptional cases, i.e., where you already know an error might occur. Never use exceptions to manage control flow in an application. Related content news Spin 3.0 supports polyglot development using Wasm components Fermyon’s open source framework for building server-side WebAssembly apps allows developers to compose apps from components created with different languages. By Paul Krill Nov 18, 2024 2 mins Microservices Serverless Computing Development Libraries and Frameworks how-to How to use DispatchProxy for AOP in .NET Core Take advantage of the DispatchProxy class in C# to implement aspect-oriented programming by creating proxies that dynamically intercept method calls. By Joydip Kanjilal Nov 14, 2024 7 mins Microsoft .NET C# Development Libraries and Frameworks news Microsoft’s .NET 9 arrives, with performance, cloud, and AI boosts Cloud-native apps, AI-enabled apps, ASP.NET Core, Aspire, Blazor, MAUI, C#, and F# all get boosts with the latest major rev of the .NET platform. By Paul Krill Nov 12, 2024 4 mins C# Generative AI Microsoft .NET feature Can Wasm replace containers? WebAssembly revolutionized browser apps, and promises to upend the server stack. How will it impact containers and Kubernetes? Six experts weigh in. By Bill Doerrfeld Nov 11, 2024 12 mins Containers Kubernetes Cloud Native Resources Videos