转到声明被认为有害吗?

时间:2010-03-21 09:58:13

标签: c# .net goto base-class-library

如果上面的陈述是正确的,那么为什么当我在.Net BCL上使用反射器时,我看到它被大量使用了?

编辑:让我重新说一下:我在人类或编译器编写的反射器中看到的所有GO-TO是什么?

15 个答案:

答案 0 :(得分:24)

我认为Wikipedia Article on Goto的以下摘录在这里特别相关:

  

可能是最着名的批评   GOTO是Edsger在1968年写的一封信   Dijkstra称之为Go To Statement   认为有害。在那封信中   Dijkstra认为不受限制的GOTO   声明应该废除   更高级的语言,因为他们   复杂的分析和任务的任务   验证程序的正确性   (特别是涉及循环的那些)。   提出了另一种观点   在唐纳德克努特的结构化   编程与转到语句   它分析了许多常见的编程   任务并在其中一些中发现   GOTO是最佳语言结构   使用。

所以,一方面,我们有Edsger Dijkstra(一位非常有才华的计算机科学家)争论反对使用GOTO声明,并专门反对过度使用GOTO语句,理由是它是一种结构化程度较低的编写方式。

另一方面,我们有Donald Knuth(另一位非常有才华的计算机科学家)认为使用GOTO,特别是明智地使用它实际上可以成为给定作品的“最佳”和最佳构造程序代码。

最终,恕我直言,我相信这两个人都是正确的。 Dijkstra是正确的,GOTO语句的过度使用肯定会使一段代码的可读性和结构性降低,从纯理论角度查看计算机编程时,这当然是正确的。

然而,Knuth也是正确的,因为在“现实世界”中,必须采用实用方法,明智地使用GOTO语句确实是最佳选择语言结构使用。

答案 1 :(得分:19)

上面的内容并不正确 - 当时getos是关于唯一使用的流量控制结构时,它是a polemical device used by Dijkstra。事实上,有几个人已经制作了反驳,包括Knuth经典的“使用Goto进行结构化编程”论文(记忆中的标题)。还有一些情况(错误处理,状态机),其中gotos可以生成更清晰的代码(恕我直言),而不是“结构化”替代品。

答案 2 :(得分:11)

这些goto通常由编译器生成,尤其是在枚举器中。 编译器总是知道她在做什么。

如果您发现自己需要使用goto,则应确保它是唯一的选择。大多数情况下,你会发现有更好的解决方案。

除此之外,很少有实例可以证明使用goto,例如使用嵌套循环时。同样,在这种情况下还有其他选择。您可以在函数中分解内部循环并使用return语句。您需要仔细查看附加方法调用是否真的太昂贵。


回复您的修改:

不,并非所有的getos都是编译器生成的,但很多都是由编译器生成的状态机(枚举器),switch case语句或优化的if else结构产生的。只有少数几个实例可以判断它是编译器还是原始开发人员。您可以通过查看函数/类名来获得良好的提示,编译器将生成“禁用”名称以避免与您的代码发生名称冲突。如果一切看起来正常并且代码未经过优化或混淆,则可能会使用goto。

答案 3 :(得分:8)

请记住,您在Reflector中看到的代码是反汇编 - Reflector正在查看已编译的字节代码并尝试将原始源代码拼凑在一起。

有了这个,您必须记住针对goto的规则适用于高级代码。用于替换gotoforwhilebreakswitch等)的所有构造都使用JMP编译成代码。

因此,Reflector看起来像这样的代码:

A:
    if !(a > b)
        goto B;
    DoStuff();
    goto A;
B:  ...

并且必须意识到它实际编码为:

 while (a > b)
    DoStuff();

有时读取的代码太复杂,无法识别模式。

答案 4 :(得分:4)

Go To声明本身无害,有时甚至非常有用。有害的用户倾向于将其置于代码中的不适当位置。

答案 5 :(得分:4)

当编译成汇编代码时,所有控件都被构造并转换为(非)条件跳转。但是,优化器可能过于强大,并且当反汇编程序无法识别跳转模式对应的控制结构时,将发出始终正确的语句,即goto label;

这与goto的伤害(无所畏惧)无关。

答案 6 :(得分:3)

对于一个双循环或许多嵌套循环,你已经突破了,例如

 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
    }

答案 7 :(得分:2)

一般规则是您不需要使用goto。与任何规则一样,当然有例外,但与任何例外情况一样,它们很少。

goto命令就像一种毒品。如果仅在特殊情况下使用数量有限,那就很好。如果你一直使用太多,它将毁了你的生活。

当您使用Reflector使用代码时,您没有看到实际的代码。您将看到从编译器从原始代码生成的内容重新创建的代码。当您在重新创建的代码中看到goto时,不确定原始代码中是否有goto。可能有一个更结构化的命令来控制流,比如breakcontinue,它已由编译器以与goto相同的方式实现,因此Reflector可以'告诉区别。

答案 8 :(得分:1)

  

goto被认为是有害的(供人使用但是   对于电脑来说还可以。)

因为无论我们(人)使用goto多么疯狂,编译器总是知道如何阅读代码。

相信我......

使用其中的goto来读取其他代码是很难的。
在其中使用goto读取您自己的代码是很难的。

这就是为什么你看到它用于低级别(机器语言)而不是高级别(人类语言,例如C#,Python ......);)

答案 9 :(得分:1)

当我想执行终止操作时,我有时会使用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,而是将其发送到最后一行,执行另一项最终操作,我可以从代码段中的各个位置引用,而不仅仅是终止。

答案 10 :(得分:1)

“C提供了无限可重用的goto语句,并且标签要分支到。正式来说,goto是永远不需要,实际上,没有它就可以很容易地编写代码。我们有本书中未使用goto。“

- K&amp; R (第2版):第65页

答案 11 :(得分:1)

在反编译代码中,您看到的几乎所有goto都是合成代码。别担心;它们是代码如何在低级别表示的工件。

关于将它们放入您自己的代码中的正当理由?我能想到的主要问题是你所使用的语言不能提供适合你正在处理的问题的控制结构;使自定义控制流系统变得容易的语言通常根本没有goto。它总是可以避免使用它们,但是将任意复杂的代码重新排列成while循环和许多带有一整套控制变量的条件......这实际上可以使代码甚至更多模糊(而且速度也慢一些;编译器通常不够聪明,无法挑选出这种复杂性)。编程的主要目标应该是生成对计算机和阅读计算机的人都清楚的程序的描述。

答案 12 :(得分:0)

如果它有害或无益,那就是每个人都喜欢和不喜欢的问题。我个人不喜欢它们,并且发现它们非常无效,因为它们试图保持代码的可维护性。

现在,有一件事是如何影响我们对代码的读取,而另一件事是抖动在找到时的表现。从Eric Lippert's Blog开始,我想引用:

  

我们首先运行一个pass来将循环转换为gotos和labels。

因此,实际上编译器将每个流控制结构转换为goto / label模式,同时发出IL。当反射器读取组件的IL时,它识别模式,并将其转换回适当的流控制结构。

在某些情况下,当发出的代码太复杂而无法理解反射器时,它只会显示使用标签和gotos的C#代码,相当于它读取的IL。例如,在使用IEnumerable<T>yield return语句实施yield break方法时就是这种情况。这些方法被转换为使用底层状态机实现IEnumerable<T>接口的自己的类。我相信BCL你会发现很多这种情况。

答案 13 :(得分:0)

如果GOTO没有如上所述过度使用,那么它可能很有用。 Microsoft甚至在.NET Framework本身的几个实例中使用它。

答案 14 :(得分:0)

这些goto经常由编译器生成,尤其是在枚举器中。编译器总是知道她在做什么。

如果您发现自己需要使用goto,则应确保它是唯一的选择。大多数情况下,你会发现有更好的解决方案。

除此之外,很少有实例可以证明使用goto,例如使用嵌套循环时。同样,在这种情况下还有其他选择。您可以在函数中分解内部循环并使用return语句。您需要仔细查看附加方法调用是否真的太昂贵。