Есть ли причина использовать goto в современном .NET-коде?

Я только что нашел этот код в рефлекторе в базовых библиотеках .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

Я слышал все, что "ты не должен использовать goto из страха изгнания в ад на вечность". Я всегда придерживался MS-кодировщиков в довольно высоком отношении, и хотя я, возможно, не соглашался со всеми их решениями, я всегда уважал их рассуждения.

Итак - есть ли веская причина для такого кода, что я пропала? Был ли этот фрагмент кода просто собранным неумелым разработчиком? или рефлектор .NET возвращает неточный код?

Я надеюсь, что есть веская причина, и я просто слепо пропустил его.

Спасибо всем за вход

Ответ 1

Отражатель не идеален. Фактический код этого метода доступен из справочного источника. Он расположен в 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;
        }

Обратите внимание на то, как не удалось обнаружить последнее предложение else и компенсировать его с помощью goto. Это почти наверняка заблокировано блоками try/catch внутри операторов if().

Очевидно, что вы захотите использовать исходный код вместо декомпилированной версии. Комментарии сами по себе весьма полезны, и вы можете рассчитывать на то, что источник является точным. Ну, в основном точный, есть небольшой урон от багги пост-обработки инструмент, который удалил имена программистов Microsoft. Идентификаторы иногда заменяются тире, и код повторяется дважды. Вы можете скачать источник здесь.

Ответ 2

Скорее всего, он не в исходном коде, это то, как выглядит дизассемблированный код.

Ответ 4

Существует несколько допустимых применений для goto в .NET(С#):

Имитация выражения коммутатора "Падающая семантика" .

Те, что исходят из фона С++, используются для написания операторов switch, которые автоматически переходят из case в case, если явно не прекращено с break. Для С# возможны только тривиальные (пустые) случаи.

Например, в С++

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

В этом коде на С++ вывод:

Case 1
Case 2
Default Case

Вот аналогичный код С#:

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

Как написано, это не будет компилироваться. Существует несколько ошибок компиляции, которые выглядят следующим образом:

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

Добавление операторов goto делает работу:

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

... другое полезное использование goto в С# - это...

Бесконечные петли и развернутая рекурсия

Здесь я не буду вдаваться в подробности, так как он менее полезен, но иногда мы пишем бесконечные циклы, используя конструкторы while(true), которые явно завершаются с помощью break или повторно выполняются с помощью инструкции continue. Это может произойти, когда мы пытаемся имитировать вызовы рекурсивных методов, но не имеем никакого контроля над потенциальной областью рекурсии.

Вы можете, очевидно, преобразовать это в цикл while(true) или реорганизовать его в отдельный метод, но также использовать метку и инструкцию goto.

Это использование Гото более спорно, но все-таки что-то стоит держать в уме в качестве опции в очень редких случаях.

Ответ 5

Я не сумасшедший по поводу gotos, но сказать, что они никогда не верны, глупо.

Я использовал один раз, чтобы исправить дефект в особенно грязной части кода. Чтобы реорганизовать код и протестировать его, он не был бы практичным с учетом ограничения времени.

Кроме того, разве мы все не видели условные конструкции, которые были настолько плохо кодированы, что они делают gotos кажущимися доброкачественными?

Ответ 6

Вы не должны смотреть на код отражателя.

Хотя если вы когда-нибудь посмотрите на разобранный ИЛ, вы увидите все по всему месту. По сути, все циклы и другие управляющие конструкции, которые мы используем, в любом случае преобразуются в gotos, просто благодаря тому, что превращая их в конструкции в нашем коде, он становится более читабельным и более простым в обслуживании.

Я не думаю, что код, который вы отправили, был бы хорошим местом для использования goto, кстати, и я не могу думать об этом.

Ответ 7

Вы можете использовать GOTO для выполнения рекурсии с лучшей производительностью. Это намного сложнее в обслуживании, но если вам нужны эти дополнительные циклы, вы можете заплатить бремя обслуживания.

Вот простой пример с результатами:

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

Вот результаты, которые я получаю на своей машине:

 Recursive Time: 820
 Goto Time: 259

Ответ 8

Я не видел действительного случая для Goto во многих, многих строках .NET-кода, которые были написаны и рассмотрены.

В языках, которые не поддерживают структурированную обработку исключений с блоком finally (PASCAL - дед структурированных языков программирования, а также классический C), тактическое использование GOTO может привести к значительному пониманию кода, когда используемый для выполнения очистки, когда выполнение завершается внутри вложенных циклов (в отличие от правильной установки нескольких условий завершения цикла). Даже в тот же день я не использовал goto лично по этой причине (вероятно, из страха "изгнания к аду вечно" ).

Ответ 9

Goto часто полезны при написании парсеров и лексеров.

Ответ 10

Нет, нет веских оснований для использования goto. Я в последний раз закодировал оператор goto в 1981 году, и я не пропустил эту конкретную конструкцию с тех пор.

Ответ 11

В дополнение ко всем этим хорошим действительным вещам, когда вы смотрите на разобранный код, имейте в виду, что разработчики МОЛИТЬ использовали обфускатор на этих сборках. Один из методов обфускации заключается в добавлении случайного goto в IL

Ответ 12

Взгляните на диаграмму состояний. Если вы считаете, что лучшая структура кода для использования - это та, которая наиболее прямо и четко выражает ваше намерение, то каждый из этих переходов состояний должен быть закодирован как goto.

Это, как правило, ломается в реальном мире. Первая проблема заключается в том, что нам часто приходится останавливать машину, выходить на другой код и возобновлять работу машины позже - это означает, что каждый из этих переходов имеет тенденцию быть изменением переменной состояния, используемой для определения правильного состояния в коммутаторе /case statement. Это на самом деле просто способ скрыть и задержать goto - запись переменной состояния не сильно отличается от записи в регистр счетчика программ. Это просто способ реализовать "goto there", но не сейчас, позже ".

Однако есть случаи, когда goto хорошо работает, чтобы выразить то, что происходит в какой-то государственной модели - я бы предположил, что примером будет одна из тех диагностических блок-схем, которые иногда используют доктора. Если вы реализуете одну из них в качестве программы без использования переходов для переходов, то на самом деле вы просто делаете жизнь трудной для себя, зашифровывая намерение своего кода.

Это просто, что наиболее распространенные случаи вряд ли будут написанными вручную. Я написал генераторы кода, которые генерируют операторы goto для переходов в разных типах государственной модели (обработка решения, регулярный анализ грамматики и т.д.), Но я не помню, как последний раз я использовал goto в рукописном коде.

Ответ 13

В отношении этой точки:

Итак - есть ли веская причина для кода как это, что мне не хватает? Было ли это извлечение кода, просто собранное дерьмовый разработчик? или является отражателем .NET возврат неточного кода?

Я не согласен с предположением, что это единственные три возможности.

Возможно, это правда, как многие другие предположили, что это просто не точное отражение реального исходного кода в библиотеке. Несмотря ни на что, мы все были виновны (ну, во всяком случае, я вообще) написала код "грязный путь" с целью:

  • Быстрая реализация функции
  • Быстрое исправление ошибки.
  • Выдавливание небольшого прироста производительности (иногда с обоснованием, иногда не так много)
  • Некоторая другая причина, которая имела смысл в то время

Это не делает кого-то "дерьмовым разработчиком". Большинство руководств, таких как "не использовать goto", в основном создаются для защиты разработчиков от самих себя; они не должны рассматриваться как ключ к различию между хорошими и плохими разработчиками.

В качестве аналогии рассмотрим простое правило, которое многие из нас учат в школе английского языка: никогда не заканчивайте предложение с предлогом. Это не настоящее правило ; это руководство, чтобы помочь людям не говорить такие вещи, как "Где машина?" Важно понять этот факт; как только вы начнете рассматривать его как фактическое правило, вместо руководства, вы обнаружите, что "исправляете" людей для совершенно хороших предложений, таких как "Чего вы боитесь?"

Учитывая это, я бы с осторожностью относился к любому разработчику, который назвал другого разработчика "shitty", потому что использовал goto.

Я, конечно, не пытаюсь защищать goto, как таковой, просто утверждая, что его использование не означает некомпетентности любыми способами.

Ответ 14

Существует один действительный случай - когда вы пытаетесь моделировать вызов рекурсивной процедуры и возвращать в нерекурсивном коде или делать что-то подобное (этот вид требования также встречается в интерпретаторе Prolog). Но в целом, если вы не делаете что-то, требующее микро-оптимизации, например, шахматной программы или переводчика языка, гораздо лучше просто использовать стек обычной процедуры и использовать вызовы функций/процедур.

Ответ 15

goto отлично подходит для очистки файлов на таких языках, как C, по крайней мере, где он несколько имитирует понятие исключений. Я уверен, что .NET имеет лучшие способы обработки таких вещей, поэтому goto просто устарел и подвержен ошибкам.

Ответ 16

Мне не нравится этот код.

Я бы предпочел сохранить Regex в члене и проверить его при его настройке, избегая при этом необходимости всей логики.

Ответ 17

Как показали другие, код, который вы видите в отражателе, обязательно является кодом, который написан в Framework. Компилятор и оптимизаторы могут изменить код на то, что функционирует аналогичным образом, если оно не изменяет фактическую работу, выполняемую кодом. Следует также указать, что компилятор реализует все ветки и петли как goto (ветки в IL или прыжки в сборке.) Когда запускается режим выпуска, и компилятор пытается оптимизировать код в простейшей форме, функционально такой же, как ваш источник.

У меня есть пример по различным методам цикла, которые все скомпилированы до 100% того же ИЛ, когда вы компилируете для выпуска. См. Другой ответ

(Я не могу найти его прямо сейчас, но Эрик Липперт написал заметку о том, как компилятор С# обрабатывает код. Один из пунктов, которые он сделал, это то, как все петли меняются на goto's.)

Говоря, у меня нет проблем с goto. Если есть лучшая структура цикла, используйте его. Но иногда вам нужно что-то немного, то, что вы можете выжать из-за, foreach, пока, do/while, но вы не хотите, чтобы добавленный беспорядок и боль, возникающие из вызовов методов (зачем тратить 5 плюс строки, чтобы преобразовать вложенные в рекурсивные методы.)

Ответ 18

Это не лучший пример, но он показывает случай, когда goto может быть очень удобным.

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

Ответ 19

Я даже не кодировал GO TO обратно, когда писал FORTRAN.

Мне никогда не приходилось его использовать. Я не понимаю, почему любой современный язык потребует от пользователя такой вещи. Я бы сказал однозначно "нет".