Reputation: 934
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
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
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