我知道 goto 是一种糟糕的设计实践。但是想象一下,我们被困在一个荒岛上,我们的工具箱里只有 goto 和 ifs。那么不会这样:
int i = 0;
while (i < 10) i++;
与此相同:
int i = 0;
loop: if (i < 10) { i++; goto loop; }
?
仅使用条件逻辑、分配/更改变量、方法调用和跳转,您可以在 c# 中做任何通常可以做的事情,这不是真的吗?
是的,您可以在不使用任何循环的情况下重新编写任何程序以获得相同的输出。方法调用都可以内联,所以从技术上讲你也不需要这些。当然,类和接口对于任何逻辑也不是必不可少的。事实上,你可以用 if、goto、assignment 和 add 来做几乎所有事情。也许你甚至不需要那么多。
技术上?是的,一点没错。Goto 程序正在完善,因此您可以将所有内容都表示为它们。最后,机器代码与 goto 程序非常相似,因为循环和其他东西都是使用条件跳转完成的。当然,在 .NET 中你不能只使用goto。在某些时候,您会遇到使用其他代码的地方,这些代码不是这样编写的,或者您处于语言结构迫使您做其他事情的地方(创建类、方法、函数等)。但从技术上讲,是的。
你应该这样做吗?绝对不。Goto 程序很难维护,而且当 C# 编译成使用跳转但在更高级别上的中间语言时,这样做可能会损失大量性能。此外,虚拟机可以在“正常”代码中进行很多优化,而当您将其放入固定的 goto-schema 时则无法优化。
顺便提一句。你的原始代码编译成这个 IL,它本质上就是你使用 goto 编写的(我的注释):
// i = 0
IL_0001: ldc.i4.0 // Load integer value 0 to stack
IL_0002: stloc.0 // i // Store stack value in local variable 0
// goto loop-condition
IL_0003: br.s IL_0009 // Jump to IL_0009
// loop-start:
// i = i + 1
IL_0005: ldloc.0 // i // Load variable 0 to stack
IL_0006: ldc.i4.1 // Load integer `1` to stack
IL_0007: add // Add top two stack values
IL_0008: stloc.0 // i // Store result in local variable 0
// loop-condition:
// if (i < 10) { goto loop-start }
IL_0009: ldloc.0 // i // Load variable 0 to stack
IL_000A: ldc.i4.s 0A // Load integer `10` to stack
IL_000C: clt // Compare top two stack values
IL_000E: stloc.1 // CS$4$0000 // Store stack value in local variable 1
IL_000F: ldloc.1 // CS$4$0000 // Load variable 1 to stack
IL_0010: brtrue.s IL_0005 // Jump to IL_0005 if stack value is true
没有什么本质上是坏的goto
。使它不应该被使用的原因如下:
极易被滥用,并且控制得当;即便如此,它也会使代码更难阅读
总是可以使用更易读的结构,例如,while
, do
,for
或foreach
由于同时使用其他东西总是可能的并且总是更好,因此永远不应该实际使用goto
.
我听说的一个可能的例外是打破深层嵌套:
for (int x = 0; x < 100; x++)
for (int y = 0; y < 100; y++)
for (int z = 0; z < 100; z++)
if (condition)
goto end;
end: ;
VS
for (int x = 0; x < 100, !condition; x++)
for (int y = 0; y < 100, !condition; y++)
for (int z = 0; z < 100, !condition; z++)
为了更直接地回答您的问题,是的,完全可以使用goto
s 而不是while
s、do
s 等来完成所有控制流......我什至可能会建议这对实践来说是一个很好的挑战,但在任何实际代码中都没有,并且尝试在汇编中进行编码而不是强迫自己使用goto
s可能是一种更好的学习体验。
对的,这是可能的。最终,这就是装配的工作方式。装配中没有循环之类的东西。要更改代码中的位置,您必须使用等效的“goto”(通常jmp
或某种形式的“分支”)或函数调用(如果它们存在)。
但是,如果我继承的代码库使用了goto
s,我要么从头开始重写它,要么在不允许的情况下退出。