Instrucciones de salto: break, continue, return y goto

Las instrucciones jump transfieren incondicionalmente el control. La instrucción break: termina la instrucción de iteración contenedora más próxima o la instrucción switch. La instrucción continue: inicia una nueva iteración de la instrucción de iteración contenedora más próxima. La instrucción return: termina la ejecución de la función en la que aparece y devuelve el control al llamador. La instrucción goto: transfiere el control a una instrucción marcada por una etiqueta.

Para obtener información sobre la throw instrucción que produce una excepción y también transfiere el control incondicionalmente, vea la sección Instrucción throw del artículo Instrucciones de control de excepciones.

Instrucción break

La instrucción break termina la instrucción de iteración contenedora más próxima (es decir, los bucles for, foreach, while o do) o la instrucción switch. La instrucción break transfiere el control a la instrucción que hay a continuación de la instrucción finalizada, si existe.

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.

En los bucles anidados, la instrucción break termina solo el bucle más interno que la contiene, como se muestra en el ejemplo siguiente:

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

Cuando usa la instrucción switch dentro de un bucle, una instrucción break al final de una sección switch transfiere el control solo fuera de la instrucción switch. El bucle que contiene la instrucción switch no se ve afectado, como se muestra en el ejemplo siguiente:

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.

Instrucción continue

La instrucción continue inicia una nueva iteración de la instrucción de iteración contenedora más próxima (es decir, los bucles for, foreach, while o do), como se muestra en el ejemplo siguiente:

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

Instrucción return

La instrucción return termina la ejecución de la función en la que aparece y devuelve el control y el resultado de la función, si existe, al llamador.

Si un miembro de función no calcula un valor, se usa la instrucción return sin expresión, como se muestra en el ejemplo siguiente:

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 se muestra en el ejemplo anterior, normalmente se usa la instrucción return sin expresión para terminar un miembro de función al principio. Si un miembro de función no contiene la instrucción return, termina después de ejecutarse su última instrucción.

Si un miembro de función calcula un valor, se usa la instrucción return con una expresión, como se muestra en el ejemplo siguiente:

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

Cuando la instrucción return tiene una expresión, esa expresión debe poderse convertir implícitamente al tipo de valor devuelto de un miembro de función a menos que sea async. La expresión devuelta de una función async debe poderse convertir implícitamente al argumento de tipo de Task<TResult> o ValueTask<TResult>, el que sea el tipo de valor devuelto de la función. Si el tipo de valor devuelto de una función async es Task o ValueTask, se usa la instrucción return sin expresión.

Devoluciones de referencias

De forma predeterminada, la instrucción return devuelve el valor de una expresión. Puede devolver una referencia a una variable. Los valores devueltos de referencia (o valores devueltos de tipo ref) son valores que devuelve un método mediante referencia al autor de la llamada. Es decir, el autor de la llamada puede modificar el valor devuelto por un método, y ese cambio se refleja en el estado del objeto del método al que se ha llamado. Para ello, use la instrucción return con la palabra clave ref, como se muestra en el ejemplo siguiente:

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

Un valor devuelto de referencia permite que un método devuelva una referencia a una variable, en lugar de un valor, al autor de una llamada. El autor de la llamada puede tratar la variable devuelta como si se hubiera devuelto por valor o por referencia. El autor de la llamada puede crear una variable que sea una referencia al valor devuelto, lo que se conoce como una referencia local. Un valor devuelto de referencia significa que un método devuelve una referencia (o un alias) a alguna variable. El ámbito de esa variable debe incluir el método. La duración de la variable debe extenderse más allá de la devolución del método. Las modificaciones en el valor del método devuelto por el autor de la llamada se realizan en la variable devuelta por el método.

Declarar que un método devuelve un valor devuelto de referencia indica que el método devuelve un alias a una variable. La intención del diseño suele ser que el código de llamada acceda a esa variable a través del alias, incluso para modificarla. Por tanto, los métodos devueltos por referencia no pueden tener el tipo de valor devuelto void.

Para que el autor de la llamada modifique el estado del objeto, el valor devuelto de referencia debe almacenarse en una variable que se defina explícitamente como una variable de referencia.

El valor devuelto ref es un alias para otra variable en el ámbito del método llamado. Puede interpretar cualquier uso del valor devuelto tipo ref como si se usara la variable a la que se asigna el alias:

  • Al asignar su valor, se asigna un valor a la variable a la que se asigna el alias.
  • Al leer su valor, se lee un valor a la variable a la que se asigna el alias.
  • Si la devolución se realiza por referencia, entonces devuelve un alias a esa misma variable.
  • Si pasa el valor a otro método por referencia, pasará una referencia a la variable a la que se asigna el alias.
  • Al asignar un alias local tipo ref, crea un alias para la misma variable.

Una devolución de referencia debe ser contexto seguro para referencias al método que realiza la llamada. Esto significa lo siguiente:

  • El valor devuelto debe tener una duración que se extienda más allá de la ejecución del método. En otras palabras, no puede tratarse de una variable local del método que la devuelve. Puede ser una instancia o un campo estático de una clase, o puede ser un argumento pasado al método. Al intentar devolver una variable local, se genera el error del compilador CS8168, "No se puede devolver por referencia la variable local 'obj' porque no es de tipo ref".
  • El valor devuelto no puede ser el literal null. Un método con un valor devuelto de referencia puede devolver un alias a una variable cuyo valor es actualmente el valor null (sin instancias) o un tipo de valor que admite un valor NULL para un tipo de valor.
  • El valor devuelto no puede ser una constante, un miembro de enumeración, el valor devuelto por valor desde una propiedad o un método de class o struct.

Además, los valores devueltos por referencia no se permiten en métodos asincrónicos. Un método asincrónico puede volver antes de que haya terminado de ejecutarse, mientras que su valor devuelto aún no se conoce.

Un método que devuelve un valor devuelto de referencia debe:

  • Incluir la palabra clave ref delante del tipo de valor devuelto.
  • Cada instrucción return del cuerpo del método incluye la palabra clave ref delante del nombre de la instancia devuelta.

En el método siguiente se muestra un método que cumple estas condiciones y devuelve una referencia a un objeto Person denominado p:

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

Este es un ejemplo de valor devuelto de referencia más completo que muestra la firma y el cuerpo del 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");
}

El método llamado también puede declarar el valor devuelto como ref readonly para devolver el valor por referencia y exigir que el código de llamada no pueda modificar el valor devuelto. El método de llamada puede evitar copiar el valor devuelto si lo almacena en una variable de referencia ref readonly local.

En el ejemplo siguiente se define una clase Book que tiene dos campos String, Title y Author. También define una clase BookCollection que incluye una matriz privada de objetos Book. Los objetos book individuales se devuelven mediante referencia llamando a su 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();
    }
}

Cuando el autor de la llamada almacena el valor devuelto mediante el método GetBookByTitle como una variable local de tipo ref, los cambios que el autor de la llamada realiza en el valor devuelto se reflejan en el objeto BookCollection, como se muestra en el ejemplo siguiente.

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

Instrucción goto

La instrucción goto transfiere el control a una instrucción marcada por una etiqueta, como se muestra en el ejemplo siguiente:

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 se muestra en el ejemplo anterior, se puede usar la instrucción goto para salir de un bucle anidado.

Sugerencia

Cuando trabaje con bucles anidados, considere la posibilidad de refactorizar bucles independientes en métodos independientes. Eso puede dar lugar a un código más sencillo y legible sin la instrucción goto.

También puede usar la instrucción goto en la instrucción switch para transferir el control a una sección switch con una etiqueta case constante, como se muestra en el ejemplo siguiente:

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 de la instrucción switch, también se puede usar la instrucción goto default; para transferir el control a la sección switch con la etiqueta default.

Si una etiqueta con el nombre especificado no existe en el miembro de función actual o si la instrucción goto no está dentro del ámbito de la etiqueta, se produce un error en tiempo de compilación. Es decir, no se puede usar la instrucción goto para transferir el control fuera del miembro de función actual o a cualquier ámbito anidado.

Especificación del lenguaje C#

Para más información, vea las secciones siguientes de la Especificación del lenguaje C#:

Consulte también