C# “c”;最后";仅在异常情况下运行的块

C# “c”;最后";仅在异常情况下运行的块,c#,exception-handling,finally,C#,Exception Handling,Finally,编辑:我已经查看了答案代码:没有一个做了我想做的事(我已经检查过了)。在本机c#中似乎没有办法实现我想要的功能。我想这不是一场灾难,只是一个遗憾,因为.NET确实支持它(见公认答案) 谢谢大家 我有这样的c#代码(测试框架的一部分,除非在调试器下运行),谁的意思是要避免实际捕获异常,因为这会使调试堆栈中未缠绕部分的代码变得非常痛苦 Bool bad = true; try { MightThrow(); bad = false; } finally { if(bad) DoS

编辑:我已经查看了答案代码:没有一个做了我想做的事(我已经检查过了)。在本机c#中似乎没有办法实现我想要的功能。我想这不是一场灾难,只是一个遗憾,因为.NET确实支持它(见公认答案)

谢谢大家


我有这样的c#代码(测试框架的一部分,除非在调试器下运行),谁的意思是要避免实际捕获异常,因为这会使调试堆栈中未缠绕部分的代码变得非常痛苦

Bool bad = true;
try
{
   MightThrow();
   bad = false;
}
finally
{
   if(bad) DoSomeLoggingOnFailure();

   //// Does not catch!!!! 
   //// exception continues to unwind stack.

   //// Note that re throwing the exception is NOT
   //// the same as not catching it in the first place
}
他们的方法更好吗

对于未捕获的异常,解决方案的行为必须与调试器下的行为完全相同。它必须导致只有一次第一次机会的异常,并且调试器在异常最初抛出时中断,而不是在catch块中

具体地说,我需要对未捕获的异常使用调试器来停止内部MightThrow

以下操作不起作用,因为它无法使调试器在正确的位置中断

try { ... } catch { throw; }
这不起作用,因为它会丢失堆栈信息(并且会在错误的位置中断)


我知道我可以用一个block,不,我认为这是一个常见的习惯用法

编辑 需要明确的是,“catch”和“rethrow”策略提供了相同的运行时语义,但是它们在连接VS调试器时会改变体验。工具和维护很重要;调试通常要求您“捕获所有第一次机会异常”,如果由于捕获而导致大量“虚假”第一次机会异常,然后在代码中重新刷新,这确实会损害调试代码的能力。这个习惯用法是关于与工具进行良好的交互,以及清楚地表达意图(你不想“捕获”,决定不能处理,然后重试,相反,你只想记录一个异常确实发生了,但让它传递过去)。

这样如何:

try
{
  MightThrow();
}
catch
{
  DoSomethingOnFailure();
  throw; // added based on new information in the original question
}
真的,你就这么做了。最后是针对无论是否发生异常都必须运行的事情

[编辑:澄清]

根据前面提到的注释,您希望继续抛出异常,而不修改其原始堆栈跟踪。在这种情况下,您希望使用我添加的朴素投掷。这将允许异常在堆栈中继续,并且仍然允许您处理部分异常。典型情况可能是关闭网络连接或文件

[第二次编辑:关于您的澄清]

特别是我需要的调试器上 未捕获的异常停止在 投掷的起始点(向内) (可能会扔)不在接球区

我反对打破最佳实践(是的,这是部分处理异常的最佳实践),为调试增加一些次要价值。您可以轻松地检查异常以确定异常抛出的位置

[最终编辑:你有你的答案]

深思熟虑地为你提供了你所寻求的答案。不要破坏最佳实践--正确使用Visual Studio!您可以将Visual Studio设置为在引发异常时完全中断。给你

事实上,我不知道这个特征,所以去给他一个公认的答案吧。但是,请不要试图用一些古怪的方式来处理异常,仅仅是为了给自己一个手动调试的机会。你所做的就是让自己面对更多的虫子

有什么问题吗:

try
{
   MightThrow();
}
catch
{
   DoSomethingOnFailure();
}
try
{
   MightThrow();
}
catch
{
   DoSomthingOnFailure();
   throw;
}

这不就是:

try 
{
    MightThrow();
}
catch (Exception e) 
{
    DoSomethingOnFailure();
    throw e;
}

对于只应在异常情况下运行的代码,请使用catch块:

try
{
   MightThrow();
}
catch (Exception ex)
{
   // this runs only when there was an exception
   DoSomthingOnFailure();
   // pass exception on to caller
   throw; 
}
finally
{
   // this runs everytime
   Cleanup();
}

这就是你想要的。它仅在发生错误时调用此方法,“throw”语句将在callstack保持不变的情况下重新抛出异常

try
{
   MightThrow();
}
catch
{
   DoSomthingOnFailure();
   throw;
}
仅在失败时运行的“finally”块称为“catch”(不带参数)。:-)

现在,有一个小警告。如果您想为特定的异常类型提供一个专门的“catch”案例,并为所有异常提供一个通用的“catch”,那么您必须执行一些自定义逻辑

因此,我会这样做:

  try
  {
    MightThrow();
  }
  catch(MyException ex)
  {
    // Runs on MyException
    MySpecificFailureHandler()
    // Since we have handled the exception and can't execute the generic
    // "catch" block below, we need to explicitly run the generic failure handler
    MyGenericFailureHandler()
  }
  catch
  {
    // Runs on any exception hot handled specifically before
    MyGenericFailureHandler()
    // If you want to mimic "finally" behavior and propagate the exception
    // up the call stack
    throw;
  }
  finally
  {
    // Runs on any failure or success
    MyGenericCleanupHandler();
  }
    public  class Executor
{
    private readonly Action mainActionDelegate;
    private readonly Action onFaultDelegate;

    public Executor(Action mainAction, Action onFault)
    {
        mainActionDelegate = mainAction;
        onFaultDelegate = onFault;
    }

    public  void Run()
    {
        bool bad = true;
        try
        {
            mainActionDelegate();
            bad = false;
        }
        finally
        {
            if(bad)
            {
                onFaultDelegate();
            }
        }
    }

}
Shared Sub TryFaultCatchFinally(Of T)(ByVal TryProc As Action(Of T), _ ByVal FaultProc As Func(Of T, Exception, Boolean), _ ByVal CatchProc As Action(Of T, Exception), _ ByVal FinallyProc As Action(Of T, Exception, Boolean), _ ByVal Value As T) Dim theException As Exception = Nothing Dim exceptionCaught As Boolean = False Try TryProc(Value) theException = Nothing exceptionCaught = False Catch Ex As Exception When CopyExceptionAndReturnFalse(Ex, theException) OrElse FaultProc(Value, Ex) exceptionCaught = True CatchProc(Value, Ex) Finally FinallyProc(Value, theException, exceptionCaught) End Try End Sub
如果您对调试器感兴趣,只需在异常发生的地方停止,那么您是否考虑过第一次出现异常

如果打开Tools | Exceptions,然后勾选Common Language Runtime Exceptions(公共语言运行时异常)框,则调试器将在异常点停止,而不考虑任何try/catch/finally块

更新:通过展开异常对话框中的[+]树,可以指定要捕获的准确异常。当然,每当指定类型[S]发生异常时,它都会开火,即使在调试会话的中间,您也可以随意打开和关闭它,因此明智地使用断点,您可以得到它来完成您的命令。我使用它成功地绕过了“调用的目标抛出了异常”的问题,该问题源于使用反射实例化对象。在这种情况下非常有用的工具。还要注意的是,据我记忆所及,局部变量和堆栈跟踪应该是稳定可用的(只是做了一个快速测试,它们是可用的),所以没有问题

当然,如果您想记录事情,那么这就超出了IDE调试器的范围;在这种情况下,第一次机会的例外不会帮助你


至少试一试吧;我发现它们非常有用,可能比您想象的更适合您的问题。

根据我的测试,到目前为止,每个示例都失去了原始的堆栈跟踪。这里有一个适合你的解决方案

private static void PreserveStackTrace(Exception exception)
{
  MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
    BindingFlags.Instance | BindingFlags.NonPublic);
  preserveStackTrace.Invoke(exception, null);
}

try
{
   MightThrow();
}
catch (Exception ex)
{
    DoSomethingOnFailure();
    PreserveStackTrace(ex);
    throw;
}

只捕获“MightThrow”不会抛出的异常如何

Bool bad = true;
try
{
   MightThrow();
   bad = false;
}
catch (SomePrivateMadeUpException foo)
{ 
   //empty
}
finally
{
   if(bad) DoSomeLoggingOnFailure();   
}

所以,在.NET中,您所要求的在理论上是可能的,但这并不容易

CIL实际上定义了五种类型的异常处理块!
尝试
捕获
最后
您习惯的
            new Executor(MightThrow, DoSomeLoggingOnFailure).Run();
Shared Sub TryFaultCatchFinally(Of T)(ByVal TryProc As Action(Of T), _ ByVal FaultProc As Func(Of T, Exception, Boolean), _ ByVal CatchProc As Action(Of T, Exception), _ ByVal FinallyProc As Action(Of T, Exception, Boolean), _ ByVal Value As T) Dim theException As Exception = Nothing Dim exceptionCaught As Boolean = False Try TryProc(Value) theException = Nothing exceptionCaught = False Catch Ex As Exception When CopyExceptionAndReturnFalse(Ex, theException) OrElse FaultProc(Value, Ex) exceptionCaught = True CatchProc(Value, Ex) Finally FinallyProc(Value, theException, exceptionCaught) End Try End Sub
[DebuggerStepThrough]
internal void MyHelper(Action someCallback)
{
    try
    {
        someCallback();
    }
    catch(Exception ex)
    {
        // Debugger will not break here
        // because of the DebuggerStepThrough attribute
        DoSomething(ex);
        throw;
    }
}
void PerformMightThrowWithExceptionLogging()
{
    try
    {
        MightThrow();
    }
    catch (Exception e) when (Log(e))
    {
        // Cannot enter here, since Log returns false.
    }
}

bool Log(Exception e)
{
   DoSomeLoggingOnFailure(e);
   // Return false so the exception filter is not matched, and therefore the stack is kept.
   // This means the debugger breaks where the exception actually happened, etc.
   return false;
}