C# - 如何重新抛出异常但不将其设置为变量

7

大家可能都知道,使用这种方式在C#中捕获并重新抛出异常是不好的,因为它会破坏堆栈跟踪:

try
{
    if(dummy)
        throw new DummyException();
}
catch (DummyException ex)
{
    throw ex;
}

在不丢失堆栈跟踪的情况下重新抛出异常的正确方法是:

try
{
    if(dummy)
        throw new DummyException();
}
catch (DummyException ex)
{
    throw;
}

唯一的问题是我收到了很多编译警告:“变量'ex'已声明但从未使用”。如果你有很多这样的警告,有用的警告可能会被隐藏在垃圾中。所以,我做了这个:

try
{
    if(dummy)
        throw new DummyException();
}
catch (DummyException)
{
    throw;
}
catch(AnotherException ex)
{
    //handle it
}

这种方法似乎是可行的,但我想知道重新抛出未设置变量的异常是否有任何缺点。.net如何处理这种情况?

提前感谢。

编辑: 我已经稍微修改了我的代码,以更清楚地表达我的意思,因为有些人误解了。


你为什么要在同一个方法中抛出异常、捕获异常并重新抛出异常呢? - Cody Gray
1
@Cody Gray- 我猜只是举例说明他了解throwthrow ex的区别,但并没有在方法中生成异常、捕获该异常、再次抛出异常。我希望是这样的。 - AllenG
2
@Cody Gray,@AllenG是对的,这只是一个简单的例子,展示我想要实现的内容,并不是真正的代码。 - Ortiga
好的,很好。这正是我所希望的,也是我将其作为评论而不是答案发布的原因。你会惊讶于在这里看到的人们使用抛出/捕获异常的疯狂方式。 - Cody Gray
6个回答

9
我想知道重新抛出未设置变量的异常是否有任何缺点。
没有,根本没有任何缺点。只有在您想要在代码中引用异常时才需要变量,但是由于您不需要使用throw语句来执行该操作,因此根本不需要变量。
您的确有正确的想法,试图消除“嘈杂”的编译器警告。它们往往会掩盖您希望修复的重要错误,并且获得干净的构建始终很重要。最好的解决方案就是简单地重新编写代码以使用无参数的catch子句。
但是,请注意,在我看到的82%的情况下*,编写使用throw的代码都是错误的。通常情况下,您不应捕获您不知道如何处理并且唯一的“处理”策略是重新抛出它们的异常。即使使用throw也会导致重置调用堆栈的情况,从而导致您丢失重要的调试信息。记录异常的更好替代方法是捕获/重新抛出。您可以在以下问题的答案中找到更多信息: 让异常上升并在一个中央位置处理它们没有任何问题。需要记住的规则是不应使用异常来控制流程。但是,在低级别代码中抛出异常并在UI代码中向用户显示错误消息没有任何问题。阅读Microsoft的最佳做法处理异常以获取一些常规提示。

*略高于当场编造的百分比统计数据


谢谢你的答案,这正是我所寻找的。我在异常处理方面已经犯了很多错误,但我认为现在我正在变得更加熟练。通常我会在方法内部或上一层进行处理,避免它向上太多层。我通常会抛出未经检查的异常,并使其上浮,给用户一个通用的错误消息。这是一个不好的做法吗? - Ortiga
@andre:让异常上升并在中心位置处理它们绝对没有问题。需要记住的规则是不应该使用异常进行流程控制。但是,在低级代码中抛出异常并在UI代码中向用户显示错误消息是没有问题的。您可能需要发布真实的示例代码,以便我更具体地说明。但是,您应该阅读Microsoft的异常处理最佳实践获取一些通用提示。 - Cody Gray

8

这样做没有任何不利影响。你只是在告诉编译器:“我打算捕获这个异常,但我不需要引用实际的异常”,它不会影响抛出异常或异常处理的方式。你后面的例子是实现你想要的功能的理想方式,然而如果你在块中什么也不做,只是立即throw;,那么为什么还要捕获呢?


5

如果你在 catch 块中没有对 DummyException 做任何处理(因为你没有给它一个标识符),为什么不完全删除 try/catch 块呢?例如,可以这样做:

throw new DummyException();

尽管如此,我认为你应该评估一下你的目标并重新思考应用程序架构,以避免依赖异常传播的方式。

1
你之所以可能会特意捕获DummyException并将其重新抛出,是因为你可能正在更高级别(靠近UI)的位置上处理它。而且,使用try/catch可以让你a)包含一个finally(如果需要),并b)捕获多个异常。 - AllenG
@AllenG,如果try/finally没有catch块也是可以的。我不确定,但编译器可能会将其整个catch优化掉,这一点也不奇怪。 - Matt Greer
@MattGree:没错,但你仍然需要try。我指的是建议“完全摆脱try/catch块”的建议,这是不可取的,因为你至少需要try - AllenG
@AllenG 很好的观点值得考虑;我的回答是基于OP的示例(没有其他catch块和finally)。 - Donut
@Donut,我举了这个例子是因为它展示了我的问题——如果我要重新抛出异常,就不能使用变量,否则会给我一个编译器警告。在真实的代码中,我会有多个catch块和/或finally块。 - Ortiga
@Andre 好的,虽然我认为将未处理的异常传递到堆栈上方有一些缺点(即从函数创建多个退出点)。 - Donut

1

为什么要这样捕获和重新抛出异常?为什么人们总是假设他们知道每种情况呢?答案是:数据库事务。

如果你有更好的方法,请提出来,Proton博士,不会有任何冒犯。请记住,目前有很多不同的数据库系统在使用,但大多数都支持事务控制(begin/commit/rollback)并且有C#接口。

伪代码(一个简化的案例):

try
{
   db.beginTrans();

   db.doStuff();

   db.doOtherStuff();

   db.commitTrans();
}
catch
{
  db.rollbackTrans();
  throw;
}

现在,失去doStuff()或doOtherStuff()失败的详细信息非常令人恼火,我不明白为什么C#会在这种情况下丢弃行号信息。但它似乎确实如此。因此,我进行了谷歌搜索并随后到达了这里。如果我漏掉了什么,请告诉我。


1
这里有一篇文章,提供了一些见解和可能的解决方案:http://weblogs.asp.net/fmarguerie/archive/2008/01/02/rethrowing-exceptions-and-preserving-the-full-call-stack-trace.aspx - AnotherOne

1

如果你只是要重新抛出,那么为什么要捕获呢?无论如何,您可能需要查看一下此前的讨论。虽然不完全相同,但讨论的大部分内容都是相关的:

抛出 VS 重新抛出:结果相同吗?


感谢提供链接。回答你的问题,此代码只是一个例子,我可能会重新抛出某种异常,但不是其他异常(如果有多个catch块)。 - Ortiga

1

实际上,使用"throw;"抛出异常是.NET的最佳实践,因为它保留了异常堆栈跟踪。

使用"throw ex;"抛出异常被认为是最差的实践,因为它会丢失原始堆栈跟踪,应该避免使用。


2
他在问题中确实表达了这一点... :) - Roja Buck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接