Instruções de salto - break, continue, return, e goto

As instruções de salto transferem incondicionalmente o controle. A break instrução encerra a instrução ouswitchinstrução de iteração anexa mais próxima. A continue instrução inicia uma nova iteração da instrução de iteração anexa mais próxima. A return instrução encerra a execução da função na qual ela aparece e retorna o controle para o chamador. A goto instrução transfere o controle para uma instrução marcada por um rótulo.

Para obter informações sobre a throw instrução que lança uma exceção e também transfere o controle incondicionalmente, consulte A throw seção de instrução do artigo Instruções de tratamento de exceções.

A break declaração

A break instrução encerra a instrução de iteração de fechamento mais próxima (ou seja, for, , foreachwhile, ou do loop) ou switch instrução. A break instrução transfere o controle para a instrução que segue a instrução terminada, se houver.

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.

Em loops aninhados, a break instrução termina apenas o loop mais interno que a contém, como mostra o exemplo a seguir:

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

Quando você usa a switch instrução dentro de um loop, uma break instrução no final de uma seção de switch transfere o controle somente para fora da switch instrução. O loop que contém a switch instrução não é afetado, como mostra o exemplo a seguir:

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.

A continue declaração

A continue instrução inicia uma nova iteração da instrução de iteração de fechamento mais próxima (ou seja, for, , foreachwhile, ou do loop), como mostra o exemplo a seguir:

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

A return declaração

A return instrução encerra a execução da função na qual ela aparece e retorna o controle e o resultado da função, se houver, para o chamador.

Se um membro da função não calcular um valor, use a return instrução sem expressão, como mostra o exemplo a seguir:

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

Como mostra o exemplo anterior, você normalmente usa a return instrução sem expressão para encerrar um membro da função antecipadamente. Se um membro da função não contiver a instrução, ela será encerrada return depois que sua última instrução for executada.

Se um membro da função calcular um valor, use a return instrução com uma expressão, como mostra o exemplo a seguir:

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

Quando a return instrução tem uma expressão, essa expressão deve ser implicitamente conversível para o tipo de retorno de um membro da função, a menos que seja assíncrona. A expressão retornada de uma async função deve ser implicitamente conversível para o argumento type de Task<TResult> or ValueTask<TResult>, seja qual for o tipo de retorno da função. Se o tipo de retorno de uma async função for Task ou ValueTask, você usará a return instrução sem expressão.

Devoluções Ref

Por padrão, a return instrução retorna o valor de uma expressão. Você pode retornar uma referência a uma variável. Valores de retorno de referência (ou retornos ref) são valores que um método retorna por referência ao chamador. Ou seja, o chamador pode modificar o valor retornado por um método, e essa alteração é refletida no estado do objeto no método chamado. Para fazer isso, use a return instrução com a ref palavra-chave, como mostra o exemplo a seguir:

int[] 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.");
}

Um valor de retorno de referência permite que um método retorne uma referência a uma variável, em vez de um valor, de volta a um chamador. O chamador pode então optar por tratar a variável retornada como se ela fosse retornada por valor ou por referência. O chamador pode criar uma nova variável que é, em si mesma, uma referência ao valor retornado, chamada de ref local. Um valor de retorno de referência significa que um método retorna uma referência (ou um alias) para alguma variável. O âmbito desta variável deve incluir o método. O tempo de vida dessa variável deve prolongar-se para além do retorno do método. Modificações no valor de retorno do método pelo chamador são feitas na variável que é retornada pelo método.

Declarar que um método retorna um valor de retorno de referência indica que o método retorna um alias para uma variável. A intenção do design geralmente é que o código de chamada acesse essa variável por meio do alias, inclusive para modificá-la. Os métodos que retornam por referência não podem ter o tipo de retorno void.

Para que o chamador modifique o estado do objeto, o valor de retorno de referência deve ser armazenado em uma variável que é explicitamente definida como uma variável de referência.

O ref valor de retorno é um alias para outra variável no escopo do método chamado. Você pode interpretar qualquer uso do retorno ref como usando a variável it aliases:

  • Ao atribuir seu valor, você está atribuindo um valor à variável que ele aliases.
  • Ao ler seu valor, você está lendo o valor da variável que ele aliases.
  • Se você retorná-lo por referência, estará retornando um alias para essa mesma variável.
  • Se você passá-lo para outro método por referência, estará passando uma referência para a variável que ele aliases.
  • Quando você cria um alias local ref, você cria um novo alias para a mesma variável.

Um retorno ref deve ser ref-safe-context para o método de chamada. Isto significa:

  • O valor de retorno deve ter um tempo de vida que se estenda além da execução do método. Em outras palavras, não pode ser uma variável local no método que a retorna. Pode ser uma instância ou campo estático de uma classe, ou pode ser um argumento passado para o método. Tentar retornar uma variável local gera erro de compilador CS8168, "Não é possível retornar local 'obj' por referência porque não é um ref local."
  • O valor de retorno não pode ser o literal null. Um método com um retorno ref pode retornar um alias para uma variável cujo valor é atualmente o null valor (não instanciado) ou um tipo de valor anulável para um tipo de valor.
  • O valor de retorno não pode ser uma constante, um membro de enumeração, o valor de retorno por valor de uma propriedade ou um método de um class ou struct.

Além disso, os valores de retorno de referência não são permitidos em métodos assíncronos. Um método assíncrono pode retornar antes de terminar a execução, enquanto seu valor de retorno ainda é desconhecido.

Um método que retorna um valor de retorno de referência deve:

O exemplo a seguir mostra um método que satisfaz essas condições e retorna uma referência a um Person objeto chamado p:

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

Aqui está um exemplo de retorno ref mais completo, mostrando a assinatura e o corpo do método.

public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return ref matrix[i, j];
    throw new InvalidOperationException("Not found");
}

O método chamado também pode declarar o valor de retorno como ref readonly para retornar o valor por referência e impor que o código de chamada não pode modificar o valor retornado. O método de chamada pode evitar copiar o valor retornado armazenando o valor em uma variável de referência local ref readonly .

O exemplo a seguir define uma Book classe que tem dois String campos Title e Author. Ele também define uma BookCollection classe que inclui uma matriz privada de Book objetos. Objetos de livro individuais são retornados por referência chamando seu GetBookByTitle método.


public class Book
{
    public string Author;
    public string Title;
}

public class BookCollection
{
    private Book[] books = { new Book { Title = "Call of the Wild, The", Author = "Jack London" },
                        new Book { Title = "Tale of Two Cities, A", Author = "Charles Dickens" }
                       };
    private Book nobook = null;

    public ref Book GetBookByTitle(string title)
    {
        for (int ctr = 0; ctr < books.Length; ctr++)
        {
            if (title == books[ctr].Title)
                return ref books[ctr];
        }
        return ref nobook;
    }

    public void ListBooks()
    {
        foreach (var book in books)
        {
            Console.WriteLine($"{book.Title}, by {book.Author}");
        }
        Console.WriteLine();
    }
}

Quando o chamador armazena GetBookByTitle o valor retornado pelo método como um local ref, as alterações que o chamador faz no valor de retorno são refletidas BookCollection no objeto, como mostra o exemplo a seguir.

var bc = new BookCollection();
bc.ListBooks();

ref var book = ref bc.GetBookByTitle("Call of the Wild, The");
if (book != null)
    book = new Book { Title = "Republic, The", Author = "Plato" };
bc.ListBooks();
// The example displays the following output:
//       Call of the Wild, The, by Jack London
//       Tale of Two Cities, A, by Charles Dickens
//
//       Republic, The, by Plato
//       Tale of Two Cities, A, by Charles Dickens

A goto declaração

A goto instrução transfere o controle para uma instrução marcada por um rótulo, como mostra o exemplo a seguir:

var matrices = new Dictionary<string, int[][]>
{
    ["A"] =
    [
        [1, 2, 3, 4],
        [4, 3, 2, 1]
    ],
    ["B"] =
    [
        [5, 6, 7, 8],
        [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.

Como mostra o exemplo anterior, você pode usar a goto instrução para sair de um loop aninhado.

Gorjeta

Ao trabalhar com loops aninhados, considere a refatoração de loops separados em métodos separados. Isso pode levar a um código mais simples e legível sem a goto declaração.

Você também pode usar a instrução na switch instrução para transferir o goto controle para uma seção de switch com um rótulo de maiúsculas e minúsculas constante, como mostra o exemplo a seguir:

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

Dentro da switch instrução, você também pode usar a instrução goto default; para transferir o controle para a seção switch com o default rótulo.

Se um rótulo com o nome próprio não existir no membro da função atual, ou se a goto instrução não estiver dentro do escopo do rótulo, ocorrerá um erro em tempo de compilação. Ou seja, você não pode usar a instrução para transferir o goto controle para fora do membro da função atual ou para qualquer escopo aninhado.

Especificação da linguagem C#

Para obter mais informações, consulte as seguintes seções da especificação da linguagem C#:

Consulte também