acarlon
acarlon

Reputation: 17264

Catch exception if debugger is not attached

Desired behaviour (the question)

In a C# application, what I would like is this:

When the debugger is not attached: -

  1. Exception is thrown.
  2. Exception is caught higher up in the stack.
  3. Log error and continue.

When the debugger is attached: -

  1. Exception is thrown.
  2. Debugger breaks at the point where the exception is thrown.

To illustrate by way of an example, here is how it might work with a conditional catch (I know that this is not supported in C#):

Note: while I am showing an example of the exception being thrown by my code, it may be thrown by a 3rd party library.

static void DoSomething()
{
    //This is where I would like the debugger to break execution and show the exception
    throw new Exception( "Something went wrong!" );
}  

static public void DoSomeStep()
{
    try
    {
        DoSomething();
    }
    catch( Exception exception when System.Diagnostics.Debugger.IsAttached == false ) //If the debugger is attached don't catch                
    {
        Console.WriteLine( exception.Message ); //Do some processing on the exception                
    }
}
static void Main( string[] args )
{
    for( int i = 0; i < 10; i++ )
    {
        DoSomeStep();
    }
}

Background

This is not a big issue, since there are stack traces and logging to piece the information together, but I was wondering if there is a good way of achieving this, since it comes up now and then (and is one of those thousand cuts that I wouldn't mind doing without). Also, I have never found an ideal method, so I am interested if there is one.

It is particularly relevant in a program where there are a number of steps (such as running tests). During normal standalone operation, if any of these steps raise an exception an error should be logged and execution should move onto the next step. However, when running in the debugger the debugger should break at the point where the exception was raised. This will speed up the debugging process as you don't need to consult stack traces and the state of the local variables will be preserved.

The rest of this question describes things that I have already tried so that they are not repeated in the answers...

Approaches that have already been considered

Conditional catch in VB DLL

I know that this is not supported in C#, but it is supported in VB.NET. So, I can get the desired behaviour by implementing the following in a VB.NET library (don't worry about the code too much, it basically wraps a method in a try...catch and calls an error handler if there is an exception and the debugger is not attached):

Public Module DebuggerNoCatch
    Public Function Run(Of T, U, V, W, X)(func As Func(Of T, U, V, W, X, Boolean), arg1 As T, arg2 As U, arg3 As V, arg4 As W, context As X, errorHandler As Action(Of System.Exception, X)) As Boolean
        Dim result As Boolean = False
        Try
            result = func(arg1, arg2, arg3, arg4, context)
        Catch ex As Exception When Not Debugger.IsAttached
            errorHandler(ex, context)
            result = False
        End Try
        Return result
    End Function
End Module

Note that there need to be different overloads for Run depending on the number of arguments (in this case mine just happens to use 4 args). Also, there is a Context parameter for the cases where some state needs to be maintained between the method being called and the error handler.

Then my code looks something like this:

static bool DoSomething( int a, int b, int c, int d, RunContext context )
{
    //Now the debugger break at this point - hooray!
    throw new Exception( "Something went wrong!" );
    return true;
}

static void HandleException( Exception exception, RunContext context )
{
    //Only see this when not attached in the debugger
    Console.WriteLine( exception.Message ); //Do some processing on the exception                            
}

class RunContext{ } //context information - not used in this example

static public void DoSomeStep()
{
    DebuggerNoCatch.Run<int, int, int, int, RunContext>( DoSomething, 1, 1, 1, 1, new RunContext(), HandleException );
}

The drawbacks of this approach are: -

Re-throw

The code (note the throw):

Example:

    static public void DoSomeStep()
    {
        try
        {
            DoSomething();
        }
        catch( Exception exception )
        {
            Console.WriteLine( exception.Message ); //Do some processing on the exception
            //If the debugger is attached throw, otherwise just continue to the next step
            if( System.Diagnostics.Debugger.IsAttached == true )
            {
                //This is where the debugger breaks execution and shows the exception
                throw;
            }
        }
    }            

The problem with this is that while throw preserves the stack trace, the debugger breaks at the line where the throw occurs rather than the original throw. It makes complete sense that it happens in this way, but it is not what I want to happen. It means that I need to look inside the exception for the stacktrace and then find the correct line of code. Also, the state of the local variables where the exception occurred is lost.

Wrapper method

Basically, just wrap the try...catch in a separate method:

    static void DoSomething()
    {
        //This is where I would like the debugger to break execution and show the exception
        throw new Exception( "Something went wrong!" );
    }
    static void DoSomethingContinueOnError()
    {
        try
        {
            DoSomething();
        }
        catch( Exception exception )
        {
            Console.WriteLine( exception.Message ); //Do some processing on the exception
        }
    }
    static public void DoSomeStep()
    {
        if( System.Diagnostics.Debugger.IsAttached == false )
        {
            DoSomethingContinueOnError();
        }
        else
        {
            DoSomething();                
        }            
    }        

But, there are a number of problems with this:

Conditional compilation symbols

This is probably my least favourite option. In this case a conditional compilation symbol such as DEBUGGING is used (note DEBUG will not work because I may be running DEBUG without the compiler attached):

   #if !DEBUGGING           
        try
   #endif
        {
            DoSomething();
        }
   #if !DEBUGGING           
        catch( Exception exception )
        {
            Console.WriteLine( exception.Message ); //Do some processing on the exception
        }
   #endif
    }          

The problems are: -

Other

Update - DebuggerStepThrough and Re-throw

Steven Liekens' comment indicates what seems to be a good solution - the DebuggerStepThroughAttribute. When this attribute is set on a method containing a re-throw, the debugger breaks at the original point of the exception, not where it is re-thrown as shown below:

static bool DoSomething()
{
     //This is where the debugger now breaks execution
     throw new Exception( "Something went wrong!" );
     return true;
}

[DebuggerStepThrough]
static public void DoSomeStep()
{
    try
    {                
        DoSomething();
    }
    catch( Exception exception )
    {
        Console.WriteLine( exception.Message );
        if( Debugger.IsAttached == true )
        {
            //the debugger no longer breaks here
            throw;
        }
    }
}
static void Main( string[] args )
{          
    for( int i = 0; i < 10; i++ )
    {
        DoSomeStep();
    }
}

The only drawback is if you do actually want to step into the code marked as DebuggerStepThrough or if there is an exception in this code. Though, this is a minor drawback because you can generally keep this code minimal.

Note the use of Debugger.IsAttached because I think its impact here is minimal and the likelihood of strange heisenbugs are minimal, but beware of using it as pointed out by Guillaume in the comments and use another option such as config setting when appropriate.

I will go with this unless a better way or someone raises concerns about it.

Upvotes: 26

Views: 4108

Answers (3)

Wolfgang
Wolfgang

Reputation: 3540

Exception Filters (C# 6+)

If you're using C# 6, this is easy to do with the new exception filter syntax:

try
{
    DoSomething()
}
catch (Exception e) when (!System.Diagnostics.Debugger.IsAttached)
{
    Console.WriteLine(exception.Message);
}

Upvotes: 10

Steven Liekens
Steven Liekens

Reputation: 14088

DebuggerStepThrough and Re-throw (Accepted Answer)

As pointed out in the comments, when the DebuggerStepThroughAttribute is set on a method containing a re-throw, the debugger breaks at the original point of the exception, not where it is re-thrown as shown below:

static bool DoSomething()
{
     //This is where the debugger now breaks execution
     throw new Exception( "Something went wrong!" );
     return true;
}

[DebuggerStepThrough]
static public void DoSomeStep()
{
    try
    {                
        DoSomething();
    }
    catch( Exception exception )
    {
        Console.WriteLine( exception.Message );
        if( Debugger.IsAttached == true )
        {
            //the debugger no longer breaks here
            throw;
        }
    }
}
static void Main( string[] args )
{          
    for( int i = 0; i < 10; i++ )
    {
        DoSomeStep();
    }
}

LINQ-inspired alternative

I spent some time writing a LINQ-inspired try...catch wrapper that does actually support conditional catch blocks.

Usage Example

Before diving into the code, here's a usage example based on the original requirements:

DangerousOperation
    .Try(() =>
    {
        throw new NotImplementedException();
    })
    .Catch((NotImplementedException exception) =>
    {
        Console.WriteLine(exception.Message);
    }).When(ex => !Debugger.IsAttached)
    .Catch((NotSupportedException exception) =>
    {
        Console.WriteLine("This block is ignored");
    }).When(ex => !Debugger.IsAttached)
    .Catch<InvalidProgramException>() /* specifying a handler is optional */
    .Catch()                          /* In fact, specifying the exception type is also optional */
    .Finally(() =>
    {
        Console.WriteLine("Goodbye");
    }).Execute();

This works by first evaluating the predicate specified in the When() statement before executing whatever is in the Catch() statement.

If you run the example, you'll notice that the debugger breaks on the line that causes the exception as the result of clever placement of a [DebuggerStepThrough] attribute.

Source Code

/// <summary>
/// Factory. Provides a static method that initializes a new try-catch wrapper.
/// </summary>
public static class DangerousOperation
{
    /// <summary>
    /// Starts a new try-catch block.
    /// </summary>
    /// <param name="action">The 'try' block's action.</param>
    /// <returns>Returns a new instance of the <see cref="TryCatchBlock"/> class that wraps the 'try' block.</returns>
    public static TryCatchBlock Try()
    {
        return new TryCatchBlock();
    }

    /// <summary>
    /// Starts a new try-catch block.
    /// </summary>
    /// <param name="action">The 'try' block's action.</param>
    /// <returns>Returns a new instance of the <see cref="TryCatchBlock"/> class that wraps the 'try' block.</returns>
    public static TryCatchBlock Try(Action action)
    {
        return new TryCatchBlock(action);
    }
}

/// <summary>
/// Wraps a 'try' or 'finally' block.
/// </summary>
public class TryCatchBlock
{

    private bool finalized;

    /// <summary>
    /// Initializes a new instance of the <see cref="TryCatchBlock"/> class;
    /// </summary>
    public TryCatchBlock()
    {
        this.First = this;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="TryCatchBlock"/> class;
    /// </summary>
    /// <param name="action">The 'try' or 'finally' block's action.</param>
    public TryCatchBlock(Action action)
        : this()
    {
        this.Action = action;
    }

    protected TryCatchBlock(TryCatchBlock antecedent)
    {
        if ( antecedent == null )
        {
            throw new ArgumentNullException("antecedent");
        }
        if ( antecedent.finalized )
        {
            throw new InvalidOperationException("This block has been finalized with a call to 'Finally()'");
        }
        this.First = antecedent.First;
        this.Antecedent = antecedent;
        antecedent.Subsequent = this;
    }

    protected TryCatchBlock(TryCatchBlock antecedent, Action action)
        : this(antecedent)
    {
        this.Action = action;
    }

    public Action Action { get; set; }

    /// <summary>
    /// Gets the 'try' block.
    /// </summary>
    public TryCatchBlock First { get; private set; }

    /// <summary>
    /// Gets the next block.
    /// </summary>
    public TryCatchBlock Antecedent { get; private set; }

    /// <summary>
    /// Gets the previous block.
    /// </summary>
    public TryCatchBlock Subsequent { get; private set; }


    /// <summary>
    /// Creates a new 'catch' block and adds it to the chain.
    /// </summary>
    /// <returns>Returns a new instance of the <see cref="TryCatchBlock{TException}"/> class that wraps a 'catch' block.</returns>
    public TryCatchBlock<Exception> Catch()
    {
        return new TryCatchBlock<Exception>(this);
    }

    /// <summary>
    /// Creates a new 'catch' block and adds it to the chain.
    /// </summary>
    /// <returns>Returns a new instance of the <see cref="TryCatchBlock{TException}"/> class that wraps a 'catch' block.</returns>
    public TryCatchBlock<Exception> Catch(Action<Exception> action)
    {
        return new TryCatchBlock<Exception>(this, action);
    }

    /// <summary>
    /// Creates a new 'catch' block and adds it to the chain.
    /// </summary>
    /// <typeparam name="TException">The type of the exception that this block will catch.</typeparam>
    /// <returns>Returns a new instance of the <see cref="TryCatchBlock{TException}"/> class that wraps a 'catch' block.</returns>
    public TryCatchBlock<TException> Catch<TException>() where TException : System.Exception
    {
        return new TryCatchBlock<TException>(this);
    }

    /// <summary>
    /// Creates a new 'catch' block and adds it to the chain.
    /// </summary>
    /// <typeparam name="TException">The type of the exception that this block will catch.</typeparam>
    /// <param name="action">The 'catch' block's action.</param>
    /// <returns>Returns a new instance of the <see cref="TryCatchBlock{TException}"/> class that wraps a 'catch' block.</returns>
    public TryCatchBlock<TException> Catch<TException>(Action<TException> action) where TException : System.Exception
    {
        return new TryCatchBlock<TException>(this, action);
    }

    /// <summary>
    /// Creates a new 'finally' block and finalizes the chain.
    /// </summary>
    /// <returns>Returns a new instance of the <see cref="TryCatchBlock"/> class that wraps the 'finally' block.</returns>
    public TryCatchBlock Finally()
    {
        return new TryCatchBlock(this) { finalized = true };
    }

    /// <summary>
    /// Creates a new 'finally' block and finalizes the chain.
    /// </summary>
    /// <param name="action">The 'finally' block's action.</param>
    /// <returns>Returns a new instance of the <see cref="TryCatchBlock"/> class that wraps the 'finally' block.</returns>
    public TryCatchBlock Finally(Action action)
    {
        return new TryCatchBlock(this, action) { finalized = true };
    }

    /// <summary>
    /// Gets a value indicating whether this 'catch' wrapper can handle and should handle the specified exception.
    /// </summary>
    /// <param name="exception">The exception.</param>
    /// <returns>Returns <c>true</c> if the exception can be handled; otherwise <c>false</c>.</returns>
    public virtual bool CanHandle(Exception exception)
    {
        return false;
    }

    /// <summary>
    /// Handles the specified exception.
    /// </summary>
    /// <param name="exception">The exception.</param>
    public virtual void Handle(Exception exception)
    {
        throw new InvalidOperationException("This is not a 'catch' block wrapper.");
    }

    /// <summary>
    /// Executes the chain of 'try-catch' wrappers.
    /// </summary>
    //[DebuggerStepThrough]
    public void Execute()
    {
        TryCatchBlock current = this.First;

        try
        {
            if ( current.Action != null )
            {
                current.Action();
            }
        }
        catch ( Exception exception )
        {
            while ( current.Subsequent != null )
            {
                current = current.Subsequent;

                if ( current.CanHandle(exception) )
                {
                    current.Handle(exception);
                    break;
                }

                if ( current.Subsequent == null )
                {
                    throw;
                }
            }
        }
        finally
        {
            while ( current.Subsequent != null )
            {
                current = current.Subsequent;
                if ( current.finalized && current.Action != null )
                {
                    current.Action();
                }
            }
        }
    }
}

/// <summary>
/// Wraps a 'catch' block.
/// </summary>
/// <typeparam name="TException">The type of the exception that this block will catch.</typeparam>
public class TryCatchBlock<TException> : TryCatchBlock where TException : System.Exception
{
    /// <summary>
    /// Initializes a new instance of the <see cref="TryCatchBlock{TException}"/> class;
    /// </summary>
    /// <param name="antecedent">The 'try' or 'catch' block that preceeds this 'catch' block.</param>
    public TryCatchBlock(TryCatchBlock antecedent)
        : base(antecedent) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="TryCatchBlock{TException}"/> class;
    /// </summary>
    /// <param name="antecedent">The 'try' or 'catch' block that preceeds this 'catch' block.</param>
    /// <param name="action">The 'catch' block's action.</param>
    public TryCatchBlock(TryCatchBlock antecedent, Action<TException> action)
        : base(antecedent)
    {
        this.Action = action;
    }

    /// <summary>
    /// Sets a predicate that determines whether this block should handle the exception.
    /// </summary>
    /// <param name="predicate">The method that defines a set of criteria.</param>
    /// <returns>Returns the current instance.</returns>
    public TryCatchBlock<TException> When(Predicate<TException> predicate)
    {
        this.Predicate = predicate;
        return this;
    }

    /// <summary>
    /// Gets a value indicating whether this 'catch' wrapper can handle and should handle the specified exception.
    /// </summary>
    /// <param name="exception">The exception.</param>
    /// <returns>Returns <c>True</c> if the exception can be handled; otherwise false.</returns>
    public override bool CanHandle(Exception exception)
    {
        if ( exception == null )
        {
            throw new ArgumentNullException("exception");
        }

        if ( !typeof(TException).IsAssignableFrom(exception.GetType()) )
        {
            return false;
        }

        if ( Predicate == null )
        {
            return true;
        }

        return Predicate((TException) exception);
    }

    /// <summary>
    /// Handles the specified exception.
    /// </summary>
    /// <param name="exception">The exception.</param>
    public override void Handle(Exception exception)
    {
        if ( this.Action != null )
        {
            this.Action((TException) exception);
        }
    }

    /// <summary>
    /// Gets the exception handler.
    /// </summary>
    public Action<TException> Action { get; private set; }

    /// <summary>
    /// Gets the predicate that determines whether this wrapper should handle the exception.
    /// </summary>
    public Predicate<TException> Predicate { get; private set; }
}

Final Notes

This is a huge edit to my original post. Look at the change history for my initial solution.

Upvotes: 5

Lorentz Vedeler
Lorentz Vedeler

Reputation: 5291

You could wrap the exception and catch a specific type of exception, that way when debugging there is no defined catch behavior for the exception and the debugger will break at the throwing code.

class Program
{
    static void Main(string[] args)
    {
        try
        {
            NotImplementedMethod();
        }
        catch (NotImplementedException)
        {
            Console.WriteLine("Exception caught");
        }
        Console.Read();
    }

    public static void NotImplementedMethod()
    {
        throw DebugException.Wrap(new NotImplementedException());//Breaks here when debugger is attached
    }
}

public class DebugException : Exception
{
    public static Exception Wrap(Exception innerException)
    {
        if(Debugger.IsAttached)
        {
            return new DebugException(innerException);
        }
        else
        {
            return innerException;
        }
    }


    public DebugException(Exception innerException)
        : base("Debug exception", innerException)
    {
    }
}

Upvotes: 0

Related Questions