deeBo
deeBo

Reputation: 934

Does C# have runtime-assigned goto?

I am creating a C# program in which I would like to use goto, but not to a compile-time constant, ie. to use a string as a label identifier.

top:

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

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

I am aware that goto is not a good programming practise, and this isn't an opinion question on whether one should use it in an application – it's for a program which generates C# code from BASIC, which has computed goto.

Upvotes: 3

Views: 1451

Answers (2)

dan04
dan04

Reputation: 90995

Please note that C# example code below is intended specifically for use as the output format of a BASIC-to-C# compiler. Anyone who writes “real” C# code like this shall have their programming license revoked.

BASIC's GOTO statement can be implemented using “tail call pessimization”: Transform every GOTO X to GOSUB X:RETURN.

You can then render your entire BASIC program as one giant Gosub(int lineNumber) function, using a switch statement with a case block for each line number, with a Main function that simply GOSUBs to the lowest line number.

So, for example, the BASIC program:

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."

can be converted line-by-line into a C# program like this:

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);
    }
}

When you have a literal line number like this, you can optimize Gosub(X); return; to goto case X;. However, the function call approach allows the line number to be an arbitrary expression, which C#'s goto case does not.

The converted code obviously isn't very maintainable, but it does compile and run.

Edit: Apparently, the C# compiler doesn't guarantee tail call optimization, which may cause a stack overflow for long or infinite loops. However, you can perform this optimization manually by re-assigning the lineNumber parameter and jumping back to the start of the switch statement, like this:

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();
    }
}

As in the previous example, a constant GOTO target can be optimized into a single goto case statement.

Upvotes: 3

Jon Skeet
Jon Skeet

Reputation: 1500495

No, there's nothing like this. If you really, really need this, I'd probably generate a switch statement:

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

If you can break the code into actions with a Dictionary<string, Action>, that would be cleaner code to look at afterwards... but if you need locals to be in scope etc, then this "nasty" code may be a simpler way to mimic the BASIC behaviour.

Upvotes: 10

Related Questions