次の方法で共有


ジャンプ ステートメント - breakcontinuereturn、および goto

ジャンプ ステートメントは無条件に制御を転送します。 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 ステートメントを使用します。

Ref が返す

既定では、 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 local と呼ばれる戻り値への参照である新しい変数を作成できます。 参照戻り値は、メソッドが何らかの変数への参照 (またはエイリアス) を返することを意味します。 その変数のスコープには、メソッドを含める必要があります。 その変数の有効期間は、メソッドの戻り値を超えて拡張する必要があります。 呼び出し元によるメソッドの戻り値に対する変更は、メソッドによって返される変数に対して行われます。

メソッドが 参照戻り値 を返すという宣言は、メソッドが変数に別名を返したことを示します。 設計の意図は、多くの場合、コードを呼び出すと、その変数にエイリアス (変更を含む) を介してアクセスすることです。 参照によって返されるメソッドは、戻り値の型を voidできません。

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

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

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

ref 戻り値は、呼び出し元のメソッドに 対する ref-safe コンテキスト である必要があります。 ということは:

  • 戻り値には、メソッドの実行を超える有効期間が必要です。 つまり、それを返すメソッドのローカル変数にすることはできません。 クラスのインスタンスまたは静的フィールド、またはメソッドに渡される引数を指定できます。 ローカル変数を返そうとすると、コンパイラ エラー CS8168 "参照によってローカル 'obj' を返すことはできません。これは ref local ではありません。" というエラーが発生します。
  • 戻り値をリテラル 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# 言語仕様」の次のセクションを参照してください。

こちらも参照ください