C# 未附加调试器时捕获异常 期望的行为(问题)
在C#应用程序中,我想要的是: 未附加调试器时:-C# 未附加调试器时捕获异常 期望的行为(问题),c#,vb.net,visual-studio,debugging,C#,Vb.net,Visual Studio,Debugging,在C#应用程序中,我想要的是: 未附加调试器时:- 异常被抛出 异常被捕获到堆栈的更高位置 记录错误并继续 附加调试器时:- 异常被抛出 调试器在引发异常的点处中断 通过一个例子来说明,下面是它如何与条件catch一起工作(我知道C#不支持这一点): 注意:当我展示代码引发异常的示例时,它可能是由第三方库引发的 static void DoSomething() { //This is where I would like the debugger to break execution
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();
}
}
注意,Run
需要有不同的重载,这取决于参数的数量(在本例中,我恰好使用了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 );
}
问题是,虽然throw
保留堆栈跟踪,但调试器在发生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();
}
}
但是,这方面存在一些问题:
- 更多代码李>
- 对于更复杂的情况,事情很快就会变得难以处理,例如当有更多的参数或
的局部变量由设置时,如果有子步骤,则需要通过引用传递到“DoSomething”try…catch
- 这是一个有点头痛的管理,我总是不会有它设置时,我需要它。具体地说,除了我手动设置符号定义之外,符号与附加调试器的事实没有任何关系
会使代码变得混乱,并使#调试
的可读性降低李>try…catch
- VisualStudio设置。我还研究了不同的VisualStudio异常中断设置,但我想为代码的特定部分打开行为,而不是为特定的异常打开行为。此外,这应该适用于所有安装李>
- 汇编IL。我已经考虑将内联IL作为生成条件异常的选项,但这需要使用第三方工具进行后期构建步骤李>
- 我不认为全局(应用程序)异常处理程序会这样做,因为需要捕获异常并将其记录在应用程序堆栈的较低位置
调试程序逐步通过属性
。在包含重新抛出的方法上设置此属性时,调试器将在异常的原始点中断,而不是在重新抛出异常的位置中断,如下所示:
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();
}
}
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();
}
}
static bool DoSomething()
{
//这就是调试器现在中断执行的地方
抛出新异常(“出错了!”);
返回true;
}
[调试步骤至]
静态公共void DoSomeStep()
{
尝试
{
DoSomething();
}
捕获(异常)
{
Console.WriteLine(异常消息);
if(Debugger.IsAttached==true)
{
//调试器不再在此中断
投掷;
}
}
}
静态void Main(字符串[]参数)
{
对于(int i=0;i<10;i++)
{
DoSomeStep();
}
}
唯一的缺点是,如果您确实希望单步执行标记为DebuggerStepThrough
的代码,或者此代码中存在异常。不过,这是一个小缺点,因为您通常可以将此代码保持最少
请注意使用Debugger.IsAttached
,因为我认为它在这里的影响最小,出现奇怪海森堡的可能性最小,但请注意不要使用Guillaume在评论中指出的方法,并在适当时使用另一个选项,如配置设置
除非有更好的方法或者有人对此表示担忧,否则我将使用这种方法。您可以包装异常并捕获特定类型的异常,这样在调试时,没有为异常定义捕获行为,并且调试器将在抛出代码时中断
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)
{
}
}
DebuggerStepThrough并重新抛出(已接受的答案)
正如注释中指出的,当在包含重新抛出的方法上设置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();
}
}
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();
}
}
在执行Catch()
语句中的任何内容之前,首先计算When()
语句中指定的谓词
如果运行该示例,您会注意到调试器在导致异常的行上中断,这是由于巧妙地放置了[DebuggerStepThrough]
属性
源代码
/// <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; }
}
//
///工厂。提供一个静态方法,用于初始化新的try-catch包装器。
///
公共静态类危险操作
{
///
///启动一个新的try-catch块。
///
///“try”块的操作。
///返回包装“try”块的类的新实例。
公共静态TryCatchBlock Try(
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();
/// <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; }
}
try
{
DoSomething()
}
catch (Exception e) when (!System.Diagnostics.Debugger.IsAttached)
{
Console.WriteLine(exception.Message);
}