Manipulação de Erros e Exceções em C# (.NET): Guia Detalhado
What is it and why? (a real-life analogy)
Imagine you are a head chef in a kitchen. You are preparing a complex dish according to a recipe. Everything is going well until suddenly... you run out of flour. Or the mixer breaks. Or you accidentally oversalt the soup. What do you do in such a situation? You don't abandon the entire kitchen and go home. You handle the problem: send an assistant for flour, fix the mixer, or add potatoes to the oversalted soup.
The same thing happens in programming. Your program is a recipe. It executes step by step. But sometimes something goes wrong: the user entered a letter instead of a number, the file that needs to be opened is not found, the internet connection is lost. If the program doesn't know what to do with such a situation, it "crashes" — terminates abruptly, showing a confusing error.
Error and exception handling is your action plan for "kitchen disasters." You tell the program in advance: "If a problem of this type occurs, do this instead of crashing." This makes your program reliable and user-friendly.
Important: An error is not always the end of the world. Sometimes it's just a signal: "Hey, something went wrong here, let's try differently or at least politely inform the user."
Theory: how it works internally
In C# (and .NET in general), errors are called exceptions. Why exceptions? Because it is an exceptional situation that disrupts the normal flow of program execution.
When an error occurs in the code (e.g., division by zero), the .NET runtime does the following:
- Creates an exception object. This object is an instance of a class that inherits from
System.Exception. Inside this object, all information about the error is stored: its type, the message text, the location in the code where it occurred (call stack). - "Throws" the exception. The runtime stops the current code block and starts looking for a "trap" for this exception.
- Searches for a handler (catch). It goes up the call stack — from the current method to the method that called it, then to the method that called that one, and so on. If it finds a
catchblock that can handle this type of exception, it transfers control to it. - If no handler is found... The program terminates abruptly. The user sees a scary error window. This is what we want to avoid.
Imagine a multi-story building. On each floor (in each method), there is a security guard (a try-catch block). If a fire (exception) breaks out on the 5th floor, the guard on the 5th floor can put it out. If not, the fire rises to the 4th floor, then the 3rd, and so on down to the first floor. If there is no one there either, the building burns down (the program crashes).
Key idea: Exceptions are not just errors. They are a mechanism for passing error information up the call chain until someone takes responsibility for handling it.
Syntax in C# (.NET): all forms and variants
In C#, three keywords are used for working with exceptions: try, catch, and finally. They form a construct that looks like this:
try
{
// Here is the code that might throw an exception.
// For example, opening a file, division, working with the network.
}
catch (ExceptionType variable)
{
// This block executes if an exception of the specified type occurred in try.
// Here we "handle" the error: log it, show a message, try to fix it.
}
finally
{
// This block ALWAYS executes, regardless of whether an exception occurred or not.
// Here we release resources: close files, database connections.
}
Let's break down each element in detail.
1. The try block
This is the "danger zone." You put code here that you think might throw an exception. Without try, the program will simply crash. With try, you give it a chance to be saved.
2. The catch block
This is the "lifeboat." You can have multiple catch blocks for different types of exceptions. C# will check them in order from top to bottom until it finds a suitable one.
Variants of the catch statement:
catch { ... }— catches any exception. Use this rarely, only if you really don't know what could go wrong.catch (Exception ex) { ... }— catches any exception but gives access to theexobject, from which you can get details.catch (DivideByZeroException ex) { ... }— catches only exceptions of typeDivideByZeroException(division by zero).catch (IOException ex) when (ex.Message.Contains("access")) { ... }— catches the exception only if an additional condition is met (exception filter).
3. The finally block
This is the "cleaner." It always executes: after a successful try execution, after handling in catch, and even if a new error occurs in catch. It is needed for guaranteed resource cleanup.
4. The throw operator
You can not only catch but also create exceptions. This is called "throwing an exception." For example, if you are writing a method that checks a user's age, and the age is negative — that is an exceptional situation.
if (age < 0)
{
throw new ArgumentException("Age cannot be negative!");
}
You can also "re-throw" an exception further up, without handling it, but simply adding and