Error Handling in C#: try/catch and Advanced Techniques

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Error Handling in C#: From try/catch to Professional Techniques



In the world of industrial development with C# and .NET, error handling is not just the try/catch construct. It is a whole philosophy that determines the reliability, readability, and fault tolerance of your code. At an advanced level, a developer must not only catch exceptions but also competently design them, filter them, log them, and restore the application state. In this article, we will break down the key aspects of error handling in C# that will help you write higher quality and more professional code.



1. Deeper into try/catch/finally: Resource and Context Management



The basic try-catch-finally construct is familiar to everyone, but let's look at it from a different angle. The main purpose of the finally block is to guarantee the release of resources, even if an exception occurs. In modern versions of C#, using is more often used for this, but finally remains indispensable when working with unmanaged resources or complex completion logic.



public void ProcessFile(string path){    FileStream? fileStream = null;    try    {        fileStream = File.OpenRead(path);        // File processing logic    }    catch (FileNotFoundException ex)    {        // Specific handling: file not found        Log.Error($"File {path} not found: {ex.Message}");        throw; // Rethrow the exception, preserving the call stack    }    catch (UnauthorizedAccessException)    {        Log.Warn($"No access to file: {path}");        // Can perform an alternative action without rethrowing the exception        return;    }    finally    {        // Guaranteed resource release        fileStream?.Dispose();    }}


Note the use of throw; (without an argument) — this is a key point. Unlike throw ex;, the throw; construct preserves the original call stack, which is critical for debugging. Never use throw ex; in real projects, as it destroys information about the location of the original error.



2. Custom Exceptions: Designing a Domain Error Model



In large applications, standard .NET exceptions are often insufficient. Creating your own exception classes allows you to accurately convey the error context and simplifies handling at higher levels. A good custom exception should be serializable, contain a meaningful message, and carry additional information (e.g., an entity identifier).



[Serializable]public class OrderProcessingException : Exception{    public int OrderId { get; }    public string FaultyStep { get; }

public OrderProcessingException(int orderId, string faultyStep, string message) : base(message) { OrderId = orderId; FaultyStep = faultyStep; }

public OrderProcessingException(int orderId, string faultyStep, string message, Exception inner) : base(message, inner) { OrderId = orderId; FaultyStep = faultyStep; }

// Constructor for deserialization protected OrderProcessingException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { OrderId = info.GetInt32(nameof(OrderId)); FaultyStep = info.GetString(nameof(FaultyStep)) ?? string.Empty; }

public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(OrderId), OrderId); info.AddValue(nameof(FaultyStep), FaultyStep); }}

// Usagepublic void ValidateOrder(int orderId){ try {

Blogs

Book Recommendations