Reputation: 17264
In a C# application, what I would like is this:
When the debugger is not attached: -
When the debugger is attached: -
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();
}
}
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...
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: -
try...catch
- other people coming to the code for the first time would need to dig around to understand exactly what is going on. 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.
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:
try...catch
that are set by need to be passed into 'DoSomething' by reference of if there are sub-steps.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: -
#DEBUGGING
clutters the code and makes the try...catch
less readable. 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
Reputation: 3540
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
Reputation: 14088
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();
}
}
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
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