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

Ben*_*ter 31 .net c# 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
Run Code Online (Sandbox Code Playgroud)

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

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

我希望有一个很好的理由,而我只是一味地失踪了.

感谢大家的投入

Han*_*ant 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;
        }
Run Code Online (Sandbox Code Playgroud)

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

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

  • 不要让你失望,但.NET框架代码包含goto语句.并不多,但在使用它们总是*澄清*代码流. (3认同)

hei*_*erg 12

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

  • 约翰:是的,你愿意.这是一个成员字段(注意`this.前缀),而不是局部变量.成员字段名称保留在已编译的代码中(除非模糊,Microsoft没有对.NET框架库执行此操作). (2认同)

May*_*nza 12

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

如何在Objective-C中打破两个嵌套的for循环?

我没有看到以这种方式使用它有什么问题.

  • 我只能想象随着时间的推移,随着代码被维护,可能被复制和重新调整,以及新程序员没有正确更新`n`,随着时间的推移会引入什么错误. (2认同)

Sim*_*bee 11

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

模拟Switch语句的下降语义.

来自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;
}
Run Code Online (Sandbox Code Playgroud)

在这个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;
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

无限循环和展开递归

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

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

goto的这种使用更值得商榷,但在非常罕见的情况下,仍然可以作为一种选择.


小智 8

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

我用了一次来修复一个特别凌乱的代码中的缺陷.为了重构代码并进行测试,考虑到时间限制,这将是不切实际的.

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


Mat*_*ell 5

您可以使用 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;
    }
Run Code Online (Sandbox Code Playgroud)

这是我在我的机器上得到的结果:

 Recursive Time: 820
 Goto Time: 259
Run Code Online (Sandbox Code Playgroud)

  • 为什么不直接使用 while 循环? (2认同)