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

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

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

A instrução break

A instrução break encerra a instrução de iteração mais próxima (ou seja, loop for, foreach, while ou do) ou instrução switch. A instrução break transfere o controle para a instrução que segue a instrução encerrada, 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 instrução break encerra apenas o loop mais interno que a contém, como mostra o seguinte exemplo:

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 instrução switch dentro de um loop, uma instrução break ao final de uma seção switch transfere o controle somente para fora da instrução switch. O loop que contém a instrução switch não é afetado, como mostra o seguinte exemplo:

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 instrução continue

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

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 instrução return

A instrução return encerra a execução da função em que aparece e devolve o controle e o resultado da função, se houver, ao chamador.

Se um membro da função não computar um valor, você usará a instrução return sem expressão, como mostra o seguinte exemplo:

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, normalmente você usa a instrução return sem expressão para encerrar um membro da função antecipadamente. Se um membro da função não contiver a instrução return, ele será encerrado após a última instrução ser executada.

Se um membro da função calcular um valor, você usará a instrução return com uma expressão, como mostra o seguinte exemplo:

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 instrução return 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 função async deve ser implicitamente conversível para o argumento de tipo de Task<TResult> ou ValueTask<TResult>, seja qual for o tipo de retorno da função. Se o tipo de retorno de uma função async for Task ou ValueTask, você usará a instrução return sem expressão.

Retornos de referências

Por padrão, a instrução return retorna o valor de uma expressão. Você pode retornar uma referência a uma variável. Valores retornados por referência (ou ref returns) são valores que um método retorna por referência para o chamador. Ou seja, o chamador pode modificar o valor retornado por um método e essa alteração será refletida no estado do objeto no método chamado. Para fazer isso, use a instrução return com a palavra-chave ref, 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 retornado por referência permite que um método retorne uma referência a uma variável, em vez de um valor, de volta para um chamador. O chamador pode optar por tratar a variável retornada como se tivesse sido retornada por valor ou referência. O chamador pode criar uma nova variável que seja uma referência ao valor retornado, chamado de ref local. Um valor retornado de referência significa que um método retorna uma referência (ou um alias) para alguma variável. O escopo da variável deve incluir o método. O tempo de vida da variável deve ultrapassar o retorno do método. As modificações no valor retornado do método pelo chamador são feitas na variável que é retornada pelo método.

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

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

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

  • Ao atribuir o valor, você atribui um valor à variável da qual ele é um alias.
  • Ao ler o valor, você lê o valor da variável da qual ele é um alias.
  • Se o retornar por referência, você retornará um alias para a mesma variável.
  • Se o passar para outro método por referência, você passará uma referência à variável da qual ele é um alias.
  • Ao criar um alias de referência local, você cria um novo alias para a mesma variável.

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

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

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

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

  • Inclua a palavra-chave ref na frente do tipo de retorno.
  • Cada instrução return no corpo do método inclui a palavra-chave ref antes do nome da instância retornada.

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

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

Aqui está um exemplo de retorno de ref mais completo, mostrando a assinatura do método 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 poderá declarar o valor retornado como ref readonly para retornar o valor por referência e, em seguida, impor que o código de chamada não possa modificar o valor retornado. O método de chamada pode evitar a cópia retornada com um valor ao armazenar o valor em uma variável de referência ref readonly local.

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


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 o valor retornado pelo método GetBookByTitle como um ref local, as alterações que o chamador faz ao valor retornado são refletidas no objeto BookCollection, conforme 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 instrução goto

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

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 instrução goto para sair de um loop aninhado.

Dica

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

Você também pode usar a instrução goto na instrução switch a fim de transferir o controle para uma seção switch com um rótulo case constante, como mostra o seguinte exemplo:

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

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

Se um rótulo com o nome fornecido não existir no membro da função atual ou se a instrução goto 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 goto para transferir o controle do membro da função atual ou para qualquer escopo aninhado.

Especificação da linguagem C#

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

Confira também