7

编辑:我查看了答案代码:他们都没有做我想要的(我已经检查过)。似乎没有办法在本机 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; }

这不起作用,因为它会丢失堆栈信息(并且还会在错误的位置中断)。

try { ... } catch(Exception e) { throw e; }

我知道在D中我可以使用scope(failure)

4

17 回答 17

35

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

CIL实际上定义了五种异常处理块!您在 C# 中习惯使用的 、 和 ,以及另外try两个catchfinally

  • filter- 类似于catch块,但可以运行任意代码来确定它是否要处理错误,而不仅仅是匹配类型。此块可以访问异常对象,并且对异常堆栈跟踪具有与catch块相同的效果。

  • fault- 类似于finally块,但它仅在发生异常时运行。此块无权访问异常对象,并且对异常堆栈跟踪没有影响(就像finally块一样)。

filter在某些 .NET 语言(例如 VB.NET、C++/CLI)中可用,但在 C# 中不可用,遗憾的是。但是,除了 CIL 之外,我不知道任何允许fault表达块的语言。

因为它可以在 IL 中完成,所以并不是所有的都丢失了。理论上,您可以使用 Reflection.Emit 动态发出具有fault块的函数,然后将要运行的代码作为 lambda 表达式传递(即,一个用于尝试部分,一个用于错误部分,依此类推),但是(a) 这并不容易,并且 (b) 我不相信这实际上会给你一个比你目前得到的更有用的堆栈跟踪。

抱歉,答案不是“这里是怎么做”类型的事情,但至少现在你知道了!你现在正在做的可能是最好的方法恕我直言。


请注意那些说问题中使用的方法是“不好的做法”的人,实际上不是。当你实现一个catch块时,你是在说“发生异常时我需要对异常对象做一些事情”,而当你实现一个块时,finally你是在说“我不需要异常对象,但我需要先做一些事情函数结束”。

如果您实际上想说的是“我不需要异常对象,但是当异常发生时我需要做一些事情”,那么您介于两者之间,即您想要一个fault块。由于这在 C# 中不可用,因此您没有理想的选项,因此您不妨选择不太可能因忘记重新抛出而导致错误并且不会破坏堆栈跟踪的选项。

于 2009-02-10T21:17:19.677 回答
27

这个怎么样:

try
{
  MightThrow();
}
catch
{
  DoSomethingOnFailure();
  throw; // added based on new information in the original question
}

真的,这就是你所做的一切。finally 用于无论是否发生异常都必须运行的事情。

[编辑:澄清]

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

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

具体来说,我需要未捕获异常的调试器停止在抛出的原始点(在 MightThrow 中)而不是在 catch 块中。

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

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

kronoz为您提供了您所寻求的答案。不要破坏最佳实践——正确使用 Visual Studio!您可以将 Visual Studio 设置为在引发异常时准确中断。这是有关该主题的官方信息

我实际上不知道这个功能,所以去给他接受的答案。但是,请不要试图以某种时髦的方式处理异常,只是为了让自己手动调试。你所做的就是向更多的错误敞开心扉。

于 2009-02-10T20:12:53.327 回答
11

如果您对调试器感兴趣,只是在发生异常的位置精确停止,那么您是否考虑过第一次机会异常?

如果您打开 Tools|Exceptions 然后勾选 Common Language Runtime Exceptions 框,调试器将在异常点停止,而不管任何 try/catch/finally 块。

更新:您可以通过展开“异常”对话框中的 [+] 树来指定要捕获的精确异常。当然,每次发生指定类型[s]的任何异常时它都会触发[s],即使在调试会话中间,您也可以随意打开和关闭它,因此明智地使用断点可以得到它做你的竞标。我成功地使用它来解决源自使用反射实例化对象的“调用目标已引发异常”的球痛。在这种情况下非常有用的工具。另请注意,据我所知,本地人和堆栈跟踪应该是可靠的(只是做了一个快速测试并且它们可用的),所以那里没有问题。

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

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

于 2009-02-10T20:38:24.263 回答
7

有什么问题:

try
{
   MightThrow();
}
catch
{
   DoSomthingOnFailure();
   throw;
}
于 2009-02-10T20:13:17.910 回答
7

对于只应在异常上运行的代码,请使用 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();
}
于 2009-02-10T20:13:39.190 回答
4

这就是你想要的。它只会在发生错误时调用该方法,而“throw”语句将在调用堆栈完好无损的情况下重新抛出异常。

try
{
   MightThrow();
}
catch
{
   DoSomthingOnFailure();
   throw;
}
于 2009-02-10T20:16:41.850 回答
3

仅在失败时运行的“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();
  }
于 2009-02-10T20:21:19.780 回答
2

根据我的测试,到目前为止,每个示例都丢失了原始的 StackTrace。这是一个适合您的解决方案。

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;
}
于 2009-02-10T20:41:34.677 回答
2

只捕获“MightThrow”不抛出的异常怎么样?

Bool bad = true;
try
{
   MightThrow();
   bad = false;
}
catch (SomePrivateMadeUpException foo)
{ 
   //empty
}
finally
{
   if(bad) DoSomeLoggingOnFailure();   
}
于 2009-02-10T21:14:12.150 回答
2

让我以我理解的方式概括您的要求:

  1. 您需要一些仅在生成异常时运行的代码,以便进行一些日志记录。
  2. 您希望在调试器下运行您的测试框架并在抛出异常时中断。

为了满足您的第一个要求,您应该按照大家建议的方式编写代码 - 使用无参数的 catch 和 throw。

为了在使用无参数 catch 时满足您的第二个要求,您可以将调试器配置为在抛出异常时中断,而不仅仅是在出现未处理的异常时。我怀疑你知道怎么做,但我把它放在这里是为了回答的完整性:在 VS 中,你可以在 Debug -> Exception -> Common Language Runtime Exceptions -> 检查 Thrown 复选框中做到这一点。

如果您知道您的应用程序会引发大量已处理的异常,那么这可能不是您的选择。在这一点上,满足您的第一个要求的唯一选择是编写代码最终用于异常记录目的,或者按照 Greg Beech 的建议查看直接发出 IL 的路线。

但是,最终代码是否正在执行取决于您使用的调试器。特别是,VS 会在 finally 执行之前因未处理的异常而中断,并且不会让您继续。因此,除非您此时从进程中分离,否则您的日志记录代码将永远不会被执行。换句话说,第二个要求会干扰满足第一个要求。

于 2009-02-10T21:37:22.123 回答
2

您可以将逻辑封装在自定义类中,例如:

    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();
            }
        }
    }

}

并将其用作:

            new Executor(MightThrow, DoSomeLoggingOnFailure).Run();

希望这可以帮助。

于 2009-02-11T00:37:06.247 回答
1

这不与以下内容相同:

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

?

于 2009-02-10T20:13:30.983 回答
1

您可以在 VB.net 中编写或让某人为您编写一个小程序集,该程序集实现了接受四个委托的 TryFaultCatchFinally(of T) 方法:

  1. TryMethod -- 执行“Try”块的 Action(of T)。
  2. FaultMethod——一个 Predicate(Of T, Exception),如果发生异常,将任何“finally”块运行之前调用;如果它返回 true,则 Catch 块将运行 - 否则不会。
  3. CatchMethod -- 如果发生异常并且 FaultMethod 返回 true,则要执行的 Action(Of T, Exception);在“终于”块运行之后发生。
  4. FinalMethod -- 一个 Action(OF T, Exception, Boolean) 作为“Finally”块执行。如果 TryMethod 运行完成,则传入的异常将为 null,或者将保留导致其退出的异常。如果异常被捕获,布尔值将为真,否则为假。

请注意,在执行 FaultMethod 时,可以检查导致异常的对象的状态,然后再由 finally 块破坏这种状态。执行此操作时必须小心(在抛出异常时持有的任何锁都将继续持有),但该功能有时仍然很方便,尤其是在调试时。

我建议例程看起来像:

    Shared Sub TryFaultCatchFinally(Of T)(ByVal TryProc As Action(Of T), _
                                          ByVal FaultProc As Func(Of T, Exception, Boolean), _
                                          ByVal CatchProc 作为动作(T,异常),_
                                          ByVal finallyProc As Action(Of T, Exception, Boolean), _
                                          ByVal 值作为 T)
        将异常调暗为异常 = 无
        Dim exceptionCaught As Boolean = False
        尝试
            TryProc(值)
            theException = 没有
            异常捕获 = 假
        当 CopyExceptionAndReturnFalse(Ex, theException) OrElse FaultProc(Value, Ex) 时将 Ex 作为异常捕获
            异常捕获=真
            CatchProc(值,前)
        最后
            finallyProc(值,异常,异常捕获)
        结束尝试
    结束子
于 2011-01-06T23:06:39.423 回答
0

不,我认为这是一个常见的习惯用法。

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

于 2009-02-10T20:12:42.857 回答
0

您是否考虑过使用 DebuggerStepThrough 属性? http://msdn.microsoft.com/en-us/library/system.diagnostics.debuggerstepthroughattribute.aspx

[DebuggerStepThrough]
internal void MyHelper(Action someCallback)
{
    try
    {
        someCallback();
    }
    catch(Exception ex)
    {
        // Debugger will not break here
        // because of the DebuggerStepThrough attribute
        DoSomething(ex);
        throw;
    }
}
于 2011-01-13T15:00:14.357 回答
0

在 C# 6 中添加异常过滤器后,一种选择是使用返回错误的异常过滤器,如下所示:

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;
}

有关异常过滤器的更多详细信息,请参阅https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/try-catch

于 2020-12-04T20:07:49.233 回答
-1
try
{
   MightThrow();
}
catch
{
   DoSomethingOnFailure();
}
于 2009-02-10T20:13:09.470 回答