ジャンプ ステートメント - breakcontinuereturngoto

ジャンプ ステートメントによって無条件で制御が移動します。 break ステートメントは、これを囲む反復ステートメントまたは switch ステートメントを終了させます。 continue ステートメントは、これを囲む反復ステートメントの新しい反復を開始させます。 return ステートメントは、それを含む関数の実行を終了させ、呼び出し元に制御を返します。 goto ステートメントは、ラベルでマークされているステートメントに制御を移動します。

例外をスローし、かつ、無条件で制御を移動させる throw ステートメントに関する詳細については、「例外処理ステートメント」記事の「throw ステートメント」セクションを参照してください。

break ステートメント

break ステートメントはこれを囲む反復ステートメント (forforeachwhile または do ループ) または switch ステートメントを終了させます。 break ステートメントは、終了したステートメントの次のステートメント (存在する場合) に制御を移動します。

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.

次の例に示すように、入れ子になったループでは、break ステートメントはそれを含む最も内側のループのみを終了します。

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

ループ内で switch ステートメントを使用すると、switch セクションの最後にある break ステートメントは、switch ステートメントからのみ制御を移動します。 次の例に示すように、switch ステートメントを含むループは影響を受けません。

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.

continue ステートメント

次の例に示すように、continue ステートメントは、これを囲む反復ステートメント (forforeachwhile または do ループ) の新しい反復を開始します。

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

return ステートメント

return ステートメントは、表示される関数の実行を終了し、呼び出し元に制御と関数の結果 (存在する場合) を返します。

関数メンバーが値を計算しない場合は、次の例に示すように、式を指定せずに return ステートメントを使用します。

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

前の例で示したように、通常は、式を指定せずに return ステートメントを使用して、関数メンバーを早期に終了させます。 関数メンバーに return ステートメントが含まれていない場合は、最後のステートメントが実行された後に終了します。

関数メンバーが値を計算する場合は、次の例に示すように、式を指定して return ステートメントを使用します。

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

return ステートメントに式が含まれている場合、その式は、非同期でない限り関数メンバーの戻り値の型に暗黙的に変換可能である必要があります。 async 関数から返される式は、Task<TResult> または ValueTask<TResult> の型引数に暗黙的に変換できる必要があります。どちらの場合も、関数の戻り値の型になります。 async 関数の戻り値の型が Task または ValueTask の場合は、式を指定せずに return ステートメントを使用します。

参照戻し

既定では、return ステートメントは式の値を返します。 変数への参照を返すことができます。 参照戻り値 (または ref 戻り値) は、メソッドから呼び出し元に参照渡しで返される値です。 つまり、呼び出し元はメソッドによって返される値を変更することができ、その変更は呼び出されたメソッド内のオブジェクトの状態に反映されます。 これを行うには、次の例に示すように、 ref キーワードと共に return ステートメントを使用します:

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

参照戻り値を使用すると、メソッドから呼び出し元に、値ではなく、変数への参照を返すことができます。 呼び出し元は、返された変数を値渡しとして処理するか、参照渡しとして処理するかを選択できます。 呼び出し元は、それ自体が戻り値への参照である、ref ローカルと呼ばれる新しい変数を作成できます。 "参照戻り値" とは、メソッドが何らかの変数への "参照" (または別名) を返すことを意味します。 その変数のスコープには、メソッドが含まれている必要があります。 その変数の有効期間は、メソッドから戻った後まで継続している必要があります。 呼び出し元によるメソッドの戻り値の変更は、メソッドによって返される変数に対して行われます。

メソッドが参照戻り値を返すという宣言があれば、それはそのメソッドが変数にエイリアスを返すことを示します。 設計の意図は、多くの場合、変更などを行うときに、呼び出し元のコードにはエイリアス経由でその変数にアクセスさせるというものです。 参照渡しで返すメソッドの戻り値の型を void にすることはできません。

呼び出し元がオブジェクトの状態を変更するには、参照戻り値を、参照変数として明示的に定義された変数に格納する必要があります。

ref 戻り値は、呼び出されるメソッドのスコープで別の変数のエイリアスになります。 ref 戻り値の使用は、それが別名を与える変数の使用として解釈できます。

  • その値を割り当てるときに、それが別名を与える変数に値を割り当てることになります。
  • その値を読み取るときに、それが別名を与える変数の値を読み取ることになります。
  • それを "参照渡し" で返す場合、その同じ変数に別名を返すことになります。
  • それを "参照渡し" で別のメソッドに渡す場合、それが別名を与える変数への参照を渡すことになります。
  • ref ローカルをエイリアスにすると、同じ変数に新しいエイリアスが作られます。

ref 戻り値は、呼び出し元のメソッドに対する ref-safe-context である必要があります。 これは次のことを意味します。

  • 戻り値の有効期間は、メソッドの実行より長くならないようにする必要があります。 言い換えると、それを返すメソッドのローカル変数にすることはできません。 クラスのインスタンスまたは静的フィールドを戻り値にすることができます。または、メソッドに渡される引数を戻り値にすることもできます。 ローカル変数を返そうとすると、コンパイラ エラー CS8168 "ローカル変数 'obj' は ref ローカル変数ではないため、参照渡しで返すことはできません" が生成されます。
  • 戻り値はリテラル null にすることができません。 ref 戻り値があるメソッドでは、現在の値が null (インスタンス化されていない) 値か、値の型が null 許容値型の変数にエイリアスを返すことができます。
  • 戻り値は、定数、列挙型のメンバー、プロパティの値渡し戻り値、class または struct のメソッドにすることはできません。

さらに、参照戻り値は非同期メソッドでは許可されません。 非同期メソッドは実行が終了する前に戻る可能性があり、戻り値はまだ不明です。

''参照戻り値'' を返すメソッドは次のようなものである必要があります。

  • 戻り値の型の前に ref キーワードが含まれる。
  • メソッド本体の各 return ステートメントで、返されるインスタンスの名前の前に ref キーワードが含まれること。

これらの条件を満たし、p という名前の Person オブジェクトへの参照を返すメソッドを、次の例に示します。

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

次に、メソッド シグネチャとメソッド本体の両方を示す、より完全な ref 戻り値の例を示します。

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

呼び出されるメソッドでは、値が参照渡しで値が返されるように戻り値を ref readonly として宣言し、返された値を呼び出し元のコードで変更できないようにすることもできます。 呼び出し元のメソッドを使うと、ローカルの ref readonly 参照変数に値を格納することで、返された値のコピーを回避できます。

次の例は、TitleAuthor という 2 つの String フィールドを持つ Book クラスを定義しています。 また、Book オブジェクトのプライベート配列を含む BookCollection クラスも定義しています。 個々のブック オブジェクトは、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();
    }
}

呼び出し元が GetBookByTitle によって返される値を ref ローカル変数として格納する場合、呼び出し元が戻り値に加えた変更が BookCollection オブジェクトに反映されます。次の例を参照してください。

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

goto ステートメント

次の例に示すように、goto ステートメントは、ラベルにマークされているステートメントに制御を移動します。

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.

前の例で示したように、goto ステートメントを使用して、入れ子になったループから抜け出すことができます。

ヒント

入れ子になったループを使用する場合は、別のメソッドに個別のループをリファクタリングすることを検討してください。 これにより、goto ステートメントを使用せずに、より簡単で読みやすいコードが発生する可能性があります。

次の例に示すように、switch ステートメントgoto ステートメントを使用して、定数ケース ラベルを持つ switch セクションに制御を移動することもできます。

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

switch ステートメント内では、ステートメント goto default; を使用して、default ラベルを持つ switch セクションに制御を移動することもできます。

指定した名前のラベルが現在の関数メンバーに存在しない場合、または goto ステートメントがラベルのスコープ内にない場合は、コンパイル時エラーが発生します。 つまり、goto ステートメントを使用し、現在の関数メンバーから、または入れ子になったスコープに制御を移動することはできません。

C# 言語仕様

詳細については、「C# 言語仕様」の次のセクションを参照してください。

関連項目