Перейти к определенной строке кода C#

У меня есть несколько методов, которые вызываются из нескольких других методов. Когда в каком-то из методов выполняется определенное действие, хочется вернуться к самому первому методу и пропустить остальной код. На данный момент я использую booleans для проверки «статуса» программы, но я хотел бы избежать этого, потому что методы должны быть void, так как по сути они не должны ничего возвращать. Я нашел что-то вроде goto, но это работает только в том же методе.

Вопрос: есть ли способ перейти к определенной точке кода другим методом в C#? Я нашел материал на других языках, но не так много на С#.

Текущая ситуация:

    void test1()
    {
        bool status = test2();

        if (!status)
            return; // the other stuff will not get done

        Debug.WriteLine("Initialization OK");
    }

    bool test2()
    {
        bool status = test3();

        if (!status)
            return false; // the other stuff will not get done

        // do other stuff 
        return true;
    }

    bool test3()
    {
        if (xxx)
            return false; // the other stuff will not get done
        else
            // do other stuff 
            return true;
    }

Разыскиваемая ситуация:

    void test1()
    {
        test2();

        // do other stuff
        Debug.WriteLine("Initialization OK");

        GOTOHERE:
             Debug.WriteLine("Initialization NOT OK");
    }

    void test2()
    {
        test3();            
        // do other stuff 
    }

    void test3()
    {
        if (xxx)
            **GOTOHERE**; // Go directly to the location in test1() so that all unnecessary code is skipped

        // do other stuff
    }

person 10a    schedule 09.08.2017    source источник
comment
Возвращать что-то из вашего метода, чтобы указать, что вы должны делать потом, — это именно то, что вы должны делать. Таким образом, верните логическое значение, указывающее, удалось ли выполнить test2 или test3, и используйте это значение, чтобы указать, хотите ли вы продолжить.   -  person HimBromBeere    schedule 09.08.2017
comment
В этом конкретном случае, когда вы хотите вернуться назад при возникновении ошибки, вам может понадобиться throw исключение и перехватить его в test1().   -  person C.Evenhuis    schedule 09.08.2017
comment
хотя это, безусловно, зависит от ситуационных условий и индивидуальных потребностей, использование goto почти всегда является плохой практикой и может быть заменено альтернативными и дружественными подходами. некоторые дополнительные чтения: stackoverflow.com/questions/11906056/goto-is-this-bad и drdobbs.com/jvm/programming-with- причина-почему-это-плохо/   -  person mcy    schedule 09.08.2017
comment
@ C.Evenhuis Нет, не надо! Мы действительно должны попытаться избавиться от мысли, что исключения — это хороший способ обработки ошибок. Хотя из этого правила есть исключения, и именно здесь следует использовать Исключения.   -  person Noel Widmer    schedule 09.08.2017
comment
@NoelWidmer Насколько я знаю, исключения не называются исключениями, потому что они являются исключением из правила ошибок, они называются исключениями, потому что они выходят за рамки возможных ошибок (части), от которых приложение может восстановиться. Возможно, я поторопился предположить, но кажется, что это то, что описывает ОП; он не хочет, чтобы test2() мог восстановиться после ошибки, которая произошла в test3(). Я не знаю, какая ошибка возникает здесь, поэтому я не могу сказать, будет ли это хорошей идеей или нет. Но я согласен с тем, что исключения не должны использоваться как странная замена goto.   -  person C.Evenhuis    schedule 09.08.2017
comment
@C.Evenhuis Я согласен с тем, что если восстановление не важно, можно использовать исключение. Но я также почти всегда считаю перехват исключений недостатком дизайна. Тем не менее, поскольку ОП хочет определенного поведения (действовать при ошибке), я бы не стал здесь бросать. Есть довольно много молодых языков, которые позволяют вам паниковать (выбрасывать), но не ловить, чтобы помешать нам построить логику по выбрасыванию и перехвату исключений.   -  person Noel Widmer    schedule 09.08.2017
comment
В моем случае необходимость завершения метода зависит (исключительно) не от ошибок, а от различных результатов трехмерного измерения.   -  person 10a    schedule 10.08.2017


Ответы (7)


Возвращать что-то из вашего метода, чтобы указать, что вы должны делать потом, — это именно то, что вы должны делать. Таким образом, верните логическое значение, указывающее, удалось ли выполнить test2 или test3, и используйте это значение, чтобы указать, хотите ли вы продолжить. Не используйте goto в настоящее время, так как это приводит только к спагетти-коду, который сложно поддерживать. Чтобы определить, при каких обстоятельствах поток управления должен перейти к GOTOHERE, вам нужно просмотреть весь код на наличие этого конкретного оператора goto.

В вашем случае вы хотите указать, правильно ли работает какой-либо код инициализации. Таким образом, вы также можете создать исключение:

void test3()
{
    if (xxx)
        throw new Exception("Some text");

    // do other stuff
}

Таким образом, вам не нужно ничего возвращать из вашего метода, но обработайте исключение соответствующим образом:

void test1()
{
    try { test2(); }
    catch { 
        // some exception-handling such as logging
        return; 
    }

    Debug.WriteLine("Initialization OK");
}

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

person HimBromBeere    schedule 09.08.2017
comment
Генерация исключений на самом деле не применима, поскольку завершение метода зависит от различных типов результатов измерения, а не от ошибки. Или, по крайней мере, это не та проблема, которую я хочу решить. - person 10a; 10.08.2017
comment
Затем вы должны указать, что должна делать вызывающая сторона вашего метода на основе возвращаемого значения метода, как я упоминал выше. Опять же: не прыгайте внутри своего класса, делая весь ваш дизайн, имеющий методы и свойства, совершенно устаревшими. - person HimBromBeere; 10.08.2017

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

Эта статья объясняет это и дает множество примеров: https://www.dotnetperls.com/goto

Однако

Если вы все еще не пишете код в 1970 году, использование GOTO считается очень плохой практикой. Это очень усложняет обслуживание кода. И это даже вызывает проблемы и проблемы с производительностью и усложняет жизнь JIT-компилятору.

Оператор go to в его нынешнем виде слишком примитивен, это слишком приглашение испортить программу.

Эдсгер В. Дейкстра

person jason.kaisersmith    schedule 09.08.2017
comment
Этот пост на самом деле не ответит на вопрос OP, он только показывает проблемы, возникающие при использовании goto. - person HimBromBeere; 09.08.2017

В C# есть ключевое слово goto, которое работает так же, как и в других языках. Объявите метку как label_you_want_to_jump_to: и используйте goto label_you_want_to_jump_to (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/goto).

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

person pikzen    schedule 09.08.2017
comment
Мне удается использовать goto только тогда, когда он используется в том же методе, а не переходить к другому методу. - person 10a; 09.08.2017

Если вы избавитесь от явных логических значений (которые являются избыточными), ваш (довольно надуманный) пример выглядит намного "чище", на мой взгляд:

void test1()
{
    if (test2())
    {
        Debug.WriteLine("Initialization OK");
    }
}

bool test2()
{
    return test3();
}

bool test3()
{
    return xxx;
}

Я также предпочитаю использовать положительные, а не отрицательные условия, поэтому «if (true)» вместо «if (!false)». Это позволяет избежать двойных отрицаний, которые труднее понять.

Фактически мы используем логику предикатов. Мы можем комбинировать наши предикаты (методы, отличные от возврата bool без побочных эффектов), используя обычные логические операторы. Рассмотрим этот пример:

bool test4()
{
    if (test1())
    {
        if (test2())
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }
}

Отметим, что это просто логическое соединение (и) тестов test1 и test2, поэтому можно просто использовать оператор соединения &&, чтобы сделать это более понятным:

bool test4()
{
    return test1() && test2();
}

Аналогично для логической дизъюнкции (или):

bool test5()
{
    if (test1())
    {
        return true;
    }
    else if (test2())
    {
        return true;
    }
    else 
    {
        return false;
    }
}

Вместо этого мы можем записать это как:

bool test5()
{
    return test1() || test2();
}
person Polyfun    schedule 09.08.2017

Я не рекомендую это делать, но простой ответ на ваш вопрос — использовать оператор goto.

См.: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/goto

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

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

person Colin Mackay    schedule 09.08.2017

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

Далее следует действительно упрощенное объяснение. Я умалчиваю о многих деталях.
Если вы знаете, как работает стек, вы будем знать, что среда выполнения будет помещать в стек новый кадр стека при каждом вызове нового метода. Этот новый кадр стека содержит локальные переменные метода, а также другие детали, относящиеся к реализации. Если вы хотите перейти к другому методу, среде выполнения потребуется добавить новый фрейм стека в ваш стек, чтобы создать эти локальные переменные для этого метода. Таким образом, ваш goto станет вызовом метода, а не оператором перехода. Что странно и не то, что вы/мы хотим. Используйте обычные вызовы методов в таких сценариях, а не операторы перехода.

Существуют широко распространенные операторы перехода, такие как return, break и continue.
goto не входит в их число, хотя я считаю goto case допустимым решением в некоторых случаях.

Возврат информации о том, что делать дальше, является здесь правильным поведением.
Я согласен с тем, что возвращение логического значения не очень выразительно с семантической точки зрения, по крайней мере, с вашими текущими именами методов. Предложение по улучшению именования:

void DoTest1();
bool TryTest2();
bool TryTest3();
person Noel Widmer    schedule 09.08.2017

к вашему сведению

bool flag;

void test1()
{
    test2();

    if (flag) {
        // do other stuff
        Debug.WriteLine("Initialization OK");
    }
    else {
         Debug.WriteLine("Initialization NOT OK");
    }
}

void test2()
{
    test3();            
    if (flag) {
        // do other stuff 
    }
}

void test3()
{
    if (xxx)
        return;

    // do other stuff

    flag = true;
}
person zuma    schedule 09.08.2017