Defining Custom Exceptions

Once throwing an exception becomes the best course of action, it is preferable to use framework exceptions because they are well established and understood. Instead of throwing a custom invalid argument exception, for example, it is preferable to use the System.ArgumentException type. However, if the developers using a particular API will take special actionthe exception-handling logic will vary to handle a custom exception type, for instanceit is appropriate to define a custom exception. For example, if a mapping API receives an address for which the ZIP Code is invalid, instead of throwing System.ArgumentException, it may be better to throw a custom InvalidAddressException. The key is whether the caller is likely to write a specific InvalidAddressException catch block with special handling rather than just a generic System.ArgumentException catch block.

Defining a custom exception simply involves deriving from System.Exception or some other exception type. Listing 10.4 provides an example.

Listing 10.4. Creating a Custom Exception

class DatabaseException : System.Exception {   public DatabaseException(       System.Data. SqlClient.SQLException exception)   {       InnerException = exception;        // ...   }   public DatabaseException(       System.Data.OracleClient.OracleException exception)   {       InnerException = exception;       // ...   }   public DatabaseException()   {       // ...   }   public DatabaseException(string message)   {       // ...   }   public DatabaseException(       string message, Exception innerException)   {       InnerException = innerException;       // ...   } }

This custom exception might be created to wrap proprietary database exceptions. Since Oracle and SQL Server (for example) each throw different exceptions for similar errors, an application could define a custom exception that standardizes the database-specific exceptions into a common exception wrapper that the application can handle in a standard manner. That way, whether the application was using an Oracle or a SQL Server backend database, the same catch block could be used to handle the error higher up the stack.

The only requirement for a custom exception is that it derives from System.Exception or one of its descendents. However, there are several more good practices for custom exceptions.

  • All exceptions should use the "Exception" suffix. This way, their purpose is easily established from the name.

  • Generally, all exceptions should include constructors that take no parameters, a string parameter, and a parameter set of a string and an inner exception. Furthermore, since exceptions are usually constructed within the same statement in which they are thrown, any additional exception data should also be allowed as part of the constructor. (The obvious exception to creating all these constructors is if certain data is required and a constructor circumvents the requirements.)

  • The inheritance chain should be kept relatively shallow (with fewer than approximately five levels).

The inner exception serves an important purpose when rethrowing an exception that is different from the one that was caught. For example, if a System.Data.SqlClient.SqlException is thrown by a database call but is caught within the data access layer to be rethrown as a DatabaseException, then the DatabaseException constructor that takes the SqlException (or inner exception) will save the original SqlException in the InnerException property. That way, when requiring additional details about the original exception, developers can retrieve the exception from the InnerException property.

Advanced Topic: Serializable Exceptions

Serializable objects are objects that the runtime can persist into a streama filestream, for exampleand then reinstantiate out of the stream. In the case of exceptions, this may be necessary for certain distributed communication technologies. To support serialization, exception declarations should include the System.SerializableAttribute attribute or they should implement ISerializable. Furthermore, they must include a constructor that takes System.Runtime.Serialization.SerializationInfo and System.Runtime.Serialization.StreamingContext. Listing 10.5 shows an example of using System.SerializableAttribute.

Listing 10.5. Defining a Serializable Exception

 // Supporting serialization via an attribute  [Serializable]                                                   class DatabaseException : System.ApplicationException {   // ...   // Used for deserialization of exceptions    public DatabaseException(       SerializationInfo serializationInfo,                         StreamingContext context)                                {       // ...   } }

The preceding DatabaseException example demonstrates both the attribute and the constructor requirement for making an exception serializable.

Beginner Topic: Checked and Unchecked Conversions

As first discussed in a Chapter 2 Advanced Topic, C# provides special keywords for marking a code block with instructions to the runtime of what should happen if the target data type is too small to contain the assigned data. By default, if the target data type cannot contain the assigned data, then the data will truncate during assignment. For an example, see Listing 10.6.

Listing 10.6. Overflowing an Integer Value

using System; public class Program {   public static void Main()   {       // int.MaxValue equals 2147483647       int n = int.MaxValue;       n = n + 1 ;       System.Console.WriteLine(n);   } }

The results of Listing 10.6 appear in Output 10.1.

Output 10.1.


It writes the value -2147483648 to the console. However, placing the code within a checked block or using the checked option when running the compiler will cause the runtime to throw an exception of type System .OverflowException. The syntax for a checked block uses the checked keyword, as shown in Listing 10.7.

Listing 10.7. A Checked Block Example

 using System;   public class Program {   public static void Main()   {       checked                                                       {                                                                 // int.MaxValue equals 2147483647            int n = int.MaxValue;           n = n + 1 ;           System.Console.WriteLine(n);       }                                                         } }

The results of Listing 10.7 appear in Output 10.2.

Output 10.2.

Unhandled Exception: System.OverflowException: Arithmetic operation resulted in an overflow. at Program.Main() in ...Program.cs:line 12

The result is that an exception is thrown if, within the checked block, an overflow assignment occurs at runtime.

The C# compiler provides a command-line option for changing the default checked behavior from unchecked to checked. C# also supports an unchecked block that truncates the data instead of throwing an exception for assignments within the block (see Listing 10.8).

Listing 10.8. An Unchecked Block Example

using System; public class Program {   public static void Main()   {       unchecked                                                         {                                                                      // int.MaxValue equals 2147483647            int n = int.MaxValue;            n = n + 1 ;            System.Console.WriteLine(n);      }   }                                                                }

The results of Listing 10.8 appear in Output 10.3.

Output 10.3.


Even if the checked option is on during compilation, the unchecked keyword in the code in Listing 10.8 will prevent the runtime from throwing an exception during execution.

Essential C# 2.0
Essential C# 2.0
ISBN: 0321150775
EAN: 2147483647
Year: 2007
Pages: 185 © 2008-2017.
If you may any questions please contact us: