有人在C#中仍然使用[goto]吗?如果是,为什么?

127

我想知道是否还有人在C#中使用“goto”关键字语法,以及可能的原因。

我倾向于认为任何导致读者跳转代码的语句都是不好的实践,但想知道是否存在使用这种语法的可信场景?

Goto关键字定义


5
“还”是什么意思?在C#中,以前有一段时间人们经常使用它吗? - Massif
6
@Massif: “still”的意图是强调现代观点中使用“goto”作为意大利面式代码和源代码可读性缺失的前奏的看法。很少看到包括这个特定关键字的代码示例,这就是我对此进行提问的原因。 - Brian Scott
65
如果读者在代码中“跳来跳去”是不好的做法,那么您是否也避免使用“break”、“continue”、“throw”和“return”?它们都会导致控制流程上的分支,有时甚至是非局部分支。“Throw”甚至不能告诉您它要去哪里,而“goto”可以。 - Eric Lippert
2
我使用 goto 根据特定条件打破循环并返回到起始语句。 - Nitin Sawant
4
我喜欢使用goto语句。由于人们普遍认为goto会使代码难以阅读,所以我曾试着避免使用它。但是,由于我学过汇编语言和分支语句,我认为有时候使用goto反而可以使代码更易读。我认为在一个方法中多次使用goto并跳得太远可能会带来更多的问题。但是,如果你觉得在这里使用goto会很好,那么偶尔使用一次简单的goto应该不会让你费尽心思地避免使用它,因为大众普遍认为应该避免使用goto。 - eaglei22
显示剩余4条评论
8个回答

116

有些(罕见的)情况下,goto实际上可以提高可读性。事实上,您链接到的文档列出了两个示例:

Goto的常见用途是将控制转移到switch语句中的特定case标签或默认标签。

Goto语句也可用于退出深度嵌套的循环。

以下是后者的示例:

for (...) {
    for (...) {
        ...
        if (something)
            goto end_of_loop;
    }
}

end_of_loop:

当然,还有其他解决这个问题的方法,比如将代码重构为一个函数,使用一些虚假的块包围它等(详见这个问题)。另外值得一提的是,Java语言设计者决定完全禁止goto语句,并引入了一个带标签的break语句。


60
通常情况下,我会试图重构这部分代码,将循环放在一个单独的方法中,以便我可以直接从该方法返回... - Jon Skeet
3
@Heinzi - 我没有看到goto有被保证的情况。就像Jon所说,如果它被"保证"了,那么代码就需要被重构了。 - manojlds
我不知道C#,但Java提供了 break <label>,这可以确保可靠性和安全性。 - mihsathe
34
标记式跳出循环(labeled break)只是说“goto”的另一种形式,因为它们都能做相同的事情。 - Jesus Ramos
24
@Jesus 但是使用goto,你可以随处跳转。而标记的break则确保你只会跳出循环外部。 - mihsathe
1
除非你冒险使用带有地址的goto(我以前见过这样做),否则该问题已得到缓解。而且我怀疑没有人会在你的C#和Java代码中使用detour hooks来利用goto语句。 - Jesus Ramos

86
我记得这部分。
switch (a)     
{ 
    case 3: 
        b = 7;
        // We want to drop through into case 4, but C# doesn't let us
    case 4: 
        c = 3;
        break; 
    default: 
        b = 2;
        c = 4;
        break; 
}

翻译成类似这样的内容

switch (a)     
{
    case 3: 
        b = 7;
        goto case 4;    
    case 4: 
        c = 3;
        break;     
    default: 
        b = 2;
        c = 4;
        break;
}

参考 这篇文章


23
我认为这是使用 [goto] 的最合理的原因。至少在这种情况下,它可以增加程序员的可读性,因为他们可能不知道 case 语句如果没有 break 会继续执行下一个 case。 - Brian Scott
12
@Brian Scott,V4Vendetta。除非我错了,第一条语句在C#中是无法编译的。这将有助于程序员的理解。 - Jodrell
3
V4Vendetta为什么在第一个代码片段中插入了一个断点?最好不要加断点展示,否则这两个片段执行的结果就会不同。第二个示例需要使用goto的原因正是因为第一个示例在C#中无法编译(虽然在C语言中可以)。 - Stephen Holt
@BrianScott 即使在支持此 drop-through 的语言中,如 C++,也有可能作者只是忘记写 break。因此,我通常会写一个注释,说明这是有意的。拥有一个特殊的语言结构会更好。 - Alex Che

27

我在Eduasync中广泛使用它,以展示在C# 5中使用异步方法时编译器为您生成的代码类型。您在迭代块中将看到相同的内容。

然而,在“普通”的代码中,我记不起上一次使用它是什么时候了...


1
你能提供一个小例子来说明为什么这种方法更受欢迎,还是只是个人偏好吗? - Brian Scott
2
@Brian:你的意思不是很清楚。Eduasync 显示了与编译器为您生成的代码等效的 C# 代码 - 它生成使用 goto 的代码,有效地... - Jon Skeet
我正在使用反编译器来检查应用程序内部发生了什么,并且它揭示了一些地方有goto语句。因此,我谷歌了它的原因,很可能是因为它被编译然后反编译,这个过程对我造成了影响。 - Kyle Burkett

13
编译器在各种生成的代码中使用goto语句,例如在生成的迭代器块类型中(在使用yield return关键字时生成 - 我很确定生成的XML序列化类型也有一些goto语句)。有关C#编译器处理此问题的更多详细信息,请参见迭代器块实现详细信息:自动生成状态机
除了生成的代码之外,在正常代码中没有使用goto语句的好理由 - 它使代码难以理解,因此更容易出错。另一方面,在像这样的生成代码中使用goto语句可以简化生成过程,并且通常是可以接受的,因为没有人会阅读(或修改)生成的代码,并且没有机会发生错误,因为是机器进行编写。
请参见有关Goto语句的危害以及经典的编程历史论点。

7
这很愚蠢:有一些情况下使用goto是有用的,正如其他答案所阐述的那样。你可以明确反驳它们,但仅仅说“这是错误的”就是不对的。 - o0'.
1
@Lohoris 我不相信 - 我看到的每个使用goto "提高可读性"的例子(包括这里的答案)在进行一些简单的重构后都会变得更加易读。 - Justin
4
不,有时嵌套循环只是完成某些任务最自然的方式,例如遍历一个数组中的多个子数组。 - o0'.
8
@Justin,把代码放进函数并不总是更清晰的做法。这样做只是为了避免你宗教上讨厌的某些东西(使用goto),明显表明了你在做一些错误的事情。 - o0'.
5
函数调用会有开销,而goto则没有。这是需要考虑的事情。 - Dan Bechard
显示剩余5条评论

11

我不记得曾经使用过goto。但是可能它可以提高永远不想退出的循环的意图(没有break,但是你仍然可以returnthrow):

forever: {
  // ...
  goto forever;
}

再说了,一个简单的 while (true) 就足够了...

此外,在你想让循环的第一次迭代从循环中间开始的情况下,可能可以使用它:请看这里有一个例子。


“linked answer”包含了一个“有趣”的goto用法,而while(true) {..}并不是一个有趣的用法。 - user2864740
这很有趣,但我不知道是否会在生产代码中使用。 - Jack

9

goto语句非常适用于跳出很多循环,如果使用break会不太合适(例如在错误条件下),正如Kragen所说,编译器也使用goto来生成switch语句和其他一些东西。


8
“break”和“continue”比让代码编辑器跳转源代码以理解下一步发生的位置更好地管理循环,这是毋庸置疑的。 - Brian Scott
9
如果你有嵌套循环,就不行。 - Jesus Ramos
1
好的,我可以看到这是一个有效的情境。 - Brian Scott
1
在错误情况下,您应该考虑抛出异常。 - Jodrell
7
如果你想在不抛出异常的情况下内部处理错误,这将是一种有效的方式。 - Jesus Ramos

3
处理器至少实现了一个跳转指令,我确定很多语句在它们的实现或解释中使用了这些指令。
使用第三代四代语言的好处之一是这些物理细节被抽象化了。虽然我们应该注意泄漏的抽象规律,但我认为我们也应该按照其预期使用工具抱歉)。如果我正在编写代码,而goto似乎是个好主意,那么就是重构的时候了。结构化语言的目的是避免这些“跳转”,并在我们的工程中创建逻辑流程。
我应该避免使用break,但我不能忽略它带来的性能好处。然而,如果我有相互需要break的嵌套循环,那么就是重构的时候了。
如果有人可以提出一个比重构更好的goto用法,我会很高兴撤回我的答案。
我希望我没有在这里匆忙地涂上“bike shed” 的颜色。就像 Kragen 说的那样,对于 Dijkstra 来说足够好的东西对我也足够好。

1
使用 dynamic 对象并遍历其包含多个字典的对象图,以获取所需的值。使用具有 dynamic 参数但期望精确对象形状的方法没有意义。使用 goto 跳出多个层并继续遍历这些对象集合。[我不拥有类型,因此无法提供更好的访问方式,因此只能使用反射或动态方式] - Chris Marisic

-8

使用goto语句并不是最好的选择。而且continue、break(除了在switch/case中)、(多个)return和throw也应该尽可能地减少使用。你永远不想从嵌套循环的中间跳出。你总是希望循环控制语句具有所有的循环控制权。缩进包含了信息,而所有这些语句都会抛弃这些信息。你最好将所有缩进都去掉。


12
如果循环没有继续执行的必要,你应该结束循环。否则,你将在没有任何理由的情况下浪费更多处理时间来执行长循环。 - Stephen
17
@Kirk,这听起来更像是一种观点,而不是量化的东西? - Brian Scott

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