Instruções de salto – break
, continue
, return
e goto
Quatro instruções C# transferem incondicionalmente o controle. A break
instrução encerra a instrução de iteração ou switch
instrução mais próxima. A continue
instrução inicia uma nova iteração da instrução de iteração mais próxima. A return
instrução encerra a execução da função na qual ela aparece e retorna o controle ao chamador. O modificador ref
em uma instrução return
indica que a expressão retornada é retornada por referência, não por valor. A goto
instrução 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 também transfere incondicionalmente o controle, confira throw.
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.
Por padrão, a instrução return
retorna o valor de uma expressão. Você pode retornar uma referência a uma variável. Para fazer isso, use a instrução return
com a palavra-chave ref
, como mostra o seguinte exemplo:
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.");
}
Retornos de referências
Os valores retornados podem ser retornados por referência (ref
retorna). 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
.
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 referência deve ser ref_safe_to_escape ao 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 nulonull
(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
oustruct
.
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;
}
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"] = 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.
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, por exemplo, um bloco try
.
Especificação da linguagem C#
Para obter mais informações, confira as seguintes seções da especificação da linguagem C#: