Exceptions and Error Handling

5.0(1)
studied byStudied by 3 people
learnLearn
examPractice Test
spaced repetitionSpaced Repetition
heart puzzleMatch
flashcardsFlashcards
Card Sorting

1/58

encourage image

There's no tags or description

Looks like no tags are added yet.

Study Analytics
Name
Mastery
Learn
Test
Matching
Spaced

No study sessions yet.

59 Terms

1
New cards

What are exceptions?

Exceptions are runtime errors or unexpected events that disrupt the normal flow of a program. They occur when a program encounters a condition it cannot handle, such as invalid input, a missing resource, or a logical inconsistency. In C# exceptions are objects, which means they are also data containers.

2
New cards

What is the System.Exception class?

The System.Exception class is the base class for all exceptions in C#, both built-in and custom. It represents errors that occur during the execution of a program and provides a framework for creating, throwing, and handling exceptions.

3
New cards

What data does the System.Exception class contain?

The System.Exception class contains several properties, methods, and constructors that provide detailed information about the error, such as:

  • Message

  • Stack Trace

  • Inner Exception

  • GetBaseException()

  • Exception(string message, Exception innerException)

  • And others

4
New cards

How can we see detailed information about an exception thrown from our code?

When an unhandled exception occurs, we can see the details of it by clicking on the View Details button in Visual Studio, which will open the QuickWatch window. In this window we can see the properties such as Message and InnerException, where we can see more details about the exception.

5
New cards

What are the main types of errors?

  • Compilation/syntax errors

  • Runtime errors

  • Logical errors

6
New cards

What is the stack trace? How can we use it?

A stack trace is a list of method calls that shows the path your program took to reach a specific point, usually where an error happened. It helps us see the order of actions leading to the problem. The stack trace shows the exact line of code where the error occurred, and lists all the methods that were called, in order (the first method called is on the bottom), leading to the error. If one error caused another, the inner exception contains its own stack trace.

7
New cards

How do we read the stack trace when the exception happens in a function?

When an exception happens inside a function, the stack trace will show the sequence of method calls that led to the error, including the function where the exception occurred.

class Program
{
    static void Main()
    {
        try
        {
            PerformCalculation();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.StackTrace);
        }
    }

    static void PerformCalculation()
    {
        int result = Divide(10, 0); // This will throw DivideByZeroException
    }

    static int Divide(int x, int y)
    {
        return x / y; // Error occurs here
    }
}

The stack trace will look like this:

   at Program.Divide(Int32 x, Int32 y) in C:\Path\Program.cs:line 17
   at Program.PerformCalculation() in C:\Path\Program.cs:line 13
   at Program.Main() in C:\Path\Program.cs:line 9

The first line shows where the exception was thrown—in this case, the Divide method. The next line shows the method that called Divide—in this case, the PerformCalculation method. The last entry shows where PerformCalculation was called, which is from the Main method.

8
New cards

Why are unhandled exceptions bad?

Unhandled exceptions can be problematic for several reasons, as they disrupt the normal flow of a program. Here are the main reasons why unhandled exceptions are considered bad:

  • Program crashes

  • Poor User Experience

  • Loss of State

  • Difficult Debugging

  • Security Risks

9
New cards

How does the try-catch-finally block work?

  • try block: The code that may throw an exception goes here. If an exception occurs, the control moves to the catch block.

  • catch block: The catch block handles the exception. Here we can specify the type of exception, or use a general one. If no exception occurs in the try block, the catch block is skipped.

  • finally block: The finally block always executes, regardless of whether an exception was thrown or not. It is typically used for cleanup code (e.g., closing files or database connections).

10
New cards

Can handling exceptions have a negative impact?

Yes, handling exceptions improperly or excessively can have a negative impact our code and application:

  • Exception handling can be costly in terms of performance, especially when used excessively or in performance-critical sections of code.

  • Overuse of exceptions can mask underlying problems by catching errors without properly addressing them.

  • Excessive exception handling can make the code hard to read and difficult to maintain.

  • If exceptions are caught and simply ignored (i.e., "swallowed"), the program may continue running, but the underlying issue won't be fixed. This can lead to hidden bugs and potential data corruption or other issues.

  • Overusing generic exception handling (like catch (Exception ex)) instead of more specific exceptions can lead to poor error management.

11
New cards

When and how should we use the try-catch block?

We should use a local try blocks when we see a potential of an error occurring, and we can actually handle the error. The try-catch blocks should be on the lowest level possible.

12
New cards

What happens when an exception occurs, and we’ve used the try-catch block?

The flow of the program gets interrupted and is moved to the first catch block that can handle the caught exception.

13
New cards

Should we throw and catch more general or more specific exceptions?

We should always start with the most specific exception types, and continue with the more general ones.

14
New cards

Is it risky not to handle all possible exceptions? What should we do then?

Not handling all exceptions is risky because it can lead to app crashing. We should always handle known exceptions and scenarios with as specific exceptions as possible. For unexpected exceptions, we should use a global try-catch block, which will handle all the exceptions that are not handled elsewhere.

15
New cards

How can we access the Exception object?

We can access the exception object in C# through the catch block, where the exception is caught and stored in a variable. This object contains details about the error, such as the message, stack trace, and other properties.

try
{
    // Code that may throw an exception
    int result = 10 / 0; // Division by zero
}
catch (Exception ex)  // ex is the exception object
{
    Console.WriteLine("Error Message: " + ex.Message);  // Accessing the error message
    Console.WriteLine("Stack Trace: " + ex.StackTrace);  // Accessing the stack trace
}

16
New cards

How do we catch exceptions of a specific type?

To catch exceptions of a specific type in C#, we can specify the type of exception in the catch block. This allows us to handle different types of exceptions separately and appropriately. For example:

try
{
    int result = 10 / 0;  // This will throw DivideByZeroException
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Cannot divide by zero: " + ex.Message);
}

This catch block will only catch exceptions of the DivideByZeroException type.

17
New cards

How can we define multiple catch blocks for a single try block?

By chaining multiple catch blocks that handle different exception types:

try
{
    // Some code that could throw different types of exceptions
    int result = 10 / 0;  // Throws DivideByZeroException
    string str = null;
    int length = str.Length;  // Throws NullReferenceException
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Cannot divide by zero: " + ex.Message);
}
catch (NullReferenceException ex)
{
    Console.WriteLine("Null reference encountered: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("General error: " + ex.Message);  // Catches any other exceptions
}

18
New cards

Why does the order of the catch blocks matter?

The order of catch blocks matters because exceptions in C# follow an inheritance hierarchy. When an exception is thrown, the catch blocks are evaluated in order, and the first block that matches the type of the exception is the one that will handle it. Once a match is found, no further catch blocks are evaluated.

If we put the more general exception type (e.g., Exception) before the more specific ones (e.g., DivideByZeroException, NullReferenceException), the general catch block will catch all exceptions, and the more specific blocks will never get a chance to handle those exceptions.

19
New cards

How can we explicitly throw exceptions from our code?

By explicitly throw exceptions using the throw new keywords:

throw new Exception("An error occurred.");

20
New cards

How can throwing an exception allow us not to return a value from a method?

Throwing an exception interrupts the normal flow of execution, meaning anything the method does won’t be used, since the flow of the program will jump to the nearest catch block that can handle the exception.

int GetNumber()
{
    throw new InvalidOperationException("No value available.");
    // No need for a return statement because the method never finishes normally.
}

21
New cards

When is throwing an exception a good design choice?

Throwing an exception is a good design choice when an error occurs that the method cannot handle, such as:

  • Invalid user input that cannot be processed (e.g. setting age of a person to a negative value)

  • Unexpected null values causing errors

  • Operations that must stop due to failures (e.g., file not found)

  • Violations of business rules (e.g., withdrawing more money than available)

Exceptions should be used for unexpected issues, not for regular control flow.

22
New cards

How do we define valuable exception messages? What should we keep in mind regarding exception types?

A valuable exception message should:

  • Clearly describe what went wrong

  • Provide relevant details (e.g., invalid value, expected format)

  • Avoid vague or generic messages

throw new ArgumentException("Age must be a positive number.");

The exception types should be as specific as possible - if the built-in exceptions don’t match our needs, we can create custom exceptions.

23
New cards

What are some built-in exception types?

Some common built-in exception types in C# include:

  • ArgumentException – Invalid argument passed to a method — the argument is not null, but is invalid

  • ArgumentNullException – Argument is null but shouldn't be

  • ArgumentOutOfRangeException – Argument value is outside the allowed range

  • NullReferenceException – Attempt to use an object that is null

  • InvalidOperationException – Operation is not valid in the current state — quite general exception (e.g. accessing first element of empty collection)

  • IndexOutOfRangeException – Index is outside the valid range of an array or list

  • DivideByZeroException – Division by zero attempted

  • NotImplementedException – Method or functionality is not implemented, but we want the code to compile

24
New cards

What is the StackOverflowException?

StackOverflowException occurs when the call stack exceeds its limit, usually due to deep or infinite recursion, and cannot store any more information about the method calls.

This exception usually happens when we call a method recursively:

void RecursiveMethod()
{
    RecursiveMethod();  // This causes infinite recursion
}

This type of exception cannot be caught or handled in code.

25
New cards

What are recursive methods?

Recursive methods are methods that call themselves in their own definition to solve smaller instances of the same problem. They continue calling themselves until a base condition is met, stopping the recursion.

int Factorial(int n)
{
    if (n == 1) return 1;  // Base condition
    return n * Factorial(n - 1);  // Recursive call
}

26
New cards

What each recursive method must have?

Each recursive method must have a stopping condition, so we don’t get the StackOverflowException.

27
New cards

Why is it important to be precise when using exceptions?

Being precise with exceptions is important because it helps identify the exact problem and where it occurred, making debugging and maintenance easier. Specific exceptions allow developers to handle different errors in different ways. They also allow developers to correctly inform users of the occurred problem.

28
New cards

Why should we avoid throwing and catching the base System.Exception type in most cases?

Avoiding the base System.Exception type is important because it makes error handling less specific. Catching general exceptions can hide the real issue or misinform the users, making debugging harder and leading to handling unexpected errors incorrectly.

29
New cards

What is exception rethrowing?

Exception rethrowing is when you catch an exception and then throw it again, either to log it or let it propagate further up the call stack. This allows you to handle the exception partially (e.g., logging) before passing it on to higher levels for full handling.

try
{
    // Code that may throw an exception
}
catch (Exception ex)
{
    // Log the exception or perform other actions
    throw;  // Rethrow the caught exception
}

This approach preserves the original exception details (stack trace).

30
New cards

What is the difference between throw and throw ex? Which one should we use?

The difference between throw and throw ex lies in how they handle the exception's stack trace:

  • throw (without specifying the exception) rethrows the original exception, preserving its stack trace.

  • throw ex creates a new exception object and throws it, which can reset the stack trace and make debugging harder.

We should use throw to rethrow exceptions in a catch block. This keeps the original stack trace intact, making it easier to trace the error's origin.

31
New cards

Why is the InnerException property so important?

The InnerException property is important because it provides additional context for a thrown exception, especially when one exception is caused by another. It helps trace the sequence of events that led to the error, making it easier to understand and fix the root cause.

try
{
    // Some code that throws an exception
}
catch (Exception ex)
{
    throw new InvalidOperationException("An error occurred.", ex);  // InnerException is set
}

In this example, the InnerException of the InvalidOperationException will contain the original exception, helping us diagnose the issue more clearly.

32
New cards

How do we manage exceptions if we don’t know exactly what exceptions can be thrown from some code?

If we’re unsure which exceptions might be thrown, it's best if we catch more general exceptions while still handling known cases explicitly. We can also throw the System.Exception object, so we can log the exception and someone can fix it later, but we don’t handle it — it will still crash the app.

33
New cards

What might be a good reason to catch exceptions of the System.Exception type?

A good reason to catch exceptions of the System.Exception type is when we want to ensure that all errors, including unexpected ones, are caught and handled, such as for logging or cleanup. This is typically done at a higher level, like at the entry point of an application or in a global error handler, to prevent the application from crashing and to gather detailed error information.

34
New cards

What should we keep in mind before using a try-catch block?

  • If the assumptions of how a method should be used are not met, it’s fine to throw an exception — if a developer allows null input values in a method that gets the first element of a collection, it’s up to them to fix their code.

  • A method should only handle an exception when it can do it in a reasonable way.

  • try-catch can slow down the code, so wrapping large blocks of code should be avoided unless necessary.

  • Exceptions should not be used for control of the flow.

35
New cards

What is a global try-catch block?

A global try-catch block is a mechanism that catches unhandled exceptions across an entire application. It's typically used in places like the main entry point or a central location to prevent the application from crashing, allowing for logging, cleanup, or graceful error handling.

We should always catch the base exception type in a global try-catch block since we have no way of knowing what kind of exception could happen across the app.

36
New cards

What code we should and shouldn’t put into the catch block?

The catch block should be simple.

In the catch block we should:

  • Log the exception.

  • Return a default value if applicable.

  • Handle specific exceptions.

  • Clean up resources.

  • Provide user-friendly messages.

We shouldn’t:

  • Put complex logic that can throw an exception inside the catch block (except rethrowing exceptions).

  • Ignore the exception (not handle it after catching it).

  • Rethrow the same exception without adding value to it.

37
New cards

What happens if an exception is thrown from a catch block?

A common misconception is that if an exception is thrown from a catch block, it will be automatically handled by the next catch block. However, that's not the case.

Once an exception is thrown inside a catch block, it will propagate outside of the current catch block and will not be handled by any subsequent catch blocks within the same try-catch structure. It will continue to propagate up the call stack, looking for another try-catch block higher up (or the global error handler, if available). If there is no other catch block to handle it, the application may terminate.

try
{
    throw new InvalidOperationException("An error occurred.");
}
catch (Exception ex)
{
    Console.WriteLine("Handling exception...");
    throw;  // This will propagate the exception outside the catch block.
}
catch (InvalidOperationException ex)
{
    // This block will NOT handle the exception thrown in the first catch block.
    Console.WriteLine("This won't be reached.");
}

In the above code, the exception thrown in the first catch block will not be caught by the second catch block. The flow of execution is broken once the exception is rethrown. This is an important distinction to make when designing error handling.

38
New cards

What are nested try-catch blocks?

Nested try-catch blocks are try-catch structures placed inside other try-catch blocks. This allows us to handle exceptions at different levels, where one block catches exceptions from a specific part of the code, and another block handles exceptions from a broader scope.

Usually the usage of nested try-catch blocks is not a very good idea because it makes the code quite messy and hard to follow.

39
New cards

What are exception filters?

Exception filters in C# allow us to apply conditions to exceptions in a catch block, enabling us to catch specific exceptions based on certain criteria, such as the exception's properties or type. This feature provides more control over exception handling and can help us refine our error-handling logic.

40
New cards

How can exception filters help us in controlling which exceptions should be processed by a catch block?

Exception filters help control which exceptions should be processed by a catch block by allowing you to specify conditions under which the exception is caught. This allows for more fine-grained control over exception handling, ensuring that only exceptions meeting certain criteria are handled by a specific catch block.

try
{
    throw new InvalidOperationException("Invalid operation");
}
catch (Exception ex) when (ex is InvalidOperationException)
{
    Console.WriteLine("Caught InvalidOperationException.");
}
catch (Exception ex) when (ex.Message.Contains("operation"))
{
    Console.WriteLine("Caught an exception with 'operation' in the message.");
}

41
New cards

When should we use exception filters?

  • In case of multiple exceptions being thrown of the same type, but with different property values.

  • When handling exceptions based on specific conditions.

  • When improving readability and maintainability — filters allow us to keep the catch blocks clean and simple by separating the condition checking from the logic for handling the exception.

  • When increasing efficiency — using filters eliminates the need for additional checks inside the catch block, which would otherwise be done manually. The filter is evaluated before entering the catch block.

42
New cards

How do we define custom exceptions?

To create custom exceptions we need to:

  • Create a class that will derive from the base class Exception.

  • Provide 3 types of constructors:

    • public CustomException()

    • public CustomException(string message) : base(message)

    • public CustomException(string message, Exception innerException) : base(message, innerException)

  • In case we want the class to have extra data (such as extra properties), we need to create additional constructors that will include the extra data as well.

43
New cards

When should we define custom exceptions?

We should define custom exceptions when the built-in exception types don't provide enough clarity or specificity for your application's error handling:

  • When we need more specific error handling — OutOfStockException for unavailable products in e-commerce is more specific than InvalidOperationException.

  • For better error identification — A UserNotFoundException is much more meaningful than a generic Exception or ArgumentException in a user management system.

  • When additional information in the exception is needed — transaction ID in a banking app.

We should always use built-in exceptions where appropriate.

44
New cards

What is the Principle of Least Surprise?

The Principle of Least Surprise (also known as the Principle of Least Astonishment) is a design principle that suggests systems, interfaces, or code should behave in a way that minimizes confusion for the user or developer. In other words, the system should behave in a way that is most intuitive and predictable, so users or developers don't encounter unexpected behavior.

45
New cards

Are the exceptions that a method can throw a part of its signature?

Yes, in a way, the exceptions that a method can throw are considered part of its contract or signature, but not in the same way as return types or parameters. They are a hidden part of the method’s signature — even though exceptions aren't explicitly part of the method signature, the method's contract implies certain exceptions that it might throw.

46
New cards

What are the drawbacks of using exceptions?

  • Exceptions can be costly in terms of performance (if they are thrown).

  • Overuse or misuse of exceptions can create complex and hard-to-maintain code.

  • Handling exceptions can lead to nested try-catch blocks, which can lead to repetitive and redundant code.

  • When exceptions are caught and handled, it may lead to unintended consequences, such as masking real errors.

  • Exceptions can lead to loss of program state.

  • Code that uses a lot of exceptions is harder to debug and maintain.

  • Exception handling can create tight coupling.

  • Exceptions are sometimes used instead of proper validation.

47
New cards

What is a goto statement? Why is using it a bad idea?

The goto statement in C# is used to transfer control to another part of the program, typically within the same method. It can jump to a specified label in the code, which is marked by a label name followed by a colon.

int i = 0;

start:
    if (i < 5)
    {
        Console.WriteLine(i);
        i++;
        goto start;  // Jumps back to the 'start' label
    }

Using the goto statement is considered one of the fastest ways to creating spaghetti code.

48
New cards

What are the alternatives for using exceptions?

There are multiple alternatives, such as functional programming, defensive programming (early returns), validating inputs, etc. but all of them come with their own drawbacks.

49
New cards

When should we throw exceptions explicitly from our code?

We should explicitly throw exceptions in our code when an error occurs that prevents the method from continuing normally and cannot be handled in a reasonable way at that level. One of those cases is the wrong assumption of how a method should be used:

public void SetAge(int age)
{
    if (age < 0)
        throw new ArgumentOutOfRangeException(nameof(age), "Age cannot be negative.");
}

In most cases, exceptions should be thrown when the developer has made a mistake by inappropriately using the methods in question. There are also cases when this cannot be known beforehand (like validation of external JSON).

50
New cards

Can exceptions be avoided?

Yes, exceptions can be avoided in multiple ways by:

  • Validating Inputs Before Execution

  • Using Try Methods

  • Returning Nullable or Default Values

  • Using Defensive Programming

  • Checking for Null Before Dereferencing

  • Using Exception Filters

51
New cards

Should exceptions happen often?

No. If the normal flow of the program continuously runs into exceptions, there’s something wrong with the code that needs fixing. Exceptions should be used for exceptional behavior, not for controlling the flow of the app.

52
New cards

When should we put code in the try-catch block? What should a catch block do?

We should put code in the try-catch block when:

  • The code might throw an exception that we can handle gracefully.

  • We need to clean up resources if an error occurs.

  • We want to log an error without crashing the program.

  • We need to provide alternative behavior when an error occurs.

A catch block should:

  • Handle the error properly (e.g., show a meaningful message, retry, or apply fallback logic).

  • Log the exception to help with debugging and monitoring.

  • Rethrow the exception if necessary using throw; to preserve stack trace.

  • Avoid swallowing exceptions silently unless absolutely necessary.

53
New cards

How can we quickly build C# classes based on types defined in JSON?

There are multiple ways we can create classes from JSON:

  • Create a new .cs file, and then go to Edit → Paste Special → Paste JSON as Classes

  • Use an online tool such as json2csharp.com

  • Deserialization

54
New cards

How can we use the default keyword to assign a default value to a variable of any type?

We can use it like this:

int number = default(int);        // 0
bool flag = default(bool);        // false
double price = default(double);   // 0.0
string text = default(string);    // null
object obj = default(object);     // null

If we’re not using var for initialization, we can omit the parenthesis, but if we do use var, we must specify the type of which we want to get the default value of.

55
New cards

How do we wrap existing exceptions into new ones?

We can do that by throwing a new exception in which the old one is passed as an inner exception:

try
{
    int x = int.Parse("invalid");
}
catch (Exception ex)
{
    throw new InvalidOperationException("Error parsing input.", ex);
}

56
New cards

How can we change the font color of the font in a console application?

In a C# console application, we can change the font color using the Console.ForegroundColor property:

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("This text is green!");
Console.ResetColor(); // Reset to default

57
New cards

How can we create a simple exceptions logger?

Like this:

class ExceptionLogger
{
    private static readonly string logFilePath = "exceptions.log";

    public static void LogException(Exception ex)
    {
        string logMessage = $"{DateTime.Now}: {ex.GetType()} - {ex.Message}\nStackTrace:\n{ex.StackTrace}\n";
        File.AppendAllText(logFilePath, logMessage + "\n");
    }
}

58
New cards

What are some logging libraries for .NET we can use?

  • Microsoft.Extensions.Logging

  • Serilog

  • NLog

  • Log4Net

59
New cards

How can we reduce the number of try-catch blocks in an app?

There are multiple things we can do to reduce the number of try-catch blocks. The most important one is to validate things through code where appropriate, instead of relying on exceptions.