Chapter 5. Debugging and Exception Handling

5.0 Introduction

This chapter contains recipes covering the exception-handling mechanism, including the try, catch, and finally blocks. Along with these recipes are others covering the mechanisms used to throw exceptions manually from within your code. The final recipes deal with the Exception classes and their uses, as well as subclassing them to create new types of exceptions.

Often, the design and implementation of exception handling is performed later in the development cycle. But with the power and complexities of C# exception handling, you need to plan and even implement your exception-handling scheme much earlier. Doing so will increase the reliability and robustness of your code while minimizing the impact of adding exception handling after most or all of the application is coded.

Exception handling in C# is very flexible. It allows you to choose a fine- or coarse-grained approach to error handling, or any level between. This means that you can add exception handling around any individual line of code (the fine-grained approach) or around a method that calls many other methods (the coarse-grained approach), or you can use a mix of the two, with mainly a coarse-grained approach and a more fine-grained approach in specific critical areas of the code. When using a fine-grained approach, you can intercept specific exceptions that might be thrown from just a few lines of code. The following method sets an object’s property to a numeric value using fine-grained exception handling:

protected void SetValue(object value)
{
    try
    {
        myObj.Property1 = value;
    }
    catch (NullReferenceException)
    {
        // Handle potential exceptions arising from this call here.
    }
}

Consequently, this approach can add a lot of extra baggage to your code if used throughout your application. This fine-grained approach to exception handling should be used when you have a single line or just a few lines of code, and you need to handle that exception in a specific manner. If you do not have specific handling for errors at that level, you should let the exception bubble up the stack. For example, using the previous SetValue method, you may have to inform the user that an exception occurred and provide a chance to try the action again. If a method exists on myObj that needs to be called whenever an exception is thrown by one of its methods, you should make sure that this method is called at the appropriate time.

Coarse-grained exception handling is quite the opposite; it uses fewer try-catch or try-catch-finally blocks. One example of a coarse-grained approach would be to place a try-catch block around all of the code in every public method in an application or component. Doing this allows exceptions to be handled at the highest level in your code. If an exception is thrown at any location in your code, it will be bubbled up the call stack until a catch block is found that can handle it. If try-catch blocks are placed on all public methods, then all exceptions will be bubbled up to these methods and handled. This allows you to write much less exception-handling code, but it diminishes your ability to handle specific exceptions that may occur in particular areas of your code. You must determine how best to add exception-handling code to your application. This means applying the right balance of fine- and coarse-grained exception handling in your application.

C# allows you to write catch blocks without any parameters. An example of this is shown here:

public void CallCOMMethod()
{
    try
    {
        // Call a method on a COM object.
        myCOMObj.Method1();
    }
    catch
    {
        //Handle potential exceptions arising from this call here.
    }
}

The catch with no parameters is a holdover from C++, where exception objects did not have to be derived from the Exception class. Writing a catch clause in this manner in C++ allows any type of object thrown as an exception to be caught. However, in C#, only objects derived from the Exception base class may be thrown as an exception. Using the catch block with no parameters allows all exceptions to be caught, but you lose the ability to view the exception and its information. A catch block written in this manner:

catch
{
     // NOT able to write the following line of code
     //Console.WriteLine(e.ToString);
}

is equivalent to this:

catch (Exception e)
{
     // Able to write the following line of code
     Console.WriteLine(e.ToString);
}

except that in the second case, the Exception object can be accessed now that the exception parameter is provided.

Avoid writing a catch block without any parameters. Doing so will prevent you from accessing the actual Exception object that was thrown.

When catching exceptions in a catch block, you should determine up front when exceptions need to be rethrown, when exceptions need to be wrapped in an outer exception and thrown, and when exceptions should be handled immediately and not rethrown.

Wrapping an exception in an outer exception is a good practice when the original exception would not make sense to the caller. When wrapping an exception in an outer exception, you need to determine what exception is most appropriate to wrap the caught exception. As a rule of thumb, the wrapping exception should always aid in tracking down the original problem by not obscuring the original exception with an unrelated or vague wrapping exception. One of the rare cases that can justify obscuring exceptions is if the exception is going to cross a trust boundary, and you have to obscure it for security reasons.

Another useful practice when catching exceptions is to provide catch blocks to handle specific exceptions in your code. And remember that base class exceptions—when used in a catch block—catch not only that type, but also all of its subclasses.

The following code uses specific catch blocks to handle different exceptions in the appropriate manner:

public void CallCOMMethod()
{
    try
    {
        // Call a method on a COM object.
        myCOMObj.Method1();
    }
    catch (System.Runtime.InteropServices.ExternalException)
    {
        // Handle potential COM exceptions arising from this call here.
    }
    catch (InvalidOperationException)
    {
        // Handle any potential method calls to the COM object that are
        // not valid in its current state.
    }
}

In this code, ExternalException and its derivatives are handled differently than InvalidOperationException and its derivatives. If any other types of exceptions are thrown from the myCOMObj.Method1, they are not handled here, but are bubbled up until a valid catch block is found. If no valid catch block is found, the exception is considered unhandled and the application terminates.

At times, cleanup code must be executed regardless of whether an exception is thrown. Any object must be placed in a stable known state when an exception is thrown. In these situations, when code must be executed, use a finally block. The following code has been modified (see boldface lines) to use a finally block:

public void CallCOMMethod()
{
    try
    {
        // Call a method on a COM object.
        myCOMObj.Method1();
    }
    catch (System.Runtime.InteropServices.ExternalException)
    {
        // Handle potential COM exceptions arising from this call here.
    }
    finally
    {
        // Clean up and free any resources here.
        // For example, there could be a method on myCOMObj to allow us to clean
        // up after using the Method1 method.
    }
}
Note

The finally block will always execute, no matter what happens in the try and catch blocks. The finally block executes even if a return, break, or continue statement is executed in the try or catch blocks or if a goto is used to jump out of the exception handler. This allows for a reliable method of cleaning up after the try (and possibly catch) block code executes.

The finally block is also very useful for final resource cleanup when no catch blocks are specified. This pattern would be used if the code being written can’t handle exceptions from calls it is making but wants to make sure that resources it uses are cleaned up properly before moving up the stack. The following example makes sure that SqlConnection and SqlCommand are cleaned up properly in the finally block through the use of the using keyword, which wraps a try-finally block around the scope of the using statement:

public static int GetAuthorCount(string connectionString)
{
    SqlConnection sqlConn = null;
    SqlCommand sqlComm = null;

    using(sqlConn = new SqlConnection(connectionString))
    {
        using (sqlComm = new SqlCommand())
        {
            sqlComm.Connection = sqlConn;
            sqlComm.Parameters.Add("@pubName",
                SqlDbType.NChar).Value = "O''Reilly";
            sqlComm.CommandText = "SELECT COUNT(*) FROM Authors " +
                "WHERE Publisher=@pubName";

            sqlConn.Open();
            object authorCount = sqlComm.ExecuteScalar();
            return (int)authorCount;
        }
    }
}

When determining how to structure exception handling in your application or component, consider doing the following:

  • Use a single try-catch or try-catch-finally exception handler at locations higher up in your code. These exception handlers can be considered coarse-grained.

  • Code farther down the call stack should contain try-finally exception handlers. These exception handlers can be considered fine-grained.

The fine-grained try-finally exception handlers allow for better control over cleanup after an exception occurs. The exception is then bubbled up to the coarser-grained try-catch or try-catch-finally exception handler. This technique allows for a more centralized scheme of exception handling and minimizes the code that you have to write to handle exceptions.

To improve performance, you should handle the case when an exception could be thrown (rather than catch the exception after it is thrown) if you know the code will be run in a single-threaded environment. If the code will run on multiple threads, there is still the potential that the initial check could succeed, but the object value could change (perhaps to null) in another thread before the actions following the check can be taken.

For example, in a single-threaded environment, if a method has a good chance of returning a null value, you should test the returned value for null before that value is used, as opposed to using a try-catch block and allowing the NullReferenceException to be thrown. If you think a null value is possible, check for it. If it shouldn’t happen, then it is an exceptional condition when it does, and exception handling should be used. To illustrate this, the following method uses exception-handling code to process the NullReferenceException:

public void SomeMethod()
{
    try
    {
        Stream s = GetAnyAvailableStream();
        Console.WriteLine("This stream has a length of " + s.Length);
    }
    catch (NullReferenceException)
    {
        // Handle a null stream here.
    }
}

Here is the method implemented to use an if-else conditional instead:

public void SomeMethod()
{
    Stream s = GetAnyAvailableStream();
    if (s != null)
    {
        Console.WriteLine("This stream has a length of " + s.Length);
    }
    else
    {
        // Handle a null stream here.
    }
}

Additionally, you should make sure that this stream is closed by using the finally block as follows:

public void SomeMethod()
{
    Stream s = null;
    using(s = GetAnyAvailableStream())
    {
        if (s != null)
        {
            Console.WriteLine("This stream has a length of " + s.Length);
        }
        else
        {
            // Handle a null stream here.
        }
    }
}

The finally block contains the method call that will close the stream, ensuring that there is no data loss.

Consider throwing exceptions instead of returning error codes. With well-placed exception-handling code, you should not have to rely on methods that return error codes, such as a Boolean true-false, to correctly handle errors, making for much cleaner code. Another benefit is that you do not have to look up any values for the error codes to understand the code.

Note

The biggest advantage to exceptions is that when an exceptional situation arises, you cannot just ignore it as you can with error codes. This helps you find and fix bugs.

Throw the most specific possible exception, not general ones. For example, throw an ArgumentNullException instead of an ArgumentException, which is the base class of ArgumentNullException. Throwing an ArgumentException just tells you that there was a problem with a parameter value to a method. Throwing an ArgumentNullException tells you more specifically what the problem with the parameter really is. Another potential problem is that a more general exception may not be caught if the catcher of the exception is looking for a more specific type derived from the thrown exception.

The FCL provides several exception types that you will find very useful to throw in your own code. Many of these exceptions are listed here with a definition of where and when they should be thrown:

  • Throw an InvalidOperationException in a property, indexer, or method when it is called with the object in an inappropriate state (e.g., when an indexer is called on an object that has not yet been initialized or methods are called out of sequence).

  • Throw ArgumentException if invalid parameters are passed into a method, property, or indexer. The ArgumentNullException, ArgumentOutOfRangeException, and InvalidEnumArgumentException are three subclasses of the ArgumentException class. It is more appropriate to throw one of these subclassed exceptions because they are more indicative of the root cause of the problem. The ArgumentNullException indicates that a parameter was passed in as null and that this parameter cannot be null under any circumstance. The ArgumentOutOfRangeException indicates that an argument was passed in that was outside of a valid acceptable range. This exception is used mainly with numeric values. The InvalidEnumArgumentException indicates that an enumeration value was passed in that does not exist in that enumeration type.

  • Throw a FormatException when an invalid formatting parameter is passed in to a method. You’d use this technique mainly when overriding/overloading methods such as ToString that can accept formatting strings, as well as in the parse methods on the various numeric types.

  • Throw ObjectDisposedException when a property, indexer, or method is called on an object that has already been disposed.

  • Many exceptions that derive from the SystemException class, such as NullReferenceException, ExecutionEngineException, StackOverflowException, OutOfMemoryException, and IndexOutOfRangeException, are thrown only by the CLR and should not be explicitly thrown with the throw keyword in your code.

The .NET Framework Class Library (FCL) also contains many classes to obtain diagnostic information about your application, as well as the environment in which it is running. In fact, there are so many classes that a namespace, System.Diagnostics, was created to contain all of them. This chapter includes recipes for instrumenting your application with debug/trace information, obtaining process information, using the built-in event log, and taking advantage of mechanisms like performance counters or Event Tracing for Windows (ETW) and EventSource. It should be noted that ETW and EventSource are becoming the preferred performance telemetry mechanism for the .NET Framework.

Debugging (via the Debug class) is turned on by default in debug builds only, and tracing (via the Trace class) is turned on by default in both debug and release builds. These defaults allow you to ship your application instrumented with tracing code using the Trace class. You ship your code with tracing compiled in but turned off in the configuration so that the tracing code is not called (for performance reasons) unless it is a server-side application (where the value of the instrumentation may outweigh the performance hit, and in the cloud, nobody can hear you scream without logs!). If a problem occurs on a production machine and you cannot re-create it on your development computer, you can enable tracing and allow the tracing information to be dumped to a file. You can then inspect this file to help you pinpoint the real problem.

Since both the Debug and Trace classes contain the same members with the same names, you can interchange them in your code by renaming Debug to Trace and vice versa. Most of the recipes in this chapter use the Trace class; to modify them so that they use the Debug class instead, simply replace each instance of Trace with Debug in the code.

5.1 Knowing When to Catch and Rethrow Exceptions

Problem

You want to establish when it is appropriate to catch and rethrow an exception.

Solution

Catching and rethrowing exceptions is appropriate if you have a section of code where you want to perform some action if an exception occurs, but not perform any actions to actually handle the exception. To get the exception so that you can perform the initial action on it, establish a catch block to catch the exception. Then, once the action has been performed, rethrow the exception from the catch block in which the original exception was handled. Use the throw keyword, followed by a semicolon, to rethrow an exception:

try
{
    Console.WriteLine("In try");
    int z2 = 9999999;
    checked { z2 *= 999999999; }
}
catch (OverflowException oe)
{
    // Record the fact that the overflow exception occurred.
    EventLog.WriteEntry("MyApplication", oe.Message, EventLogEntryType.Error);
    throw;
}

Here, you create an EventLog entry that records the occurrence of an overflow exception. Then the exception is propagated up the call stack by the throw statement.

Discussion

Establishing a catch block for an exception is essentially saying that you want to do something about that exceptional case.

Note

If you do not rethrow the exception, or create a new exception to wrap the original exception and throw it, the assumption is that you have handled the condition that caused the exception and that the program can continue normal operation.

By choosing to rethrow the exception, you are indicating that there is still an issue to be dealt with and that you are counting on code farther up the stack to handle the condition. If you need to perform an action based on a thrown exception and need to allow the exception to continue after your code executes, then rethrowing is the mechanism to handle this. If both of those conditions are not met, don’t rethrow the exception; just handle it or remove the catch block.

Note

Remember that throwing exceptions is expensive. Try not to needlessly throw and rethrow exceptions, because this might bog down your application.

When rethrowing an exception, use throw; instead of throw ex; as the former will preserve the original call stack of the exception. Using throw with the catch parameter will reset the call stack to that location, and information about the error will be lost. There might be some scenarios where you want the call stack changed (to hide details of the internals of a portion of your application that performs sensitive operations, for example) but on the whole, give yourself the best chance to debug things and don’t truncate the call stack.

5.2 Handling Exceptions Thrown from Methods Invoked via Reflection

Problem

Using reflection, you invoke a method that generates an exception. You want to obtain the real exception object and its information in order to diagnose and fix the problem.

Solution

The real exception and its information can be obtained through the InnerException property of the TargetInvocationException that is thrown by MethodInfo.Invoke.

Discussion

Example 5-1 handles an exception that occurs within a method invoked via reflection. The Reflect class contains a ReflectionException method that invokes the static TestInvoke method using the reflection classes.

Example 5-1. Obtaining information on an exception invoked by a method accessed through reflection
using System;
using System.Reflection;

public static class Reflect
{
    public static void ReflectionException()
    {
        Type reflectedClass = typeof(DebuggingAndExceptionHandling);

        try
        {
            MethodInfo methodToInvoke = reflectedClass.GetMethod("TestInvoke");
            methodToInvoke?.Invoke(null, null);
        }
        catch(Exception e)
        {
            Console.WriteLine(e.ToShortDisplayString());
        }
    }

    public static void TestInvoke()
    {
        throw (new Exception("Thrown from invoked method."));
    }
}

This code displays the following text:

Message: Exception has been thrown by the target of an invocation.
Type: System.Reflection.TargetInvocationException
Source: mscorlib
TargetSite: System.Object InvokeMethod(System.Object, System.Object[], System.Si
gnature, Boolean)
**** INNEREXCEPTION START ****
Message: Thrown from invoked method.
Type: System.Exception
Source: CSharpRecipes
TargetSite: Void TestInvoke()
**** INNEREXCEPTION END ****

When the methodToInvoke?.Invoke method is called, the TestInvoke method is called and throws an exception. The question mark next to methodToInvoke is a null-conditional operator to handle the case where the MethodInfo could not be retrieved and is null. This way, we didn’t have to write the check for null around the invocation. The outer exception is the TargetInvocationException; this is the generic exception thrown when a method invoked through reflection throws an exception. The CLR automatically wraps the original exception thrown by the invoked method inside of the TargetInvocationException object’s InnerException property. In this case, the exception thrown by the invoked method is of type System.Exception. This exception is shown after the section that begins with the text **** INNEREXCEPTION START ****.

To display the exception information, we call the ToShortDisplayString method:

Console.WriteLine(e.ToShortDisplayString());

The ToShortDisplayString extension method for Exception uses a StringBuilder to create the string of information about the exception and all inner exceptions. The WriteExceptionShortDetail method populates the StringBuilder with specific parts of the exception data. To get the inner exceptions, we use the GetNestedExceptionList extension method:

public static string ToShortDisplayString(this Exception ex)
{
    StringBuilder displayText = new StringBuilder();
    WriteExceptionShortDetail(displayText, ex);
    foreach(Exception inner in ex.GetNestedExceptionList())
    {
       displayText.AppendFormat("**** INNEREXCEPTION START ****{0}",
           Environment.NewLine);
       WriteExceptionShortDetail(displayText, inner);
       displayText.AppendFormat("**** INNEREXCEPTION END ****{0}{0}",
           Environment.NewLine);
    }
    return displayText.ToString();
}

public static IEnumerable<Exception> GetNestedExceptionList(
         this Exception exception)
{
    Exception current = exception;
    do
    {
        current = current.InnerException;
        if (current != null)
            yield return current;
    }
    while (current != null);
}

public static void WriteExceptionShortDetail(StringBuilder builder, Exception ex)
{
    builder.AppendFormat("Message: {0}{1}", ex.Message, Environment.NewLine);
    builder.AppendFormat("Type: {0}{1}";, ex.GetType(), Environment.NewLine);
    builder.AppendFormat("Source: {0}{1}", ex.Source, Environment.NewLine);
    builder.AppendFormat("TargetSite: {0}{1}", ex.TargetSite, 
        Environment.NewLine);
}

See Also

The “Type Class,” “Null-Conditional Operator,” and “MethodInfo Class” topics in the MSDN documentation.

5.3 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 user-defined exceptions.

You should follow the naming convention for exceptions when determining the name of your exception. The convention is very simple: decide on the exception’s name, and add the word Exception to the end of it (e.g., use UnknownException as the exception name instead of just Unknown).

Every user-defined exception should include at least three constructors, which are 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.

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 => $"{base.Message}{Environment.NewLine}" +
            $"The server ({this.ServerName ?? "Unknown"})" +
           "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.

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 and 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, we need a new overridden constructor that accepts information to deserialize this object:

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

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 application domain boundaries.

For the case where 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.

Note

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.

At this point, the RemoteComponentException class contains everything you need for a complete user-defined exception class.

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 11.7 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 5-2.

Example 5-2. RemoteComponentException class
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;

[Serializable]
public class RemoteComponentException : Exception, ISerializable
{

    #region Constructors
    // Normal exception ctor's
    public RemoteComponentException() : base()
    {
    }

    public RemoteComponentException(string message) : base(message)
    {
    }

    public RemoteComponentException(string message, Exception innerException)
        : base(message, innerException)
    {
    }

    // Exception ctor's 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
    protected RemoteComponentException(SerializationInfo exceptionInfo,
        StreamingContext exceptionContext)
        : base(exceptionInfo, exceptionContext)
    {
        this.ServerName = exceptionInfo.GetString("ServerName");
    }
    #endregion // Constructors

    #region Properties
    // Read-only property for server name
    public string ServerName { get; }

    public override string Message => $"{base.Message}{Environment.NewLine}" +
                $"The server ({this.ServerName ?? "Unknown"})" +
                "has encountered an error.";
    #endregion // Properties

    #region Overridden methods
    // ToString method
    public override string ToString() =>
          "An error has occurred in a server component of this client." +
            $"{Environment.NewLine}Server Name: " +
            $"{this.ServerName}{Environment.NewLine}" +
            $"{this.ToFullDisplayString()}";


    // Used during serialization to capture information about extra fields
    [SecurityPermission(SecurityAction.LinkDemand, Flags =
         SecurityPermissionFlag.SerializationFormatter)]
    public override void GetObjectData(SerializationInfo info,
        StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue("ServerName", this.ServerName);
    }
    #endregion // Overridden methods

    public string ToBaseString() => (base.ToString());
}

The ToFullDisplayString call made in the ToString override is an extension method for Exception, with the GetNestedExceptionList extension method used to get the list of exceptions and the WriteExceptionDetail method to handle each Exception’s details:

public static string ToFullDisplayString(this Exception ex)
{
    StringBuilder displayText = new StringBuilder();
    WriteExceptionDetail(displayText, ex);
    foreach (Exception inner in ex.GetNestedExceptionList())
    {
        displayText.AppendFormat("**** INNEREXCEPTION START ****{0}",
            Environment.NewLine);
        WriteExceptionDetail(displayText, inner);
        displayText.AppendFormat("**** INNEREXCEPTION END ****{0}{0}",
            Environment.NewLine);
    }
    return displayText.ToString();
}

public static IEnumerable<Exception> GetNestedExceptionList(
    this Exception exception)
{
    Exception current = exception;
    do
    {
        current = current.InnerException;
        if (current != null)
            yield return current;
    }
    while (current != null);
}

public static void WriteExceptionDetail(StringBuilder builder, Exception ex)
{
    builder.AppendFormat("Message: {0}{1}", ex.Message, Environment.NewLine);
    builder.AppendFormat("Type: {0}{1}", ex.GetType(), Environment.NewLine);
    builder.AppendFormat("HelpLink: {0}{1}", ex.HelpLink, Environment.NewLine);
    builder.AppendFormat("Source: {0}{1}", ex.Source, Environment.NewLine);
    builder.AppendFormat("TargetSite: {0}{1}", ex.TargetSite, 
        Environment.NewLine);
    builder.AppendFormat("Data:{0}", Environment.NewLine);
    foreach (DictionaryEntry de in ex.Data)
    {
        builder.AppendFormat("	{0} : {1}{2}",
            de.Key, de.Value, Environment.NewLine);
    }
    builder.AppendFormat("StackTrace: {0}{1}", ex.StackTrace, 
        Environment.NewLine);
}

A partial listing of the code to test the RemoteComponentException class is shown in Example 5-3.

Example 5-3. 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");

    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 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());
    Console.WriteLine(Environment.NewLine + "END TEST" + Environment.NewLine);
}

The output from Example 5-3 is presented in Example 5-4.

Example 5-4. Output displayed by the RemoteComponentException class
TEST -OVERRIDDEN- MESSAGE PROPERTY
se1.Message == Exception of type 'CSharpRecipes.ExceptionHandling+RemoteComponen
tException' was thrown.
A server with an unknown name has encountered an error.
se2.Message == A Test Message for se2
A server with an unknown name has encountered an error.
se3.Message == A Test Message for se3
A server with an unknown name 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:
Message: Exception of type 'CSharpRecipes.ExceptionHandling+RemoteComponentExcep
tion' was thrown.
A server with an unknown name has encountered an error.

Type: CSharpRecipes.ExceptionHandling+RemoteComponentException
HelpLink:
Source:
TargetSite:
Data:
StackTrace:

se2.ToString() == An error has occurred in a server component of this client.
Server Name:
Message: A Test Message for se2
A server with an unknown name has encountered an error.
Type: CSharpRecipes.ExceptionHandling+RemoteComponentException
HelpLink:
Source:
TargetSite:
Data:
StackTrace:

se3.ToString() == An error has occurred in a server component of this client.
Server Name:
Message: A Test Message for se3
A server with an unknown name has encountered an error.
Type: CSharpRecipes.ExceptionHandling+RemoteComponentException
HelpLink:
Source:
TargetSite:
Data:
StackTrace:
**** INNEREXCEPTION START ****
Message: The Inner Exception
Type: System.Exception
HelpLink:
Source:
TargetSite:
Data:
StackTrace:
**** INNEREXCEPTION END ****

se4.ToString() == An error has occurred in a server component of this client.
Server Name: MyServer
Message: A Test Message for se4
The server (MyServer) has encountered an error.
Type: CSharpRecipes.ExceptionHandling+RemoteComponentException
HelpLink:
Source:
TargetSite:
Data:
StackTrace:

se5.ToString() == An error has occurred in a server component of this client.
Server Name: MyServer

Message: A Test Message for se5
The server (MyServer) has encountered an error.
Type: CSharpRecipes.ExceptionHandling+RemoteComponentException
HelpLink:
Source:
TargetSite:
Data:
StackTrace:
**** INNEREXCEPTION START ****
Message: The Inner Exception
Type: System.Exception
HelpLink:
Source:
TargetSite:
Data:
StackTrace:
**** INNEREXCEPTION END ****

END TEST

See Also

Recipe 11.7, and the “Using User-Defined Exceptions” and “Exception Class” topics in the MSDN documentation.

5.4 Breaking on a First-Chance Exception

Problem

You need to fix a problem with your code that is throwing an exception. Unfortunately, an exception handler is trapping the exception, and you are having a tough time pinpointing where and when the exception is being thrown.

Forcing the application to break on an exception before the application has a chance to handle it is very useful in situations in which you need to step through the code at the point where the exception is first being thrown. If this exception were thrown and not handled by your application, the debugger would intervene and break on the line of code that caused the unhandled exception. In this case, you can see the context in which the exception was thrown. However, if an exception handler is active when the exception is thrown, the exception handler will handle it and continue on, preventing you from being able to see the context at the point where the exception was thrown. This is the default behavior for all exceptions.

Solution

Select Debug→Exceptions or press Ctrl-Alt-E within Visual Studio 2015 to display the Exception Settings tool window (see Figure 5-1). Select the exception from the tree that you want to modify and then click on the checkbox in the tree view. Click OK and then run your application. Any time the application throws a System.ArgumentOutOfRangeException, the debugger will break on that line of code before your application has a chance to handle it.

Using the Exception Settings tool window, you can target specific exceptions or sets of exceptions for which you wish to alter the default behavior. This dialog has three main sections. The first is the TreeView control, which contains the list of categorized exceptions. Using this TreeView, you can choose one or more exceptions or groups of exceptions whose behavior you wish to modify.

The next section on this dialog is the column Thrown in the list next to the TreeView. This column contains a checkbox for each exception that will enable the debugger to break when that type of exception is first thrown. At this stage, the exception is considered a first-chance exception. Checking the checkbox in the Thrown column forces the debugger to intervene when a first-chance exception of the type chosen in the TreeView control is thrown. Unchecking the checkbox allows the application to attempt to handle the first-chance exception.

The Exceptions Settings tool window
Figure 5-1. The Exceptions Settings tool window

You can also click on the Filter icon in the top left of the window in order to narrow down the view of the exceptions to just the ones you have selected to break on a first-chance exception, as shown in Figure 5-2.

The Exceptions Settings tool window filtered
Figure 5-2. The Exceptions Settings tool window filtered

The Exception Settings tool window also provides a Search bar at the top to allow you to search for exceptions in the window. If you type argumentnullexception in the window, you will see the selection narrow to just items that match that text, as shown in Figure 5-3.

To add a user-defined exception to the Exception Settings, click the Add button. You’ll see the dialog box shown in Figure 5-4.

Adding a user-defined exception to the Exception Settings
Figure 5-4. Adding a user-defined exception to the Exception Settings

Press Yes to use the original Exceptions dialog, which is shown in Figure 5-5.

The Exceptions dialog
Figure 5-5. The Exceptions dialog

This dialog contains two helpful buttons, Find and Find Next, to allow you to search for an exception rather than dig into the TreeView control and search for it on your own. In addition, three other buttons—Reset All, Add, and Delete—allow you to reset to the original state and to add and remove user-defined exceptions, respectively.

For example, you can create your own exception, as you did in Recipe 5.3, and add this exception to the TreeView list. You must add any managed exception such as this to the TreeView node entitled Common Language Runtime Exceptions. This setting tells the debugger that this is a managed exception and should be handled as such. Figure 5-6 shows the addition of the custom exception.

Adding a user-defined exception to the Exceptions dialog
Figure 5-6. Adding a user-defined exception to the Exceptions dialog

Type the name of the exception—exactly as its class name is spelled with the full namespace scoping into the Name field of this dialog box. Do not append any other information to this name, such as the namespace it resides in or a class name that it is nested within. Doing so will prevent the debugger from seeing this exception when it is thrown. Clicking the OK button places this exception into the TreeView under the Common Language Runtime Exceptions node. The Exceptions dialog box will look something like the one in Figure 5-7 after you add this user-defined exception.

The Exceptions dialog box after you add a user-defined exception to the TreeView
Figure 5-7. The Exceptions dialog box after you add a user-defined exception to the TreeView

The Delete button deletes any selected user-defined exception that you added to the TreeView. The Reset All button deletes any and all user-defined exceptions that have been added to the TreeView. Check the Thrown column to have the debugger stop when that exception type is thrown.

There is one other setting that can affect your exception debugging: Just My Code (Figure 5-8). You should turn this off to get the best picture of what is really happening in your application when debugging; when it is enabled, you cannot see the related actions of the framework code that your code calls. Being able to see where your code calls into the framework and where it goes from there is very educational and can help you understand the issue you are debugging better. The setting is under ToolsOptionsDebuggingGeneral in Visual Studio 2015.

The Just My Code setting disabled
Figure 5-8. The Just My Code setting disabled

See Also

The “Exception Handling (Debugging)” topic in the MSDN documentation.

5.5 Handling Exceptions Thrown from an Asynchronous Delegate

Problem

When using a delegate asynchronously, you want to be notified if the delegate has thrown any exceptions.

Solution

Wrap the EndInvoke method of the delegate in a try-catch block:

using System;
using System.Threading;

public class AsyncAction
{
    public void PollAsyncDelegate()
    {
        // Create the async delegate to call Method1 and call its 
        // BeginInvokemethod.
        AsyncInvoke MI = new AsyncInvoke(TestAsyncInvoke.Method1);
        IAsyncResult AR = MI.BeginInvoke(null, null);

        // Poll until the async delegate is finished.
        while (!AR.IsCompleted)
        {
           System.Threading.Thread.Sleep(100);
           Console.Write('.'),
        }
        Console.WriteLine("Finished Polling");
        // Call the EndInvoke method of the async delegate.
        try
        {
            int RetVal = MI.EndInvoke(AR);
            Console.WriteLine("RetVal (Polling): " + RetVal);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}

The following code defines the AsyncInvoke delegate and the asynchronously invoked static method TestAsyncInvoke.Method1:

public delegate int AsyncInvoke();

public class TestAsyncInvoke
{
    public static int Method1()
    {
        throw (new Exception("Method1")); // Simulate an exception being thrown.
    }
}

Discussion

If the code in the PollAsyncDelegate method did not contain a call to the delegate’s EndInvoke method, the exception thrown in Method1 either would simply be discarded and never caught or, if the application had the top-level exception handlers wired up (Recipes 5.2, 5.7, and 5.8), it would be caught. If EndInvoke is called, then this exception would occur when EndInvoke is called and could be caught there. This behavior is by design; for all unhandled exceptions that occur within the thread, the thread immediately returns to the thread pool, and the exception is lost.

If a method that was called asynchronously through a delegate throws an exception, the only way to trap that exception is to include a call to the delegate’s EndInvoke method and wrap this call in an exception handler. You must call the EndInvoke method to retrieve the results of the asynchronous delegate; in fact, you must call it even if there are no results. You can obtain these results through a return value or any ref or out parameters of the delegate.

See Also

For information about wiring up top-level exception handlers in your application, see Recipes 5.2, 5.7, and 5.8.

5.6 Giving Exceptions the Extra Info They Need with Exception.Data

Problem

You want to send some additional information along with an exception.

Solution

Use the Data property on the System.Exception object to store key/value pairs of information relevant to the exception.

For example, say there is a System.ArgumentException being thrown from a section of code, and you want to include the underlying cause and the length of time it took. You would add two key/value pairs to the Exception.Data property by specifying the key in the indexer and then assigning the value.

In the example that follows, the Data for the irritable exception uses "Cause" and "Length" for its keys. Once the items have been set in the Data collection, the exception can be thrown and caught, and more data can be added in subsequent catch blocks for as many levels of exception handling as the exception is allowed to traverse:

try
{
    try
    {
        try
        {
            try
            {
                ArgumentException irritable =
                    new ArgumentException("I'm irritable!");
                irritable.Data["Cause"]="Computer crashed";
                irritable.Data["Length"]=10;
                throw irritable;
            }
            catch (Exception e)
            {
                // See if I can help...
                if(e.Data.Contains("Cause"))
                    e.Data["Cause"]="Fixed computer"
                throw;
            }
        }
        catch (Exception e)
        {
            e.Data["Comment"]="Always grumpy you are";
            throw;
        }
    }
    catch (Exception e)
    {
        e.Data["Reassurance"]="Error Handled";
        throw;
    }
}

The final catch block can then iterate over the Exception.Data collection and display all of the supporting data that has been gathered in the Data collection since the initial exception was thrown:

catch (Exception e)
{
    Console.WriteLine("Exception supporting data:");
    foreach(DictionaryEntry de in e.Data)
    {
        Console.WriteLine("	{0} : {1}",de.Key,de.Value);
    }
}

Discussion

Exception.Data is an object that supports the IDictionary interface. This allows you to:

  • Add and remove name/value pairs.

  • Clear the contents.

  • Search the collection to see if it contains a certain key.

  • Get an IDictionaryEnumerator for rolling over the collection items.

  • Index into the collection using the key.

  • Access an ICollection of all of the keys and all of the values separately.

Note

Items placed into Exception.Data need to be Serializable or they will throw an ArgumentException on the addition to the collection. If you are adding a class to Exception.Data, mark it as Serializable and make sure it can be serialized.

public void TestExceptionDataSerializable()
{
    Exception badMonkey =
        new Exception("You are a bad monkey!");
    try
    {
        badMonkey.Data["Details"] = new Monkey();
    }
    catch (ArgumentException aex)
    {
        Console.WriteLine(aex.Message);
    }
}

//[Serializable]  // Uncomment to make serializable and work
public class Monkey
{
    public string Name { get; } = "George";
}

It is very handy to be able to tack on code-specific data to the system exceptions, as it allows you to give a more complete picture of what happened in the code when the error occurred. The more information available to the poor soul (probably yourself) who is trying to figure out why the exception was thrown in the first place, the better the chance of it being fixed. Do yourself and your team a favor and give a little bit of extra information when throwing exceptions; you won’t be sorry you did.

See Also

The “Exception.Data Property” topic in the MSDN documentation.

5.7 Dealing with Unhandled Exceptions in WinForms Applications

Problem

You have a WinForms-based application in which you want to catch and log any unhandled exceptions on any thread.

Solution

You need to hook up handlers for both the System.Windows.Forms.Application. ThreadException event and the System.Appdomain.UnhandledException event. Both of these events need to be hooked up, as the WinForms support in the Framework does a lot of exception trapping itself. It exposes the System.Windows.Forms.Application.ThreadException event to allow you to get any unhandled exceptions that happen on the UI thread that the WinForms and their events are running on. In spite of its deceptive name, the System.Windows.Forms.Application.ThreadException event handler will not catch unhandled exceptions on worker threads constructed by the program or from ThreadPool threads. In order to catch all of those possible routes for unhandled exceptions in a WinForms application, you need to hook up a handler for the System.AppDomain.UnhandledException event (System.Windows.Forms.Application.ThreadException will catch UI thread exceptions).

To hook up the necessary event handlers to catch all of your unhandled exceptions in a WinForms application, add the following code to the Main function in your application:

static void Main()
{
    // Adds the event handler to catch any exceptions that happen
    // in the main UI thread.
    Application.ThreadException +=
        new ThreadExceptionEventHandler(OnThreadException);

    // Add the event handler for all threads in the appdomain except
    // for the main UI thread.
    appdomain.CurrentDomain.UnhandledException +=
        new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

    Application.EnableVisualStyles();
    Application.Run(new Form1());
}

The System.AppDomain.UnhandledException event handler is hooked up to the current Appdomain via the appdomain.CurrentDomain property, which gives access to the current Appdomain. The ThreadException handler for the application is accessed through the Application.ThreadException property.

The event handler code is established in the CurrentDomain_UnhandledException and OnThreadException handler methods. See Recipe 5.8 for more information on the UnhandledExceptionEventHandler. The ThreadExceptionEventHandler is passed the sender object and a ThreadExceptionEventArgs object. ThreadExceptionEventArgs has an Exception property that contains the unhandled exception from the WinForms UI thread:

// Handles the exception event for all other threads
static void CurrentDomain_UnhandledException(object sender,
                            UnhandledExceptionEventArgs e)
{
    // Just show the exception details.
    MessageBox.Show("CurrentDomain_UnhandledException: " +
                e.ExceptionObject.ToString());
}

// Handles the exception event from a UI thread
static void OnThreadException(object sender, ThreadExceptionEventArgs t)
{
    // Just show the exception details.
    MessageBox.Show("OnThreadException: " + t.Exception.ToString());
}

Discussion

Exceptions are the primary way to convey errors in .NET, so when you build an application, it is imperative that there be a final line of defense against unhandled exceptions. An unhandled exception will crash the program (even if it looks a bit nicer in .NET); this is not the impression you wish to make on your customers. It would have been nice to have one event to hook up to for all unhandled exceptions. The appdomain.UnhandledException event comes pretty close to that, but having to handle one extra event isn’t the end of the world, either. In coding event handlers for both appdomain.UnhandledException and Application.ThreadException, you can easily call a single handler that writes the exception information to the event log, the debug stream, or custom trace logs or even sends you an email with the information. The possibilities are limited only by how you want to handle errors that can happen to any program given enough exposure.

See Also

The “ThreadExceptionEventHandler Delegate” and “UnhandledExceptionEventHandler Delegate” topics in the MSDN documentation.

5.8 Dealing with Unhandled Exceptions in WPF Applications

Problem

You have a Windows Presentation Foundation (WPF)–based application in which you want to catch and log any unhandled exceptions on any thread.

Solution

To hook up the necessary event handlers to catch all of your unhandled exceptions in a WPF application, add the following code to the App.xaml file in your application:

<Application x:Class="UnhandledWPFException.App"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   StartupUri="Window1.xaml"
   DispatcherUnhandledException="Application_DispatcherUnhandledException">
   <Application.MainWindow>
       <Window />
   </Application.MainWindow>
   <Application.Resources>
   </Application.Resources>
</Application>

Then, in the codebehind file App.xaml.cs, add the Application_DispatcherUnhandledException method to handle otherwise unhandled exceptions:

private void Application_DispatcherUnhandledException(object sender,
          System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
    // Log the exception information in the event log
    EventLog.WriteEntry("UnhandledWPFException Application",
        e.Exception.ToString(), EventLogEntryType.Error);
    // Let the user know what happenned
    MessageBox.Show("Application_DispatcherUnhandledException: " +
        e.Exception.ToString());
    // indicate we handled it
    e.Handled = true;
    // shut down the application
    this.Shutdown();
}

Discussion

Windows Presentation Foundation provides another way to create Windows-based applications for .NET. Protecting users from unsightly unhandled exceptions requires a bit of code in WPF, just as it does in WinForms (see Recipe 5.7 for doing this in WinForms).

The System.Windows.Application class is the base class for WPF-based applications, and it is from here that the unhandled exceptions are handled via the DispatcherUnhandledException event. You set up this event handler by specifying the method to handle the event in the App.xaml file shown here:

DispatcherUnhandledException="Application_DispatcherUnhandledException">

You can also set this up in code directly instead of doing it the XAML way by adding the Startup event handler (which is where Microsoft recommends you put the initialization code for the application in WPF) to the XAML file like this:

<Application x:Class="UnhandledWPFException.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml"
  Startup="Application_Startup" >
    <Application.MainWindow>
        <Window />
    </Application.MainWindow>
    <Application.Resources>

    </Application.Resources>
</Application>

In the Startup event, establish the event handler for the DispatcherUnhandledException like this:

private void Application_Startup(object sender, StartupEventArgs e)
{
    this.DispatcherUnhandledException +=
          new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(
               Application_DispatcherUnhandledException);
}

This is great for handling exceptions for WPF applications: just hook up and get all those unhandled exceptions delivered to your single handler, right? Wrong. Just as was necessary in WinForms applications, if you have any code running on any threads other than the UI thread (which you almost always will), you still have to hook up to the AppDomain for the AppDomain.UnhandledException handler to catch those exceptions on threads other than the UI thread. In order to do that, we update our App.xaml.cs file as follows:

/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
    private void Application_DispatcherUnhandledException(object sender,
          System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
    {
        // indicate we handled it
        e.Handled = true;
        ReportUnhandledException(e.Exception);
    }

    private void Application_Startup(object sender, StartupEventArgs e)
    {
       // WPF UI exceptions
       this.DispatcherUnhandledException +=
         new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(
           Application_DispatcherUnhandledException);

       // Those dirty thread exceptions
       AppDomain.CurrentDomain.UnhandledException +=
           new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
    }

    private void CurrentDomain_UnhandledException(object sender,
                                    UnhandledExceptionEventArgs e)
    {
        ReportUnhandledException(e.ExceptionObject as Exception);
    }

    private void ReportUnhandledException(Exception ex)
    {
        // Log the exception information in the event log
        EventLog.WriteEntry("UnhandledWPFException Application",
            ex.ToString(), EventLogEntryType.Error);
        // Let the user know what happenned
        MessageBox.Show("Unhandled Exception: " + ex.ToString());
        // shut down the application
        this.Shutdown();
    }
}

See Also

Recipe 5.7; the “DispatcherUnhandledException event” and “AppDomain. UnhandledException handler” topics in the MSDN documentation.

5.9 Determining Whether a Process Has Stopped Responding

Problem

You need to watch one or more processes to determine whether the user interface has stopped responding to the system. This functionality is similar to the column in the Task Manager that displays the text “Responding” or “Not Responding,” depending on the state of the application.

Solution

Use the GetProcessState method and ProcessRespondingState enumeration shown in Example 5-5 to determine whether a process has stopped responding.

Example 5-5. Determining whether a process has stopped responding
public enum ProcessRespondingState
{
    Responding,
    NotResponding,
    Unknown
}

public static ProcessRespondingState GetProcessState(Process p)
{
    if (p.MainWindowHandle == IntPtr.Zero)
    {
        Trace.WriteLine($"{p.ProcessName} does not have a MainWindowHandle");
        return ProcessRespondingState.Unknown;
    }
    else
    {
        // This process has a MainWindowHandle
        if (!p.Responding)
            return ProcessRespondingState.NotResponding;
        else
            return ProcessRespondingState.Responding;
    }
}

Discussion

The GetProcessState method accepts a single parameter, process, identifying a process. The Responding property is then called on the Process object represented by the process parameter. This property returns a ProcessRespondingState enumeration value to indicate that a process is currently responding (Responding), that it is not currently responding (NotResponding), or that a response cannot be determined for this process as there is no main window handle (Unknown).

The Responding property always returns true if the process in question does not have a MainWindowHandle. Processes such as Idle, spoolsv, Rundll32, and svchost do not have a main window handle, and therefore the Responding property always returns true for them. To weed out these processes, you can use the MainWindowHandle property of the Process class, which returns the handle of the main window for a process. If this property returns 0, the process has no main window.

To determine whether all processes on a machine are responding, you can call the GetProcessState method as follows:

var processes = Process.GetProcesses().ToArray();
Array.ForEach(processes, p =>
    {
        var processState = GetProcessState(p);
        switch (processState)
        {
            case ProcessRespondingState.NotResponding:
                Console.WriteLine($"{p.ProcessName} is not responding.");
                break;
            case ProcessRespondingState.Responding:
                Console.WriteLine($"{p.ProcessName} is responding.");
                break;
            case ProcessRespondingState.Unknown:
                Console.WriteLine(
                    $"{p.ProcessName}'s state could not be determined.");
                break;
        }
    });

This code snippet iterates over all processes currently running on your system. The static GetProcesses method of the Process class takes no parameters and returns an array of Process objects with information for all processes running on your system. Each Process object is then passed in to your GetProcessState method to determine whether it is responding. Other static methods on the Process class that retrieve Process objects are GetProcessById, GetCurrentProcess, and GetProcessesByName.

See Also

The “Process Class” topic in the MSDN documentation.

5.10 Using Event Logs in Your Application

Problem

You need to add the ability for your application to log events that occur in your application, such as startup, shutdown, critical errors, and even security breaches. Along with reading and writing to a log, you need the ability to create, clear, close, and remove logs from the event log.

Your application might need to keep track of several logs at one time. For example, your application might use a custom log to track specific events, such as startup and shutdown, as they occur in your application. To supplement the custom log, your application could make use of the security log already built into the event log system to read/write security events that occur in your application.

Support for multiple logs comes in handy when one log needs to be created and maintained on the local computer and another, duplicate log needs to be created and maintained on a remote machine. This remote machine might contain logs of all running instances of your application on each user’s machine. An administrator could use these logs to quickly find any problems that occur or discover if security is breached in your application. In fact, an application could be run in the background on the remote administrative machine that watches for specific log entries to be written to this log from any user’s machine. Recipe 13.6 uses an event mechanism to watch for entries written to an event log and could easily be used to enhance this recipe.

Solution

Use the event log built into the Microsoft Windows operating system to record specific events that occur infrequently.

Note

Don’t flood the event log with many different entries that you could handle by enabling or disabling tracing. Errors are a must, followed by the very important items, but not everything should be written to the event log. Be judicious when writing to the event log so you don’t have to sort through all of it when you are looking for the clues.

The AppEvents class shown in Example 5-6 contains all the methods needed to create and use an event log in your application.

Example 5-6. Creating and using an event log
using System;
using System.Diagnostics;

public class AppEvents
{
    // If you encounter a SecurityException trying to read the registry 
    // (Security log) follow these instructions:
    // 1) Open the Registry Editor (search for regedit or type regedit at the Run 
    // prompt) 2) Navigate to the following key:
    // 3) HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesEventlogSecurity
    // 4) Right-click on this entry and select Permissions
    // 5) Add the user you are logged in as and give the user the Read permission

    // If you encounter a SecurityException trying to write to the event log
    // "Requested registry access is not allowed.", then the event source has not 
    // been created.  Try re-running the EventLogInstaller for your custom event or 
    // for this sample code, run %WINDOWS%Microsoft.NETFrameworkv4.0.30319
    // InstallUtil.exe AppEventsEventLogInstallerApp.dll"
    // If you just ran it, you may need to wait a bit until Windows catches up and
    // recognizes the log that was added.

    const string localMachine = ".";
    // Constructors
    public AppEvents(string logName) :
        this(logName, Process.GetCurrentProcess().ProcessName)
    { }

    public AppEvents(string logName, string source) :
        this(logName, source, localMachine)
    { }

    public AppEvents(string logName, string source,
        string machineName = localMachine)
    {
        this.LogName = logName;
        this.SourceName = source;
        this.MachineName = machineName;

        Log = new EventLog(LogName, MachineName, SourceName);
    }


    private EventLog Log { get; set; } = null;

    public string LogName { get; set; }

    public string SourceName { get; set; }

    public string MachineName { get; set; } = localMachine;


    // Methods
    public void WriteToLog(string message, EventLogEntryType type,
        CategoryType category, EventIDType eventID)
    {
        if (Log == null)
            throw (new ArgumentNullException(nameof(Log),
                "This Event Log has not been opened or has been closed."));

        EventLogPermission evtPermission =
            new EventLogPermission(EventLogPermissionAccess.Write, MachineName);
        evtPermission.Demand();

        // If you get a SecurityException here, see the notes at the
        // top of the class
        Log.WriteEntry(message, type, (int)eventID, (short)category);
    }

    public void WriteToLog(string message, EventLogEntryType type,
        CategoryType category, EventIDType eventID, byte[] rawData)
    {
        if (Log == null)
            throw (new ArgumentNullException(nameof(Log),
                "This Event Log has not been opened or has been closed."));

        EventLogPermission evtPermission =
            new EventLogPermission(EventLogPermissionAccess.Write, MachineName);
        evtPermission.Demand();

        // If you get a SecurityException here, see the notes at the
        // top of the class
        Log.WriteEntry(message, type, (int)eventID, (short)category, rawData);
    }

    public IEnumerable<EventLogEntry> GetEntries()
    {
        EventLogPermission evtPermission =
            new EventLogPermission(EventLogPermissionAccess.Administer, MachineName);
        evtPermission.Demand();
        return Log?.Entries.Cast<EventLogEntry>().Where(evt =>
            evt.Source == SourceName);
    }

    public void ClearLog()
    {
        EventLogPermission evtPermission =
            new EventLogPermission(EventLogPermissionAccess.Administer, MachineName);
        evtPermission.Demand();
        if (!IsNonCustomLog())
            Log?.Clear();
    }

    public void CloseLog()
    {
        Log?.Close();
        Log = null;
    }

    public void DeleteLog()
    {
        if (!IsNonCustomLog())
            if (EventLog.Exists(LogName, MachineName))
                EventLog.Delete(LogName, MachineName);

        CloseLog();
    }

    public bool IsNonCustomLog()
    {
        // Because Application, Setup, Security, System, and other non-custom logs
        // can contain crucial information  you can't just delete or clear them
        if (LogName == string.Empty || // same as application
            LogName == "Application" ||
            LogName == "Security" ||
            LogName == "Setup" ||
            LogName == "System")
        {
            return true;
        }
        return false;
    }
}

The EventIDType and CategoryType enumerations used in this class are defined as follows:

public enum EventIDType
{
    NA = 0,
    Read = 1,
    Write = 2,
    ExceptionThrown = 3,
    BufferOverflowCondition = 4,
    SecurityFailure = 5,
    SecurityPotentiallyCompromised = 6
}

public enum CategoryType : short
{
    None = 0,
    WriteToDB = 1,
    ReadFromDB = 2,
    WriteToFile = 3,
    ReadFromFile = 4,
    AppStartUp = 5,
    AppShutDown = 6,
    UserInput =7
}

As a last note, the EventIDType and CategoryType enumerations are designed mainly to log security-type breaches as well as potential attacks on the security of your application. Using these event IDs and categories, the administrator can more easily track down potential security threats and do postmortem analysis after security is breached. You can easily modify or replace these enumerations with your own to track different events that occur as a result of your application running.

Discussion

The AppEvents class created for this recipe provides applications with an easy-to-use interface for creating, using, and deleting single or multiple event logs in your application. The methods of the AppEvents class are described as follows:

WriteToLog
This method is overloaded to allow an entry to be written to the event log with or without a byte array containing raw data.
GetEntries
Returns all the event log entries for this event log and source in an IEnumerable<EventLogEntry>.
ClearLog
Removes all the event log entries from this event log if it is a custom log.
CloseLog
Closes this event log, preventing further interaction with it.
DeleteLog
Deletes this event log if it is a custom log.

You can add an AppEvents object to an array or collection containing other AppEvents objects; each AppEvents object corresponds to a particular event log. The following code creates two AppEvents classes and adds them to a ListDictionary collection:

public void CreateMultipleLogs()
{
    AppEvents AppEventLog = new AppEvents("AppLog", "AppLocal");
    AppEvents GlobalEventLog = new AppEvents("AppSystemLog", "AppGlobal");

    ListDictionary LogList = new ListDictionary();
    LogList.Add(AppEventLog.Name, AppEventLog);
    LogList.Add(GlobalEventLog.Name, GlobalEventLog);

To write to either of these two logs, obtain the AppEvents object by name from the ListDictionary object, cast the resultant object type to an AppEvents type, and call the WriteToLog method:

((AppEvents)LogList[AppEventLog.Name]).WriteToLog("App startup",
    EventLogEntryType.Information, CategoryType.AppStartUp,
    EventIDType.ExceptionThrown);

((AppEvents)LogList[GlobalEventLog.Name]).WriteToLog(
   "App startup security check",
    EventLogEntryType.Information, CategoryType.AppStartUp,
    EventIDType.BufferOverflowCondition);

Containing all AppEvents objects in a ListDictionary object allows you to easily iterate over all the AppEvents that your application has instantiated. Using a foreach loop, you can write a single message to both a local and a remote event log:

foreach (DictionaryEntry Log in LogList)
{
    ((AppEvents)Log.Value).WriteToLog("App startup",
        EventLogEntryType.FailureAudit,
        CategoryType.AppStartUp, EventIDType.SecurityFailure);
}

To delete each log in the logList object, you can use the following foreach loop:

foreach (DictionaryEntry Log in LogList)
{
    ((AppEvents)Log.Value).DeleteLog();
}
    LogList.Clear();

You should be aware of several key points. The first concerns a small problem with constructing multiple AppEvents classes. If you create two AppEvents objects and pass in the same source string to the AppEvents constructor, an exception will be thrown. Consider the following code, which instantiates two AppEvents objects with the same source string:

AppEvents appEventLog = new AppEvents("AppLog", "AppLocal");
AppEvents globalEventLog = new AppEvents("Application", "AppLocal");

The objects are instantiated without errors, but when the WriteToLog method is called on the globalEventLog object, the following exception is thrown:

An unhandled exception of type 'System.ArgumentException' occurred in system.dll.

Additional information: The source 'AppLocal' is not registered in log
'Application'. (It is registered in log 'AppLog'.) " The Source and Log
properties must be matched, or you may set Log to the empty string, and 
it will automatically be matched to the Source property.

This exception occurs because the WriteToLog method internally calls the WriteEntry method of the EventLog object. The WriteEntry method internally checks to see whether the specified source is registered to the log you are attempting to write to. In this case, the AppLocal source was registered to the first log it was assigned to—the AppLog log. The second attempt to register this same source to another log, Application, failed silently. You do not know that this attempt failed until you try to use the WriteEntry method of the EventLog object.

Another key point about the AppEvents class is the following code, placed at the beginning of each method (except for the DeleteLog method):

if (log == null)
    throw (new ArgumentNullException("log",
        "This Event Log has not been opened or has been closed."));

This code checks to see whether the private member variable log is a null reference. If so, an ArgumentException is thrown, informing the user of this class that a problem occurred with the creation of the EventLog object. The DeleteLog method does not check the log variable for null since it deletes the event log source and the event log itself. The EventLog object is not involved in this process except at the end of this method, where the log is closed and set to null, if it is not already null. Regardless of the state of the log variable, the source and event log should be deleted in this method.

The ClearLog and DeleteLog methods make a critical choice when determining whether to delete a log. The following code prevents the application, security, setup, and system event logs from being deleted from your system:

public bool IsNonCustomLog()
{
    // Because Application, Setup, Security, System, and other non-custom logs
    // can contain crucial information  you can't just delete or clear them
    if (LogName == string.Empty || // same as application
        LogName == "Application" ||
        LogName == "Security" ||
        LogName == "Setup" ||
        LogName == "System")
    {
        return true;
    }
    return false;
}

If any of these logs is deleted, so are the sources registered with the particular log. Once the log is deleted, it is permanent; believe us, it is not fun to try to re-create the log and its sources without a backup.

In order for the AppEvents class to work, however, it first needs an event source created. The event log uses event sources to determine which application logged the event. You can establish an event source only when running in an administrative context, and there are two ways to accomplish this:

  • Call the EventLog.CreateEventSource method.

  • Use an EventLogInstaller.

While you could create a console application that calls the CreateEventSource method and have a user run it in an administrative context on her machine, the recommended option is to build an EventLogInstaller class that can be used with InstallUtil.exe (provided by the .NET Framework) to create your initial event sources and custom logs.

The AppEventsEventLogInstaller, shown next, will establish the event logs and sources for us. This installer can be called not only by InstallUtil but also by most major installation packages, so you can plug it into your favorite installation software to register your event logs and sources at install time when your users have administrative access (or at least the help of an IT professional with said access):

/// <summary>
/// To INSTALL: C:WindowsMicrosoft.NETFrameworkv4.0.30319InstallUtil.exe
[PathToBinary]AppEventsEventLogInstallerApp.dll
/// To UNINSTALL: C:WindowsMicrosoft.NETFrameworkv4.0.30319InstallUtil.exe -u
[PathToBinary]AppEventsEventLogInstallerApp.dll
/// </summary>
[RunInstaller(true)]
public class AppEventsEventLogInstaller : Installer
{
    private EventLogInstaller evtLogInstaller;

    public AppEventsEventLogInstaller()
    {
        evtLogInstaller = new EventLogInstaller();
        evtLogInstaller.Source = "APPEVENTSSOURCE";
        evtLogInstaller.Log = ""; // Default to Application
        Installers.Add(evtLogInstaller);

        evtLogInstaller = new EventLogInstaller();
        evtLogInstaller.Source = "AppLocal";
        evtLogInstaller.Log = "AppLog";
        Installers.Add(evtLogInstaller);

        evtLogInstaller = new EventLogInstaller();
        evtLogInstaller.Source = "AppGlobal";
        evtLogInstaller.Log = "AppSystemLog";
        Installers.Add(evtLogInstaller);
    }
    public static void Main()
    {
        AppEventsEventLogInstaller appEventsEventLogInstaller =
            new AppEventsEventLogInstaller();
    }
}

If you are using InstallUtil to set this up locally, here is a sample of what you may see when installing using a proper administrative context (“Run As Administrator”):

C:WindowsMicrosoft.NETFrameworkv4.0.30319InstallUtil.exe
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll


C:WINDOWSsystem32>C:WindowsMicrosoft.NETFrameworkv4.0.30319InstallUtil.ex
e C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
Microsoft (R) .NET Framework Installation utility Version 4.0.30319.33440
Copyright (C) Microsoft Corporation.  All rights reserved.


Running a transacted installation.

Beginning the Install phase of the installation.
See the contents of the log file for the
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
assembly's progress.
The file is located at C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.InstallLog.
Installing assembly 'C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll'.
Affected parameters are:
   logtoconsole =
   logfile = C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.InstallLog
   assemblypath =
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
Creating EventLog source APPEVENTSSOURCE in log ...
Creating EventLog source AppLocal in log AppLog...
Creating EventLog source AppGlobal in log AppSystemLog...

The Install phase completed successfully, and the Commit phase is beginning.
See the contents of the log file for the
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
assembly's progress.
The file is located at C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.InstallLog.
Committing assembly
'C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll'.
Affected parameters are:
   logtoconsole =
   logfile = C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.InstallLog
   assemblypath =
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll

The Commit phase completed successfully.

The transacted install has completed.

If you attempt to run InstallUtil in a nonadministrative context, you will see results similar to the following:

C:WindowsMicrosoft.NETFrameworkv4.0.30319InstallUtil.exe
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
Microsoft (R) .NET Framework Installation utility Version 4.0.30319.33440
Copyright (C) Microsoft Corporation.  All rights reserved.


Running a transacted installation.

Beginning the Install phase of the installation.
See the contents of the log file for the
C:CSCB6AppEventsEventLogInstallerAppinDebugAppEventsEventLogInstallerApp.dll
assembly's progress.
The file is located at C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.InstallLog.
Installing assembly
'C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll'.
Affected parameters are:
   logtoconsole =
   logfile = C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.InstallLog
   assemblypath =
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
Creating EventLog source APPEVENTSSOURCE in log APPEVENTSLOG...

An exception occurred during the Install phase.
System.Security.SecurityException: The source was not found, but some or all 
event logs could not be searched. Inaccessible logs: Security.

The Rollback phase of the installation is beginning.
See the contents of the log file for the
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
assembly's progress.
The file is located at C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.InstallLog.
Rolling back assembly
'C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll'.
Affected parameters are:
   logtoconsole =
   logfile = C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.InstallLog
   assemblypath =
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
Restoring event log to previous state for source APPEVENTSSOURCE.
An exception occurred during the Rollback phase of the
System.Diagnostics.EventLogInstaller installer.
System.Security.SecurityException: Requested registry access is not allowed.
An exception occurred during the Rollback phase of the installation. This except
ion will be ignored and the rollback will continue. However, the machine might n
ot fully revert to its initial state after the rollback is complete.

The Rollback phase completed successfully.

The transacted install has completed.
The installation failed, and the rollback has been performed.

Not only can InstallUtil install your event logs and sources, it can help remove them too using the –u parameter!

C:WindowsMicrosoft.NETFrameworkv4.0.30319InstallUtil.exe -u
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
Microsoft (R) .NET Framework Installation utility Version 4.0.30319.33440
Copyright (C) Microsoft Corporation.  All rights reserved.

The uninstall is beginning.
See the contents of the log file for the
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
assembly's progress.
The file is located at C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.InstallLog.
Uninstalling assembly
'C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll'.
Affected parameters are:
   logtoconsole =
   logfile = C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.InstallLog
   assemblypath =
C:CSCB6AppEventsEventLogInstallerAppinDebug
AppEventsEventLogInstallerApp.dll
Removing EventLog source AppGlobal.
Deleting event log AppSystemLog.
Removing EventLog source AppLocal.
Deleting event log AppLog.
Removing EventLog source APPEVENTSSOURCE.

The uninstall has completed.
Warning

You should minimize the number of entries written to the event log from your application, as writing to the event log causes a performance hit and some logs are not set to roll over or clear after a certain number of entries. Writing too much information to the event log can noticeably slow your application or cause the server problems. Choose the entries you write to the event log wisely.

See Also

The “EventLog Class,” “InstallUtil.exe,” and “EventLogInstaller Class” topics in the MSDN documentation.

5.11 Watching the Event Log for a Specific Entry

Problem

You may have multiple applications that write to a single event log. For each of these applications, you want a monitoring application to watch for one or more specific log entries to be written to the event log. For example, you might want to watch for a log entry that indicates that an application encountered a critical error or shut down unexpectedly. These log entries should be reported in real time.

Solution

Monitoring an event log for a specific entry requires the following steps:

  1. Create the following method to set up the event handler to handle event log writes:

    public void WatchForAppEvent(EventLog log)
    {
        log.EnableRaisingEvents = true;
        // Hook up the System.Diagnostics.EntryWrittenEventHandler.
        log.EntryWritten += new EntryWrittenEventHandler(OnEntryWritten);
    }
  2. Create the event handler to examine the log entries and determine whether further action is to be performed. For example:

    public static void OnEntryWritten(object source,
                                      EntryWrittenEventArgs entryArg)
    {
        if (entryArg.Entry.EntryType == EventLogEntryType.Error)
        {
            Console.WriteLine(entryArg.Entry.Message);
            Console.WriteLine(entryArg.Entry.Category);
            Console.WriteLine(entryArg.Entry.EntryType.ToString());
            // Do further actions here as necessary...
        }
    }

Discussion

This recipe revolves around the EntryWrittenEventHandler delegate, which calls back to a method whenever any new entry is written to the event log. The EntryWrittenEventHandler delegate accepts two arguments: a source of type object and an entryArg of type EntryWrittenEventArgs. The entryArg parameter is the more interesting of the two. It contains a property called Entry that returns an EventLogEntry object. This EventLogEntry object contains all the information you need concerning the entry that was written to the event log.

This event log that you are watching is passed as the WatchForAppEvent method’s log parameter. This method performs two actions. First, it sets log’s EnableRaisingEvents property to true. If this property were set to false, no events would be raised for this event log when an entry is written to it. The second action this method performs is to add the OnEntryWritten callback method to the list of event handlers for this event log.

To prevent this delegate from calling the OnEntryWritten callback method, you can set the EnableRaisingEvents property to false, effectively turning off the delegate.

Note that the Entry object passed to the entryArg parameter of the OnEntryWritten callback method is read-only, so the entry cannot be modified before it is written to the event log.

See Also

The “EventLog.EntryWritten Event” topic in the MSDN documentation.

5.12 Implementing a Simple Performance Counter

Problem

You need to use a performance counter to track application-specific information. The simpler performance counters find, for example, the change in a counter value between successive samplings or just count the number of times an action occurs. Other, more complex counters exist but are not dealt with in this recipe. For example, you could build a custom counter to keep track of the number of database transactions, the number of failed network connections to a server, or even the number of users connecting to your web service per minute.

Solution

Create a simple performance counter that finds, for example, the change in a counter value between successive samplings or that just counts the number of times an action occurs. Use the following method (CreateSimpleCounter) to create a simple custom counter:

public static PerformanceCounter CreateSimpleCounter(string counterName,
    string counterHelp, PerformanceCounterType counterType, string categoryName,
    string categoryHelp)
{
    CounterCreationDataCollection counterCollection =
        new CounterCreationDataCollection();

    // Create the custom counter object and add it to the collection of counters
    CounterCreationData counter =
        new CounterCreationData(counterName, counterHelp, counterType);
    counterCollection.Add(counter);

    // Create category
    if (PerformanceCounterCategory.Exists(categoryName))
        PerformanceCounterCategory.Delete(categoryName);

    PerformanceCounterCategory appCategory =
        PerformanceCounterCategory.Create(categoryName, categoryHelp,
            PerformanceCounterCategoryType.SingleInstance, counterCollection);

    // Create the counter and initialize it
    PerformanceCounter appCounter =
        new PerformanceCounter(categoryName, counterName, false);

    appCounter.RawValue = 0;

    return (appCounter);
}

Discussion

The first action this method takes is to create the CounterCreationDataCollection object and CounterCreationData object. The CounterCreationData object is created using the counterName, counterHelp, and counterType parameters passed to the CreateSimpleCounter method. The CounterCreationData object is then added to the counterCollection.

Warning

The ASPNET user account, as well as many other user accounts, by default prevents performance counters from being read for security reasons. You can either increase the permissions allowed for these accounts or use impersonation with an account that has access to enable this functionality. However, this then becomes a deployment requirement of your application.

There is also risk in doing this, as you as the developer are the first line of defense for security matters. If you build it and make choices that loosen security restrictions, you are assuming that responsibility, so please don’t do it indiscriminately or without a full understanding of the repercussions.

If categoryName—a string containing the name of the category that is passed as a parameter to the method—is not registered on the system, a new category is created from a PerformanceCounterCategory object. If one is registered, it is deleted and created anew. Finally, the actual performance counter is created from a PerformanceCounter object. This object is initialized to 0 and returned by the method. PerformanceCounterCategory takes a PerformanceCounterCategoryType as a parameter. The possible settings are shown in Table 5-1.

Table 5-1. PerformanceCounterCategoryType enumeration values
Name Description
MultiInstance There can be multiple instances of the performance counter.
SingleInstance There can be only one instance of the performance counter.
Unknown Instance functionality for this performance counter is unknown.

The CreateSimpleCounter method returns a PerformanceCounter object that will be used by an application. The application can perform several actions on a PerformanceCounter object. An application can increment or decrement it using one of these three methods:

long value = appCounter.Increment();
long value = appCounter.Decrement();
long value = appCounter.IncrementBy(i);
// Additionally, a negative number may be passed to the
// IncrementBy method to mimic a DecrementBy method
// (which is not included in this class). For example:
long value = appCounter.IncrementBy(-i);

The first two methods accept no parameters, while the third accepts a long containing the number by which to increment the counter. All three methods return a long type indicating the new value of the counter.

In addition to incrementing or decrementing this counter, you can also take samples of the counter at various points in the application. A sample is a snapshot of the counter and all of its values at a particular instance in time. You may take a sample using the following line of code:

CounterSample counterSampleValue = appCounter.NextSample();

The NextSample method accepts no parameters and returns a CounterSample structure.

At another point in the application, a counter can be sampled again, and both samples can be passed in to the static Calculate method on the CounterSample class. These actions may be performed on a single line of code as follows:

float calculatedSample = CounterSample.Calculate(counterSampleValue,
                                                 appCounter.NextSample());

The calculated sample calculatedSample may be stored for future analysis.

The simpler performance counters already available in the .NET Framework are:

CounterDelta32/CounterDelta64
Determines the difference (or change) in value between two samplings of this counter. The CounterDelta64 counter can hold larger values than CounterDelta32.
CounterTimer
Calculates the percentage of the CounterTimer value change over the CounterTimer time change. Tracks the average active time for a resource as a percentage of the total sample time.
CounterTimerInverse
Calculates the inverse of the CounterTimer counter. Tracks the average inactive time for a resource as a percentage of the total sample time.
CountPerTimeInterval32/CountPerTimeInterval64
Calculates the number of items waiting within a queue to a resource over the time elapsed. These counters give the delta of the queue length for the last two sample intervals divided by the interval duration.
ElapsedTime
Calculates the difference in time between when this counter recorded the start of an event and the current time, measured in seconds.
NumberOfItems32/NumberOfItems64
These counters return their value in decimal format. The NumberOfItems64 counter can hold larger values than NumberOfItems32. This counter does not need to be passed to the static Calculate method of the CounterSample class; there are no values that must be calculated. Instead, use the RawValue property of the PerformanceCounter object (i.e., in this recipe, the appCounter.RawValue property would be used).
RateOfCountsPerSecond32/RateOfCountsPerSecond64
Calculates the RateOfCountsPerSecond* value change over the RateOfCountsPerSecond* time change, measured in seconds. The RateOfCountsPerSecond64 counter can hold larger values than the RateOfCountsPerSecond32 counter.
Timer100Ns
Shows the active component time as a percentage of the total elapsed time of the sample interval measured in 100 nanoseconds (ns) units. Processor % User Time is an example of this type of counter.
Timer100nsInverse
Percentage-based counter showing the average active percentage of time tracked during the sample interval. Processor % Processor Time is one example of this type of counter.

See Also

The “PerformanceCounter Class,” “PerformanceCounterType Enumeration,” “PerformanceCounterCategory Class,” “ASP.NET Impersonation,” and “Monitoring Performance Thresholds” topics in the MSDN documentation.

5.13 Creating Custom Debugging Displays for Your Classes

Problem

You have a set of classes that are used in your application. You would like to see at a glance in the debugger what a particular instance of the class holds. The default debugger display doesn’t show any useful information for your class today.

Solution

Add a DebuggerDisplayAttribute to your class to make the debugger show you something you consider useful about your class. For example, if you had a Citizen class that held the honorific and name information, you could add a DebuggerDisplayAttribute like this one:

[DebuggerDisplay("Citizen Full Name = {Honorific}{First}{Middle}{Last}")]
public class Citizen
{
    public string Honorific { get; set; }
    public string First { get; set; }
    public string Middle { get; set; }
    public string Last { get; set; }
}

Now, when instances of the Citizen class are instantiated, the debugger will show the information as directed by the DebuggerDisplayAttribute on the class. To see this, instantiate two Citizens, Mrs. Alice G. Jones and Mr. Robert Frederick Jones, like this:

Citizen mrsJones = new Citizen()
{
    Honorific = "Mrs.",
    First = "Alice",
    Middle = "G.",
    Last = "Jones"
};
Citizen mrJones = new Citizen()
{
    Honorific = "Mr.",
    First = "Robert",
    Middle = "Frederick",
    Last = "Jones"
};

When this code is run under the debugger, the custom display is used, as shown in Figure 5-9.

Debugger display controlled by DebuggerDisplayAttribute
Figure 5-9. Debugger display controlled by DebuggerDisplayAttribute

Discussion

It is nice to be able to quickly see the pertinent information for classes that you are creating, but the more powerful part of this feature is the ability for your team members to quickly understand what this class instance holds. The this pointer is accessible from the DebuggerDisplayAttribute declaration, but any properties accessed via the this pointer will not evaluate the property attributes before processing. Essentially, if you access a property on the current object instance as part of constructing the display string, if that property has attributes, they will not be processed, and therefore you may not get the value you thought you would. If you have custom ToString() overrides in place already, the debugger will use these as the DebuggerDisplayAttribute without your specifying it, provided the correct option is enabled under ToolsOptionsDebugging, as shown in Figure 5-10.

Setting the debugger to call ToString() for object display
Figure 5-10. Setting the debugger to call ToString() for object display

See Also

The “Using DebuggerDisplayAttribute” and “DebuggerDisplayAttribute” topics in the MSDN documentation.

5.14 Tracking Where Exceptions Come From

Problem

You want to be able to determine what method an exception was caught in or who called the method that caused an exception to be thrown to help debug the issue.

Solution

Use the CallerMemberName, CallerFilePath, and CallerLineNumber attributes (also known as the Caller Info attributes) from the System.Runtime.CompilerServices namespace to determine the calling method.

For example, if you wanted to record the location of the catch block that caught an exception, you could use a method like RecordCatchBlock:

public void RecordCatchBlock(Exception ex,
    [CallerMemberName] string memberName = "",
    [CallerFilePath] string sourceFilePath = "",
    [CallerLineNumber] int sourceLineNumber = 0)
{
    string catchDetails =
        $"{ex.GetType().Name} caught in member "{memberName}" " +
        $"in catch block encompassing line {sourceLineNumber} " +
        $"in file {sourceFilePath} " +
        $"with message "{ex.Message}"";
    Console.WriteLine(catchDetails);
}

You would then call this method from your catch blocks like this:

public void TestCallerInfoAttribs()
{
    try
    {
        LibraryMethod();
    }
    catch(Exception ex)
    {
        RecordCatchBlock(ex);
    }
}

This would allow you to see the type of the exception caught, the class member name it was caught in, and the source file and line number that is encompassed by the catch block where the exception was caught without having to traverse the call stack:

LibraryException caught in member "TestCallerInfoAttribs" in catch block encompa
ssing line 1303 in file C:CSCB6CSharpRecipes
05_DebuggingAndExceptionHandling.cs
with message "Object reference not set to an instance of an object."

You could also use this to help determine what method called into a library method, as it can sometimes be difficult to debug which function called the library method:

public void LibraryMethod(
            [CallerMemberName] string memberName = "",
            [CallerFilePath] string sourceFilePath = "",
            [CallerLineNumber] int sourceLineNumber = 0)
{
    try
    {
        // Do some library action
        // had a problem
        throw new NullReferenceException();
    }
    catch(Exception ex)
    {
        // Wrap the exception and capture the source of where the
        // library method was called from
        throw new LibraryException(ex)
        {
            CallerMemberName = memberName,
            CallerFilePath = sourceFilePath,
            CallerLineNumber = sourceLineNumber
        };
    }
}

Using the LibraryException, you can record at runtime the attributes of the calling method and convey that with the originating exception:

[Serializable]
public class LibraryException : Exception
{
    public LibraryException(Exception inner) : base(inner.Message,inner)
    {
    }
    public string CallerMemberName { get; set; }
    public string CallerFilePath { get; set; }
    public int CallerLineNumber { get; set; }

    public override void GetObjectData(SerializationInfo info,
                                       StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue("CallerMemberName", this.CallerMemberName);
        info.AddValue("CallerFilePath", this.CallerFilePath);
        info.AddValue("CallerLineNumber", this.CallerLineNumber);
    }

    public override string ToString() => "LibraryException originated in " +
        $"member "{CallerMemberName}" " +
        $"on line {CallerLineNumber} " +
        $"in file {CallerFilePath} " +
        $"with exception details: {Environment.NewLine}" +
        $"{InnerException.ToString()}";
}

The LibraryException.ToString method will provide a synopsis of the issue:

LLibraryException originated in member "TestCallerInfoAttribs" on line 1299 in 
file C:CSCB6CSharpRecipes5_DebuggingAndExceptionHandling.cs with exception 
details:
System.NullReferenceException: Object reference not set to an instance of an obj
etc.
   at CSharpRecipes.DebuggingAndExceptionHandling.LibraryMethod(String memberNam
e, String sourceFilePath, Int32 sourceLineNumber) in D:PRJ32Book_6_0CS60_Cook
bookCSCB6CSharpRecipes5_DebuggingAndExceptionHandling.cs:line 1318

Discussion

As the CallerInfo attributes are determined at compile time, there is no cost during runtime to retrieve the information about where the previous method on the stack came from. While not as comprehensive as a full stack trace, it is a cheaper and simpler alternative that could give you method, file, and line information with which you can enhance your exception logging. Any time you can have that sort of information handed to you with a defect/bug report/issue ticket (pick your favorite way to be notified that the code is broken), your life will become much easier.

You may notice that the CallerInfo attributes require a default value:

public void RecordCatchBlock(Exception ex,
    [CallerMemberName] string memberName = "",
    [CallerFilePath] string sourceFilePath = "",
    [CallerLineNumber] int sourceLineNumber = 0)

Those parameters need a default value because the CallerInfo attributes were implemented using optional parameters, and optional parameters require a default value. Here’s how you can still call the method hosting the attributes without providing a value for them:

RecordCatchBlock(ex);

See Also

See the “CallerMemberNameAttribute,” “CallerFilePathAttribute,” and “CallerLineNumberAttribute” topics in the MSDN documentation.

5.15 Handling Exceptions in Asynchronous Scenarios

Problem

You are working with asynchronous methods using async and await and you need to be able to catch any possible exceptions that may result during the method (or methods) execution.

Solution

When you’re handling exceptions from the invocation of a single method, the .NET Framework will handle the return of an exception that occurs between the asynchronous invocation and awaiting of the return. When you’re handling exceptions from the simultaneous invocation of multiple methods asynchronously, a bit more work is required to extract all of the exception detail. Finally, when dealing with the result of an exception, you can call an asynchronous method in the catch block to handle the work.

To demonstrate this, let’s work with the rather common scenario of a software development team manager named Bill who needs to have some work done.

User story 1: Bill needs Steve to implement a new feature in the product

Bill comes to Steve’s desk and asks him to implement the new feature in this sprint, then walks away, leaving Steve to implement on his own (asynchronously from Bill’s request):

try
{
    // Steve, get that project done!
    await SteveCreateSomeCodeAsync();
}
catch (DefectCreatedException dce)
{
    Console.WriteLine($"Steve introduced a Defect: {dce.Message}");
}

Steve works hard, as all developers do, but even the best of us can have an off day. Steve happens to generate a defect in the feature he was asked to implement in SteveCreateSomeCodeAsync. Luckily, even though Steve was doing this asynchronously, we can still catch the DefectCreatedException and handle it normally, as the async and await support transports the exception back to the catch block automatically. (More about how this happens in the Discussion section. Look for ExceptionDispatchInfo!)

The output we captured from the caught exception lets us know where the issue is so Steve can fix it later:

Steve introduced a Defect: A defect was introduced: (Null Reference on line 42)

User story 2: Bill has a large set of features to be implemented by Jay, Tom, and Seth

Bill knows Steve is busy, so he approaches the other members of the team (Jay, Tom, and Seth) to get a whole new set of features completed in the sprint. It looks like they are going to have to come in on Saturday! Jay, Tom, and Seth get together, divide up the work, and all start coding at the same time. Even though they may finish at different times, Bill still wants to be on the lookout for any defects they created:

// OK Team, make that new thing this weekend! You guys better hurry up with that!
Task jayCode = JayCreateSomeCodeAsync();
Task tomCode = TomCreateSomeCodeAsync();
Task sethCode = SethCreateSomeCodeAsync();

Task teamComplete = Task.WhenAll(new Task[] { jayCode, tomCode, sethCode });
try
{
    await teamComplete;
}
catch
{
    // Get the messages from the exceptions thrown from
    // the set of actions
    var defectMessages =
        teamComplete.Exception?.InnerExceptions.Select(e =>
            e.Message).ToList();
    defectMessages?.ForEach(m =>
        Console.WriteLine($"{m}"));
}

First, each unit of work (JayCreateSomeCodeAsync, TomCreateSomeCodeAsync, SethCreateSomeCodeAsync) is turned into a Task. The Task.WhenAll method is then called to create an encompassing Task (teamComplete), which will complete when all of the individual Tasks are completed.

Once all of the tasks have completed, the await will throw an AggregateException if any of the Tasks threw an Exception during execution. This AggregateException is accessed on the teamComplete.Exception property, and it holds a list of InnerExceptions, which is of type ReadOnlyCollection<Exception>.

Since the developers introduced this set of exceptions, they are more DefectCreatedExceptions!

The resulting logging tells us where the team will need to clean up:

A defect was introduced: (Ambiguous Match on line 2)
A defect was introduced: (Quota Exceeded on line 11)
A defect was introduced: (Out Of Memory on line 8)

User story 3: Bill wants to record if there are any issues implementing a new feature

Finally, Bill realizes that he needs a better system to determine if there were defects introduced into the code. Bill adds logging to the code that will write the details of the defect to the EventLog when a DefectCreatedException is caught. Since writing to the EventLog can be a hit to performance, he decides to do this asynchronously.

try
{
    await SteveCreateSomeCodeAsync();
}
catch (DefectCreatedException dce)
{
    await WriteEventLogEntryAsync("ManagerApplication", dce.Message,
        EventLogEntryType.Error);
    throw;
}

Discussion

Running code asynchronously doesn’t mean you don’t still need to have proper error handling, it just complicates the process a bit. Luckily, the C# and .NET teams at Microsoft have done a lot to make this task as painless as possible.

Awaiting these operations means that they are run in a context, either the current SynchronizationContext or the TaskScheduler. This context is captured when the async method awaits another method, and is restored when work resumes in the async method.

The context captured depends on where your async method code is executing:

  • User interface (WinForms/WPF): UI context

  • ASP.NET: ASP.NET request context

  • Other: ThreadPool context

In user story 1, we mentioned that the implementation of async and await used a class called System.Runtime.ExceptionServices.ExceptionDispatchInfo to handle when an exception is thrown on one thread and needs to be caught on another as the result of an asynchronous operation.

ExceptionDispatchInfo allows the capture of an exception that was thrown in one thread and then allows it to be rethrown—without losing any of the information (exception data and stack trace)—from another thread. This is what happens from an error-handling standpoint when you await an async method.

One other item of note is the use of ConfigureAwait, which allows you to change the behavior of context resumption after the async method has completed. If you pass false to ConfigureAwait, it will not attempt to resume the original context:

await MyAsyncMethod().ConfigureAwait(false);
Warning

If you use ConfigureAwait(false), then any code after the await finishes and the async method resumes cannot rely on the original context, as the thread on which the code continues will not have it. If this async method were called from an ASP.NET context, for example, the request context will not be available once it resumes.

The code for the hardworking team is presented in Example 5-7.

Example 5-7. Teamwork in action!
public async Task TestHandlingAsyncExceptionsAsync()
{
    // Team producing software
    // Manager sends Steve to create code, exception"DefectCreatedException" thrown
    // Manager sends Jay, Tom, Seth to write code, all throw DefectCreatedExceptions

    // Single async method call
    try
    {
        // Steve, get that project done!
        await SteveCreateSomeCodeAsync();
    }
    catch (DefectCreatedException dce)
    {
        Console.WriteLine($"Steve introduced a Defect: {dce.Message}");
    }

    // Multiple async methods (WaitAll)
    // OK Team, make that new thing this weekend! You guys better hurry up with that!
    Task jayCode = JayCreateSomeCodeAsync();
    Task tomCode = TomCreateSomeCodeAsync();
    Task sethCode = SethCreateSomeCodeAsync();

    Task teamComplete = Task.WhenAll(new Task[] { jayCode, tomCode, sethCode });
    try
    {
        await teamComplete;
    }
    catch
    {
        // Get the messages from the exceptions thrown from
        // the set of actions
        var defectMessages =
            teamComplete.Exception?.InnerExceptions.Select(e =>
                e.Message).ToList();
        defectMessages?.ForEach(m =>
            Console.WriteLine($"{m}"));
    }

    // awaiting an action in an exception handler
    // discuss how the original throw location is preserved via
    // System.Runtime.ExceptionServices.ExceptionDispatchInfo
    try
    {
        try
        {
            await SteveCreateSomeCodeAsync();
        }
        catch (DefectCreatedException dce)
        {
            Console.WriteLine(dce.ToString());
            await WriteEventLogEntry("ManagerApplication", dce.Message,
                EventLogEntryType.Error);
            throw;
        }
    }
    catch(DefectCreatedException dce)
    {
        Console.WriteLine(dce.ToString());
    }
}

public async Task WriteEventLogEntryAsync(string source, string message,
    EventLogEntryType type)
{
    await Task.Factory.StartNew(() => EventLog.WriteEntry(source, message, type));
}

public async Task SteveCreateSomeCodeAsync()
{
    Random rnd = new Random();
    await Task.Delay(rnd.Next(100, 1000));
    throw new DefectCreatedException("Null Reference",42);
}

public async Task JayCreateSomeCodeAsync()
{
    Random rnd = new Random();
    await Task.Delay(rnd.Next(100, 1000));
    throw new DefectCreatedException("Ambiguous Match",2);
}

public async Task TomCreateSomeCodeAsync()
{
    Random rnd = new Random();
    await Task.Delay(rnd.Next(100, 1000));
    throw new DefectCreatedException("Quota Exceeded",11);
}
public async Task SethCreateSomeCodeAsync()
{
    Random rnd = new Random();
    await Task.Delay(rnd.Next(100, 1000));
    throw new DefectCreatedException("Out Of Memory", 8);
}

The custom DefectCreatedException is listed in Example 5-8.

Example 5-8. Defect tracking
[Serializable]
public class DefectCreatedException : Exception
{
    #region Constructors
    // Normal exception ctor's
    public DefectCreatedException() : base()
    {
    }

    public DefectCreatedException(string message) : base(message)
    {
    }

    public DefectCreatedException(string message, Exception innerException)
        : base(message, innerException)
    {
    }

    // Exception ctor's that accept the new parameters
    public DefectCreatedException(string defect, int line) : base(string.Empty)
    {
        this.Defect = defect;
        this.Line = line;
    }

    public DefectCreatedException(string defect, int line, Exception innerException)
        : base(string.Empty, innerException)
    {
        this.Defect = defect;
        this.Line = line;
    }


    // Serialization ctor
    protected DefectCreatedException(SerializationInfo exceptionInfo,
        StreamingContext exceptionContext)
        : base(exceptionInfo, exceptionContext)
    {
    }
    #endregion // Constructors

    #region Properties
    public string Defect { get; }
    public int Line { get; }

    public override string Message =>
        $"A defect was introduced: ({this.Defect ?? "Unknown"} on line {this.Line})";
    #endregion // Properties

    #region Overridden methods
    // ToString method
    public override string ToString() =>
        $"{Environment.NewLine}{this.ToFullDisplayString()}";

    // Used during serialization to capture information about extra fields
    [SecurityPermission(SecurityAction.LinkDemand,
        Flags = SecurityPermissionFlag.SerializationFormatter)]
    public override void GetObjectData(SerializationInfo info,
        StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue("Defect", this.Defect);
        info.AddValue("Line", this.Line);
    }
    #endregion // Overridden methods

    public string ToBaseString() => (base.ToString());
}

See Also

The “async,” “await,” “AggregateException,” “ConfigureAwait,” and “System.Runtime.ExceptionServices.ExceptionDispatchInfo” topics in the MSDN documentation.

5.16 Being Selective About Exception Processing

Problem

You want to handle only a particular instance of an exception that is thrown for multiple reasons.

Solution

Use exception filters to catch only exceptions where you want to do something about the condition.

As an example, say you called a database and you wanted to handle timeouts differently. If you were calling from ASP.NET WebApi, you might even want to return a 503 Service Unavailable message to indicate that the service is busy when you start seeing timeout errors from the database.

The ProtectedCallTheDatabase method wraps the CallTheDatabase method in a try-catch block and adds an exception filter (using the when keyword) to check for a DatabaseException where the allotted Number property is set to –2. When the Number property is set to –2 on a DatabaseException, it indicates a timeout (much like a current Microsoft database offering) and we will catch the exception and can handle it from there. If Number is not set to –2, we will not catch the exception and it will propagate up the call stack to the caller of the ProtectedCallTheDatabase method:

private void ProtectedCallTheDatabase(string problem)
{
    try
    {
        CallTheDatabase(problem);
        Console.WriteLine("No error on database call");
    }
    catch (DatabaseException dex) when (dex.Number == -2) // watch for timeouts
    {
        Console.WriteLine(
            "DatabaseException catch caught a database exception: " +
           $"{dex.Message}");
    }
}

The CallTheDatabase method simulates calling the database and encountering a problem:

private void CallTheDatabase(string problem)
{
    switch (problem)
    {
        case "timeout":
            throw new DatabaseException(
                "Timeout expired. The timeout period elapsed prior to " +
                "completion of the operation or the server is not " +
                "responding. (Microsoft SQL Server, Error: -2).")
            {
                Number = -2,
                Class = 11
            };
        case "loginfail":
            throw new DatabaseException("Login failed for user")
            {
                Number = 18456,
            };
    }
}

We can call the ProtectedCallTheDatabase method in the three ways shown in Example 5-9.

Example 5-9. Testing exception filters
Console.WriteLine("Simulating database call timeout");
try
{
    ProtectedCallTheDatabase("timeout");
}
catch(Exception ex)
{
    Console.WriteLine($"Exception catch caught a database exception: {ex.Message}");
}
Console.WriteLine("");

Console.WriteLine("Simulating database call login failure");
try
{
    ProtectedCallTheDatabase("loginfail");
}
catch (Exception ex)
{
    Console.WriteLine($"Exception catch caught a database exception: {ex.Message}");
}
Console.WriteLine("");

Console.WriteLine("Simulating successful database call");
try
{
    ProtectedCallTheDatabase("noerror");
}
catch (Exception ex)
{
    Console.WriteLine($"Exception catch caught a database exception: {ex.Message}");
}
Console.WriteLine("");

We get the output shown in Example 5-10.

Example 5-10. Exception filter testing output
Simulating database call timeout
DatabaseException catch caught a database exception: Timeout expired.
The timeout period elapsed prior to completion of the operation or the server
is not responding. (Microsoft SQL Server, Error: -2).

Simulating database call login failure
Exception catch caught a database exception: Login failed for user

Simulating successful database call
No error on database call

Note that the timeout was caught in the catch block in ProtectedCallTheDatabase, while the login failure was not caught until it returned to the catch block in the testing code.

Discussion

Exception filters allow you to conditionally evaluate if a catch block should catch an exception, which is quite powerful and allows you to handle only exceptions you can do something about at a finer-grained level than was previously possible.

Another advantage of using exception filters is that it does not require the constant catching and rethrowing of exceptions. When this is done improperly it can affect the call stack of the exception and hide errors (see Recipe 5.1 for more details), whereas exception filters let you examine and even perform operations with exceptions (like logging) without interfering with the original flow of the exception. In order to not interfere, the code executed in the exception filter must return false so that the exception continues to propagate normally. The code introduced to make the true or false determination in the exception filter should be kept to a minimum, as you are in a catch handler and the same rules apply. Don’t do things that could cause other exceptions and mask the original error condition you were trying to trap for in the first place.

The full listing for the DatabaseException is shown in Example 5-11.

Example 5-11. Exception filter testing output
[Serializable]
public class DatabaseException : DbException
{
    public DatabaseException(string message) : base(message) { }
    public byte Class { get; set; }
    public Guid ClientConnectionId { get; set; }
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public SqlErrorCollection Errors { get; set; }
    public int LineNumber { get; set; }
    public int Number { get; set; }
    public string Procedure { get; set; }
    public string Server { get; set; }
    public override string Source => base.Source;
    public byte State { get; set; }
    public override void GetObjectData(SerializationInfo si,
        StreamingContext context)
    {
        base.GetObjectData(si, context);
    }
}

See Also

The “Exception Filters” topic in the MSDN documentation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.171.5