점프 문 - break, continue, returngoto

점프 문은 컨트롤을 무조건 전송합니다. break은 가장 가까운 바깥쪽 반복 문 또는 switch을 종료합니다. continue은 가장 가까운 바깥쪽 반복 문의 새 반복을 시작합니다. return은 표시되는 함수의 실행을 종료하고 호출자에게 컨트롤을 반환합니다. goto은 레이블로 표시된 문으로 컨트롤을 전달합니다.

예외를 throw하고 무조건 컨트롤을 전송하는 throw 문에 대한 자세한 내용은 예외 처리 문 문서의 throw 섹션을 참조하세요.

break

break 문은 가장 가까운 복 문(즉, for, foreach, while 또는 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 문은 가장 가까운 바깥쪽 복 문(즉, for, foreach, while 또는 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 return)은 메서드가 호출자에게 참조로 반환하는 값입니다. 즉, 호출자는 메서드에서 반환된 값을 수정할 수 있으며 해당 변경 내용은 호출된 메서드의 개체 상태에 반영됩니다. 이렇게 하려면 다음 예제와 같이 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.");
}

참조 반환 값을 사용하면 메서드가 값이 아니라 변수 참조를 호출자에게 다시 반환할 수 있습니다. 그러면 호출자는 반환된 변수를 마치 값이나 참조로 반환된 것처럼 처리하도록 선택할 수 있습니다. 호출자는 참조 지역 변수라고 하는, 반환된 값에 대한 참조 자체인 새 변수를 만들 수 있습니다. ‘참조 반환 값’은 메서드가 일부 변수에 대한 ‘참조’(또는 별칭)를 반환한다는 것을 의미합니다. 해당 변수의 범위는 메서드를 포함해야 합니다. 해당 변수의 수명은 메서드의 반환 이후로 연장되어야 합니다. 호출자가 메서드의 반환 값을 수정하면 메서드가 반환한 변수가 수정됩니다.

메서드가 ‘참조 반환 값’을 반환한다는 선언은 메서드가 변수에 별칭을 반환함을 나타냅니다. 설계 의도에 따라 호출 코드가 별칭을 통해 해당 변수에 액세스하는 경우가 많습니다(변수 수정 포함). 참조로 반환하는 메서드는 void 반환 형식을 사용할 수 없습니다.

호출자가 개체의 상태를 수정하려면 참조 반환 값을 참조 변수로 명시적으로 정의된 변수에 저장해야 합니다.

ref 반환 값은 호출된 메서드 범위 내 다른 변수에 대한 별칭입니다. 참조 반환의 사용은 다음과 같이 별칭이 있는 변수를 사용하는 것으로 해석할 수 있습니다.

  • 해당 값을 할당할 경우 별칭이 있는 변수에 값을 할당하는 것입니다.
  • 해당 값을 읽을 경우 별칭이 있는 변수의 값을 읽는 것입니다.
  • 참조로 반환하는 경우 동일한 변수에 대한 별칭을 반환하는 것입니다.
  • 다른 메서드에 참조로 전달하는 경우 별칭이 있는 변수에 대한 참조를 전달하는 것입니다.
  • 참조 로컬 별칭을 만들 경우 동일한 변수에 대한 새 별칭을 만드는 것입니다.

ref 반환은 호출 메서드에 대한 ref-safe-context이어야 합니다. 이는 다음을 의미합니다.

  • 반환 값의 수명은 메서드 실행 이후까지 연장되어야 합니다. 즉, 반환하는 메서드의 지역 변수일 수 없습니다. 클래스의 인스턴스 또는 정적 필드이거나 메서드에 전달된 인수일 수 있습니다. 지역 변수를 반환하려고 하면 컴파일러 오류 CS8168, “’obj’ 로컬은 참조 로컬이 아니므로 참조로 반환할 수 없습니다.”가 생성됩니다.
  • 반환 값은 리터럴 null일 수 없습니다. 참조 반환이 있는 메서드는 값이 현재 null(인스턴스화되지 않음) 값이거나 값 형식이 nullable 값 형식인 변수에 별칭을 반환할 수 있습니다.
  • 반환 값은 상수, 열거형 멤버, 속성의 값 형식 반환 값 또는 classstruct의 메서드일 수 없습니다.

또한 참조 반환 값은 비동기 메서드에서 허용되지 않습니다. 비동기 메서드는 실행을 마치기 전에 반환할 수 있지만 반환 값을 여전히 알 수 없습니다.

참조 반환 값을 반환하는 메서드는 다음을 수행해야 합니다.

  • 반환 형식 앞에 ref 키워드를 포함합니다.
  • 메서드 본문의 각 return 문에는 반환된 인스턴스의 이름 앞에 ref 키워드가 포함됩니다.

다음 예제에서는 이러한 조건을 충족하면서 p라는 이름의 Person 개체에 대한 참조를 반환하는 메서드를 보여줍니다.

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

다음은 메서드 시그니처와 메서드 본문을 모두 보여 주는 더 완전한 참조 반환의 예입니다.

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 참조 변수에 값을 저장하여 반환된 값을 복사하지 않도록 할 수 있습니다.

다음 예제에서는 두 개의 String 필드 TitleAuthor가 있는 Book 클래스를 정의합니다. 또한 Book 개체의 private 배열을 포함하는 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 메서드에서 참조 로컬로 반환된 값을 저장하는 경우 호출자가 반환 값을 변경하면 다음 예제와 같이 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 문을 사용하여 상수 case 레이블이 있는 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# 언어 사양의 다음 섹션을 참조하세요.

참고 항목