¿Hay alguna razón para usar goto en modern.NET ¿código?


Acabo de encontrar este código en reflector en las bibliotecas base.NET...

    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

He escuchado todo el discurso de "no usarás a goto por miedo al exilio al infierno para la eternidad". Siempre he tenido MS coders en bastante alta estima y aunque puede que no haya estado de acuerdo con todas sus decisiones, siempre respeté su razonamiento.

Entonces, ¿hay una buena razón para un código como este que me falta? ¿Este extracto de código fue creado por un desarrollador inepto? o es reflector. NET ¿devolviendo código inexacto?

Espero que haya es una buena razón, y me la estoy perdiendo ciegamente.

Gracias por la aportación de todos

 30
Author: ArtOfWarfare, 2010-03-30

19 answers

El reflector no es perfecto. El código real de este método está disponible en la Fuente de referencia. Se encuentra en 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;
        }

Tenga en cuenta cómo no detectó la última cláusula else y la compensó con un goto. Es casi seguro que se tropieza con los bloques try / catch dentro de las sentencias if ().

Claramente querrá favorecer el código fuente real en lugar de la versión descompilada. Los comentarios en ellos mismos son muy útiles y puede contar con que la fuente sea precisa. Bueno, en su mayoría precisa, hay algunos daños menores de una herramienta de postprocesamiento con errores que eliminó los nombres de los programadores de Microsoft. Los identificadores a veces son reemplazados por guiones y el código se repite dos veces. Puede descargar la fuente aquí.

 43
Author: Hans Passant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-03 15:28:34

Probablemente no esté en el código fuente, así es como se ve el código desensamblado.

 12
Author: heisenberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 01:37:10

He visto que goto solía romper bucles anidados:

¿Cómo puedo romper dos bucles for anidados en Objective-C?

No veo nada malo en usarlo de esa manera.

 12
Author: Maynza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:24:30

Hay varios usos válidos para goto en. NET (C# específicamente):

Simular la Semántica Descendente de la Instrucción Switch.

Los que vienen de un fondo de C++ se utilizan para escribir instrucciones switch que automáticamente se desplacen de caso a caso a menos que terminen explícitamente con break. Para C#, solo los casos triviales (vacíos) fallan.

Por ejemplo, en 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;
}

En este código C++ la salida es:

Case 1
Case 2
Default Case

Aquí es similar C# código:

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

Tal como está escrito, esto no compilará. Hay varios errores de compilación que se ven así:

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

Agregar instrucciones goto hace que funcione:

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

... el otro uso útil de goto en C# es...

Bucles Infinitos y Recursión Desenrollada

No entraré en detalles aquí ya que es menos útil, pero ocasionalmente escribimos bucles infinitos usando while(true) construcciones que se terminan explícitamente con un break o se vuelven a ejecutar con un continue instrucción. Esto puede suceder cuando estamos tratando de simular llamadas a métodos recursivos pero no tenemos ningún control sobre el alcance potencial de la recursión.

Obviamente puede refactorizarlo en un bucle while(true) o refactorizarlo en un método separado, pero también puede usar una etiqueta y una sentencia goto.

Este uso de goto es más discutible, pero aún así es algo que vale la pena mantener en su mente como una opción en circunstancias muy raras.

 10
Author: Simon Gillbee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-02 02:40:32

No estoy loco por los goto, pero decir que nunca son válidos es una tontería.

Usé uno una vez para corregir un defecto en una pieza de código particularmente desordenada. Refactorizar el código y probarlo no habría sido práctico dada la restricción de tiempo.

Además, ¿no hemos visto todos construcciones condicionales que estaban tan mal codificadas que hacen que los goto parezcan benignos?

 8
Author: uncle brad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 05:22:42

No mirarás el código del reflector.

Aunque si alguna vez miras a IL desmontado, verás gotos por todas partes. En esencia, todos los bucles y otras construcciones de control que usamos se convierten a gotos de todos modos, es solo que al convertirlos en construcciones en nuestro código, se vuelve más legible y más fácil de mantener.

No creo que el código que publicaste sea un buen lugar para usar goto, por cierto, y me cuesta pensar en uno.

 4
Author: Anthony Pegram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 01:34:48

Puede usar un GOTO para realizar recursión con mejor rendimiento. Es mucho más difícil de mantener, pero si necesita esos ciclos adicionales, puede estar dispuesto a pagar la carga de mantenimiento.

Aquí hay un ejemplo simple, con resultados:

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

Aquí están los resultados que obtengo en mi máquina:

 Recursive Time: 820
 Goto Time: 259
 4
Author: Matt Brunell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-02 17:28:37

No he visto un caso válido para Goto en muchas, muchas líneas de código.NET tanto escritas como revisadas.

En lenguajes que no soportan el manejo de excepciones estructuradas con un bloque final (PASCAL-un abuelo de lenguajes de programación estructurados, así como C clásico vienen a la mente), el uso táctico de un GOTO podría conducir a un código mucho más fácil de entender cuando se usa para realizar limpieza cuando la ejecución termina dentro de bucles anidados (en lugar de establecer correctamente la terminación de condición). Incluso en el pasado, no usé goto personalmente por esa razón (probablemente por temor a "exiliarme al infierno eternamente").

 3
Author: Eric J.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 01:38:30

Los Goto son frecuentemente útiles cuando se escriben analizadores y lexers.

 3
Author: i_am_jorf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-02 17:05:08

No, no hay una buena razón para usar goto. Codifiqué por última vez una declaración goto en 1981, y no me he perdido esa construcción en particular desde entonces.

 2
Author: MusiGenesis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 01:47:14

Eche un vistazo a un diagrama de estados. Si cree que la mejor estructura de código a usar es la que expresa más directa y claramente su intención, entonces cada una de esas transiciones de estado debe codificarse como un goto.

Esto tiende a romperse en el mundo real, sin embargo. El primer problema es que a menudo necesitamos detener la máquina, salir a otro código y reanudar la máquina más tarde, lo que significa que cada una de esas transiciones tiende a ser una variable de cambio de estado, utilizada para identifique el estado correcto en una instrucción switch/case. Esto es realmente solo una manera de ocultar y retrasar el goto - escribir en una variable de estado no es muy diferente a escribir en el registro del contador de programas, en realidad. Es solo una manera de implementar "goto allí-pero no ahora, más tarde".

Hay casos, sin embargo, donde un goto funciona bien para expresar lo que está sucediendo en algún tipo de modelo de estado - supongo que un ejemplo sería uno de esos diagramas de flujo de diagnóstico que los médicos a veces usan. Si implemente uno de esos como un programa sin usar gotos para transiciones, entonces realmente solo se está haciendo la vida difícil al cifrar la intención de su código.

Es solo que, con mucho, los casos más comunes no son propensos a ser código escrito a mano. He escrito generadores de código que generan sentencias goto para transiciones en varios tipos de modelos de estado (manejo de decisiones, análisis gramatical regular, etc.) pero no recuerdo la última vez que usé un goto en código escrito a mano.

 2
Author: Steve314,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 02:02:54

Con respecto a este punto:

Entonces, ¿hay una buena razón para el código ¿así que me estoy perdiendo? Fue este extracto de código simplemente reunido por un mierda desarrollador? o es reflector. NET ¿devolviendo código inexacto?

No estoy de acuerdo con la premisa de que estas son las únicas tres posibilidades.

Tal vez sea cierto, como muchos otros han sugerido, que esto simplemente no es un reflejo exacto del código fuente real en la biblioteca. A pesar de todo, todos hemos he sido culpable (bueno, He, de todos modos) de escribir código "el camino sucio" con el propósito de:

  1. Obtener una característica implementada rápidamente
  2. Arreglando un error rápidamente
  3. Exprimir una ligera ganancia de rendimiento (a veces con justificación, a veces no tanto)
  4. Alguna otra razón que tenía sentido en ese momento

Eso no convierte a alguien en un "desarrollador de mierda"."La mayoría de las directrices como" no usarás goto " se aplican principalmente a proteja a los desarrolladores de sí mismos; no deben ser tratados como una clave para distinguir entre desarrolladores buenos y malos.

Como analogía, considere la regla simple que a muchos de nosotros se nos enseña en el inglés de la escuela primaria: nunca termine una oración con una preposición. Esta no es una regla real ; es una guía para ayudar a evitar que la gente diga cosas como, "¿Dónde está el coche?"Es importante entender este hecho; una vez que empiezas a tratarlo como una regla real, en lugar de una guía, te encontrarás "corrigiendo" a la gente por frases perfectamente buenas como " ¿De qué tienes miedo?"

Con esto en mente, desconfiaría de cualquier desarrollador que llamara a otro desarrollador "mierda" porque usó goto.

Ciertamente no estoy tratando de defender goto, per se arguing solo argumentando que su uso no indica incompetencia, de ninguna manera.

 2
Author: Dan Tao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 04:51:35

Además de todas estas buenas cosas válidas, cuando esté buscando código desensamblado, tenga en cuenta que los desarrolladores PODRÍAN haber utilizado un ofuscador en esos ensamblados. Una técnica de ofuscación es agregar goto al azar a la IL

 2
Author: Muad'Dib,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 14:34:06

Como han mostrado otros, el código que ves en reflector es necesariamente el código que está escrito en el Framework. El compilador y los optimizadores pueden cambiar el código a algo que funcione de manera similar, siempre y cuando no cambie el trabajo real realizado por el código. También se debe indicar que el compilador implementa todas las ramas y bucles como goto (ramas en IL, o saltos en ensamblado.) Cuando se ejecuta el modo de liberación y el compilador intenta optimizar el código al más simple forma que es funcionalmente la misma que tu fuente.

Tengo un ejemplo sobre diferentes técnicas de bucle que están compiladas al 100% del mismo IL cuando compilas para su publicación. Ver Otra Respuesta

(No puedo encontrarlo en este momento, pero Eric Lippert publicó una nota sobre cómo el compilador de C# procesa el código. Uno de los puntos que hizo es cómo todos los bucles se cambian a goto.)

Dicho esto, no tengo ningún problema con goto. Si hay una mejor estructura de bucle, úsala. Pero a veces se necesita algo ligeramente entonces lo que se puede exprimir fuera de for, foreach, while, do/while pero no quería el desorden añadido y el dolor que viene de las llamadas de método (por qué perder 5 líneas más para convertir un anidado para en métodos recursivos.)

 1
Author: Matthew Whited,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:07:11

Hay un caso válido - cuando está tratando de simular una llamada a un procedimiento recursivo y regresar en un código no recursivo, o hacer algo similar (este tipo de requisito también ocurre en un intérprete Prolog). Pero en general, a menos que esté haciendo algo que requiera microoptimización como un programa de ajedrez o un intérprete de lenguaje, mucho mejor simplemente usar la pila de procedimientos regulares y usar llamadas a funciones/procedimientos.

 0
Author: Larry Watanabe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 01:36:50

Goto es perfectamente válido para cosas de limpieza en lenguajes como C al menos, donde simula de alguna manera la noción de excepciones. Estoy seguro de que.NET tiene mejores formas de manejar cosas como esta, por lo que goto es obsoleto y propenso a errores.

 0
Author: Maister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 02:02:40

No me gusta ese código.

Preferiría almacenar la expresión regular en el miembro, y validarla al configurarla, evitando toda la necesidad de lógica al leerla.

 0
Author: kyoryu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 02:12:04

Este puede no ser el mejor ejemplo, pero muestra un caso en el que goto puede ser muy útil.

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);
}
 0
Author: ChaosPandion,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-02 03:05:30

Ni siquiera codifiqué con GO TO back cuando escribí FORTRAN.

Nunca he tenido que usarlo. No puedo ver por qué cualquier lenguaje moderno exigiría tal cosa a un usuario. Yo diría inequívocamente "no".

 -1
Author: duffymo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-30 02:01:51