Google


   


You are here: CodeIdol.com > C# > C# Cookbook, 2nd Edition > Exception Handling > Creating A New Exception Type

SAVE
Digg
Shown on del.icio.us del.icio.us
See Whos Talking About This on Technorati Technorati
I've Reddit reddit

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.


SAVE
Digg
Shown on del.icio.us del.icio.us
See Whos Talking About This on Technorati Technorati
I've Reddit reddit

You are here: CodeIdol.com > C# > C# Cookbook, 2nd Edition > Exception Handling > Creating A New Exception Type


ADBRITE ads links
   
Related tags







Popular Categories
Unix books and guides

AJAX popular information
C# language guides
Windows books and cookbooks

.......








Business Key Top Sites

be number one
rate your site




    С 2009 года мы стали переводить структура сайта на различные языки. Сайт теперь будет содержать книги не только на английском языке, но также и на других европейских языках, в том числе и на Русском языке.

    Русский Polski Francais Deutsch
    support sitemap terms

© CodeIdol Labs, 2007 - 2009