C# Throw Exception: Syntax and Correct Usage
c# throw exception: Learn how to throw exceptions in C# with correct syntax, custom exception types, rethrowing, and common pitfalls to avoid.
The throw statement is the core mechanism for raising exceptions in C#. Understanding how to use c# throw exception correctly is essential for robust error handling. The basic syntax is straightforward: you follow the throw keyword with an exception instance, as shown below.
throw new InvalidOperationException("The operation is not valid in this state.");
The throw keyword is followed by an expression that evaluates to an instance of Exception or a derived type. The exception is then propagated up the call stack until a matching catch block handles it, or the program terminates.
The Basic Syntax of the throw Statement
The simplest form of the throw statement creates a new exception and raises it. You can also throw a previously caught exception inside a catch block, but that has different semantics, as discussed later. The throw statement can be used anywhere in a method, including inside conditional blocks, loops, and helper methods.
public void ValidateAge(int age) { if (age < 0) { throw new ArgumentOutOfRangeException(nameof(age), "Age cannot be negative."); } }
When you throw an exception, the runtime captures the current stack trace and associates it with the exception object. This stack trace is critical for debugging because it shows the exact call path that led to the failure.
When to Throw an Exception
Throwing an exception is appropriate when a method cannot fulfill its contract. For example, if a method expects a non-null argument and receives null, throwing an ArgumentNullException is clearer than silently returning a default value. Similarly, if a method cannot parse input, throwing a FormatException or a custom exception communicates the failure precisely.
Exceptions should be used for exceptional conditions, not for normal control flow. If a condition is expected and handled regularly, returning a result object or using a Try pattern is often better. The .NET framework itself uses TryParse methods to avoid exception overhead when invalid input is common.
if (int.TryParse(input, out int result)) { // Use result } else { // Handle invalid input without throwing }
Creating and Throwing Custom Exceptions
When the built-in exception types do not describe the failure accurately, create a custom exception class. A custom exception should derive from Exception (or a more specific base like ApplicationException or InvalidOperationException). The standard pattern includes at least three constructors:
public class OrderNotFoundException : Exception { public OrderNotFoundException() { } public OrderNotFoundException(string message) : base(message) { } public OrderNotFoundException(string message, Exception inner) : base(message, inner) { } }
This pattern matches the common exception constructor conventions in .NET and makes your exception consistent with the rest of the framework. When throwing the custom exception, you can pass a descriptive message and optionally an inner exception that caused the problem.
throw new OrderNotFoundException($"Order {orderId} was not found.");
Custom exceptions give callers a precise type to catch. They also allow you to add additional properties, such as an order ID, that can help with diagnostics.
Rethrowing an Exception Without Losing the Stack Trace
Inside a catch block, you often need to log the exception, perform cleanup, and then let the exception continue propagating. The correct way to rethrow the same exception is to use throw; without an exception object:
try { DoWork(); } catch (Exception ex) { Log(ex); throw; // preserves the original stack trace }
Using throw ex; instead resets the stack trace to the point of the throw statement, losing the original call stack. This makes debugging significantly harder. The throw; form is the recommended way to rethrow the current exception.
In some scenarios, such as when capturing an exception in one thread and rethrowing it in another, you need ExceptionDispatchInfo.Capture(ex).Throw() to preserve the original stack trace. This is common in async or parallel code where the exception is observed at a later point.
ExceptionDispatchInfo.Capture(ex).Throw();
This technique preserves the original stack trace even across asynchronous boundaries.
Common Mistakes and How to Avoid Them
A frequent mistake is throwing new Exception() with a generic message. This makes it difficult for callers to handle specific failure types. Prefer the most specific built-in exception or a custom exception.
Another mistake is using throw ex; inside a catch block. As explained above, this destroys the original stack trace. Always use throw; unless you intentionally want to change the exception.
Throwing exceptions in finally blocks is also problematic. If a finally block throws an exception while another exception is already propagating, the original exception is lost. Keep finally blocks free of operations that can throw, or handle exceptions locally.
finally { // Avoid throwing here; use try/catch inside if necessary }
Performance and Maintainability Considerations
Throwing an exception is not free. The runtime has to collect stack trace information and unwind the stack. For performance-sensitive paths, avoid throwing exceptions for expected conditions. Use the Try pattern or return result objects when the failure is a normal possibility.
From a maintainability perspective, throwing exceptions with clear messages and appropriate types makes the code easier to reason about. When a method can fail in multiple ways, define distinct exception types so callers can catch and handle each case separately. This reduces the need for broad catch (Exception) blocks that hide the actual problem.
Also consider that exception filters (when clauses) allow you to catch only exceptions that meet a condition. This can reduce the number of catch blocks and make the intent clearer:
catch (OrderNotFoundException ex) when (ex.OrderId == orderId) { // handle only the relevant order }
This is a useful technique when the same exception type can occur in different contexts. It lets you apply targeted handling without losing the ability to catch other instances of the same type elsewhere.