Операторы перехода — break, continue, returnи goto

Четыре оператора C# безоговорочно передают управление. Оператор break завершает ближайший оператор или операторитерацииswitch. Операторcontinue запускает новую итерацию ближайшего включающего оператора итерации. Операторreturn: завершает выполнение функции, в которой она отображается, и возвращает управление вызывающему объекту. Модификатор ref в операторе return указывает, что возвращаемое выражение возвращается по ссылке, а не по значению. Операторgoto передает управление инструкции, помеченной меткой.

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

Инструкция break

Оператор break завершает выполнение ближайшего внешнего оператора итерации (то есть цикла for, foreach, while или do) или оператора switch. Оператор break передает управление оператору, который расположен после завершенного оператора (если таковой есть).

int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach (int number in numbers)
{
    if (number == 3)
    {
        break;
    }

    Console.Write($"{number} ");
}
Console.WriteLine();
Console.WriteLine("End of the example.");
// Output:
// 0 1 2 
// End of the example.

Во вложенных циклах оператор break завершает только самый внутренний цикл, содержащий его, как показано в следующем примере:

for (int outer = 0; outer < 5; outer++)
{
    for (int inner = 0; inner < 5; inner++)
    {
        if (inner > outer)
        {
            break;
        }

        Console.Write($"{inner} ");
    }
    Console.WriteLine();
}
// Output:
// 0
// 0 1
// 0 1 2
// 0 1 2 3
// 0 1 2 3 4

При использовании оператора switch внутри цикла оператор break в конце раздела switch передает управление только из оператора switch. Цикл, содержащий оператор switch, не затрагивается, как показано в следующем примере:

double[] measurements = { -4, 5, 30, double.NaN };
foreach (double measurement in measurements)
{
    switch (measurement)
    {
        case < 0.0:
            Console.WriteLine($"Measured value is {measurement}; too low.");
            break;

        case > 15.0:
            Console.WriteLine($"Measured value is {measurement}; too high.");
            break;

        case double.NaN:
            Console.WriteLine("Failed measurement.");
            break;

        default:
            Console.WriteLine($"Measured value is {measurement}.");
            break;
    }
}
// Output:
// Measured value is -4; too low.
// Measured value is 5.
// Measured value is 30; too high.
// Failed measurement.

Инструкция continue

Оператор continue начинает новую итерацию ближайшего внешнего оператора итерации (то есть цикла for, foreach, while или do), как показано в следующем примере:

for (int i = 0; i < 5; i++)
{
    Console.Write($"Iteration {i}: ");
    
    if (i < 3)
    {
        Console.WriteLine("skip");
        continue;
    }
    
    Console.WriteLine("done");
}
// Output:
// Iteration 0: skip
// Iteration 1: skip
// Iteration 2: skip
// Iteration 3: done
// Iteration 4: done

Инструкция return

Оператор return завершает выполнение метода, в котором он присутствует, и возвращает управление и результат функции (при его наличии) вызывавшему методу.

Если член функции не вычисляет значение, используется оператор return без выражения, как показано в следующем примере:

Console.WriteLine("First call:");
DisplayIfNecessary(6);

Console.WriteLine("Second call:");
DisplayIfNecessary(5);

void DisplayIfNecessary(int number)
{
    if (number % 2 == 0)
    {
        return;
    }

    Console.WriteLine(number);
}
// Output:
// First call:
// Second call:
// 5

Как показано в предыдущем примере, для досрочного завершения члена функции обычно используется оператор return без выражения. Если член функции не содержит оператор return, он завершается после выполнения последнего оператора.

Если член функции вычисляет значение, используется оператор return с выражением, как показано в следующем примере:

double surfaceArea = CalculateCylinderSurfaceArea(1, 1);
Console.WriteLine($"{surfaceArea:F2}"); // output: 12.57

double CalculateCylinderSurfaceArea(double baseRadius, double height)
{
    double baseArea = Math.PI * baseRadius * baseRadius;
    double sideArea = 2 * Math.PI * baseRadius * height;
    return 2 * baseArea + sideArea;
}

Если оператор return содержит выражение, это выражение должно допускать неявное преобразование в тип возвращаемого значения члена функции, если только он не является асинхронным. Выражение, возвращаемое функцией async , должно быть неявно преобразовано в аргумент Task<TResult> типа или ValueTask<TResult>, в зависимости от типа возвращаемого значения функции. Если тип возвращаемого значения функции async — Task или ValueTask, используется оператор return без выражения.

По умолчанию оператор return возвращает значение выражения. Вы можете вернуть ссылку на переменную. Для этого используйте оператор return с ключевым словом ref, как показано в следующем примере:

var xs = new int[] { 10, 20, 30, 40 };
ref int found = ref FindFirst(xs, s => s == 30);
found = 0;
Console.WriteLine(string.Join(" ", xs));  // output: 10 20 0 40

ref int FindFirst(int[] numbers, Func<int, bool> predicate)
{
    for (int i = 0; i < numbers.Length; i++)
    {
        if (predicate(numbers[i]))
        {
            return ref numbers[i];
        }
    }
    throw new InvalidOperationException("No element satisfies the given condition.");
}

Возвраты по ссылке

Возвращаемые значения могут быть возвращены по ссылке (ref return). Возвращаемое ссылочное значение позволяет методу вернуть вызывающей стороне ссылку на переменную, а не фиксированное значение. После этого вызывающий может самостоятельно решить, как обрабатывать полученную переменную: по значению или по ссылке. Вызывающий объект может создать новую переменную, которая сама по себе является ссылкой на возвращаемое значение, называемое ref local. Возвращаемое ссылочное значение означает, что метод возвращает ссылку на некоторую переменную (или ее псевдоним). В область действия переменной должен входить этот метод. Время существования переменной должно продолжаться после того, как метод возвращает управление. Все изменения, которые вызывающий производит с возвращаемым значением метода, применяются к возвращенной переменной.

Если для метода объявлено возвращаемое ссылочное значение, значит он возвращает псевдоним переменной. Цель проектирования часто заключается в том, что вызывающий код обращается к этой переменной через псевдоним, в том числе для ее изменения. Методы, возвращающие по ссылке, не могут иметь тип возвращаемого значения void.

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

  • При назначении его значения вы присваиваете значение переменной, которую он псевдонимирует.
  • При чтении его значения вы считываете значение переменной, которую он псевдонимирует.
  • Если вы возвращаете его по ссылке, вы возвращаете псевдоним той же переменной.
  • Если вы передаете его в другой метод по ссылке, вы передаете ссылку на переменную, которую он псевдонимирует.
  • Создавая для псевдонима локальную ссылочную переменную, вы создаете новый псевдоним для той же переменной.

Возвращаемый аргумент ref должен быть ref_safe_to_escape в вызывающий метод. Это означает:

  • Время существования возвращаемого значения должно превышать период выполнения метода. Другими словами, она не может быть локальной переменной в методе, который возвращает ее. Это может быть экземпляр статического поля или класса, а также переданный в метод аргумент. Попытка вернуть локальную переменную приводит к ошибке компилятора CS8168 : "Не удается вернуть локальный obj по ссылке, так как он не является ссылочным локальным".
  • Возвращаемое значение не может быть литеральным null. Метод с возвращаемым значением ref может возвращать псевдоним переменной, значение которой в настоящее время является значением null (uninstantiated) или типом значения, допускающего значение NULL для типа значения.
  • Возвращаемое значение не может быть константой, элементом перечисления, возвращаемым значением по значению из свойства или методом class или struct.

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

Метод, возвращающий возвращаемое значение ссылки , должен:

  • Включите ключевое слово ref перед типом возвращаемого значения.
  • Каждый оператор return в теле метода включает ключевое слово ref перед именем возвращаемого экземпляра.

В следующем примере показан метод, который удовлетворяет указанным условиям и возвращает ссылку на объект Person с именем p:

public ref Person GetContactInformation(string fname, string lname)
{
    // ...method implementation...
    return ref p;
}

Инструкция goto

Оператор goto передает управление оператору, помеченному меткой, как показано в следующем примере:

var matrices = new Dictionary<string, int[][]>
{
    ["A"] = new[]
    {
        new[] { 1, 2, 3, 4 },
        new[] { 4, 3, 2, 1 }
    },
    ["B"] = new[]
    {
        new[] { 5, 6, 7, 8 },
        new[] { 8, 7, 6, 5 }
    },
};

CheckMatrices(matrices, 4);

void CheckMatrices(Dictionary<string, int[][]> matrixLookup, int target)
{
    foreach (var (key, matrix) in matrixLookup)
    {
        for (int row = 0; row < matrix.Length; row++)
        {
            for (int col = 0; col < matrix[row].Length; col++)
            {
                if (matrix[row][col] == target)
                {
                    goto Found;
                }
            }
        }
        Console.WriteLine($"Not found {target} in matrix {key}.");
        continue;

    Found:
        Console.WriteLine($"Found {target} in matrix {key}.");
    }
}
// Output:
// Found 4 in matrix A.
// Not found 4 in matrix B.

Как показано в предыдущем примере, для выхода из вложенного цикла можно использовать оператор goto.

Совет

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

В операторе switch можно также использовать оператор goto для передачи управления в раздел switch с меткой константы case, как показано в следующем примере:

using System;

public enum CoffeeChoice
{
    Plain,
    WithMilk,
    WithIceCream,
}

public class GotoInSwitchExample
{
    public static void Main()
    {
        Console.WriteLine(CalculatePrice(CoffeeChoice.Plain));  // output: 10.0
        Console.WriteLine(CalculatePrice(CoffeeChoice.WithMilk));  // output: 15.0
        Console.WriteLine(CalculatePrice(CoffeeChoice.WithIceCream));  // output: 17.0
    }

    private static decimal CalculatePrice(CoffeeChoice choice)
    {
        decimal price = 0;
        switch (choice)
        {
            case CoffeeChoice.Plain:
                price += 10.0m;
                break;

            case CoffeeChoice.WithMilk:
                price += 5.0m;
                goto case CoffeeChoice.Plain;

            case CoffeeChoice.WithIceCream:
                price += 7.0m;
                goto case CoffeeChoice.Plain;
        }
        return price;
    }
}

В операторе switch можно также использовать оператор goto default; для передачи управления в раздел switch с меткой default.

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

Спецификация языка C#

Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:

См. также