有没有理由在现代.NET代码中使用goto?

时间:2010-03-30 01:26:00

标签: c# .net goto

我刚刚在.NET基础库的反射器中找到了这段代码......

    if (this._PasswordStrengthRegularExpression != null)
    {
        this._PasswordStrengthRegularExpression = this._PasswordStrengthRegularExpression.Trim();
        if (this._PasswordStrengthRegularExpression.Length == 0)
        {
            goto Label_016C;
        }
        try
        {
            new Regex(this._PasswordStrengthRegularExpression);
            goto Label_016C;
        }
        catch (ArgumentException exception)
        {
            throw new ProviderException(exception.Message, exception);
        }
    }
    this._PasswordStrengthRegularExpression = string.Empty;
Label_016C:
    ... //Other stuff

我听说所有的“你都不会因为害怕被流放到地狱而永远使用goto”spiel。我总是高度重视MS编码员,虽然我可能不同意他们的所有决定,但我始终尊重他们的推理。

那么 - 这样的代码是否有充分的理由让我失踪?这个代码提取是由一个不合格的开发人员组装而成的吗?或.NET反射器返回不准确的代码?

我希望 是一个很好的理由,而我只是一味地错过它。

感谢大家的投入

19 个答案:

答案 0 :(得分:44)

反光板并不完美。可以从参考源获得此方法的实际代码。它位于ndp \ fx \ src \ xsp \ system \ web \ security \ admembershipprovider.cs:

        if( passwordStrengthRegularExpression != null )
        { 
            passwordStrengthRegularExpression = passwordStrengthRegularExpression.Trim();
            if( passwordStrengthRegularExpression.Length != 0 ) 
            { 
                try
                { 
                    Regex regex = new Regex( passwordStrengthRegularExpression );
                }
                catch( ArgumentException e )
                { 
                    throw new ProviderException( e.Message, e );
                } 
            } 
        }
        else 
        {
            passwordStrengthRegularExpression = string.Empty;
        }

注意它是如何检测到最后一个else子句并用goto补偿它的。它几乎肯定会被if()语句中的try / catch块绊倒。

显然,您需要支持实际的源代码而不是反编译版本。这些评论本身非常有用,您可以指望来源准确无误。好吧,大多数都是准确的,一个有缺陷的后处理工具会删除微软程序员的名字造成一些轻微损坏。标识符有时被破折号替换,代码重复两次。您可以下载来源here

答案 1 :(得分:12)

它可能不在源代码中,这就是反汇编代码的外观。

答案 2 :(得分:12)

我见过goto用于打破嵌套循环:

How can I break out of two nested for loops in Objective-C?

我认为使用它没有任何问题。

答案 3 :(得分:11)

.NET中的goto有几种有效用途(特别是C#):

模拟开关语句落入语义

来自C ++背景的那些用于编写switch语句,这些语句在不同情况下自动落空,除非用break明确终止。对于C#,只有琐碎(空)的情况会发生。

例如,在C ++中

int i = 1;
switch (i)
{
case 1:
  printf ("Case 1\r\n");
case 2:
  printf ("Case 2\r\n");
default:
  printf ("Default Case\r\n");
  break;
}

在此C ++代码中,输出为:

Case 1
Case 2
Default Case

以下是类似的C#代码:

int i = 1;
switch (i)
{
case 1:
  Console.Writeline ("Case 1");
case 2:
  Console.Writeline ("Case 2");
default:
  Console.Writeline ("Default Case");
  break;
}

如上所述,这将无法编译。有几个编译错误如下所示:

Control cannot fall through from one case label ('case 1:') to another

添加goto语句使其有效:

int i = 1;
switch (i)
{
case 1:
    Console.WriteLine ("Case 1");
    goto case 2;
case 2:
    Console.WriteLine("Case 2");
    goto default;
default:
    Console.WriteLine("Default Case");
    break;
}

...在C#中使用的另一个有用的goto是......

无限循环和展开的递归

我不会在这里详细介绍,因为它不太有用,但偶尔我们使用while(true)构造来编写无限循环,这些构造用break显式终止或用{{1重新执行声明。当我们尝试模拟递归方法调用但无法控制递归的潜在范围时,可能会发生这种情况。

您显然可以将其重构为continue循环或将其重构为单独的方法,但也可以使用标签和goto语句。

这种goto的使用更具争议性,但在非常罕见的情况下,仍然值得记住这一点。

答案 4 :(得分:8)

我并没有为gotos而疯狂,但是说它们永远无效是愚蠢的。

我曾用过一次来修复特别凌乱的代码中的缺陷。考虑到时间限制,重构代码并测试它是不切实际的。

此外,难道我们都没有看到编码很差的条件结构使得它们看起来是良性的吗?

答案 5 :(得分:4)

你不应该看反射器代码。

虽然如果你看过反汇编的IL,你会看到满满的地方。本质上,我们使用的所有循环和其他控件构造都转换为gotos,只需将它们转换为我们的代码中的构造,它就变得更易读,更易于维护。

顺便提一下,我认为你发布的代码不是一个使用goto的好地方,而且我很难想到一个。

答案 6 :(得分:4)

您可以使用GOTO以更好的性能执行递归。维护起来要困难得多,但如果你需要那些额外的周期,你可能愿意支付维护费用。

这是一个简单的例子,结果如下:

class Program
{
    // Calculate (20!) 1 million times using both methods.
    static void Main(string[] args)
    {
        Stopwatch sw = Stopwatch.StartNew();
        Int64 result = 0;
        for (int i = 0; i < 1000000; i++)
            result += FactR(20);
        Console.WriteLine("Recursive Time: " + sw.ElapsedMilliseconds);

        sw = Stopwatch.StartNew();
        result = 0;
        for (int i = 0; i < 1000000; i++)
            result += FactG(20);
        Console.WriteLine("Goto Time: " + sw.ElapsedMilliseconds);
        Console.ReadLine();
    }

    // Recursive Factorial
    static Int64 FactR(Int64 i)
    {
        if (i <= 1)
            return 1;
        return i * FactR(i - 1);
    }

    // Recursive Factorial (using GOTO)
    static Int64 FactG(Int64 i)
    {
        Int64 result = 1;

    Loop:
        if (i <= 1)
            return result;

        result *= i;
        i--;
        goto Loop;
    }

以下是我在我的机器上获得的结果:

 Recursive Time: 820
 Goto Time: 259

答案 7 :(得分:3)

在编写解析器和词法分析器时,Goto经常很有用。

答案 8 :(得分:3)

在很多很多.NET代码编写和审核过程中,我都没有看到Goto的有效案例。

在不支持带有finally块的结构化异常处理的语言中(PASCAL - 结构化编程语言的祖父,以及经典的C),GOTO的战术使用可以使代码更容易理解用于在嵌套循环内执行终止时执行清理(与正确设置多个循环终止条件相反)。即使在白天,我也没有亲自使用goto(可能因为害怕“永远流放到地狱”)。

答案 9 :(得分:2)

看一下状态图。如果您认为使用的最佳代码结构是最直接且明确地表达您的意图的代码结构,那么每个状态转换都应编码为goto。

但是,这往往会在现实世界中崩溃。第一个问题是我们经常需要暂停机器,退出到其他代码,然后恢复机器 - 这意味着每个转换往往是状态变量的变化,用于识别交换机中的正确状态/案例陈述。这实际上只是一种隐藏和延迟goto的方法 - 写入状态变量与写入程序计数器寄存器并没有多大区别。这只是一种实现“转到那里 - 但不是现在,以后”的方法。

但是,有些情况下goto可以很好地表达某种状态模型中发生的情况 - 我猜这个例子可能是医生有时会使用的诊断流程图之一。如果你将其中一个作为程序实现而不使用gotos进行转换,那么实际上你只是通过加密代码的意图来让自己变得困难。

到目前为止,最常见的案例不太可能是手写代码。我编写了代码生成器,为各种状态模型(决策处理,常规语法分析等)中的转换生成goto语句,但我不记得上次在手写代码中使用goto时。

答案 10 :(得分:2)

除了所有这些非常有效的东西之外,当您查看反汇编代码时请记住,开发人员可能已经在这些程序集上使用了混淆器。一种混淆技术是将随机goto添加到IL

答案 11 :(得分:2)

不,没有充分理由使用goto。我最后在1981年编写了一个goto语句,我从那以后就没有错过那个特定的结构。

答案 12 :(得分:1)

关于这一点:

  

所以 - 代码有充分的理由吗?   这样我就错过了?是这个吗   代码提取只是由一个   糟透的开发者?或者是.NET反射器   返回不准确的代码?

我不同意这些只有三种可能性的前提。

正如许多其他人所暗示的那样,这可能不是对库中真实源代码的准确反映。无论如何,我们都感到内疚(好吧,,无论如何)为了以下目的编写代码“肮脏的方式”:

  1. 快速实施功能
  2. 快速修复错误
  3. 挤出略微的性能提升(有时候是合理的,有时不是那么多)
  4. 当时有道理的其他原因
  5. 这不会让某人成为“糟糕的开发者”。大多数指南,例如“你不应该使用goto”,主要是为了保护开发者自己;它们不应被视为区分好坏开发者的关键。

    作为类比,考虑我们许多人在小学英语中教授的简单规则:永远不要以介词结束句子。这不是真正的规则;它是一个指导方针,可以帮助阻止人们说出“汽车在哪里?”理解这个事实很重要;一旦你开始把它当成一个实际的规则,而不是一个准则,你会发现自己“纠正”人们的句子,如“你害怕什么?”

    考虑到这一点,我要警惕任何开发人员打电话给另一位开发人员“糟糕”,因为他使用了goto

    我当然不会试图捍卫goto - 只是认为它的使用并不表示无能为力。

答案 13 :(得分:1)

正如其他人所示,您在反射器中看到的代码必然是框架中编写的代码。编译器和优化器可以将代码更改为以类似方式运行的代码,只要它不会更改代码所执行的实际工作即可。还应该声明编译器将所有分支和循环实现为goto(IL中的分支,或汇编中的跳转)。当运行发布模式并且编译器尝试将代码优化为最简单的形式时,功能上与您的相同源。

我有一个关于不同循环技术的示例,当您编译发布时,这些技术都被编译为100%相同的IL。 See Other Answer

(我现在找不到它,但Eric Lippert发布​​了关于C#编译器如何处理代码的说明。他提出的一点是如何将所有循环更改为goto的。)

话虽如此,我对goto没有任何问题。如果有更好的循环结构,请使用它。但是有时候你需要一些东西,然后你可以挤出来,foreach,while,do / while但你不希望来自方法调用的额外混乱和痛苦(为什么浪费5加上线来转换嵌套for递归方法。)

答案 14 :(得分:0)

goto完全适用于像C这样的语言中的清理内容,它在某种程度上模拟了异常的概念。我确信.NET有更好的方法来处理这样的东西,所以goto只是过时且容易出错。

答案 15 :(得分:0)

我不喜欢那段代码。

我更愿意将Regex存储在成员中,并在设置时对其进行验证,从而避免在阅读时对逻辑的所有需要​​。

答案 16 :(得分:0)

有一个有效的案例 - 当您尝试模拟递归过程调用并以非递归代码返回或执行类似操作时(此类要求也出现在Prolog解释器中)。但总的来说,除非你正在做一些像国际象棋程序或语言翻译这样需要微优化的事情,否则最好只使用常规程序堆栈并使用函数/过程调用。

答案 17 :(得分:0)

这可能不是最好的例子,但确实显示goto可以非常方便的情况。

private IDynamic ToExponential(Engine engine, Args args)
{
    var x = engine.Context.ThisBinding.ToNumberPrimitive().Value;

    if (double.IsNaN(x))
    {
        return new StringPrimitive("NaN");
    }

    var s = "";

    if (x < 0)
    {
        s = "-";
        x = -x;
    }

    if (double.IsPositiveInfinity(x))
    {
        return new StringPrimitive(s + "Infinity");
    }

    var f = args[0].ToNumberPrimitive().Value;
    if (f < 0D || f > 20D)
    {
        throw new Exception("RangeError");
    }

    var m = "";
    var c = "";
    var d = "";
    var e = 0D;
    var n = 0D;

    if (x == 0D)
    {
        f = 0D;
        m = m.PadLeft((int)(f + 1D), '0');
        e = 0;
    }
    else
    {
        if (!args[0].IsUndefined) // fractionDigits is supplied
        {
            var lower = (int)Math.Pow(10, f);
            var upper = (int)Math.Pow(10, f + 1D);
            var min = 0 - 0.0001;
            var max = 0 + 0.0001; 

            for (int i = lower; i < upper; i++)
            {
                for (int j = (int)f;; --j)
                {
                    var result = i * Math.Pow(10, j - f) - x;
                    if (result > min && result < max)
                    {
                        n = i;
                        e = j;
                        goto Complete;
                    }
                    if (result <= 0)
                    {
                        break;
                    }
                }

                for (int j = (int)f + 1; ; j++)
                {
                    var result = i * Math.Pow(10, j - f) - x;
                    if (result > min && result < max)
                    {
                        n = i;
                        e = j;
                        goto Complete;
                    }
                    if (result >= 0)
                    {
                        break;
                    }
                }
            }
        }
        else
        {
            var min = x - 0.0001;
            var max = x + 0.0001; 

            // Scan for f where f >= 0
            for (int i = 0;; i++)
            {
                // 10 ^ f <= n < 10 ^ (f + 1)
                var lower = (int)Math.Pow(10, i);
                var upper = (int)Math.Pow(10, i + 1D);
                for (int j = lower; j < upper; j++)
                {
                    // n is not divisible by 10
                    if (j % 10 == 0)
                    {
                        continue;
                    }

                    // n must have f + 1 digits
                    var digits = 0;
                    var state = j;
                    while (state > 0)
                    {
                        state /= 10;
                        digits++;
                    }
                    if (digits != i + 1)
                    {
                        continue;
                    }

                    // Scan for e in both directions
                    for (int k = (int)i; ; --k)
                    {
                        var result = j * Math.Pow(10, k - i);
                        if (result > min && result < max)
                        {
                            f = i;
                            n = j;
                            e = k;
                            goto Complete;
                        }
                        if (result <= i)
                        {
                            break;
                        }
                    }
                    for (int k = (int)i + 1; ; k++)
                    {
                        var result = i * Math.Pow(10, k - i);
                        if (result > min && result < max)
                        {
                            f = i;
                            n = j;
                            e = k;
                            goto Complete;
                        }
                        if (result >= i)
                        {
                            break;
                        }
                    }
                }
            }
        }

    Complete:

        m = n.ToString("G");
    }

    if (f != 0D)
    {
        m = m[0] + "." + m.Substring(1);
    }

    if (e == 0D)
    {
        c = "+";
        d = "0";
    }
    else
    {
        if (e > 0D)
        {
            c = "+";
        }
        else
        {
            c = "-";
            e = -e;
        }
        d = e.ToString("G");
    }

    m = m + "e" + c + d;
    return new StringPrimitive(s + m);
}

答案 18 :(得分:-1)

当我写FORTRAN时,我甚至从未用GO TO编码。

我从来没有使用它。我不明白为什么任何现代语言会要求用户提供这样的东西。我会毫不含糊地说“不”。