开发者

Is there ever a reason to use goto in modern .NET code?

I just found this code in reflector in the .NET base libraries...

    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

I've heard all of the "thou shalt not use goto on fear of exile to hell for eternity" spiel开发者_运维知识库. I always held MS coders in fairly high regard and while I may not have agreed with all of their decisions, I always respected their reasoning.

So - is there a good reason for code like this that I'm missing? Was this code extract just put together by an inept developer? or is .NET reflector returning inaccurate code?

I'm hoping there is a good reason, and I'm just blindly missing it.

Thanks for everyone's input


Reflector is not perfect. The actual code of this method is available from the Reference Source. It is located in 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;
        }

Note how it failed to detect the last else clause and compensated for it with a goto. It is almost certainly tripped-up by the try/catch blocks inside the if() statements.

Clearly you'll want to favor the actual source code instead of the decompiled version. The comments in themselves are quite helpful and you can count on the source being accurate. Well, mostly accurate, there's some minor damage from a buggy post-processing tool that removed the names of the Microsoft programmers. Identifiers are sometimes replaced by dashes and the code is repeated twice. You can download the source here.


Its probably not in the source code, that's just how the disassembled code looks.


I have seen goto used to break out of nested loops:

How can I break out of two nested for loops in Objective-C?

I don't see anything wrong with using it that way.


There are several valid uses for goto in .NET (C# specifically):

Simulating Switch Statement Fall-through Semantics.

Those coming from a C++ background are used to writing switch statements which automatically fall-through from case to case unless explicitly terminated with break. For C#, only trivial (empty) cases fall-through.

For example, in 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;
}

In this C++ code the output is:

Case 1
Case 2
Default Case

Here is similar C# code:

int i = 1;
switch (i)
{
case 1:
  Console.Writeline ("Case 1");
case 2:
  Console.Writeline ("Case 2");
default:
  Console.Writeline ("Default Case");
  break;
}

As written, this will not compile. There are several compilation errors that look like this:

Control cannot fall through from one case label ('case 1:') to another

Adding goto statements make it work:

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

... the other useful goto use in C# is...

Infinite Loops and Unrolled Recursion

I won't got into detail here since it is less useful, but occasionally we write infinite loops using while(true) constructs that are explicitly terminated with a break or re-executed with a continue statement. This might happen when we are trying to simulate recursive method calls but don't have any control over the potential scope of the recursion.

You can obviously refactor that into a while(true) loop or refactor it into a separate method, but also using a label and a goto statement works.

This use of goto is more debatable, but still something worth keeping in your mind as an option in very rare circumstances.


I'm not crazy about gotos, but to say that they're never valid is silly.

I used one once to fix a defect in a particularly messy piece of code. To refactor the code and test it would not have been practical given the time constraint.

Besides, haven't we all seen conditional constructs that were so poorly coded that they make gotos seem benign?


You can use a GOTO to perform recursion with better performance. It's a lot harder to maintain, but if you need those extra cycles you may be willing to pay the maintenance burden.

Here's a simple example, with results:

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

Here are the results I get on my machine:

 Recursive Time: 820
 Goto Time: 259


Thou shalt not look at reflector code.

Although if you ever look at disassembled IL, you'll see gotos all over the place. In essence, all of the loops and other control constructs we use are converted to gotos anyway, it's just that by turning them into constructs in our code, it becomes more readable and easier to maintain.

I don't think the code you posted would be a good place to use goto, by the way, and I struggle to think of one.


I have not seen a valid case for Goto in many, many lines of .NET code both written and reviewed.

In languages that do not support structured exception handling with a finally block (PASCAL - a grandfather of structured programming languages, as well as classic C come to mind), tactical use of a GOTO could lead to much easier to understand code when used to perform cleanup when execution terminated inside nested loops (as opposed to correctly setting multiple loop termination conditions). Even back in the day, I did not use goto personally for that reason (probably for fear of "exile to hell eternally").


No, there's no good reason to use goto. I last coded a goto statement in 1981, and I haven't missed that particular construct since.


Goto's are frequently useful when writing parsers and lexers.


Take a look at a state diagram. If you believe that the best code structure to use is the one that most directly and clearly expresses your intent, then every one of those state transitions should be coded as a goto.

This tends to break down in the real world, though. The first issue is that we often need to halt the machine, exit out to other code, and resume the machine later - meaning that every one of those transitions tends to be a change to state variable, used to identify the correct state in a switch/case statement. This is really just a way to hide and delay the goto - writing to a state variable isn't much different to writing to the program-counter register, really. It's just a way to implement "goto there - but not now, later".

There are cases, though, where a goto works well to express what is happening in some kind of state model - I'd guess that an example would be one of those diagnostic flowcharts that doctors sometimes use. If you implement one of those as a program without using gotos for transitions, then really you're just making life difficult for yourself by encrypting the intent of your code.

It's just that by far the most common cases aren't likely to be hand-written code. I've written code generators that generate goto statements for transitions in various kinds of state model (decision handling, regular grammar parsing, etc) but I don't remember the last time I used a goto in hand-written code.


In addition to all of these nice valid things, when you are looking at disassembled code keep in mind that the developers COULD have used an obfuscator on those assemblies. One technique of obfuscation is adding random goto's to the IL


With respect to this point:

So - is there a good reason for code like this that I'm missing? Was this code extract just put together by a shitty developer? or is .NET reflector returning inaccurate code?

I disagree with the premise that these are the only three possibilities.

Maybe it's true, as many others have suggested, that this simply isn't an accurate reflection of the real source code in the library. Regardless, we've all been guilty (well, I have, anyway) of writing code "the dirty way" for the purpose of:

  1. Getting a feature implemented quickly
  2. Fixing a bug quickly
  3. Squeezing out a slight performance gain (sometimes with justification, sometimes not so much)
  4. Some other reason that made sense at the time

That doesn't make someone a "shitty developer." Most guidelines such as "thou shalt not use goto" are mainly put in place to protect developers from themselves; they shouldn't be treated as a key for distinguishing between good and bad developers.

As an analogy, consider the simple rule many of us are taught in grade school English: never end a sentence with a preposition. This isn't a real rule; it's a guideline to help prevent people from saying things like, "Where's the car at?" It's important to understand this fact; once you start treating it like an actual rule, instead of a guideline, you'll find yourself "correcting" people for perfectly good sentences like "What are you afraid of?"

With this in mind, I'd be wary of any developer who called another developer "shitty" because he used goto.

I'm certainly not trying to defend goto, per se--just arguing that its use doesn't indicate incompetence, by any means.


As other's have shown the code you see in reflector is necessarily the code that is written in the Framework. The compiler and optimizers can change code around to something that functions in a similar manner as long as it does not change the actual work done by the code. It should also be stated that the compiler implements all branches and loops as goto's (branches in IL, or jumps in assembly.) When the release mode is ran and the compiler tries to optimizes code to the simplest form that is functionally the same as your source.

I have an example on different looping techniques that are all compiled to 100% the same IL when you compile for release. See Other Answer

(I can't find it right now but Eric Lippert posted an note on how the C# compiler processes code. One of the points he made is how all loops are changed to goto's.)

That being said, I have no problem with goto. If there is a better looping structure, use it. But sometimes you need something slightly then what you can squeeze out of for, foreach, while, do/while but you don't wanted the added mess and pain that comes from method calls (why waste 5 plus lines to convert a nested for into recursive methods.)


There is one valid case - when you are trying to simulate a recursive procedure call and return in a non-recursive code, or do something similar (this kind of requirement also occurs in a Prolog interpreter). But in general, unless you are doing something requiring micro-optimization like a chess program or language interpreter, much better to just use the regular procedure stack and use function/procedure calls.


goto is perfectly valid for cleanup stuff in languages like C at least, where it somewhat simulates the notion of exceptions. I'm sure that .NET has better ways of handling stuff like this, so goto is just obsolete and error prone.


I don't like that code.

I would prefer to store the Regex in the member, and validate it when setting it, avoiding all of the need for logic when reading it.


This may not be the best example but it does show a case where goto can be very handy.

private IDynamic ToExponential(Engine engine, Args args)
{
    var x = engine.Context.ThisBinding.ToNumberPrimitive().Value;

    if (double.IsNaN(x))
    {
        return new StringPrimitive("NaN");
    }

    var s = "";

    if (x < 0)
    {
        s = "-";
        x = -x;
    }

    if (double.IsPositiveInfinity(x))
    {
        return new StringPrimitive(s + "Infinity");
    }

    var f = args[0].ToNumberPrimitive().Value;
    if (f < 0D || f > 20D)
    {
        throw new Exception("RangeError");
    }

    var m = "";
    var c = "";
    var d = "";
    var e = 0D;
    var n = 0D;

    if (x == 0D)
    {
        f = 0D;
        m = m.PadLeft((int)(f + 1D), '0');
        e = 0;
    }
    else
    {
        if (!args[0].IsUndefined) // fractionDigits is supplied
        {
            var lower = (int)Math.Pow(10, f);
            var upper = (int)Math.Pow(10, f + 1D);
            var min = 0 - 0.0001;
            var max = 0 + 0.0001; 

            for (int i = lower; i < upper; i++)
            {
                for (int j = (int)f;; --j)
                {
                    var result = i * Math.Pow(10, j - f) - x;
                    if (result > min && result < max)
                    {
                        n = i;
                        e = j;
                        goto Complete;
                    }
                    if (result <= 0)
                    {
                        break;
                    }
                }

                for (int j = (int)f + 1; ; j++)
                {
                    var result = i * Math.Pow(10, j - f) - x;
                    if (result > min && result < max)
                    {
                        n = i;
                        e = j;
                        goto Complete;
                    }
                    if (result >= 0)
                    {
                        break;
                    }
                }
            }
        }
        else
        {
            var min = x - 0.0001;
            var max = x + 0.0001; 

            // Scan for f where f >= 0
            for (int i = 0;; i++)
            {
                // 10 ^ f <= n < 10 ^ (f + 1)
                var lower = (int)Math.Pow(10, i);
                var upper = (int)Math.Pow(10, i + 1D);
                for (int j = lower; j < upper; j++)
                {
                    // n is not divisible by 10
                    if (j % 10 == 0)
                    {
                        continue;
                    }

                    // n must have f + 1 digits
                    var digits = 0;
                    var state = j;
                    while (state > 0)
                    {
                        state /= 10;
                        digits++;
                    }
                    if (digits != i + 1)
                    {
                        continue;
                    }

                    // Scan for e in both directions
                    for (int k = (int)i; ; --k)
                    {
                        var result = j * Math.Pow(10, k - i);
                        if (result > min && result < max)
                        {
                            f = i;
                            n = j;
                            e = k;
                            goto Complete;
                        }
                        if (result <= i)
                        {
                            break;
                        }
                    }
                    for (int k = (int)i + 1; ; k++)
                    {
                        var result = i * Math.Pow(10, k - i);
                        if (result > min && result < max)
                        {
                            f = i;
                            n = j;
                            e = k;
                            goto Complete;
                        }
                        if (result >= i)
                        {
                            break;
                        }
                    }
                }
            }
        }

    Complete:

        m = n.ToString("G");
    }

    if (f != 0D)
    {
        m = m[0] + "." + m.Substring(1);
    }

    if (e == 0D)
    {
        c = "+";
        d = "0";
    }
    else
    {
        if (e > 0D)
        {
            c = "+";
        }
        else
        {
            c = "-";
            e = -e;
        }
        d = e.ToString("G");
    }

    m = m + "e" + c + d;
    return new StringPrimitive(s + m);
}


No. (The point has been made that in this case it's a de-compiler artefact, mind.)

The only exception is CASE GOTO which allows you to share code between switch cases. This isn't really a GOTO though, it just happens to share the same name.

The issue with the GOTO statement is not ideological like many suggest, it's that there are always better, clearer, more maintainable alternatives in modern high level languages, and using a GOTO always adds complexity, which isn't justified by saving a minute or so coding it properly. It's worth taking the time to use one of those instead, even if it means you have to meet the criteria to exit a number of nested loops or conditions.

Block constructs like WHILE and DO-UNTIL and FOR do looping much better and in a standard form that is inherently obvious to someone maintaining the code, and you can't fall out of the block or forget to terminate it in some code paths. They are inherently better code for that reason. That's why they were invented and widely adopted. You can, of course achieve the same loop thing with a GOTO, but you can also make a mistake somewhere within it (especially if you GOTO a place outside of the loop). These dedicated statements are also optimised for that purpose so they probably save the return address in an more efficient manner than a GOTO finds it's target.

Similarly conditional blocks of code, negate the need to jump over multiple lines that don't apply in a given circumstances (yup, in early languages, a condition could only be applied to a single line of code), and a conditional block is easier to see as an atomic section of code, and the condition is quite clearly specified at the top of the block. If you are using GOTOs there could be several places you jump over it and the code flow has to be carefully and faultlessly worked out each time the code is visited, which is very time consuming and prone to errors and misunderstanding. I used to print out programs and use a highlighter pen to mark up the flow. You don't have to with modern constructs.

Better still, modern best practice would be to encapsulate that conditional functionality into a function with a self explanatory name, so instead of having to read through it to see what it is doing or skip over it, if it's not relevant, you just see a nice little function name that tells you what it does (which of course you can look at in isolation if you need to, and which can use local variables if it needs working variables, that definitely can't interfere with other code elsewhere).

Another issue is stack state. I don't know that .net doesn't check if you are jumping out of a function, or loop, or other block, but traditionally GOTO doesn't do any such checks, which means that any state or local information like local variables, or return addresses stored on the stack, are left on the stack, and when you end up entering that construct again, new copies get added, until eventually the stack overflows. It's the oldest bug in the book; it's even inspired a well known technical Q&A site name.

I used to use GOTO extensively in languages without any alternative 35 years ago, and indeed on retro computers in more recent years (because they still don't have alternatives), but in modern languages, I've not had to since about 1990. I didn't even know it existed in C# until I googled it a month ago following a similar discussion elsewhere.

You might also see people say it's equivalent to a machine code jump. It isn't at all. It very much depends on the language implementation but it has to find the exact memory address to go to, and that can involve a brute force search of the source code (normal in interpreters, where you are most likely to use a GOTO), a vector table lookup (more likely with C# or compiled languages), or some such. It doesn't really matter but it's unlikely to be the equivalent of just loading a hardcoded address into the CPU Program Counter, and even if it is when you write it, it might not be when it's running in an emulated implementation in the future. Dedicated loop constructs are probably more optimised and therefore faster.


I never even coded with GO TO back when I wrote FORTRAN.

I've never had to use it. I can't see why any modern language would demand such a thing from a user. I'd say unequivocally "no".

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜