Istruzioni di spostamento (C++): break, continue, return e goto

Le istruzioni jump trasferisce in modo incondizionato il controllo. L'istruzionebreak termina l’istruzione di iterazione o l’istruzioneswitch più vicina. L'istruzionecontinue avvia una nuova iterazione dell'istruzione di iterazione racchiusa più vicina. L'istruzione return termina l'esecuzione del metodo in cui viene visualizzata e restituisce il controllo al metodo di chiamata. L'istruzione goto trasferisce il controllo a un'istruzione contrassegnata da un'etichetta.

Per informazioni sull'istruzione che genera un'eccezione throw e trasferisce in modo incondizionato anche il controllo, vedere la sezione throwIstruzione dell'articolo Istruzioni di gestione delle eccezioni.

Istruzione break

L'istruzione break termina l'istruzione di iterazione racchiusa più vicina (ovvero il ciclo for, foreach, while o do) o l’istruzione switch. L'istruzione break trasferisce il controllo all'istruzione che segue l'istruzione terminata, se presente.

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.

Nei cicli annidati, l'istruzione break termina solo il ciclo più interno che la contiene, come illustrato nell'esempio seguente:

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 si usa l'istruzione switch all'interno di un ciclo, un'istruzione break alla fine di una sezione switch trasferisce il controllo solo all'esterno dell'istruzione switch. Il ciclo che contiene l'istruzione switch non è interessato, come illustrato nell'esempio seguente:

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.

Istruzione continue

L'istruzione continue avvia una nuova iterazione dell'istruzione di iterazione racchiusa più vicina (ovvero il ciclo for, foreach, while o do), come illustrato nell'esempio seguente:

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

Istruzione return

L'istruzione return termina l'esecuzione della funzione in cui viene visualizzata e restituisce il controllo e il risultato della funzione, se presente, al chiamante.

Se un membro della funzione non calcola un valore, usare l'istruzione return senza espressione, come illustrato nell'esempio seguente:

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

Come illustrato nell'esempio precedente, in genere si usa l'istruzione return senza espressione per terminare un membro della funzione in anticipo. Se un membro della funzione non contiene l'istruzione return, termina dopo l'esecuzione dell'ultima istruzione.

Se un membro della funzione calcola un valore, usare l'istruzione return con un'espressione, come illustrato nell'esempio seguente:

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 l'istruzione return ha un'espressione, tale espressione deve essere convertibile in modo implicito nel tipo restituito di un membro di funzione, a meno che non sia asincrona. L'espressione restituita da una funzione async deve essere convertibile in modo implicito nell'argomento di tipo di Task<TResult> o ValueTask<TResult>, indipendentemente dal tipo restituito della funzione. Se il tipo restituito di una funzione async è Task o ValueTask, usare l'istruzione return senza espressione.

Valori restituiti

Per impostazione predefinita, l'istruzione return restituisce il valore di un'espressione. È possibile restituire un riferimento a una variabile. I valori restituiti di riferimento (o valori restituiti ref) sono i valori che un metodo restituisce per riferimento al chiamante. In altre parole, il chiamante può modificare il valore restituito da un metodo e tale modifica viene riflessa nello stato dell'oggetto nel metodo chiamato. A tale scopo, usare l'istruzione return con la parola chiave ref, come illustrato nell'esempio seguente:

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 valore restituito di riferimento consente a un metodo di restituire a un chiamante un riferimento a una variabile, invece di un valore. Il chiamante può quindi scegliere di trattare la variabile come se fosse stata restituita da un valore o un riferimento. Il chiamante può creare una nuova variabile che è di per sé un riferimento al valore restituito, chiamata variabile locale ref. Un valore restituito di riferimento indica che un metodo restituisce un riferimento (o alias) a una variabile. L'ambito di tale variabile deve includere il metodo. La durata della variabile deve estendersi oltre la restituzione del controllo del metodo. Le modifiche effettuate dal chiamante al valore restituito del metodo vengono apportate alla variabile restituita dal metodo.

La dichiarazione che un metodo restituisce un valore restituito di riferimento indica che il metodo restituisce un alias a una variabile. La finalità di progettazione è spesso che la chiamata di codice accede a tale variabile tramite l'alias, che viene incluso per modificarla. I metodi restituiti per riferimento non possono avere il tipo restituito void.

Affinché il chiamante modifichi lo stato dell'oggetto, il valore restituito di riferimento deve essere archiviato in una variabile definita in modo esplicito come variabile referenza.

Il valore restituito ref è un alias per un'altra variabile nell'ambito del metodo chiamato. È possibile interpretare qualsiasi uso del valore restituito ref come uso della variabile di cui effettua l'aliasing:

  • Quando si assegna il valore, si assegna un valore alla variabile di cui effettua l'aliasing.
  • Quando si legge il valore, si legge il valore della variabile di cui effettua l'aliasing.
  • Se lo si restituisce per riferimento, si restituisce un alias alla stessa variabile.
  • Se lo si passa a un altro metodo per riferimento, si passa un riferimento alla variabile di cui effettua l'aliasing.
  • Quando si crea un alias locale ref, si crea un nuovo alias per la stessa variabile.

Una restituzione ref deve essere un contesto di riferimento sicuro per il metodo chiamante. Ciò significa che:

  • Il valore restituito deve avere una durata che si estende oltre l'esecuzione del metodo. In altre parole, non può essere una variabile locale nel metodo che la restituisce. Può essere un'istanza o un campo statico di una classe oppure un argomento passato al metodo. Se si tenta di restituire una variabile locale, viene generato l'errore del compilatore CS8168, "Non è possibile restituire la variabile locale 'obj' per riferimento perché non è una variabile locale ref".
  • Il valore restituito non può essere il valore letterale null. Un metodo con un valore restituito ref può restituire un alias a una variabile il cui valore è attualmente il valore null (senza istanza) o un tipo valore nullable per un tipo valore.
  • Il valore restituito non può essere una costante, un membro di enumerazione, il valore restituito per valore da una proprietà o un metodo di un oggetto class o struct.

Inoltre, i valori restituiti di riferimento non sono consentiti per i metodi asincroni. Un metodo asincrono può restituire il controllo prima del termine dell'esecuzione, quando il valore restituito è ancora sconosciuto.

Un metodo che restituisce un valore restituito di riferimento deve:

  • Includere la parola chiave ref davanti al tipo restituito.
  • Ogni istruzione return nel corpo del metodo include la parola chiave ref davanti al nome dell'istanza restituita.

L'esempio seguente illustra un metodo che soddisfa le condizioni e restituisce un riferimento a un oggetto Person denominato p:

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

Ecco un esempio di restituzione ref più completo, che mostra sia la firma del metodo sia il corpo del metodo.

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

Il metodo chiamato può anche dichiarare il valore restituito come ref readonly per restituire il valore per riferimento e specificare che il codice chiamante non può modificare il valore restituito. Il metodo chiamante può evitare la copia del valore restituito archiviando il valore in una ref readonlyvariabile di riferimento locale.

Nell'esempio seguente viene definita una classe Book che ha due campi String, Title e Author. Definisce inoltre una classe BookCollection che include una matrice privata di oggetti Book. I singoli oggetti book vengono restituiti per riferimento chiamando il relativo metodo 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 il chiamante archivia il valore restituito dal metodo GetBookByTitle come una variabile locale ref, le modifiche apportate al valore restituito dal chiamante vengono riflesse nell'oggetto BookCollection, come illustrato nell'esempio seguente.

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

Istruzione goto

L'istruzione goto trasferisce il controllo a un'istruzione contrassegnata da un'etichetta, come illustrato nell'esempio seguente:

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.

Come illustrato nell'esempio precedente, è possibile usare l'istruzione goto per uscire da un ciclo annidato.

Suggerimento

Quando si lavora con cicli annidati, è consigliabile effettuare il refactoring di cicli separati in metodi separati. Ciò può portare a un codice più semplice e leggibile in assenza dell'istruzione goto.

È inoltre possibile usare l'istruzione goto nell'switchistruzione per trasferire il controllo a una sezione switch con un'etichetta caso costante, come illustrato nell'esempio seguente:

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

All'interno dell'istruzione switch è anche possibile usare l'istruzione goto default; per trasferire il controllo alla sezione switch con l'etichetta default.

Se un'etichetta con il nome specificato non esiste nel membro della funzione corrente o se l'istruzione goto non rientra nell'ambito dell'etichetta, si verifica un errore in fase di compilazione. Ciò significa che non è possibile usare l'istruzione goto per trasferire il controllo dal membro della funzione corrente o in qualsiasi ambito annidato.

Specifiche del linguaggio C#

Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:

Vedi anche