如果上面的陈述是正确的,那么为什么当我在 .Net BCL 上使用反射器时,我看到它被大量使用?
编辑:让我换个说法:我在反射器中看到的所有 GO-TO 都是由人类或编译器编写的吗?
如果上面的陈述是正确的,那么为什么当我在 .Net BCL 上使用反射器时,我看到它被大量使用?
编辑:让我换个说法:我在反射器中看到的所有 GO-TO 都是由人类或编译器编写的吗?
我认为以下关于 Goto的 Wikipedia 文章摘录在这里特别相关:
对 GOTO 最著名的批评可能是 Edsger Dijkstra 在 1968 年写的一封信,名为 Go To Statement Considered Harmful。在那封信中,Dijkstra 认为不受限制的 GOTO 语句应该从高级语言中废除,因为它们使分析和验证程序正确性的任务变得复杂(尤其是那些涉及循环的)。Donald Knuth 的带有 go to 语句的结构化编程中提出了另一种观点,它分析了许多常见的编程任务,并发现在其中一些任务中,GOTO 是最适合使用的语言结构。
因此,一方面,我们让Edsger Dijkstra(一位非常有才华的计算机科学家)反对使用该GOTO
语句,并特别反对过度使用该GOTO
语句,理由是它是一种结构化得多的代码编写方式。
另一方面,我们有Donald Knuth(另一位非常有才华的计算机科学家)认为使用GOTO
,尤其是明智地使用它实际上可以成为给定程序代码的“最佳”和最优化的构造。
最终,恕我直言,我相信这两个人都是正确的。Dijkstra 是正确的,因为过度使用该GOTO
语句肯定会降低一段代码的可读性和结构化程度,从纯理论的角度来看计算机编程时,这当然是正确的。
然而,Knuth 也是正确的,因为在“现实世界”中,必须采取务实的方法,GOTO
明智地使用该语句确实可以成为使用语言结构的最佳选择。
上面的内容并不正确——它是Dijkstra在 goto 是唯一使用的流量控制结构时使用的一个有争议的设备。事实上,已经有好几个人提出了反驳,包括 Knuth 的经典“Structured Programming Using Goto”论文(标题来自记忆)。在某些情况下(错误处理、状态机),与“结构化”替代方案相比,goto 可以生成更清晰的代码(恕我直言)。
这些goto
's 经常由编译器生成,尤其是在枚举器内部。编译器总是知道她在做什么。
如果您发现自己需要使用goto
,您应该确保它是唯一的选择。大多数情况下,您会发现有更好的解决方案。
除此之外,很少有实例goto
可以证明使用 是合理的,例如在使用嵌套循环时。同样,在这种情况下还有其他选择。您可以打破函数中的内部循环并改用 return 语句。如果额外的方法调用真的太昂贵,您需要仔细查看。
回应您的编辑:
不,不是所有的 goto 都是编译器生成的,但其中很多是由编译器生成的状态机(枚举器)、switch case 语句或优化的 if else 结构产生的。只有少数情况下您可以判断它是编译器还是原始开发人员。您可以通过查看函数/类名称来获得一个很好的提示,编译器将生成“禁止”名称以避免名称与您的代码冲突。如果一切看起来正常并且代码没有经过优化或混淆,则可能是有意使用 goto 的。
请记住,您在 Reflector 中看到的代码是反汇编代码——Reflector 正在查看已编译的字节码并尝试将原始源代码拼凑在一起。
这样,您必须记住针对goto
s 的规则适用于高级代码。所有用于替换goto
s ( for
, while
,break
等switch
) 的结构都使用 JMP 编译成代码。
因此,Reflector 看起来很像这样的代码:
A:
if !(a > b)
goto B;
DoStuff();
goto A;
B: ...
并且必须意识到它实际上被编码为:
while (a > b)
DoStuff();
有时读取的代码太复杂以至于无法识别模式。
Go To
声明本身无害,有时甚至非常有用。有害的是那些倾向于将其放在代码中不适当位置的用户。
当编译为汇编代码时,所有控件都结构化并转换为(无)条件跳转。但是,优化器可能过于强大,当反汇编器无法识别跳转模式对应的控制结构时,goto label;
就会发出永远正确的语句,即。
这与goto
.
例如,您已经突破了双循环或许多嵌套循环呢?
foreach (KeyValuePair<DateTime, ChangedValues> changedValForDate in _changedValForDates)
{
foreach (KeyValuePair<string, int> TypVal in changedValForDate.Value.TypeVales)
{
RefreshProgress("Daten werden geändert...", count++, false);
if (IsProgressCanceled)
{
goto TheEnd; //I like goto :)
}
}
}
TheEnd:
在这种情况下,您应该考虑在此使用 break 进行以下操作:
foreach(KeyValuePair<DateTime, ChangedValues> changedValForDate in _changedValForDates)
{
foreach (KeyValuePair<string, int> TypVal in changedValForDate.Value.TypeVales)
{
RefreshProgress("Daten werden geändert...", count++, false);
if (IsProgressCanceled)
{
break; //I miss goto :|
}
}
if (IsProgressCanceled)
{
break; //I really miss goto now :|
}//waaaAA !! so many brakets, I hate'm
}
一般规则是您不需要使用goto
. 与任何规则一样,当然有例外,但与任何例外一样,它们很少。
goto
命令就像毒品一样。如果仅在特殊情况下少量使用,那很好。如果你一直使用太多,它会毁了你的生活。
当您使用 Reflector 查看代码时,您看不到实际代码。您看到的代码是根据编译器从原始代码生成的内容重新创建的。当您goto
在重新创建的代码中看到 a 时,并不确定goto
原始代码中是否存在 a。可能有一个更结构化的命令来控制流程,例如编译器以与 a 相同的方式实现的abreak
或 a ,因此 Reflector 无法区分。continue
goto
goto
被认为是有害的(对于人类使用,但对于计算机来说还可以)。
因为无论我们(人类)多么疯狂地使用goto
,编译器总是知道如何阅读代码。
相信我...
阅读其他带有goto
s 的代码是困难的。
用 s 阅读你自己的代码goto
更难。
这就是为什么您看到它用于低级(机器语言)而不是高级(人类语言,例如 C#、Python ...);)
当我想执行终止操作时,我有时会使用 goto:
static void DoAction(params int[] args)
{
foreach (int arg in args)
{
Console.WriteLine(arg);
if (arg == 93) goto exit;
}
//edit:
if (args.Length > 3) goto exit;
//Do another gazillion actions you might wanna skip.
//etc.
//etc.
exit:
Console.Write("Delete resource or whatever");
}
因此,我没有点击return
,而是将其发送到执行另一个最终操作的最后一行,我可以从片段中的各个位置引用而不是仅仅终止。
“C 提供了可无限滥用的 goto 语句,以及分支到的标签。形式上,goto从来都不是必需的,实际上,没有它几乎总是很容易编写代码。我们在本书中没有使用 goto。”
-- K&R(第 2 版):第 65 页
在反编译的代码中,几乎所有goto
你看到的 s 都是合成的。不要担心他们;它们是代码如何在低级别表示的工件。
至于将它们放入您自己的代码中的正当理由?我能想到的主要问题是您使用的语言没有提供适合您正在解决的问题的控制结构;使定制控制流系统变得容易的语言通常根本没有goto
。也总是可以完全避免使用它们,但是将任意复杂的代码重新排列到一个 while 循环和大量带有一整套控制变量的条件......这实际上可以使代码更加模糊(而且速度也更慢;编译器通常不够聪明,无法区分这种复杂性)。编程的主要目标应该是生成对计算机和阅读程序的人都清楚的程序描述。
有害与否,那是各人好恶的问题。我个人不喜欢它们,并且发现它们在尝试维护代码时效率很低。
现在,一件事是 goto 如何影响我们对代码的阅读,而另一件事是发现时抖动如何执行。从Eric Lippert 的博客中,我想引用:
我们首先运行一个 pass 将循环转换为 goto 和标签。
因此,实际上编译器在发出 IL 时将每个流控制结构转换为 goto/label 模式。当反射器读取程序集的 IL 时,它会识别模式,并将其转换回适当的流控制结构。
在某些情况下,当发出的代码太复杂而反射器无法理解时,它只会向您显示使用标签和 goto 的 C# 代码,相当于它正在读取的 IL。例如,在使用and语句实现IEnumerable<T>
方法时就是这种情况。这些方法被转换为它们自己的类,使用底层状态机实现接口。我相信在 BCL 中你会发现很多这样的案例。yield return
yield break
IEnumerable<T>
如果 GOTO 没有像上面所说的那样过度使用,它会很有用。Microsoft 甚至在 .NET Framework 本身的多个实例中使用它。
这些 goto 通常由编译器生成,尤其是在枚举器内部。编译器总是知道她在做什么。
如果您发现自己需要使用 goto,您应该确保它是唯一的选择。大多数情况下,您会发现有更好的解决方案。
除此之外,很少有使用 goto 的情况是合理的,例如在使用嵌套循环时。同样,在这种情况下还有其他选择。您可以打破函数中的内部循环并改用 return 语句。如果额外的方法调用真的太昂贵,您需要仔细查看。