.NET C# Throw exception best practices

Abhishek Malaviya
4 min readSep 17, 2024

--

In general, global exception handling is essential for managing errors in an application efficiently. However, when discussing best practices for throwing exceptions, here are a few key points to consider:

Use throw; to Rethrow Exceptions:

When rethrowing an exception, use throw; without specifying the exception object. This preserves the original stack trace, which is important for debugging.

Don’t

catch (Exception ex) 
{
// Throw exception object.
// This resets the stack trace.
throw ex;
}

Do

try 
{
// Expected code
}
catch (Exception)
{
// Rethrow exception
throw;
}

If you notice, the exact exception occurs at line number 12.

Handle specific exceptions — Throw the most specific exception possible, instead of general exceptions like Exception .

Common exception types include:

  • ArgumentException,ArgumentNullException,ArgumentOutOfRangeException – for invalid arguments.
  • InvalidOperationException – for inappropriate method calls.
  • NotImplementedException – for unimplemented functionality.
  • CustomException – for domain-specific errors.

Don’t

try 
{
// Business logic
}
catch (Exception ex)
{
// Use to break normal control flow
}

Do

if (input is null) 
{
throw new ArgumentNullException(nameof(input));
}

If we are dealing with APIs, using the result pattern instead of throwing exceptions is indeed a more efficient and recommended approach. This pattern allows for better error handling and avoids the performance costs associated with exceptions.

Handle exceptions in a structured order

When catching exceptions in C#, it’s important to catch more specific exceptions first and then catch more generic exceptions like Exception last. This ensures that specific issues are handled properly before falling back to a general catch-all block.

Don’t

try
{
// Code that might throw an exception
}
catch (Exception ex) // More generic exception at the end
{
// Handle any other exceptions that weren't caught by earlier catch blocks
Console.WriteLine("An unexpected error occurred: " + ex.Message);
}
catch (ArgumentNullException ex)
{
// Handle null argument error
Console.WriteLine("A null argument was passed: " + ex.Message);
}

Once the code is built, you’ll receive a compile-time error.

Do

try
{
// Code that might throw an exception
}
catch (ArgumentNullException ex)
{
// Handle null argument error
Console.WriteLine("A null argument was passed: " + ex.Message);
}
catch (InvalidOperationException ex)
{
// Handle invalid operation error
Console.WriteLine("Invalid operation: " + ex.Message);
}
catch (Exception ex) // More generic exception at the end
{
// Handle any other exceptions that weren't caught by earlier catch blocks
Console.WriteLine("An unexpected error occurred: " + ex.Message);
}

Key Points:

  1. Order matters: Catch the most specific exceptions first, and then more general ones.
  2. Handle accordingly: Each catch block can handle a specific error type with the appropriate logic.
  3. Catch-all: The last catch (Exception ex) acts as a fallback for any unhandled exceptions.
  4. Avoid too many specific catches: Catch only the specific exceptions you expect and can handle meaningfully.

This pattern ensures that your application behaves predictably when exceptions occur and makes it easier to debug by not suppressing specific errors unintentionally.

Consider custom exceptions — In some cases, creating custom exceptions can improve code clarity and help differentiate different error types. For business logic or domain-specific errors, define your own custom exception classes that derive from Exception. This improves clarity in exception handling.

public class CustomException : Exception
{
public CustomException(string message) : base(message)
{
// Custom exception handling
}
}

// Usage
throw new CustomException("customize exception message");

Avoid Swallowing Exceptions — Don’t catch exceptions just to suppress them unless there’s a very specific reason. Always handle exceptions properly by logging them or taking necessary corrective actions.

Don’t

try 
{
// Expected code
}
catch (Exception)
{
// Ignored
}

Do

try 
{
// Expected code
}
catch (Exception ex)
{
// Log exception or take corrective action
Log.Error(ex);

// Rethrow if necessary
throw;
}

Provide Meaningful Exception Messages — Always provide a clear and meaningful error message with the exception to help other developers or future you understand the cause of the problem.

throw new InvalidOperationException("The requested operation cannot be completed because the user is not authenticated. Please log in to proceed.");

Conclusion

In conclusion, proper exception handling in C# ensures your code is more robust, maintainable, and easier to debug. Always use specific exceptions, avoid relying on exceptions for control flow, include meaningful messages, and handle exceptions appropriately by logging or rethrowing them when necessary. Rethrow exceptions using throw; to preserve the stack trace, and only create custom exceptions when absolutely necessary. Overusing exception handling can negatively impact performance. Use exceptions judiciously, reserving them for critical situations.

By adhering to these best practices, you help improve the clarity and reliability of your applications.

Thanks for reading.

--

--

No responses yet