【问题标题】:Does C# have runtime-assigned goto?C# 是否有运行时分配的 goto?
【发布时间】:2019-08-01 09:20:53
【问题描述】:

我正在创建一个 C# 程序,我想在其中使用 goto,但不是编译时常量,即。使用字符串作为标签标识符。

top:

string label = "top";
// Doesn't work
goto label;

// Doesn't work either
goto "top"; 

我知道goto 不是一个好的编程实践,这不是关于是否应该在应用程序中使用它的意见问题——它适用于从 BASIC 生成 C# 代码的程序,它已经计算了 goto .

【问题讨论】:

  • 它没有
  • 我认为您可以通过使用函数字典来实现相同的行为,从而允许您按字符串值调度某些代码。当然,处理变量和其他东西会遇到一些重大挑战。
  • 这真是个好主意,谢谢!您可以保留行号的 Dictionary 并跳转到它们,本质上是给出计算的 goto。如果您想发布答案,我会接受。
  • BASIC 是否只支持 goto 行号?如果是这种情况,也许有更简单的方法,例如 Jon Skeet 发布的答案。
  • 我正在寻找您所说的 Atari BASIC 的 goto。在我看来是 Sinclair BASIC,但我没有确切的规范可以链接。

标签: c# .net goto


【解决方案1】:

不,没有这样的事。如果你真的真的需要这个,我可能会生成一个 switch 语句:

switch (label):
{
    case "top":
        goto top;
    case "bottom":
        goto bottom;
    // ...
}

如果您可以使用Dictionary<string, Action> 将代码分解为操作,那将是以后查看的更清晰的代码...但是如果您需要本地人在范围内等,那么这个“ nasty" 代码可能是模仿 BASIC 行为的更简单方法。

【讨论】:

    【解决方案2】:

    请注意,下面的 C# 示例代码专门用作 BASIC-to-C# 编译器的输出格式。任何像这样编写“真正的”C# 代码的人都将被吊销其编程许可证。

    BASIC 的 GOTO 语句可以使用“尾调用悲观化”实现:将每个 GOTO X 转换为 GOSUB X:RETURN

    然后,您可以将整个 BASIC 程序渲染为一个巨大的 Gosub(int lineNumber) 函数,使用带有 case 块的每个行号的 switch 语句,以及简单地 GOSUB 到最低行号的 Main 函数。

    例如,BASIC 程序:

    10 PRINT "Enter an integer: ";
    20 INPUT N%
    30 IF N% MOD 2 = 0 THEN GOTO 60
    40 PRINT N%;" is odd."
    50 GOTO 70
    60 PRINT N%;" is even."
    70 PRINT "Goodbye."
    

    可以像这样逐行转换成C#程序:

    using System;
    
    static class Program
    {
        // BASIC variables are always global and zero-initialized.
        static int n_int = 0;
    
        static void Gosub(int lineNumber)
        {
            switch (lineNumber)
            {
                case 10: // 10 PRINT "Enter an integer: ";N%
                    Console.Write("Enter an integer: ");
                    // By default, the end of each line falls thru to next line number
                    goto case 20;
                case 20: // 20 INPUT N%
                    n_int = int.Parse(Console.ReadLine());
                    goto case 30;
                case 30: // 30 IF N% MOD 2 = 0 THEN GOTO 60
                    if (n_int % 2 == 0)
                    {
                        Gosub(60);
                        return;
                    }
                    goto case 40;
                case 40: // 40 PRINT N%;" is odd."
                    Console.WriteLine("{0} is odd.", n_int);
                    goto case 50;
                case 50: // 50 GOTO 70
                    Gosub(70);
                    return;
                case 60: // 60 PRINT N%;" is even."
                    Console.WriteLine("{0} is even.", n_int);
                    goto case 70;
                case 70: // 70 PRINT "Goodbye."
                    Console.WriteLine("Goodbye.");
                    // Falling off the end of the program exits it.
                    return;
            }
        }
    
        static void Main()
        {
            Gosub(10);
        }
    }
    

    当您有这样的文字行号时,您可以将Gosub(X); return; 优化为goto case X;。但是,函数调用方式允许行号为任意表达式,而 C# 的goto case 则不允许。

    转换后的代码显然不是很容易维护,但它确实可以编译和运行。

    编辑:显然,C# 编译器不保证尾调用优化,这可能会导致长循环或无限循环的堆栈溢出。但是,您可以通过重新分配lineNumber 参数并跳回到switch 语句的开头来手动执行此优化,如下所示:

    using System;
    
    static class Program
    {
        // BASIC variables are always global and zero-initialized.
        static int n_int = 0;
    
        static void Gosub(int lineNumber)
        {
            START:
            switch (lineNumber)
            {
                case 10: // 10 PRINT "Enter an integer: ";N%
                    Console.Write("Enter an integer: ");
                    // By default, the end of each line falls thru to next line number
                    goto case 20;
                case 20: // 20 INPUT N%
                    n_int = int.Parse(Console.ReadLine());
                    goto case 30;
                case 30: // 30 IF N% MOD 2 = 0 THEN GOTO 60
                    if (n_int % 2 == 0)
                    {
                        lineNumber = 60;
                        goto START;
                    }
                    goto case 40;
                case 40: // 40 PRINT N%;" is odd."
                    Console.WriteLine("{0} is odd.", n_int);
                    goto case 50;
                case 50: // 50 GOTO 70
                    lineNumber = 70;
                    goto START;
                case 60: // 60 PRINT N%;" is even."
                    Console.WriteLine("{0} is even.", n_int);
                    goto case 70;
                case 70: // 70 PRINT "Goodbye."
                    Console.WriteLine("Goodbye.");
                    // Falling off the end of the program exits it.
                    return;
            }
        }
    
        static void Main()
        {
            Gosub(10);
            Console.ReadKey();
        }
    }
    

    与前面的示例一样,常量 GOTO 目标可以优化为单个 goto case 语句。

    【讨论】:

    • 天才!我对这些的唯一想法是块内的 goto,例如,在 for/if 内。我想如果你不允许跳出或跳入一个块,你可以在 for 循环的 switch case 中有一个嵌套函数?
    • 好吧,FOR...NEXT 循环可以很容易地转换为WHILE...WEND 循环,然后可以转换为GOTO 加上@987654341 @...GOTO。可能仍然会有一些奇怪的边缘情况跳入或跳出循环。
    • 好的,这导致了另一个问题。一个简单的程序,例如 10 Print "Hello" 11 goto 10。(编译为一系列 Gosub(10))由于大量递归而导致 StackOverflowException。我不确定这是否可以修复?有时 BASIC 程序需要有无限循环,例如。用于游戏。
    • 好吧,正如我在回答中已经提到的,如果 BASIC GOTO 目标是恒定的,您始终可以将其优化为简单的 goto case 语句。只有计算 GOTO(和GOSUB)需要Gosub 调用。而且由于行号 BASIC 没有局部变量,堆栈帧会相对便宜。
    • 谢谢。我还意识到,如果您使用 /optimize 运行 csc,则堆栈不会溢出。可能是因为您在编辑开始时提到的内容。不过,我想我还是会尝试自己优化不断和无限的 goto 调用,因为它们太常见了。
    猜你喜欢
    • 1970-01-01
    • 2019-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-20
    • 1970-01-01
    相关资源
    最近更新 更多