Recipe7.13.Creating a New Exception Type


Recipe 7.13. Creating a New Exception Type

Problem

None of the built-in exceptions in the .NET Framework provide the implementation details that you require for an exception that you need to throw. You need to create your own exception class that operates seamlessly with your application, as well as other applications. Whenever an application receives this new exception, it can inform the user that a specific error occurred in a specific component. This report will greatly reduce the time required to debug the problem.

Solution

Create your own exception class. To illustrate, let's create a custom exception class, RemoteComponentException, that will inform a client application that an error has occurred in a remote server assembly.

Discussion

The exception hierarchy starts with the Exception class; from this are derived two classes: ApplicationException and SystemException. The SystemException class and any classes derived from it are reserved for the developers of the FCL. Most of the common exceptions, such as the NullReferenceException or the OverflowException, are derived from SystemException. The FCL developers created the ApplicationException class for other developers using the .NET languages to derive their own exceptions from. This partitioning allows for a clear distinction between user-defined exceptions and the built-in system exceptions. However, Microsoft now recommends deriving directly from Exception, rather than ApplicationException. Nothing actively prevents you from deriving a class from either SystemException or ApplicationException. But it is better to be consistent and use the convention of always deriving from the Exception class for userdefined exceptions.

You should follow the naming convention for exceptions when determining the name of your exception. The convention is very simple. Whatever you decide on for the exception's name, add the word Exception to the end of the name (e.g., use UnknownException as the exception name instead of just Unknown). Every user-defined exception should include at least three constructors, described next. This is not a requirement, but it makes your exception classes operate similarly to every other exception class in the FCL and minimizes the learning curve for other developers using your new exception. These three constructors are:


The default constructor

This constructor takes no arguments and simply calls the base class's default constructor.


A constructor with a parameter that accepts a message string

This message string overwrites the default contents of the Message field of this exception. Like the default constructor, this constructor also calls the base class's constructor, which also accepts a message string as its only parameter.


A constructor that accepts a message string and an inner exception as parameters

The object contained in the innerException parameter is added to the InnerException property of this exception object. Like the other two constructors, this constructor calls the base class's constructor of the same signature.

If this exception will be caught in unmanaged code, such as a COM object, you can also set the hrESULT value for this exception. An exception caught in unmanaged code becomes an hrESULT value. If the exception does not alter the hrESULT value, it defaults to the hrESULT of the base class exception, which, in the case of a user-defined exception object that inherits from ApplicationException, is COR_E_ APPLICATION (0x80131600). To change the default hrESULT value, simply set the value of this field in the constructor. The following code demonstrates this technique:

 public class RemoteComponentException : Exception {     public RemoteComponentException( ) : base( )     {         HResult = 0x80040321;     }     public RemoteComponentException(string message) : base(message)     {         HResult = 0x80040321;     }          public RemoteComponentException(string message, Exception innerException)         : base(message, innerException)     {         HResult = 0x80040321;     } } 

Now the hresult that the COM object will see is the value 0x80040321. See Table 7-2 in Recipe 7.8 for more information on the mapping of hrESULT values to their equivalent managed exception classes.

It is usually a good idea to override the Message property in order to incorporate any new fields into the exception's message text. Always remember to include the base class's message text along with any additional text you add to this property.


Fields and their accessors should be created to hold data specific to the exception. Since this exception will be thrown as a result of an error that occurs in a remote server assembly, you will add a private field to contain the name of the server or service. In addition, you will add a public read-only property to access this field. Since you're adding this new field, you should add two constructors that accept an extra parameter used to set the value of the serverName field.

If necessary, override any base class members whose behavior is inherited by the custom exception class. For example, since you have added a new field, you need to determine whether it will need to be added to the default contents of the Message field for this exception. If it does, you must override the Message property.

 public override string Message {     get     {         if (this.ServerName.Length == 0)             return (base.Message + Environment.NewLine +                 "An unnamed server has encountered an error.");         else             return (base.Message + Environment.NewLine +                 "The server " + this.ServerName +                  " has encountered an error.");     } } 

Notice that the Message property in the base class is displayed on the first line and your additional text is displayed on the next line. This organization takes into account that a user might modify the message that will appear in the Message property by using one of the overloaded constructors that takes a message string as a parameter.

In certain cases (such as remoting), your exception object should be serializable and deserializable. This involves performing the following two additional steps:

  1. Add the Serializable attribute to the class definition. This attribute specifies that this class can be serialized or deserialized. A SerializationException is thrown if this attribute does not exist on this class and an attempt is made to serialize this class.

  2. The class should implement the ISerializable interface if you want control over how serialization and deserialization are performed, and it should provide an implementation for its single member, GetObjectData. Here you implement it because the base class implements it, which means that you have no choice but to reimplement it if you want the fields you added (e.g., serverName) to get serialized.

 // Used during serialization to capture information about extra fields public override void GetObjectData(SerializationInfo exceptionInfo,                                    StreamingContext exceptionContext) {     base.GetObjectData(exceptionInfo, exceptionContext);     exceptionInfo.AddValue("ServerName", this.ServerName); } 

In addition, a new overridden constructor is needed that accepts information to deserialize this object:

 // Serialization ctor public RemoteComponentException(SerializationInfo exceptionInfo,         StreamingContext exceptionContext)         : base(exceptionInfo, exceptionContext) {     this.serverName = exceptionInfo.GetString("ServerName"); } 

Even though it is not required, you should make all user-defined exception classes serializable and deserializable. That way, the exceptions can be propagated properly over remoting and appdomain boundaries.


At this point, the RemoteComponentException class contains everything you need for a complete user-defined exception class. You could stop at this point, but let's continue a bit farther and override some default functionality that deals with the hash code, equality, and inequality.

Overriding the GetHashCode method

Since you have overridden the Equals method, you should override the GetHashCode method, which overrides the hash code generation algorithm:

 // GetHashCode public override int GetHashCode( ) {     return (ServerName.GetHashCode( )); } 

Overriding the == and != operators

When overriding the Equals method, both the == and != operators should be overloaded as well. Notice that both operators ultimately use the Equals method to determine equality. Therefore, they are simple to write.

 // == operator public static bool operator ==(RemoteComponentException v1,     RemoteComponentException v2) {     return (v1.Equals(v2)); } // != operator public static bool operator !=(RemoteComponentException v1,   RemoteComponentException v2) {     return (!(v1 == v2)); } 

As a final note, it is generally a good idea to place all user-defined exceptions in a separate assembly, which allows for easier reuse of these exceptions in other applications and, more importantly, allows other application domains and remotely executing code to both throw and handle these exceptions correctly no matter where they are thrown. The assembly that holds these exceptions should be signed with a strong name and added to the Global Assembly Cache (GAC), so that any code that uses or handles these exceptions can find the assembly that defines them. See Recipe 17.10 for more information on how to do this.

If you are sure that the exceptions being defined won't ever be thrown or handled outside of your assembly, then you can leave the exception definitions there. But if for some reason an exception that you throw finds its way out of your assembly, the code that ultimately catches it will not be able to resolve it.

The complete source code for the RemoteComponentException class is shown in Example 7-4.

Example 7-4. RemoteComponentException class

 using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; [SerializableAttribute] public class RemoteComponentException :   Exception, ISerializable {     // New exception field     private string serverName = "";     // Normal exception ctors     public RemoteComponentException( ) : base( )     {     }     public RemoteComponentException(string message) : base(message)     {     }     public RemoteComponentException(string message,       Exception innerException)         : base(message, innerException)     {     }     // Exception ctors that accept the new ServerName parameter     public RemoteComponentException(string message,       string serverName) : base(message)     {         this.serverName = serverName;     }     public RemoteComponentException(string message,       Exception innerException, string serverName)             : base(message, innerException)     {         this.serverName = serverName;     }     // Serialization ctor     public RemoteComponentException(SerializationInfo exceptionInfo,             StreamingContext exceptionContext)             : base(exceptionInfo, exceptionContext)     {         this.serverName = exceptionInfo.GetString("ServerName");     }     // Read-only property     public string ServerName     {         get{return (serverName.Trim( ));}     }     public override string Message     {         get         {             if (this.ServerName.Length == 0)                 return (base.Message + Environment.NewLine +                         "An unnamed server has encountered an error.");             else                 return (base.Message + Environment.NewLine +                         "The server " + this.ServerName +                         " has encountered an error.");         }     }     // Overridden methods     // ToString method     public override string ToString( )     {         string errorString = "An error has occured in a server " +             "component of this client.";         errorString += Environment.NewLine + "Server Name: " +           this.ServerName;         if (this.InnerException == null)         {             errorString += Environment.NewLine +                 "Server component failed to provide an " +                 "underlying exception!";         }         else         {             string indent = "\t";             Exception ie = this;             while(ie.InnerException != null)             {                 ie = ie.InnerException;                 errorString += Environment.NewLine + indent +                   "inner exception type thrown by server component: " +                   ie.GetType( ).Name.ToString( );                 errorString += Environment.NewLine + indent + "Message: "                   + ie.Message;                 errorString += Environment.NewLine + indent +                   "StackTrace: " + ie.StackTrace;                 indent += "\t";             }         }         errorString += Environment.NewLine + "StackTrace of client " +                        "component: " + this.StackTrace;         return (errorString);     }     // Call base.ToString method.     public string ToBaseString( )     {         return (base.ToString( ));     }     // GetHashCode     public override int GetHashCode( )     {         return (ServerName.GetHashCode( ));     }     // Equals     public override bool Equals(object obj)     {         bool isEqual = false;         if (obj == null || (this.GetType( ) != obj.GetType( )))         {             isEqual = false;         }         else         {             RemoteComponentException se = (RemoteComponentException)obj;             if ((this.ServerName.Length == 0)               && (se.ServerName.Length == 0))                 isEqual = false;             else                 isEqual = (this.ServerName == se.ServerName);         }         return (isEqual);     }     // == operator     public static bool operator ==(RemoteComponentException v1,         RemoteComponentException v2)     {         return (v1.Equals(v2));     }     // != operator     public static bool operator !=(RemoteComponentException v1,       RemoteComponentException v2)     {         return (!(v1 == v2));     }     // Used during serialization to capture information about extra fields     public override void GetObjectData(SerializationInfo exceptionInfo,                                        StreamingContext exceptionContext)     {         base.GetObjectData(exceptionInfo, exceptionContext);         exceptionInfo.AddValue("ServerName", this.ServerName);     } } 

The code to test the RemoteComponentException class is shown in Example 7-5.

Example 7-5. Testing the RemoteComponentException class

 public void TestSpecializedException( ) {     // Generic inner exception used to test the     // RemoteComponentException's inner exception.     Exception inner = new Exception("The inner Exception");     // Test each ctor.     Console.WriteLine(Environment.NewLine + Environment.NewLine +       "TEST EACH CTOR");     RemoteComponentException se1 = new RemoteComponentException ( );     RemoteComponentException se2 =       new RemoteComponentException ("A Test Message for se2");     RemoteComponentException se3 =       new RemoteComponentException ("A Test Message for se3", inner);     RemoteComponentException se4 =       new RemoteComponentException ("A Test Message for se4",                                      "MyServer");     RemoteComponentException se5 =       new RemoteComponentException ("A Test Message for se5", inner,                                       "MyServer");     // Test new ServerName property.     Console.WriteLine(Environment.NewLine +       "TEST NEW SERVERNAME PROPERTY");     Console.WriteLine("se1.ServerName == " + se1.ServerName);     Console.WriteLine("se2.ServerName == " + se2.ServerName);     Console.WriteLine("se3.ServerName == " + se3.ServerName);     Console.WriteLine("se4.ServerName == " + se4.ServerName);     Console.WriteLine("se5.ServerName == " + se5.ServerName);     // Test overridden Message property.     Console.WriteLine(Environment.NewLine +       "TEST -OVERRIDDEN- MESSAGE PROPERTY");     Console.WriteLine("se1.Message == " + se1.Message);     Console.WriteLine("se2.Message == " + se2.Message);     Console.WriteLine("se3.Message == " + se3.Message);     Console.WriteLine("se4.Message == " + se4.Message);     Console.WriteLine("se5.Message == " + se5.Message);     // Test -overridden- ToString method.     Console.WriteLine(Environment.NewLine +       "TEST -OVERRIDDEN- TOSTRING METHOD");     Console.WriteLine("se1.ToString( ) == " + se1.ToString( ));     Console.WriteLine("se2.ToString( ) == " + se2.ToString( ));     Console.WriteLine("se3.ToString( ) == " + se3.ToString( ));     Console.WriteLine("se4.ToString( ) == " + se4.ToString( ));     Console.WriteLine("se5.ToString( ) == " + se5.ToString( ));     // Test ToBaseString method.     Console.WriteLine(Environment.NewLine +       "TEST TOBASESTRING METHOD");     Console.WriteLine("se1.ToBaseString( ) == " + se1.ToBaseString( ));     Console.WriteLine("se2.ToBaseString( ) == " + se2.ToBaseString( ));     Console.WriteLine("se3.ToBaseString( ) == " + se3.ToBaseString( ));     Console.WriteLine("se4.ToBaseString( ) == " + se4.ToBaseString( ));     Console.WriteLine("se5.ToBaseString( ) == " + se5.ToBaseString( ));     // Test -overridden- == operator.     Console.WriteLine(Environment.NewLine +       "TEST -OVERRIDDEN- == OPERATOR");     Console.WriteLine("se1 == se1 == " + (se1 == se1));     Console.WriteLine("se2 == se1 == " + (se2 == se1));     Console.WriteLine("se3 == se1 == " + (se3 == se1));     Console.WriteLine("se4 == se1 == " + (se4 == se1));     Console.WriteLine("se5 == se1 == " + (se5 == se1));     Console.WriteLine("se5 == se4 == " + (se5 == se4));     // Test -overridden- != operator.     Console.WriteLine(Environment.NewLine +       "TEST -OVERRIDDEN- != OPERATOR");     Console.WriteLine("se1 != se1 == " + (se1 != se1));     Console.WriteLine("se2 != se1 == " + (se2 != se1));     Console.WriteLine("se3 != se1 == " + (se3 != se1));     Console.WriteLine("se4 != se1 == " + (se4 != se1));     Console.WriteLine("se5 != se1 == " + (se5 != se1));     Console.WriteLine("se5 != se4 == " + (se5 != se4));     // Test -overridden- GetBaseException method.     Console.WriteLine(Environment.NewLine +       "TEST -OVERRIDDEN- GETBASEEXCEPTION METHOD");     Console.WriteLine("se1.GetBaseException( ) == " + se1.GetBaseException( ));     Console.WriteLine("se2.GetBaseException( ) == " + se2.GetBaseException( ));     Console.WriteLine("se3.GetBaseException( ) == " + se3.GetBaseException( ));     Console.WriteLine("se4.GetBaseException( ) == " + se4.GetBaseException( ));     Console.WriteLine("se5.GetBaseException( ) == " + se5.GetBaseException( ));     // Test -overridden- GetHashCode method.     Console.WriteLine(Environment.NewLine +       "TEST -OVERRIDDEN- GETHASHCODE METHOD");     Console.WriteLine("se1.GetHashCode( ) == " + se1.GetHashCode( ));     Console.WriteLine("se2.GetHashCode( ) == " + se2.GetHashCode( ));     Console.WriteLine("se3.GetHashCode( ) == " + se3.GetHashCode( ));     Console.WriteLine("se4.GetHashCode( ) == " + se4.GetHashCode( ));     Console.WriteLine("se5.GetHashCode( ) == " + se5.GetHashCode( ));     // Test serialization.     Console.WriteLine(Environment.NewLine +       "TEST SERIALIZATION/DESERIALIZATION");     BinaryFormatter binaryWrite = new BinaryFormatter( );     Stream ObjectFile = File.Create("se1.object");     binaryWrite.Serialize(ObjectFile, se1);     ObjectFile.Close( );     ObjectFile = File.Create("se2.object");     binaryWrite.Serialize(ObjectFile, se2);     ObjectFile.Close( );     ObjectFile = File.Create("se3.object");     binaryWrite.Serialize(ObjectFile, se3);     ObjectFile.Close( );     ObjectFile = File.Create("se4.object");     binaryWrite.Serialize(ObjectFile, se4);     ObjectFile.Close( );     ObjectFile = File.Create("se5.object");     binaryWrite.Serialize(ObjectFile, se5);     ObjectFile.Close( );     BinaryFormatter binaryRead = new BinaryFormatter( );     ObjectFile = File.OpenRead("se1.object");     object Data = binaryRead.Deserialize(ObjectFile);     Console.WriteLine("----------" + Environment.NewLine + Data);     ObjectFile.Close( );     ObjectFile = File.OpenRead("se2.object");     Data = binaryRead.Deserialize(ObjectFile);     Console.WriteLine("----------" + Environment.NewLine + Data);     ObjectFile.Close( );     ObjectFile = File.OpenRead("se3.object");     Data = binaryRead.Deserialize(ObjectFile);     Console.WriteLine("----------" + Environment.NewLine + Data);     ObjectFile.Close( );     ObjectFile = File.OpenRead("se4.object");     Data = binaryRead.Deserialize(ObjectFile);     Console.WriteLine("----------" + Environment.NewLine + Data);     ObjectFile.Close( );     ObjectFile = File.OpenRead("se5.object");     Data = binaryRead.Deserialize(ObjectFile);     Console.WriteLine("----------" + Environment.NewLine +       Data + Environment.NewLine + "----------");     ObjectFile.Close( );     Console.WriteLine(Environment.NewLine + "END TEST" + Environment.NewLine); } 

The output from Example 7-5 is presented in Example 7-6.

Example 7-6. Output displayed by the RemoteComponentException class

 TEST EACH CTOR TEST NEW SERVERNAME PROPERTY se1.ServerName == se2.ServerName == se3.ServerName == se4.ServerName == MyServer se5.ServerName == MyServer TEST -OVERRIDDEN- MESSAGE PROPERTY se1.Message == Error in the application. An unnamed server has encountered an error. se2.Message == A Test Message for se2 An unnamed server has encountered an error. se3.Message == A Test Message for se3 An unnamed server has encountered an error. se4.Message == A Test Message for se4 The server MyServer has encountered an error. se5.Message == A Test Message for se5 The server MyServer has encountered an error. TEST -OVERRIDDEN- TOSTRING METHOD se1.ToString( ) == An error has occurred in a server component of this client. Server Name: Server component failed to notify of the underlying exception! StackTrace of client component: se2.ToString( ) == An error has occured in a server component of this client. Server Name: Server component failed to notify of the underlying exception! StackTrace of client component: se3.ToString( ) == An error has occurred in a server component of this client. Server Name:     Inner exception type thrown by server component: Exception     Message: The Inner Exception     StackTrace: StackTrace of client component: se4.ToString( ) == An error has occured in a server component of this client. Server Name: MyServer Server component failed to notify of the underlying exception! StackTrace of client component: se5.ToString( ) == An error has occurred in a server component of this client. Server Name: MyServer     Inner exception type thrown by server component: Exception     Message: The Inner Exception     StackTrace: StackTrace of client component: TEST TOBASESTRING METHOD se1.ToBaseString( ) == CSharpRecipes.ExceptionHandling+RemoteComponentException: Error in the application. An unnamed server has encountered an error. se2.ToBaseString( ) == CSharpRecipes.ExceptionHandling+RemoteComponentException: A Test Message for se2 An unnamed server has encountered an error. se3.ToBaseString( ) == CSharpRecipes.ExceptionHandling+RemoteComponentException: A Test Message for se3 An unnamed server has encountered an error. ---> System.Exception: The Inner Exception    --- End of inner exception stack trace --- se4.ToBaseString( ) == CSharpRecipes.ExceptionHandling+RemoteComponentException: A Test Message for se4 The server MyServer has encountered an error. se5.ToBaseString( ) == CSharpRecipes.ExceptionHandling+RemoteComponentException: A Test Message for se5 The server MyServer has encountered an error. ---> System.Exception: The Inner Exception     --- End of inner exception stack trace --- TEST -OVERRIDDEN- == OPERATOR se1 == se1 == False se2 == se1 == False se3 == se1 == False se4 == se1 == False se5 == se1 == False se5 == se4 == True TEST -OVERRIDDEN- != OPERATOR se1 != se1 == True se2 != se1 == True se3 != se1 == True se4 != se1 == True se5 != se1 == True se5 != se4 == False TEST -OVERRIDDEN- GETBASEEXCEPTION METHOD se1.GetBaseException() == An error has occurred in a server component of this client. Server Name: Server component failed to notify of the underlying exception! StackTrace of client component: se2.GetBaseException() == An error has occurred in a server component of this client. Server Name: Server component failed to notify of the underlying exception! StackTrace of client component: se3.GetBaseException() == System.Exception: The Inner Exception se4.GetBaseException() == An error has occurred in a server component of this client. Server Name: MyServer Server component failed to notify of the underlying exception! StackTrace of client component: se5.GetBaseException() == System.Exception: The Inner Exception TEST -OVERRIDDEN- GETHASHCODE METHOD se1.GetHashCode() == 757602046 se2.GetHashCode() == 757602046 se3.GetHashCode() == 757602046 se4.GetHashCode() == -1303092675 se5.GetHashCode() == -1303092675 TEST SERIALIZATION/DESERIALIZATION ---------- An error has occurred in a server component of this client. Server Name: Server component failed to notify of the underlying exception! StackTrace of client component: ---------- An error has occurred in a server component of this client. Server Name: Server component failed to notify of the underlying exception! StackTrace of client component: ---------- An error has occurred in a server component of this client. Server Name:     Inner exception type thrown by server component: Exception     Message: The Inner Exception     StackTrace: StackTrace of client component: ---------- An error has occurred in a server component of this client. Server Name: MyServer Server component failed to notify of the underlying exception! StackTrace of client component: ---------- An error has occurred in a server component of this client. Server Name: MyServer     Inner exception type thrown by server component: Exception     Message: The Inner Exception     StackTrace: StackTrace of client component: ---------- END TEST 

See Also

See Recipe 17.10; see the "Using User-Defined Exceptions" and "Exception Class" topics in the MSDN documentation.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net