Managing Exceptions in .NET

Exception and error handling techniques is always a hot topic. There are so many articles and books written on this subject. But, when you go through any of the articles you might end up with the decision that this is not what you want or this cannot be implemented in your project. In that case, what is the solution? Decide your own exception handling practices right from the beginning. Implement them and test them.

Basically, there is lot of difference between an error and an exception. As said, an exception should be a real exception. Most errors can be validated and controlled on the UI layer and need not to be carried to the business layer. Errors should be controlled and validated; they should not be converted to exceptions unless and until it is necessary to do so.

For example, suppose you have a function called GetBillCyclePeriod(Customer cust) in your business logic. The UI sends the customer object. If the function receives the customer object as null, it is an error and not an exception. People code like this:

Public DateTime[] GetBillCyclePeriod(Customer cust)
{
   if (cust == null)
   {
      throw new ArgumentNullException();
   }
}

This is very bad practice because exceptions are very costly and, if not used properly, they harm performance. In fact, the example above is an error from the UI and should be validated on the UI itself. Some examples of exceptions can be that the database server is down, network is very slow, system crashes; these really are exceptions. In the coming sections, you will see how you can deal gracefully with the exceptions.

To build a successful, bug free, and stable application, you should design the exception handling architecture and strategy in a way that will help you maintain the application and fix any bugs by providing you with detailed information. (100% bug free is not possible. The code should at least be less prone to errors and errors should not recur.) The exception handling system should be able to perform the following tasks:

  1. Detect an exception.
  2. Log an exception.
  3. Report an exception (by means of mail or something like that).

Understanding Exceptions

The .NET framework provides a very good exception handling framework. Even if you use DLLs created in VB .NET in C# and the DLL throws an exception, the C# application block can handle that exception easily. This is possible because of CTS and CLS. I'll not go into depth with CTS and CLS because this article is intended for advanced users who are pretty much familiar with .NET framework concepts.

As said earlier, there is a difference between an error and an exception. Trying to read a file that does not exist will throw an exception. But, this might not be exception. You can check whether the given file is present at the given location. The boundary line between exception and error can be defined by the application's requirement. An error in one application might be considered an exception in another application.

Exception Hierarchy in .NET

All exception classes derive from the System.Exception base class. The .NET framework provides extensive classes to handle exceptions. All these classes are derived from System.Exception. Again extending the exception hierarchy, the .NET framework provides an ApplicationException base class to derive the application specific exception classes. You should always derive exception classes from the ApplicationException base class.

Managing Exceptions in .NET

How to Handle Exceptions

At first sight, handling exceptions looks quite easy in .NET. Just put a simple try/catch block in the method and encapsulate the code in this block. But, this is not as simple as it looks because putting in the try/catch block will catch all exceptions and will swallow the specific exception. Also, putting so many try/catch blocks will hamper performance. Putting a try/catch block with a specific exception catching block is the main step in designing an exception management strategy. You should catch exceptions when you want to process them; otherwise, let them propagate to the caller.

Detecting and Logging Exceptions

The .NET framework provides a very structured handling mechanism to detect the exception and process it. As said earlier, you can use try, catch, and finally blocks to handle the exception. Here is the sample structure that will describe how to use try, catch, and finally.

try
{
  // Write some code that is expected to throw an exception
}
catch (SomeException ex)
{
   //Write code here to process the exception. For example, log,
   // report, mail
}
finally
{
   // Very important block that is always executed in spite of
   // whether the exception occurred or not.
   // All final clean up code should be written here. For example,
   // closing connection, disposing unmanaged objects, and so on.
}

As said earlier, putting in so many try/catch blocks will hamper performance. There should be some basic guidelines for catching exceptions. You should catch the exception only if you need to do following actions:

  1. Log the exception. (Use Exception.ToString() instead of Exception.Message because it will maintain the stack trace. Typically, exceptions are logged into an event viewer, database, or text files. It depends upon the application's needs.)
  2. Add relevant information.
  3. Do some clean up.
  4. Try to recover. For example, because of heavy network traffic if you are getting a timeout exception, you can catch the same and try to execute the same operation.

If a particular method is not doing any of the above-mentioned tasks, it should propagate the exception back to the call stack.

Propagating Exceptions

Sometimes, it is necessary that, instead of catching an exception, it should be propagated to the caller. When you create an assembly and handle all the exceptions there itself, the person using this library need not to handle the exceptions. Instead, he should propagate the exception so that your assembly will receive proper information that you can act upon.

There are three main ways to propagate exceptions:

  1. Let the exception propagate automatically: Here, you do nothing and let the exception propagate to the level until it finds the matching catch block. For example,

    private void GetConnection()
    {
       SqlConnection conn = new SqlConnection(connString);
       conn.Open();
    }
    

    Here, you are not catching any exception that might be caused because of a wrong connection string, invalid credentials, insufficient privileges, or the SQL server is down. Let the exception to be propagated to the upper level.

  2. Catch and re-throw the exception: Here, you catch the exception and then do some clean up or logging. If you cannot recover from the exception, you re-throw it to the caller.
  3. private void GetConnection()
    {
       try
       {
          SqlConnection conn = new SqlConnection(connString);
             conn.Open();
       }
       catch (SqlException ex)
       {
          // Log any SQL-specific error
       }
       catch (Exception ex)
       {
          throw;
       }
    }
    
  4. Catch, wrap, and re-throw the exception: As an exception propagates up the call stack, the exception type becomes less relevant. When an exception is wrapped, more relevant information can be sent to the caller. With this approach, you catch the exception and process it. If you cannot recover, add relevant information and wrap the exception into a new exception type and throw it. The InnerException property will preserve the original exception. When you re-throw the exception, you can set the inner exception property to the desired exception type.
  5. try
    {
    }
    catch (ExceptionTypeA aex)
    {
    }
    catch (ExceptionTypeb bex)
    {
       // Wrap into new exception and throw it
       throw new ExceptionTypeC(bex.ToString(), bex);
    }
    finally
    {
       // Clean up code
    }
    

Managing Exceptions in .NET

Creating Your Own Exception Class

When you need to create your own exception class for customization and providing application-specific details, you should derive the class from ApplicationException as shown below. You can put application-specific properties in your base class and later add classes. As your catch blocks catch base class exceptions, they will automatically catch the derived ones also. This makes your application scalable. Because there are many exception classes already available in .NET, you should create your custom exception classes for something that is not already available. In the same way, you want to handle some situation in a different manner you can go for custom classes.

Example:

using System;
public class CustomException : Exception
{
   // Default Constructor
   public CustomException()
   {
   }
   public CustomException(string str) : base(str)
   {
   }
   public CustomException(string str, Exception inner) :
      base(str, inner)
   {
   }
}

Best Practices for Exception Handling

Until now, you have seen:

  • What exceptions are
  • How to Detect and log exceptions
  • How to handle unhandled exceptions
  • How they can be handled
  • How to propagate them
  • The difference between an error and an exception
  • How to creating a custom exception class
  • Exception hierarchy

From all of these, you can say that a well-handled set of error handling code blocks can make an application robust and less prone to crashing. Now, you will see some of the best practices to handle and tackle exceptions.

  1. Make a clear difference between an error and an exception. Treat a real exception as an exception; for errors, provide proper validations at different layers.
  2. Design the exception handling in such a way that the application should never crash unless and until the system running the application crashes.
  3. Remember that not all methods throw an exception. Encapsulating all methods in a try/catch block will hamper performance.
  4. You should or try to know where to set up a try/catch block. The best way to find such a place is to run code without using a try/catch block.
  5. Use try/catch/finally blocks around the code that is prone to throwing an exception. You will perform a final cleanup task regardless of whether an exception occurred or not.
  6. Always order exceptions from the most specific to the least specific. Putting in a general catch block will swallow all exceptions.
  7. When creating custom classes, postfix Exception to their name and always derive from System.Exception. This is modified in .NET 2.0. Previously, in .NET 1.1, Microsoft recommended that you should derive from ApplicationException and not from System.Exception. This has not proven to be a significant value addition. Hence, Microsoft recommends that you derive from Exception.
    For example:
  8. CustomException : Exception {}
  9. When creating user-defined exceptions, make sure that the meta data is available to code remotely. This can be achieved by sharing a common class between the client and server or creating a string named assembly and deploying it to the GAC.
  10. Use three common constructors, as discussed earlier, when creating custom exception classes.
  11. .NET provides many exception classes. Therefore, create your own class only when it is not available in .NET classes already and you want to separately process that type of exception.
  12. Use a localized description string in every exception.
  13. Use meaningful messages and avoid grammatical mistakes.
  14. While logging an exception, use Exception.ToString() to maintain the stack trace for detailed information.
  15. If you cannot handle an exception, do not catch it and let it propagate to the caller.
  16. Always check the InnerException property for more details.
  17. Throw appropriate exceptions depending upon the exception type. For example, throw ArgumentException if invalid arguments are passed.
  18. In asynchronous programming, the del.BeginInvoke() method will never throw an exception. Instead, the exception is thrown when del.EndInvoke() is called. You exception handling block should be placed in the callback method in this case.
  19. Try to catch exceptions as specifically as possible. It will give you exact information about the exception.
  20. Strong type checking and validation will prevent most exceptions. Try to use maximum validations for any input. Validate data at all layers. Distribute which validation should occur where. For example, a password field should not contain "=" and "\". It can be validated on the UI. Finding a customer for a given customer ID can be done in the business level or data access level.
  21. Do not re-throw exceptions except in extreme cases. It is a very costly matter to re-throw exceptions. If you are re-throwing the exceptions from a layer from where there is no one to catch the error, the application will crash.
  22. Put a single catch per thread.
  23. Always use a finally block to clean up the code. This is the block that is always executed regardless of whether the exception occurred or not.
  24. Use "using" blocks wherever possible because it will prevent resource leaks even when an exception occurs.
  25. Do not use exceptions to build logic of your functions.
  26. Do not use exceptions to indicate the absence of resources.
  27. Do not use exception handling as a means of returning information from a method.
  28. Do not clear the stack trace when re-throwing exceptions. In other words, instead of using throw ex; use throw;.
  29. When re-throwing an exception, add meaningful information to it.
  30. Always mark custom exception classes with the [Serializable()] attribute. This will make the exception cross physical boundaries.
  31. Remove Debug.Assert from release code.
  32. Be careful when using an AppDomain.UnhandledException event. According to my experience, it never works as expected.
  33. Do not do this: { return x / y; }. What if y is sent as zero?
  34. Throw exceptions instead of returning an error code or HRESULT. Do not return zero or something that may confuse the user. Simply throw the exception and let the user handle it.
  35. Check null when cleaning resources in finally block. For example.

  36. try
    {
    }
    catch (Exception ex)
    {
       // Some exception handling code.
    }
    finally
    {
       if (conn != null && conn.ConnectionState ==
           ConnectionState.Open)
       {
          // Dispose will close connection internally.
          // Always a good practice
         conn.Dispose();
       }
    }
    
  37. Use emptry catch blocks to catch unmanaged exceptions.

Tools for Exception Handling

There are a lot of frameworks and libraries for handling exceptions. Microsoft suggests using the following:

  • Microsoft Exception Management Application Block
  • Microsoft Enterprise Instrumentation Framework
  • Fx Cop is a code review and optimization tool and also a good tool to check the exception handling in your current code.

Conclusion

From the discussion above, you can conclude that developing a robust, scalable application is not very difficult if you properly decide the exception handling strategy. Defining an Exception Management strategy is big challenge. I hope this article will help all those who want to make their applications more robust and less error-prone.



About the Author

Jayant Kulkarni

Dear Friends, I'm from Pune and curently working with Symantec Corp. I'm having more than 7 years of exp in software field and have worked on areas like ASP.NET, C#, .NET remoting, web services, pocket pc applciations. I'm a brainbench certified software engineer in .NET framework, C#, ADO.NET and ASP.NET. If you like any of my articles or you want to suggest some changes and improve the way I code, write articles feel free to mail me at: jayantdotnet@gmail.com

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds