(How)
C^# You Are - 2 December 2007
This week's questions are related to exceptions. Exceptions are the
standard mechanism for reporting errors in .NET. If you write anything
beyond a simple academic example you'll run into exceptions.
- What exception is thrown when an argument to a method is invalid? Answer
Ultimately the exception should be, or derive from, ArgumentException. The actual exception could be ArgumentException, ArgumentNullException or any number of other derived types.
- What exception is thrown when an object is not in a state that would allow an
operation to occur (such as reading from a closed file)? Answer
InvalidOperationException should be thrown when the current state of an object would prevent the method from succeeding properly.
- What exception is thrown when an object does not support some functionality
(such as attempting to add to a read only collection)? Answer
The general exception to use is NotSupportedException. There is a related exception known as NotImplementedException but this exception should only occur during development and indicates the member has not been implemented yet.
- What is the difference between Exception and ApplicationException? Answer
In the original release of .NET it was recommended that applications create new exceptions from ApplicationException while class libraries would derive from Exception or another class.
System exceptions derive from SystemException. In subsequent releases it was decided that there was no real benefit in distinguishing between application and non-application exceptions. Therefore
most people derive from Exception or a derived type and ignore ApplicationException.
- When is it appropriate to catch all exceptions? Answer
There are only a few cases where "eating" exceptions is a good idea. In several cases, however, it is feasible to catch all exceptions and perform some action. The most common
scenario is for logging. All exceptions can be logged to an error log for later analysis. Generally the exception would be rethrown after the logging. Another common scenario is exception nesting. With
exception nesting the actual exception is nested inside a "better" exception to hide the implementation details and/or provide a consistent exception target. Web services generally do this. Again, though,
an exception is still thrown.
- How do you alter an exception so that the user-invoked method is seen as the
thrower rather than an internal method that might have been called? Answer
The callstack associated with an exception occurs when the exception is thrown, not when it is created. Therefore to take an exception thrown in a called member
and expose it as though it occurred in some user-defined method you would need only catch all exceptions and then rethrow the exception, specifying the original
exception object. Normally when rethrowing exceptions you would just use throw by itself which would rethrow the original exception. However if you
specify throw with the exception (passed to the catch block) then the exception is effectively thrown again but with the updated call stack. Sometimes this is
useful but you will lose potentially useful callstack information. This is recommended, therefore, only in special situations.
- What are the standard signatures all exception constructors should have? Answer
For reusability purposes, all exception classes should define at least the following constructors. Additional constructors can be defined as needed.
- Exception ( ) - For derived class convenience
- Exception ( string message ) - Standard exception with a message
- Exception ( string message, Exception innerException ) - Standard exception with a nested exception
- Exception ( SerializationInfo info, StreamingContext context ) - Used for serialization
- How do you ensure that any exception classes you create can be serialized? Answer
It is important that exceptions be serializable so that they can be passed from the process/domain they were thrown in to the caller's context (app domain, process, etc).
The base Exception class is serializable so it requires very little effort to make derived classes serializable.
- Mark the class with the SerializableAttribute attribute.
- Create a constructor that accepts a SerializationInfo and StreamingContext as a parameter. The constructor should be private for sealed
classes and protected for inheritable classes.
- Within the constructor use Get... to retrieve the value of each field that the class has.
- Override the GetObjectData method and call AddValue for each field in the class. This will store the field's value for serialization.
Here is an example for a DriveAlreadyExistsException class that contains the name of the drive that exists along with the standard class properties.
[Serializable]
public sealed class DriveAlreadyExistsException : IOException
{
private DriveAlreadyExistsException(SerializationInfo info, StreamingContext context) : base(info, context)
{
m_strName = info.GetString("Name");
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("Name", m_strName);
}
private string m_strName;
}