Determining When to Catch Exceptions in C# or Other High-level language
Determining When to Catch Exceptions in C#
Code in a given method should generally
catch exceptions only when at least one of the following actions needs to be
performed:
- Logging or gathering information about the current exception
and context
- Adding information to the exception (e.g. throwing a
custom/wrapping exception)
- Performing any cleanup
- Attempting to recover from any exception (or a set of specific
exceptions)
If none of these actions need to be
performed, do not wrap the code in try-catch
(although a try-finally may still be
appropriate.) Simply allow exceptions to
propagate to the caller of the method (the next highest context in the call
stack.) This keeps code clean, with
well-defined areas responsible for managing exceptions.
Exception Processing Standards
- Applications and components which log or send notifications of
exception details must use the methods
of Monster.GlobalFramework.Diagnostics.EventLog
namespace.
using GDES =
Monster.GlobalFramework.Diagnostics.EventLog.Standards;
...
...
...
try
{
// Code which may throw exception
ShoppingCartManager.DeleteShoppingCart(cart);
}
catch (Exception ex)
{
GDES.Publish(ex);
}
- Use standard framework-defined exceptions whenever possible
(e.g. System.NullReferenceException). Tip: Use the “wincv” tool from the .Net
Command Prompt and type “Exception” to see a list of classes deriving from
Exception.
- Do not write code in such a
manner that exceptions are common
- Example: When opening a file, check for file existence first instead of relying
on a FileNotFoundException to
be thrown from the Open() call.
- Throw exceptions to caller
only for abnormal behavior
- Example: In a Customer class’ GetOrders()
method, do not throw an OrdersNotFoundException
when a simple empty or null
result will do.
- Within a catch region, do not use throw exp,
unless you are raising an exception instance you created inside the catch region. Instead, use throw
without the exception variable.
- Explanation: Using throw with an exception variable
causes the CLR to alter the starting point for the exception to the line
the throw is on. This means
logging and stack traces for the exception will incorrectly point to the catch region, not to the
originally offending code. Using a
“naked” throw does not affect
the stack trace, keeping it pointing at the original line of code that introduced
the exception.
Using Framework Exceptions
Following are some usage guidelines for
standard Framework exceptions:
- Exception – Use only if no other
custom or framework exception applies
- ApplicationException - Do not use. [See ApplicationException on page 19.]
- ArgumentNullException- Use if an argument is null
- ArgumentOutOfRangeException – Use if an argument's value
is outside acceptable range (e.g. -1 for channelID)
- ArgumentException - All other argument
exceptions
- NotImplementedException – Used as a temporary
indicator that functionality has not yet been written. Used when initially stubbing out methods
for development.
- InvalidOperationException - Use when calling certain
functionality is not permissible given the current state of the object or
system (e.g. You might not be able
to read a file that is not yet open.)
- NotSupportedException – Indicates functionality
missing by design, not because someone hasn’t done it. Typically used with interface
implementations. This is rarely
used.
General Exception Handling
Approaches
There are five broad approaches for
managing exceptions. These basic
approaches can be combined in many ways but should serve as building blocks for
your own implementation.
- Catch the exception and process:
try
{
// Code which may throw an exception
}
catch(Exception exp)
{
catch(Exception exp)
{
// Completely process here
}
}
- Catch the exception, perform some processing, and rethrow it to
the caller for further handling:
try
{
// Code which may throw an exception
}
catch(Exception exp)
{
catch(Exception exp)
{
// Perhaps code here to log some details
throw; // Rethrow to caller
}
}
- Catch the exception and add more information to the exception
or throw a custom exception:
try
{
// Code which may throw an exception
}
catch(Exception exp)
{
catch(Exception exp)
{
throw new CustomExampleException(“Some further description here”,
exp);
}
}
- Ignore the exception and let it propagate to the caller,
expecting it to be handled there (ala Chain of Responsibility design
pattern.)
public int
ScreamChokeAndDie(int someValue)
{
{
int lameDivisor = 0;
return someValue/lameDivisor; // This will throw a DivideByZeroException
// which would fly up to the caller (which
// hopefully has code to handle it.)
return someValue/lameDivisor; // This will throw a DivideByZeroException
// which would fly up to the caller (which
// hopefully has code to handle it.)
}
- Completely ignore/suppress the exception:
try
{
// Code which may throw an exception
}
catch(){/* Ignore exception */}
catch(){/* Ignore exception */}
General Exception Handling
Guidelines
- Catch exceptions as specifically as possible with more general
exception types in lower catch regions.
The order of catch
regions is important because the first valid match will be used even
though a more exact match may be in later catches.
Example:
try
{
// Code which may throw ConstraintException, DataException or other
Exception
}
catch(ConstraintException
exp)
{
{
// Handle ConstraintException, which is a subset of DataException
}
}
catch(DataException
exp)
{
{
// Handle general DataException
}
catch(Exception exp)
{
}
catch(Exception exp)
{
// Handle general exception
}
}
If “catch(Exception
exp)” were the first catch block, the
other two would be unreachable since all exceptions ultimately derive from System.Exception.
- Keep in mind that exceptions encountered in catch or finally blocks are treated like any other unhandled exception. For example, if you have cleanup code in
your finally block, and it
throws an exception, that exception will be raised to the caller –
probably not what was intended. It
is certainly possible to nest try-catch(-finally)
blocks.
[For
further information on handling exceptions, see Exception Management in .Net by Jones and Jezierski on MSDN.]
Creating Custom Exceptions
When none of the built-in exceptions are of
sufficient detail for your code, you can implement your own custom
exception. Custom exceptions should be
derived from System.Exception (or
from another custom exception base) and have a name ending with
“Exception”. As with all other
independent classes, your exception should be defined in a separate file named <exceptionname>.cs.
The Exception
class defines three public constructors:
public Exception();
public Exception(string message);
public Exception(string message, Exception innerException);
public Exception(string message);
public Exception(string message, Exception innerException);
All three of these should be implemented in
custom exceptions.
Custom exceptions should have the
[Serializable] attribute and implement an additional (protected) constructor
accepting SerializationInfo and StreamingConext (see the example
below.) This will allow your exceptions
to be marshaled (i.e. for Remoting) or stored in out-of-process session
state. If you add custom fields, you
need manually serialize and deserialize them (using an override of GetObjectData and the various Get
methods of SerializationInfo.
Finally, if your exception defines any
additional properties, you should consider an override of ToString() which writes the value of those properties (in
addition to the standard details!)
Serializable Custom Exception Example:
[Serializable]
public class CustomException : Exception
{
public class CustomException : Exception
{
private string _additionalDetails;
///<summary>
/// Additional information about the exception
///</summary>
public string AdditionalDetails
{
get {return _additionalDetails;}
set {_additionalDetails = value;}
}
{
get {return _additionalDetails;}
set {_additionalDetails = value;}
}
///<summary>
/// Default constructor
///</summary>
public CustomException() : base() {}
///<summary>
///<summary>
/// Constructor accepting only a string description
///</summary>
public CustomException(string message) : base(message) {}
///<summary>
/// Constructor accepting a description and the original exception
/// to embed as the InnerException
///</summary>
/// to embed as the InnerException
///</summary>
public CustomException(string message, Exception innerException) :
base(message, innerException) {}
// Constructor to allow proper deserialization
protected CustomException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
// Restore internal state from SerializationInfo
// Restore internal state from SerializationInfo
_additionalDetails =
info.GetString(“_additionalDetails”);
}
///</summary>
/// Serializes custom exception’s state
///</summary>
public override void GetObjectData(SerializationInfo info,
StreamingContext context)
{
// Persist internal state to SerializationInfo
// Persist internal state to SerializationInfo
info.AddValue(“_additionalDetails”,
_additionalDetails, typeof(string));
base.GetObjectData(info, context);
base.GetObjectData(info, context);
}
///</summary>
/// Returns a string describing the exception’s state
///</summary>
public override string ToString()
{
return String.Format("{0} [AdditionalDetails=\"{1}\"]",
base.ToString(),
_additionalDetails);
}
}
return String.Format("{0} [AdditionalDetails=\"{1}\"]",
base.ToString(),
_additionalDetails);
}
}
ApplicationException
There is a substantial amount of literature
which indicates to use ApplicationException as the base type for custom
exceptions and that applications should throw ApplicationExceptions in
favor of Exceptions. This has
been amended by Microsoft as erroneous:
“We added ApplicationException thinking
it would add value by grouping exceptions declared outside of the .NET
Framework, but there is no scenario for catching ApplicationException and it
only adds unnecessary depth to the hierarchy.”
“You should not define new exception
classes derived from ApplicationException; use Exception instead. In addition,
you should not write code that catches ApplicationException.”
Do not use catch ApplicationExceptions
directly or use them as a custom exception base.
Throwing Extended Exceptions
Alternately, instead of implementing a
custom exception, you can choose to throw a new System.Exception, embedding the original exception.
Example:
try
{
// Code which may throw an exception
}
catch(Exception exp)
{
catch(Exception exp)
{
throw new Exception(“Some further description here”, exp);
}
}
However, you must keep in mind that the
caller will only receive a generic Exception
type and not the specific type of exception originally thrown (e.g. ArgumentNullException.) The calling code is forced to check the
InnerException property to strongly identify the original exception. Deriving a custom exception allows a
finer-grained handling of the exceptions by the caller using multiple catch
regions specifically handing the custom exception (see General Exception Handling Guidelines above.)
Comments
Post a Comment