为什么在 catch 块中必须先写 return 语句再写 throw 语句?

15
以下代码将会报错。
try
{
    session.Save(obj);
    return true;
}
catch (Exception e)
{
    throw e;
    return false;  // this will be flagged as unreachable code
}

这种方法不会产生以下结果:
try
{
    session.Save(obj);
    return true;
}
catch (Exception e)
{
    return false;
    throw e;
}

我不明白...我以为我的csc101告诉我return语句应该总是作为函数中的最后一条语句,它会退出函数并将控制权返回给调用代码。为什么这违反了我的教授的逻辑,并且其中只有一个会生成警告?


3
"csc101告诉我,返回语句应该总是在函数的最后一个语句中。" 我认为这是Djikstra提出的过时建议,因为它是针对更老式的编程语言,这些语言具有不同的资源分配/清理模型。 - spender
C#...和Java有什么不同? - sirbombay
1
throw 语句和 return 语句一样,会退出函数。在同一个代码块中,这两个语句之后的任何内容都不会被执行。在您的情况下,您需要使用 throw ereturn false。如果您希望同时使用两者,则需要稍微修改您的函数。 - Michael Aquilina
这里有一个有趣的关于“单入口,单出口”的讨论:http://programmers.stackexchange.com/questions/118703/where-did-the-notion-of-one-return-only-come-from - spender
3
嘿;顺便说一句 - 我认为你的意思是“违抗”,而不是“玷污”。 - Marc Gravell
显示剩余6条评论
5个回答

19

return将退出该方法; throw也将退出该方法,假设它不在try中。 它只能退出一次!

因此,无论顺序如何——throw / return的第一个都会有效地结束该方法。

总的反馈是:如果意图在失败时返回false,则只需要:

try
{
    session.Save(obj);
    return true;
}
catch
{
    return false;
}

就我个人而言,我认为这是糟糕的代码——它隐藏了调用者面临的实际问题,使得调试变得非常困难。它没有告诉我们失败的原因。我认为更好的方法是简单地让异常冒泡。在这种情况下,返回 true 没有意义,因为我们永远不会返回 false,捕获异常再抛出也没有意义。因此,整个方法变成了:

session.Save(obj);

如果你的问题是“为什么只有其中一个会产生警告”,这是一个合理的问题,但编译器并不要求为你发现任何一个。也许它应该发现。我猜测gmcs会发现并警告这一点——mono中的编译器更愿意指出愚蠢的行为。

编辑:正如预期的那样,[g]mcs 输出:

Program.cs(15,13): warning CS0162: Unreachable code detected

Program.cs(28,13): warning CS0162: Unreachable code detected

对于下面的代码——因此它确实将两个使用都报告为警告:

class Program
{
    static void Main() { }
    static void DoSomething() { }
    bool ReturnFirst()
    {
        try
        {
            DoSomething();
            return true;
        }
        catch
        {
            return false;
            throw; // line 15
        }
    }
    bool ThrowFirst()
    {
        try
        {
            DoSomething();
            return true;
        }
        catch
        {
            throw;
            return false; // line 28
        }
    }
}

您的回答更加详细,值得成为正确答案。请更新以包括意图是让调用者知道是否成功保存(对象)的情况...并且仍然希望向上传播异常。谢谢。 - sirbombay
@sirbombay,这没有意义:他们知道它是否成功的方式只是通过是否出现异常。它不能同时返回false并抛出异常 - 这根本没有意义。 - Marc Gravell
今天我遇到了这个标准编译器的愚蠢问题,真的在想即使有了return语句,throw语句是否还能执行呢?谢谢你的回答。 - Tarık Özgün Güner
经过测试,您可以在返回时使用throw语句作为“Exception ex2; try{ ... } catch(Exception ex) {ex2 = ex; return; } finally{ throw ex2;}” - Tarık Özgün Güner
作为改进的解决方案,这里是:"Exception ex2 = null; try{ ... } catch(Exception ex) {ex2 = ex; return; } finally{ if(ex2!= null){throw ex2;}}" 对于垃圾信息我很抱歉。 - Tarık Özgün Güner
@TarıkÖzgünGüner,我尝试了你的解决方案,但它没有起作用。 - Vineet Agarwal

13
你错了: 你提到的这两个例子都会引发“死代码”编译器错误,因为无论是throw还是return,它们都标志着方法的退出点,超出该点不允许再有任何代码执行。
然而,无论编译器是否允许,throw或者return之后的代码仍然是死代码,永远不会被执行。
(注意:最初将此问题标记为Java,我的第一句话涉及Java编译器语义)

@musefan:我的意思是指出这个问题本身就是错误的,因为两个代码都会产生编译错误。 - Ajinkya
2
在VS2012中,return -> throw不会给出警告,而throw -> return会。 - Alessandro D'Andria
@AlessandroD'Andria:请确认这是正确的(在VS 2012中使用C#)。 - musefan
我正在使用VS2008,它允许第二个代码块,同时标记第一个。 - sirbombay
@musefan,关于此事,Mono编译器(mcs / gmcs)都会报告它们。 - Marc Gravell

1
因为在代码块内return语句之后的任何代码都无法被执行。

0

这个答案基于C#,可能适用于Java。

在这种情况下,你实际上不需要return语句。 throw将是函数的最后一步。

在这个例子中,无论你把returnthrow放在哪里,第一个总是防止第二个被执行。

注意:当throw语句被包裹在try块中时,它不会结束函数。在这种情况下,throw函数将结束剩余的try块代码,并移动到最相关的catch块 - 或者如果不适用catch块,则移动到finally块。

你的代码应该像这样:

try
{
    session.Save(obj);

    return true;
}
catch(Exception e)
{
    throw e;
}

然而,如果你只是重新抛出异常的话,使用 try/catch 没有太多意义。

针对你的唯一问题做出具体回答:

为什么这会违背我的教授逻辑?

那么,要么你的教授是错的,要么你误解了他们的意思。


0
在catch块中的“return false;”是无法到达的,因为在它之前有一个“throw e;”。当代码在catch块中执行时,第一行是一个throw语句,这意味着您立即将异常抛到调用方法,因此任何后续代码都不会被执行。
try
    {
        session.Save(obj);
        return true;
    }
    catch(Exception e)
    {
        throw e; //Throws exception to calling method
        return false;  //this will be flagged as unreachable code

    }

希望这能有所帮助。


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