Exception handling best practices in C#

Errors in any language are classified into Compile Time Errors, Runtime Errors and Logical Errors.

Compile Time Errors

Compile time errors are the errors that are found during compilation. In general, these errors are syntactical errors in the program. It is not possible to execute the program without rectifying the compile time errors. Hence exception handling is not related to compile time errors.

Runtime Errors

Runtime errors are the errors that occur at runtime i.e., while executing the program. These are also called exceptions. When an exception occurs, then the program will be terminated abnormally without giving a proper message to the user regarding the exception. Exception handling is used to handle exceptions and give a proper message to the user regarding exception and avoid abnormal termination of the program. Exception handling in C# was based on the keywords try, catch, finally and throw.

Try block is used to write the statements that may cause an exception. When an exception was raised in the try block, control will be taken to catch block where we have to write the exception handling code. Once control reaches the catch block, it is not possible to take it back to try. The complete code in the try block will be executed only when there is no exception raised, and the code in the catch block is executed only when an exception was raised. But you may have the code to be executed both when an exception was raised, and no exception was raised. 

Finally block is used to write the code that needs to be executed both when an exception was raised and no exception was raised. 

Throw is used to raise the exceptions manually.

try
{
}
catch
{
}
finally
{
}

Example :

The following example accepts two integers and performs division. If the value given for the denominator is zero, then an exception will be raised, and the program handles this exception with exception handling.

namespace ExceptionHandling
{
    class Program
    {
        static void Main(string[] args)
        {
            int A, B, R;
            Console.WriteLine("Enter Two Integers");
            A = int.Parse(Console.ReadLine());
            B = int.Parse(Console.ReadLine());
            try
            {
                R = A / B;
                Console.WriteLine("Ratio Of {0} And {1} Is {2}", A, B, R);
            }
            catch (DivideByZeroException Ex)
            {
                Console.WriteLine("Division With Zero Not Possible");
            }
        }
    }
}

Handling Multiple Exceptions

Within a method, there is a possibility for more than one exception. In this case no need to write separate try…catch for each exception and related to a single try block, you can create any number of catch blocks, one for each type of exception.

Example :

The following example handles two exceptions divided by zero exception and overflow exception that will be raised when the variable's value is out of range for its data type.

namespace ExceptionHandling
{
    class MultipleExceptions
    {
        static void Main()
        {
            int A, B, R;
            Console.WriteLine("Enter Two Integers")
            try
            {
                A = int.Parse(Console.ReadLine());
                B = int.Parse(Console.ReadLine());
                R = A / B;
                Console.WriteLine("Ratio Of {0} And {1} Is {2}", A, B, R);
            }
            catch (DivideByZeroException Ex)
            {
                Console.WriteLine("Division With Zero Not Possible");
            }
            catch (OverflowException Ex)
            {
                Console.WriteLine("Value Must Be Within The Range Of Integer");
            }
            catch (FormatException Ex)
            {
                Console.WriteLine("Value Must Be Numeric");
            }
            Console.Read();
        }
    }
}

Handling Any Type of Exception

A programmer will write the exception handling code for all the exceptions that are up to his expectation. But there is a possibility for an exception that was not up to the expectation of the programmer. Hence, exception handling will be complete only when a catch block can catch any exception. For this, you have to create the catch block by specifying the exception type as an Exception. Every exception in .NET is inherited from the Exception class, and hence it can catch any exception. This type of catch block must be the last catch in a series of catch blocks.

Example :

The following example handles three exceptions DivideByZeroException, OverflowException, and FormatException, and can also handle any other type of exception.

namespace ExceptionHandling
{
    class AnyException
    {
        static void Main()
        {
            int A, B, R;
            Console.WriteLine("Enter Two Integers");
            try
            {
                A = int.Parse(Console.ReadLine());
                B = int.Parse(Console.ReadLine());
                R = A / B;
                Console.WriteLine("Ratio Of {0} And {1} Is {2}", A, B, R);
            }
            catch (DivideByZeroException Ex)
            {
                Console.WriteLine("Division With Zero Not Possible");
            }
            catch (OverflowException Ex)
            {
                Console.WriteLine("Value Must Be Within The Range Of Integer");
            }
            catch (Exception Ex)
            {
                Console.WriteLine(Ex.Message);
            }
            Console.Read();
        }
    }
}

User Defined Exceptions

There may be a situation where the system will not raise an exception for your requirement, and you want to raise the exception manually. In this case, you have to create a class for your exception, and it must be inherited from the Exception class. To raise the exception, use the throw keyword.

Example :

The following example creates a user-defined exception to raise an exception when a user enters a negative value for either A or B.

namespace ExceptionHandling
{
    class MyException : Exception
    {
        public string MyMessage;
        public MyException(string Mes)
        {
            MyMessage = Mes;
        }
    }
    class UserExceptions
    {
        static void Main()
        {
            int A, B, R;
            Console.WriteLine("Enter Two Integers");
            try
            {
                A = int.Parse(Console.ReadLine());
                if(A < 0)
                throw new MyException("-ve Values Are Not Allowed");
                B = int.Parse(Console.ReadLine());
                if(B<0)
                throw new MyException("-ve Values Are Not Allowed");
                R = A / B;
                Console.WriteLine("Ratio Of {0} And {1} Is {2}", A, B, R);
            }
            catch (DivideByZeroException Ex)
            {
                Console.WriteLine("Division With Zero Not Possible");
            }
            catch (OverflowException Ex)
            {
                Console.WriteLine("Value Must Be Within The Range Of Integer");
            }
            catch(MyException Ex)
            {
                Console.WriteLine(Ex.MyMessage);
            }
            catch (Exception Ex)
            {
                Console.WriteLine(Ex.Message);
            }           
            Console.Read();
        }
    }
}

Logical Errors

The logical error is an error that will not be found during compilation or at runtime and will cause the program output to be wrong. To find and rectify the logical errors tracing and debugging are used. Executing the program step by step and finding logical errors is called tracing and debugging. F10 and F11 keys are used to execute the program step by step.

Break Point

When you don’t want to execute the entire program step by step and execute only a particular method step by step, then breakpoints are used. When a breakpoint is placed in the program, then during execution, when control reaches the breakpoint, then the program will enter into debug mode, and from there, you can execute the program step by step. A shortcut to place or remove a breakpoint is F9.

Debug Tools

For debugging a .NET application, debugging tools are provided and are as follows.

Watch Window :

This can be used to add a variable to it and observe how the value of that variable changes while executing the program step by step. VS.net provides four watch windows, and shortcuts to open watch windows are

  • CTRL + ALT + W, 1
  • CTRL + ALT + W, 2             
  • CTRL + ALT + W, 3
  • CTRL + ALT + W, 4

For adding a variable to the watch window, select the variable, right-click on it, and then choose to Add to watch. Otherwise, select the variable and drag and drop it in the watch window. You can also modify the value of a variable in the watch window. For this, right-click on the variable in the watch window and choose edit value.

Locals Window :

This is used to observe how the values of local variables of the current method are changed as the program is executed step by step. There is no need to add variables to the locals window, and all local variables of the current method are automatically added to the locals window. Shortcut to open locals window is CTRL + ALT + V, L. Same as the watch window, in the locals window, you can edit the value of a variable.

Immediate Window : 

This is used to print the values of variables or expressions and even change the values of variables. “?” is used to print the value of a variable or expression within the immediate window. To change the value of a variable, write the assignment statement. Shortcut to open immediate window is CTRL + ALT + I. Advantage of this is you can execute your expressions, and it is available even at design time. But watch and locals windows are available only at runtime, and they can be used only to observe values of variables, and we can't write our expressions.