Instructions de saut : break, continue, return et goto

Les instruction de saut transfèrent le contrôle de manière inconditionnelle. L’instruction break met un terme à l’instruction d’itération englobante la plus proche ou à l’instruction switch. L’instruction continue lance une nouvelle itération de l’instruction d’itération englobante la plus proche. L’instruction return met un terme à l’exécution de la fonction dans laquelle elle apparaît et renvoie le contrôle à l’appelant. L’instructiongoto transfère le contrôle à une instruction marquée par une étiquette.

Pour plus d’informations sur l’instruction throw qui lève une exception et transfère aussi de manière inconditionnelle le contrôle, consultez la section Instruction throw de l’article Instructions de gestion des exceptions.

Instruction break

L’instruction break met un terme à l’instruction d’itération englobante la plus proche (boucle for, foreach, while ou do) ou à l’instruction switch. L’instruction break transfère le contrôle à l’instruction qui suit l’instruction terminée, le cas échéant.

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.

Dans les boucles imbriquées, l’instruction break ne met un terme qu’à la boucle la plus interne qui la contient, comme l’illustre l’exemple suivant :

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

Lorsque vous utilisez l’instruction switch au sein d’une boucle, une instruction break à la fin d’une section switch transfère uniquement le contrôle en dehors de l’instruction switch. La boucle qui contient l’instruction switch n’est pas affectée, comme l’illustre l’exemple suivant :

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.

Instruction continue

L’instruction continue lance une nouvelle itération de l’instruction d’itération englobante la plus proche (boucle for, foreachwhile ou do), comme l’illustre l’exemple suivant :

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

Instruction return

L’instruction return met un terme à l’exécution de la fonction dans laquelle elle apparaît, et renvoie le contrôle et le résultat de la fonction, le cas échéant, à l’appelant.

Si une fonction membre ne calcule pas de valeur, vous devez utiliser l’instruction return sans expression, comme l’illustre l’exemple suivant :

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

Comme le montre l’exemple précédent, l’instruction return sans expression est généralement utilisée pour mettre prématurément un terme à une fonction membre. Si une fonction membre ne contient pas l’instruction return, elle se termine après l’exécution de sa dernière instruction.

Si une fonction membre calcule une valeur, vous devez utiliser l’instruction return avec une expression, comme l’illustre l’exemple suivant :

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

Lorsque l’instruction return comporte une expression, celle-ci doit être implicitement convertible en type de retour d’une fonction membre, sauf si elle est asynchrone. L’expression renvoyée à partir d’une fonction async doit être implicitement convertible en argument de type de Task<TResult> ou ValueTask<TResult>, selon le type de retour de la fonction. Si le type de retour d’une fonction async est Task ou ValueTask, vous devez utiliser l’instruction return sans expression.

Retours par référence

Par défaut, l’instruction return renvoie la valeur d’une expression. Vous pouvez renvoyer une référence à une variable. Les valeurs de retour de référence (ou retours ref) sont des valeurs qu’une méthode retourne par référence à l’appelant. Autrement dit, l’appelant peut modifier la valeur retournée par une méthode, et cette modification est reflétée dans l’état de l’objet dans la méthode appelée. Pour ce faire, utilisez l’instruction return avec le mot clé ref, comme l’illustre l’exemple suivant :

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

Une valeur de retour de référence permet à une méthode de retourner à un appelant une référence à une variable, plutôt qu’à une valeur. L’appelant peut alors choisir de traiter la variable retournée comme si elle était retournée par valeur ou par référence. L’appelant peut créer une nouvelle variable qui est elle-même une référence à la valeur renvoyée, appelée variable ref local. Une valeur de retour de référence signifie qu’une méthode retourne une référence (ou un alias) à une variable. L’étendue de cette variable doit inclure la méthode. La durée de vie de cette variable doit s’étendre au-delà du retour de la méthode. Des modifications apportées par l’appelant à la valeur de retour de la méthode portent sur la variable qui est retournée par la méthode.

La déclaration qu’une méthode retourne une valeur de retour de référence indique que la méthode retourne un alias vers une variable. L’intention de conception est souvent que le code appelant accède à cette variable par le biais de l’alias, y compris à des fins de modification. Le type de retour void ne peut pas être utilisé pour les méthodes de retour par référence.

Pour que l’appelant puisse modifier l’état de l’objet, la valeur de retour de référence doit être stockée dans une variable qui est explicitement définie comme variable de référence.

La valeur renvoyée ref est l’alias d’une autre variable dans l’étendue de la méthode appelée. Toute utilisation d’une valeur de retour de référence revient à utiliser la variable dont elle est l’alias :

  • Lorsque vous lui attribuez une valeur, vous attribuez une valeur à la variable dont elle est l’alias.
  • Lorsque vous lisez sa valeur, vous lisez la valeur de la variable dont elle est l’alias.
  • Si vous la renvoyez par référence, vous renvoyez un alias de cette même variable.
  • Si vous la transmettez à une autre méthode par référence, vous transmettez une référence à la variable dont elle est l’alias.
  • Quand vous créez un alias de variable locale ref, vous créez un nouvel alias vers la même variable.

Un retour ref doit être ref-safe-context pour la méthode d’appel. Cela signifie que :

  • La valeur de retour doit avoir une durée de vie qui s’étend au-delà de l’exécution de la méthode. En d’autres termes, il ne peut pas s’agir d’une variable locale dans la méthode de retour. Il peut s’agir d’un champ d’instance ou statique d’une classe, ou bien un argument passé à la méthode. Toute tentative de retour d’une variable locale génère l’erreur de compilation CS8168, « Impossible de renvoyer la variable locale 'obj' par référence, car il ne s’agit pas d’une variable ’ref local’ ».
  • La valeur renvoyée ne peut pas être le littéral null. Une méthode avec retour par référence peut renvoyer l’alias d’une variable dont la valeur est actuellement la valeur (non instanciée) null ou un type valeur pouvant accepter la valeur Null.
  • La valeur renvoyée ne peut être ni une constante, ni le membre d’une énumération, ni la valeur renvoyée « par valeur » d’une propriété, ni la méthode d’une class ou d’un struct.

En outre, les valeurs renvoyées par référence ne sont pas autorisées sur les méthodes asynchrones. Une méthode asynchrone peut être retournée avant la fin de son exécution, même si sa valeur de retour est encore inconnue.

Une méthode qui renvoie une valeur renvoyée par référence doit :

  • Comprendre le mot clé ref devant le type de retour.
  • Pour chaque instruction return dans le corps de la méthode, le mot clé ref précède le nom de l’instance retournée.

L’exemple suivant montre une méthode qui remplit ces conditions et retourne une référence à un objet Person nommé p :

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

Voici un exemple de retour ref plus complet, montrant à la fois la signature de méthode et le corps de méthode.

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

La méthode appelée peut également déclarer la valeur renvoyée en tant que ref readonly pour renvoyer la valeur par référence et faire en sorte que le code d'appel ne puisse pas modifier la valeur renvoyée. La méthode appelante peut éviter de copier la valeur retournée en stockant cette valeur dans une variable de référence ref readonly locale.

L’exemple suivant définit une classe Book qui a deux champs String, Title et Author. Il définit également une classe BookCollection qui inclut un tableau privé d’objets Book. Des objets livres individuels sont retournés par référence en appelant sa méthode 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();
    }
}

Quand l’appelant stocke la valeur retournée par la méthode GetBookByTitle comme variable locale ref, les modifications apportées par l’appelant à la valeur de retour sont reflétées dans l’objet BookCollection, comme le montre l’exemple suivant.

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

Instruction goto

L’instruction goto transfère le contrôle à une instruction marquée par une étiquette, comme l’illustre l’exemple suivant :

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.

Comme le montre l’exemple précédent, vous pouvez utiliser l’instruction goto pour sortir d’une boucle imbriquée.

Conseil

Lorsque vous utilisez des boucles imbriquées, pensez à refactoriser les différentes boucles en méthodes distinctes. Sans l’instruction goto, le code sera probablement plus simple et plus lisible.

Vous pouvez également utiliser l’instruction goto dans l’instruction switch pour transférer le contrôle vers une section switch avec une étiquette de casse constante, comme l’illustre l’exemple suivant :

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

Dans l’instruction switch, vous pouvez également utiliser l’instruction goto default; pour transférer le contrôle vers la section switch dotée de l’étiquette default.

S’il n’existe aucune étiquette portant le nom donné dans la fonction membre actuelle, ou si l’instruction goto ne se trouve pas dans l’étendue de l’étiquette, une erreur se produit au moment de la compilation. Autrement dit, vous ne pouvez pas utiliser l’instruction goto pour transférer le contrôle en dehors de la fonction membre actuelle ou vers une étendue imbriquée.

spécification du langage C#

Pour plus d’informations, consultez les sections suivantes de la spécification du langage C# :

Voir aussi