1/58
Looks like no tags are added yet.
Name | Mastery | Learn | Test | Matching | Spaced |
---|
No study sessions yet.
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.
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.
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
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.
What are the main types of errors?
Compilation/syntax errors
Runtime errors
Logical errors
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.
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.
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
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).
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.
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.
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.
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.
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.
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
}
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.
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
}
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.
How can we explicitly throw exceptions from our code?
By explicitly throw exceptions using the throw new
keywords:
throw new Exception("An error occurred.");
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.
}
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.
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.
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
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.
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
}
What each recursive method must have?
Each recursive method must have a stopping condition, so we don’t get the StackOverflowException
.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.");
}
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.
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.
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.
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.
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.
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.
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.
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.
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).
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
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.
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.
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
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.
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);
}
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
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");
}
}
What are some logging libraries for .NET we can use?
Microsoft.Extensions.Logging
Serilog
NLog
Log4Net
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.