Sprunganweisungen – break, continue, return und goto

Die Sprunganweisungen übertragen die Steuerung bedingungslos. Die break-Anweisung beendet die nächstgelegene umschließende Iterationsanweisung oder switch-Anweisung. Die continue-Anweisung startet eine neue Iteration der nächstgelegenen umschließenden Iterationsanweisung. Die return-Anweisung beendet die Ausführung der Funktion, in der sie enthalten ist, und gibt die Steuerung an die aufrufende Funktion zurück. Die goto-Anweisung überträgt die Steuerung an eine Anweisung, die durch eine Bezeichnung markiert ist.

Informationen zur throw-Anweisung, die eine Ausnahme auslöst und die Steuerung bedingungslos überträgt, finden Sie im Abschnitt Die throw-Anweisung des Artikels Anweisungen zur Ausnahmebehandlung.

Die Anweisung break

Die break-Anweisung beendet die nächste umschließende Iterations-Anweisung (d. h. for, foreach, while oder do-Schleife) oder die switch-Anweisung. Die break-Anweisung überträgt die Steuerung an die Anweisung, die auf die beendete Anweisung folgt, sofern eine beliebige Anweisung ist.

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.

In geschachtelten Schleifen beendet die break-Anweisung nur die innerste Schleife, die sie enthält, wie im folgenden Beispiel gezeigt:

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

Wenn Sie die switch-Anweisung innerhalb einer Schleife verwenden, überträgt eine break-Anweisung am Ende eines switch-Abschnitts die Steuerung nur aus der switch-Anweisung. Die Schleife, die die switch-Anweisung enthält, ist nicht betroffen, wie das folgende Beispiel zeigt:

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.

Die Anweisung continue

Die continue-Anweisung startet eine neue Iteration der nächsten umschließenden Iteration-Anweisung (d. h. for, foreach, while oder do-Schleife), wie im folgenden Beispiel gezeigt:

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

Die Anweisung return

Die return-Anweisung beendet die Ausführung der Funktion, in der sie angezeigt wird, und gibt die Steuerung und das Ergebnis der Funktion an den Aufrufer zurück, falls dies der Fall ist.

Wenn ein Funktions-Member keinen Wert berechnet, verwenden Sie die return-Anweisung ohne Ausdruck, wie im folgenden Beispiel gezeigt:

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

Wie das vorherige Beispiel zeigt, verwenden Sie in der Regel die return-Anweisung ohne Ausdruck, um ein Funktions-Member frühzeitig zu beenden. Wenn ein Funktions-Member die return-Anweisung nicht enthält, wird er beendet, nachdem die letzte Anweisung ausgeführt wurde.

Wenn ein Funktions-Member einen Wert berechnet, verwenden Sie die return-Anweisung mit Ausdruck, wie im folgenden Beispiel gezeigt:

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

Wenn die return-Anweisung über einen Ausdruck verfügt, muss dieser Ausdruck implizit in den Rückgabetyp eines Funktionsmitglieds konvertierbar sein, es sei denn, er ist asynchron. Der von einer async-Funktion zurückgegebene Ausdruck muss implizit in das Typargument Task<TResult> oder ValueTask<TResult> konvertierbar sein, je nachdem, welcher der Rückgabetyp der Funktion ist. Wenn der Rückgabetyp einer async-Funktion Task oder ValueTask ist, verwenden Sie die return-Anweisung ohne Ausdruck.

Ref-Rückgaben

Standardmäßig gibt die return-Anweisung den Wert eines Ausdrucks zurück. Sie können einen Verweis auf eine Variable zurückgeben. Verweisrückgabewerte (auch ref-Rückgaben genannt) sind Werte, die von einer Methode an den Aufrufer als Verweis zurückgegeben werden. Das bedeutet, dass der Aufrufer den von einer Methode zurückgegebenen Wert ändern kann. Diese Änderung wird im Zustand des Objekts in der aufgerufenen Methode wiedergegeben. Verwenden Sie dazu die return-Anweisung mit dem ref-Schlüsselwort, wie im folgenden Beispiel gezeigt:

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

Mit einem Verweisrückgabewert kann eine Methode einen Verweis auf eine Variable statt eines Werts an eine aufrufende Funktion zurückgeben. Die aufrufende Funktion kann dann wählen, ob sie die zurückgegebene Variable behandelt, als wäre sie als Wert oder als Verweis zurückgegeben worden. Die aufrufende Funktion kann eine neue Variable erstellen, die ihrerseits einen Verweis auf den zurückgegebenen Wert (als lokaler Verweis bezeichnet) darstellt. Ein Verweisrückgabewert bedeutet, dass eine Methode einen Verweis (oder Alias) an einige Variablen zurückgibt. Der Geltungsbereich der Variable muss die Methode enthalten. Die Lebensdauer dieser Variable muss über die Rückgabe der Methode hinausgehen. Änderungen am Methodenrückgabewert durch die aufrufende Funktion werden auf die Variable angewendet, die von der Methode zurückgegeben wird.

Die Deklaration der Rückgabe eines Verweisrückgabewerts durch eine Methode weist darauf hin, dass die Methode einen Alias an eine Variable zurückgibt. Das Entwurfsziel besteht häufig darin, dass der aufrufende Code über den Alias auf diese Variable zugreift, einschließlich der Möglichkeit zu deren Änderung. Methoden, die als Verweis zurückgeben, können nicht den Rückgabetyp void aufweisen.

Zum Ändern des Zustands des Objekts durch den Aufrufer muss der Verweisrückgabewert in einer Variablen gespeichert werden, die explizit als Verweisvariable definiert ist.

Der ref-Rückgabewert ist ein Alias für eine andere Variable im Bereich der aufgerufenen Methode. Sie können jede Verwendung der Verweisrückgabe als Verwendung der Variable interpretieren, für die diese als Alias fungiert:

  • Wenn Sie den jeweiligen Wert zuweisen, weisen Sie der Variable, für die diese als Alias fungiert, einen Wert zu.
  • Wenn Sie ihren Wert lesen, lesen Sie den Wert der Variable, für die diese als Alias fungiert.
  • Wenn Sie diesen als Verweis zurückgeben, geben Sie einen Alias für die gleiche Variable zurück.
  • Wenn Sie ihn als Verweis an eine andere Methode übergeben, übergeben Sie einen Verweis auf die Variable, für die sie als Alias fungiert.
  • Wenn Sie einen Alias für einen lokalen Verweis erstellen, erstellen Sie einen neuen Alias für diese Variable.

Eine Ref-Rückgabe muss für die aufrufende Methode ref-safe-context sein. Dies bedeutet:

  • Der Rückgabewert muss eine Lebensdauer aufweisen, die über die Ausführung der Methode hinausgeht. Mit anderen Worten: Sie darf in der Methode, die diese zurückgibt, keine lokale Variable sein. Es kann sich dabei um eine Instanz oder ein statisches Feld einer Klasse oder um ein Argument handeln, das an die Methode übergeben wurde. Beim Versuch, eine lokale Variable zurückzugeben, tritt der Compilerfehler CS8168 auf „Cannot return local 'obj' by reference because it is not a ref local.“ (Der lokale Wert „obj“ kann nicht als Verweis zurückgegeben werden, da er kein lokaler ref-Wert ist.).
  • Der Rückgabewert darf nicht das null-Literal sein. Eine Methode mit einer Verweisrückgabe kann einen Alias an eine Variable zurückgeben, deren Wert derzeit null ist (nicht instanziiert) oder einen Nullable-Werttyp für einen Werttyp darstellt.
  • Der Rückgabewert darf keine Konstante, kein Enumerationsmember, nicht der By-Value-Rückgabewert einer Eigenschaft und keine Methode einer class oder struct sein.

Außerdem sind Verweisrückgabewerte nicht für asynchrone Methoden zulässig. Eine asynchrone Methode wird möglicherweise zurückgegeben, bevor die Ausführung abgeschlossen ist, wobei der Rückgabewert noch unbekannt ist.

Eine Methode, die einen Verweisrückgabewert zurückgibt, muss:

  • Das ref-Schlüsselwort vor dem Rückgabetyp enthalten.
  • Jede return-Anweisung im Hauptteil der Methode enthält das ref-Schlüsselwort vor den Namen der zurückgegebenen Instanz.

Das folgende Beispiel zeigt eine Methode, die die Bedingungen erfüllt und einen Verweis auf ein Person-Objekt mit dem Namen p zurückgibt:

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

Hier ist ein vollständigeres Beispiel für die Verweisrückgabe, das sowohl die Methodensignatur als auch den Methodentext zeigt.

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

Die aufgerufene Methode kann den Rückgabewert auch als ref readonly deklarieren, um den Wert als Verweis zurückzugeben, und durchsetzen, dass der aufrufende Code den zurückgegebenen Wert nicht ändern kann. Die aufrufende Methode kann das Kopieren des Rückgabewerts verhindern, indem sie den Wert in einer lokalen ref readonly-Verweisvariablen speichert.

Im folgenden Beispiel wird eine Book-Klasse mit zwei String-Feldern definiert: Title und Author. Außerdem wird eine BookCollection-Klasse definiert, die ein privates Array von Book-Objekten enthält. Einzelne Buchobjekte werden durch Aufrufen der GetBookByTitle-Methode als Verweis zurückgegeben.


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

Speichert der Aufrufer den von der GetBookByTitle-Methode zurückgegeben Wert als lokale ref-Variable, werden Änderungen, die der Aufrufer am Rückgabewert vornimmt, im BookCollection-Objekt wiedergegeben. Dies wird im folgenden Beispiel gezeigt:

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

Die Anweisung goto

Die goto-Anweisung überträgt die Steuerung an eine Anweisung, die durch eine Bezeichnung markiert ist, wie im folgenden Beispiel gezeigt:

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.

Wie das vorherige Beispiel zeigt, können Sie die goto-Anweisung verwenden, um aus einer geschachtelten Schleife heraus zu kommen.

Tipp

Wenn Sie mit geschachtelten Schleifen arbeiten, sollten Sie erwägen, separate Schleifen in separate Methoden umzugestalten. Dies kann zu einem einfacheren, besser lesbaren Code ohne die goto-Anweisung führen.

Sie können auch die goto-Anweisung in der switch-Anweisung verwenden, um die Steuerung an einen switch-Abschnitt mit einer konstanten Case-Bezeichnung zu übertragen, wie im folgenden Beispiel gezeigt:

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

Innerhalb der switch-Anweisung können Sie auch die Anweisung goto default; verwenden, um die Steuerung an den switch-Abschnitt mit der Bezeichnung default zu übertragen.

Wenn eine Bezeichnung mit dem angegebenen Namen im aktuellen Funktions-Member nicht vorhanden ist oder sich die goto-Anweisung nicht innerhalb des Bereichs der Bezeichnung befindet, tritt ein Kompilierzeitfehler auf. Das heißt, Sie können die goto-Anweisung nicht dazu verwenden, die Steuerung aus dem aktuellen Funktionsmember oder in einen geschachtelten Bereich zu übertragen.

C#-Sprachspezifikation

Weitere Informationen finden Sie in den folgenden Abschnitten der C#-Sprachspezifikation:

Weitere Informationen