Существует ли когда-нибудь причина использовать 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 возвращает неточный код?

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

Спасибо за общий вход

31
задан ArtOfWarfare 15 September 2013 в 16:19
поделиться

19 ответов

Отражатель не идеален. Фактический код этого метода доступен в Справочном источнике. Он расположен в 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 и компенсировал его переходом. Это почти наверняка вызвано блоками try / catch внутри операторов if ().

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

44
ответ дан 27 November 2019 в 21:28
поделиться

Относительно этого момента:

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

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

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

  1. быстрого внедрения функции
  2. быстрого исправления ошибки
  3. Выдавливание небольшого прироста производительности (иногда с оправданием, иногда не так сильно)
  4. Другая причина, которая имела смысл в то время

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

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

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

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

1
ответ дан 27 November 2019 в 21:28
поделиться

Goto часто используются при написании синтаксических анализаторов и лексеров.

3
ответ дан 27 November 2019 в 21:28
поделиться

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

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

0
ответ дан 27 November 2019 в 21:28
поделиться

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

У меня есть пример с различными техниками циклов, которые все компилируются в 100% один и тот же IL, когда вы компилируете в режиме release. See Other Answer

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

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

.
1
ответ дан 27 November 2019 в 21:28
поделиться

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

Однажды я использовал один из них, чтобы исправить дефект в особенно грязном куске кода. Рефакторить код и тестировать его было бы нецелесообразно, учитывая ограничение по времени.

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

.
8
ответ дан 27 November 2019 в 21:28
поделиться

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

2
ответ дан 27 November 2019 в 21:28
поделиться

Я видел, как goto используется для выхода из вложенных циклов:

Как я могу выйти из двух вложенных циклов for в Objective-C?

Я не вижу ничего плохого в том, чтобы использовать его таким образом.

12
ответ дан 27 November 2019 в 21:28
поделиться

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

2
ответ дан 27 November 2019 в 21:28
поделиться

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

0
ответ дан 27 November 2019 в 21:28
поделиться

Не смотрите на код отражателя.

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

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

4
ответ дан 27 November 2019 в 21:28
поделиться

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

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

3
ответ дан 27 November 2019 в 21:28
поделиться

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

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

-1
ответ дан 27 November 2019 в 21:28
поделиться

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

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

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

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

2
ответ дан 27 November 2019 в 21:28
поделиться

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

0
ответ дан 27 November 2019 в 21:28
поделиться

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

12
ответ дан 27 November 2019 в 21:28
поделиться

Возможно, это не лучший пример, но он показывает случай, когда 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);
}
0
ответ дан 27 November 2019 в 21:28
поделиться

Вы можете использовать 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
4
ответ дан 27 November 2019 в 21:28
поделиться

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

Симуляция сквозной семантики оператора переключения .

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

Например, в 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;
}

В этом коде C ++ вывод следующий:

Case 1
Case 2
Default Case

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

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 в C # - это ...

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

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

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

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

11
ответ дан 27 November 2019 в 21:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: