C# 是否具有运行时分配的 goto?

Does C# have runtime-assigned goto?

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

top:

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

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

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

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

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

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

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

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

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

因此,例如 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 语句。