Желаемое поведение (вопрос)
В приложении С# я бы хотел:
Когда отладчик не подключен: -
- Исключение выбрано.
- Исключение происходит в стеке выше.
- Ошибка журнала и продолжить.
Когда отладчик подключен: -
- Исключение выбрано.
- Отладчик ломается в точке, где генерируется исключение.
Чтобы проиллюстрировать в качестве примера, вот как это могло бы работать с условным catch (я знаю, что это не поддерживается в С#):
Обратите внимание: пока я показываю пример исключения, создаваемого моим кодом, он может быть брошен сторонней библиотекой.
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();
}
}
Задний план
Это не большая проблема, так как есть трассировка стека и ведение журнала, чтобы собрать информацию вместе, но мне было интересно, есть ли хороший способ достичь этого, так как он появляется сейчас и потом (и является одним из тех тысяч разрезов, которые Я бы не прочь сделать без). Кроме того, я никогда не находил идеальный метод, поэтому меня интересует, есть ли он.
Это особенно актуально в программе, где есть несколько этапов (например, запуск тестов). Во время обычной автономной операции, если какой-либо из этих шагов вызывает исключение, ошибка должна быть зарегистрирована, и выполнение должно перейти на следующий шаг. Однако при запуске в отладчике отладчик должен разорваться в точке, где было создано исключение. Это ускорит процесс отладки, так как вам не нужно конспектировать трассировку стека, и состояние локальных переменных будет сохранено.
Остальная часть этого вопроса описывает вещи, которые я уже пробовал, чтобы они не повторялись в ответах...
Подходы, которые уже рассмотрены
Условный улов в VB DLL
Я знаю, что это не поддерживается в С#, но поддерживается в VB.NET. Таким образом, я могу получить желаемое поведение, реализовав следующее в библиотеке VB.NET (не беспокойтесь о коде слишком много, он в основном обертывает метод в try...catch
и вызывает обработчик ошибок, если есть исключение и отладчик не прилагается):
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
Обратите внимание, что для Run
должны быть разные перегрузки, зависящие от количества аргументов (в этом случае my просто использует 4 аргумента). Кроме того, существует параметр Context
для случаев, когда некоторое состояние должно поддерживаться между вызываемым методом и обработчиком ошибок.
Тогда мой код выглядит примерно так:
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 );
}
Недостатками такого подхода являются:
- Добавлена сложность другой VB.NET DLL в проекте.
- Не так интуитивно, как простая
try...catch
- другие люди, приходящие в код в первый раз, должны будут копаться, чтобы точно понять, что происходит.
Re-бросок
Код (обратите внимание на throw
):
Пример:
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;
}
}
}
Проблема заключается в том, что, в то время как throw
сохраняет трассировку стека, отладчик разбивается на линию, где происходит бросок, а не исходный бросок. Совершенно очевидно, что так происходит, но это не то, что я хочу. Это означает, что мне нужно заглянуть внутрь исключения для stacktrace, а затем найти правильную строку кода. Кроме того, теряется состояние локальных переменных, в которых произошло исключение.
Метод обертки
В принципе, просто заверните try...catch
в отдельном методе:
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();
}
}
Но есть ряд проблем с этим:
- Больше кода.
- Вещи быстро становятся громоздкими для более сложных случаев, например, когда есть больше параметров или есть локальные переменные в
try...catch
, которые устанавливаются путем передачи в "DoSomething" по ссылке, если есть подэтапы.
Условные символы компиляции
Это, наверное, мой наименее любимый вариант. В этом случае используется условный символ компиляции, такой как DEBUGGING (примечание DEBUG не будет работать, потому что я могу запускать DEBUG без добавления компилятора):
#if !DEBUGGING
try
#endif
{
DoSomething();
}
#if !DEBUGGING
catch( Exception exception )
{
Console.WriteLine( exception.Message ); //Do some processing on the exception
}
#endif
}
Проблемы заключаются в следующем:
- Это все немного головная боль для управления, и я бы неизменно не задавал ее, когда мне это нужно. В частности, символ и тот факт, что отладчик подключен, никак не связаны друг с другом, кроме меня, вручную устанавливая определение символа.
-
#DEBUGGING
загромождает код и делаетtry...catch
менее удобочитаемую.
Другие
- Настройка Visual Studio. Я также изучил различные настройки исключения исключений Visual Studio, но я хочу включить поведение для определенных частей кода, а не для конкретных исключений. Кроме того, это должно работать во всех установках.
- Ассамблея ИЛ. Я рассмотрел inline IL как возможность генерировать условное исключение, но для этого требуются шаги после сборки с сторонними инструментами.
- Я не думаю, что глобальные (прикладные) обработчики исключений будут делать это, потому что исключение должно быть уловлено и зарегистрировано ниже в стеке приложений.
Обновление - DebuggerStepThrough и Re-throw
Комментарий Стивена Лийкенса указывает на то, что кажется хорошим решением - DebuggerStepThroughAttribute
. Когда этот атрибут установлен в методе, содержащем повторный бросок, отладчик ломается в исходной точке исключения, а не там, где он перебрасывается, как показано ниже:
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();
}
}
Единственным недостатком является то, что вы действительно хотите войти в код, отмеченный как DebuggerStepThrough
или если в этом коде есть исключение. Хотя, это незначительный недостаток, потому что вы можете вообще сохранить этот код минимальным.
Обратите внимание на использование Debugger.IsAttached
потому что я думаю, что его влияние здесь минимально и вероятность появления странных heisenbugs минимальна, но остерегайтесь использовать его, как указано Guillaume в комментариях, и использовать другой вариант, например, настройку конфигурации.
Я пойду с этим, если не будет лучшего способа, или кто-то вызывает озабоченность по поводу этого.