Re-throwing Exceptions in C# with InternalPreserveStackTrace

It is often the case that you need to re-throw an exception after handling it. The common problem with re-throwing exceptions is that you often lose stack trace information that can be extraordinarily useful for debugging purposes. I'll explain a technique that can be used to force the entire stack to be propagated when an exception is re-thrown.

The solution is to perform a reflective invocation of the InternalPreserveStackTrace method, which is an internal method belonging to System.Exception. Usually, this can be accomplished in a single line of code using reflection as follows:

try
{
   // DOES SOMETHING
}
catch (Exception ex)
{
   typeof(System.Exception).GetMethod("InternalPreserveStackTrace",
     System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
     .Invoke(ex, null);

   throw;
}

The InternalPreserveStackTrace method simply grabs the StackTrace from the point of the exception to the location where InternalPreserveStackTrace method was invoked. It stores this trace information in a temporary field for usage in subsequent requests to StackTrace.

A little deeper analysis using Reflector shows exactly how this method works.

internal void InternalPreserveStackTrace()
{
    string stackTrace = this.StackTrace;
    if ((stackTrace != null) && (stackTrace.Length > 0))
    {
        this._remoteStackTraceString = stackTrace + Environment.NewLine;
    }
    this._stackTrace = null;
    this._stackTraceString = null;
}

 
public virtual string StackTrace
{
    get
    {
        if (this._stackTraceString != null)
        {
            return (this._remoteStackTraceString + this._stackTraceString);
        }
        if (this._stackTrace == null)
        {
            return this._remoteStackTraceString;
        }
        string stackTrace = Environment.GetStackTrace(this, true);
        return (this._remoteStackTraceString + stackTrace);
    }
}

If you use reflection to analyze what this code does during execution you will see that it follows this process:

When the Exception enters the initial catch block it contains the following information: _stackTrace field contains an sbyte array, _stackTraceString is null, and _remoteStackTraceString is null.

Upon the invocation of the InternalPreserveStackTrace method, the StackTrace property's getter is invoked in the first line of InternalPreserveStackTrace.

When get_StackTrace is called neither of the the first two conditional statements execute because _stackTraceString is null and _stackTrace is not null. The Environment.GetStrackTrace method is invoked and the stack from the source of the exception to the first catch block is returned and stored in the stackTrace variable.

InternalPreserveStackTrace then checks if the stackTrace variable is not null and has a length. Because this variable now contains the stack trace from the source of the exception, the condition is true and it stores the stackTrace information in the _remoteStackTraceString field of the current Exception. Finally, it sets the _stackTrace and _stackTraceString fields to null.

If the StackTrace property is now accessed it will only return the contents of the _remoteStackTraceString field.

At this point in our application the Exception is re-thrown using either

throw;

or

throw ex;

When the exception is subsequently caught again, the _stackTrace field will once again be set to an sbyte array since the stack has changed. This time, the _remoteStackTraceString field will contain our original stack trace from the source of the exception to the first catch block. This stack trace information is then combined with the stack trace from the re-throw to give a complete picture of the stack at the time the exception occurred.

CAVEAT EMPTOR

Because this technique uses reflection to access private fields in the Exception class by using the NonPublic BindingFlag, it may not work if the system does not have FullTrust enabled. I have not investigated this yet... so beware in hosted environments that do not execute with Full Trust.

Recent comments