Welcome back all, I hope you all are keeping yourself in good health during this pandemic.
First, we need to know what the exception is. And let's not get confused between an exception & the error.
In fact, let me give you an overview of how the error is different from an exception.
- As per the definition, an exception is a problem/issue which occurs during the program's execution. which means it takes place during runtime.
- Fair enough! But who defines exceptions in c#? well, there are various types of exceptions & they are defined by a system class Exception under System namespace.
A few types of exceptions:
- ArithmeticException: Divide by 0
- IndexOutOfRangeException: Try to access an index which does not exist
- FileNotFoundException: If we try to locate a file in an invalid path, or the file's name does not match etc.
- The best & my personal favourite: NullReferenceException: when the object is set to null and we try to access its properties or methods. Fortunately, we have a workaround for this, find out how we can avoid this exception in my article on null-pointer-design-pattern
The Error,
- As per the definition, an error is a mistake made by a developer or designer, or it could be a fault in a system such as power failure or any hardware related failure. Such failures are out of a programmer's control.
- The thing that differentiates the error from exception is that you can't handle an error, whereas exceptions are meant to be handled.
- An error caused termination of the program.
A few types of errors,
- Compile-time error - such as syntax error.
- Run time error - it is nothing but an exception.
- Logical error - if the user gets unexpected output because of logical mistakes made by a programmer.
What happens if we don't handle an exception? Why is it so important to handle an exception?
There are 2 main reasons why we handle exceptions:
- First, we don't want to close our program abnormally.
- Second, when the program closes with an exception, it exposes classes/ properties which are used in the project. This information is vital and vulnerable. Any malicious activities can be performed with this kind of information. SO BE CAREFUL!!!
How to handle an Exception
We can handle an exception with 4 beautiful keywords in C#.
- Try
It covers a block of statements. When you feel there are chances of getting an exception within a bunch of statements, then we have to encapsulate those statements inside a try block.
- In the below example, let's try to read a file which might throw an exception if the file is not present in a specified directory. So I will have those statements inside a try block.
-
- try
- {
- StreamReader reader = new StreamReader("D://Myfile.txt");
- }
- Catch
Above code will throw an exception & we need to catch that exception somewhere & that somewhere is Catch block,
- Catch block only executes, if an exception has occurred.
- catch (Exception ex)
- {
- Console.WriteLine("File was not found!");
- Console.WriteLine("This exception has occured while executing your program " +ex.Message);
- }
- Finally
Finally block executes regardless of exception occurrence. This is mostly used to free up any resources. In our example, we have opened a file which we need to close.
- Note
Always check for null condition, as we don't know if the file is opened or not.
-
- finally
- {
- if (reader != null)
- {
- reader.Close();
- }
- }
- Most of you might have a question. That is, I can release the resources after a catch block anyway. Why do I need finally?
Answer
Let's say you've encountered an exception within a try block, then control moves to a corresponding catch block. Now, what if you encountered another exception inside a catch block? Then statements below your catch block won't execute. But if you have those statements inside a finally they will surely execute.
- So no matter where you get an exception or how many exceptions you get, finally will always execute.
- Throw
It is used when one wants to throw an exception manually.
Flow chart for a throw.
- The below code is throwing an ArithmeticException manually.
Note
The throw is mostly used with a user-defined exception. That we will learn down the road as we proceed further in the article.
- StreamReader reader = null;
- try
- {
- int divider = 0;
- int result = 100 / divider;
- throw new ArithmeticException();
- }
- catch (FileNotFoundException ex)
- {
- Console.WriteLine("This exception has occured while executing your program " + ex.Message);
- }
- catch (ArithmeticException ex)
- {
- Console.WriteLine("Sorry! But cannot divide by 0");
- Console.WriteLine("This exception has occured while executing your program " + ex.Message);
- }
Let's see a full-fledged example,
- class Program
- {
- static void Main(string[] args)
- {
- ReadFile();
- }
-
-
-
-
- private static void ReadFile()
- {
- StreamReader reader = null;
- try
- {
- reader = new StreamReader("D://Myfile.txt");
- }
- catch (Exception ex)
- {
- Console.WriteLine("File was not found!");
- Console.WriteLine("This exception has occured while executing your program " + ex.Message);
- }
- finally
- {
- if (reader != null)
- {
- reader.Close();
- }
- }
- }
- }
As we have seen there is a single catch, but we can always have multiple catches.
Filtering using multiple Catches
- Our code has 2 possible exceptions.
- FileNotFound
If this exception occurs then "catch block with FileNotFoundException" will take care of it.
If no exception occurs then code will execute the next line.
- DivideByZero
In the next lines, we are diving a number by 0 which is not acceptable. So code will throw an exception which will be caught by "catch block with ArithmeticException".
If any other kind of exception occurs apart from these 2 then it will be caught by "catch block with an Exception".
-
-
-
- private static void ReadFile()
- {
- StreamReader reader = null;
- try
- {
-
- reader = new StreamReader("E://Myfile.txt");
-
- int divider = 0;
- int result = 100 / divider;
- }
- catch (ArithmeticException ex)
- {
- Console.WriteLine("Sorry! But cannot divide by 0");
- Console.WriteLine("This exception has occured while executing your program " + ex.Message);
- }
- catch (FileNotFoundException ex)
- {
- Console.WriteLine("This exception has occured while executing your program " + ex.Message);
- }
- catch (Exception ex)
- {
- Console.WriteLine("This exception has occured while executing your program " + ex.Message);
- }
- finally
- {
- if (reader != null)
- {
- reader.Close();
- }
- }
- }
Only one catch gets executed
- Even though you can have multiple catches, still only one "catch block" gets executed. For example, in the above code when FileNotFoundException occurred, it looks for a catch block with FileNotFoundException exception where it gets handled. And then last catch block with Exception was simply ignored.
The parent of all exceptions: The class Exception
- What if we move "catch block with an Exception" to the first position?
- We will get a compile-time error. Saying the previous catch clause already catches all the exceptions. So the hierarchy is important, from a derived class to the base class.
Why did this happen?
Because Exception is a parent class of all kinds of exceptions ever used in C#.
Wait a minute, if that is the case, If Exception is meant for inheritance. Then we can have our own exception. Same as FileNotFoundException or ArithmeticException.
User-defined Exception
Let's define a class which ensure the earth's shape is spheroid not flat.
- First, create a class named as WrongShapeOfEarthException, Extend an Exception class. So as per IS-A relationship, our WrongShapeOfEarthException is officially an Exception.
- Now create a parameterized constructor, which will take one string as a parameter. And this string is a nothing but a message of an exception.
- You can also override the ToString() method of an object class. To print decorative information.
- public class WrongShapeOfEarthException : Exception
- {
- public WrongShapeOfEarthException(string errorMsg): base (errorMsg)
- {
-
- }
- public override string ToString()
- {
- return "Earth's shape is Spheroid";
- }
- }
Now let's call this exception,
In order to do that, we need to use a throw keyword.
-
-
-
- private static void ReadFile()
- {
- try
- {
- string shapeOfTheEarth = "Flat";
- if(!string.IsNullOrWhiteSpace(shapeOfTheEarth) && shapeOfTheEarth != "Spheroid")
- {
- throw new WrongShapeOfEarthException("Earth is not Flat");
- }
- }
- catch (WrongShapeOfEarthException ex)
- {
- Console.WriteLine("This exception has occured while executing your program " + Environment.NewLine);
- Console.WriteLine(ex.Message + Environment.NewLine);
- Console.WriteLine(ex.ToString());
- }
- }
Let's check the output,
Perfect!
Let's make it more complicated. Because WHY NOT? Right?
Nested try-catch block.
If we have nested try-catch blocks, then an exception will be caught by the first matching catch block. Let's understand this behavior with an example.
- using I_Cant_See_Sharp.Apple;
- using I_Cant_See_Sharp.Entities;
- using System;
- using System.IO;
-
- namespace I_Cant_See_Sharp
- {
-
-
- class Program
- {
- static void Main(string[] args)
- {
- ReadFile();
- }
-
-
-
-
- private static void ReadFile()
- {
- try
- {
- try
- {
- string shapeOfTheEarth = "Flat";
- if (!string.IsNullOrWhiteSpace(shapeOfTheEarth) && shapeOfTheEarth != "Spheroid")
- {
- throw new WrongShapeOfEarthException("Earth is not Flat");
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine("Caught by Exception");
- }
-
- }
- catch (WrongShapeOfEarthException ex)
- {
- Console.WriteLine("This exception has occured while executing your program " + Environment.NewLine);
- Console.WriteLine(ex.Message + Environment.NewLine);
- Console.WriteLine(ex.ToString());
- }
- }
- }
- }
first "catch block with Exception" got executed.
Let's replace Exception catch block with WrongShapeOfEarthException.
- using I_Cant_See_Sharp.Apple;
- using I_Cant_See_Sharp.Entities;
- using System;
- using System.IO;
-
- namespace I_Cant_See_Sharp
- {
-
-
- class Program
- {
- static void Main(string[] args)
- {
- ReadFile();
- }
-
-
-
-
- private static void ReadFile()
- {
- try
- {
- try
- {
- string shapeOfTheEarth = "Flat";
- if (!string.IsNullOrWhiteSpace(shapeOfTheEarth) && shapeOfTheEarth != "Spheroid")
- {
- throw new WrongShapeOfEarthException("Earth is not Flat");
- }
- }
- catch (WrongShapeOfEarthException ex)
- {
- Console.WriteLine("Caught by WrongShapeOfEarthException");
- Console.WriteLine("This exception has occured while executing your program " + Environment.NewLine);
- Console.WriteLine(ex.Message + Environment.NewLine);
- Console.WriteLine(ex.ToString());
- }
- }
-
- catch (Exception ex)
- {
- Console.WriteLine("Caught by Exception");
- }
- }
- }
- }
Works as expected, First matching "catch block with a WrongShapeOfEarthException" got executed, ignoring the second "catch block with an Exception".
Catch block with no arguments
- We can also have catch block with absolutely 0 arguments.
- This catch block will catch all exceptions, regardless of their type.
- However, you won’t be able to access any information about the exception.
- private static void ReadFile()
- {
- try
- {
- int divider = 0;
- int result = 6 / divider;
- }
- catch
- {
- Console.WriteLine("Caught by Exception");
- }
- }
But we can not have 2 catches, one with no arguments & other with argument.
The compiler will just give you a warning, it won't throw you a compile-time error. But better to avoid such code.
However, as you can see, as per the sequence we have parameterized catch block first followed by the parameterless catch block.
If we interchange this sequence then the compiler will throw you an error instead of just a warning.
Some important points we should know,
- Try block at least needs one catch or one finally block. The presence of either of them is enough.
- Else compiler will throw an error. "Either catch or finally is expected"
- You can not have a catch or finally without a try block
- The compiler will throw an error. "Try expected"
- You need to have a proper sequence of try-catch-finally, else compiler will throw an exception if the flow is interrupted by other executable statements.
- Comments are allowed, as comments are not executable statements.
- If we write finally before the catch, the program won't execute.
- Multiple catch blocks with the same exceptions are not allowed.
- The parent exceptions should always be the last one.
- Only one finally block is allowed, else syntax error will be thrown.
- Finally Block
- Finally block will always execute regardless of error's occurrence.
- Finally block will execute even if the program returns a value before finally.
e.g. after executing return statement inside a try or catch block => control will come to the finally for the execution => then it will move at the end of the method.
- Finally block cannot have the return, continue, or break keywords.
Throw vs Throw ex
- the throw: prints complete hierarchy from the origin. (origin: where exception was occurred and thrown)
- throw ex: resets the stack trace - it breaks the hierarchy and makes itself an origin then prints errors from self.