선택 문(Selection statements) - `if`, `if-else`, `switch`

선택 문(Selection statements) - if, if-else, switch

프로그램이 여러 갈래 중 어떤 길을 따라 실행될지는 보통 표현식의 값에 따라 달라져요. C#에서 그 분기를 담당하는 게 바로 if, if-else, switch 문이에요. 조건이 참인지에 따라 한 덩어리의 코드를 실행할지, 둘 중 어떤 길로 갈지, 아니면 여러 패턴 중 어떤 걸 실행할지 정해주는 문법이라고 생각하면 돼요.

출처: Microsoft Learn

본문

선택 문은 표현식의 값에 따라 실행 가능한 여러 경로 중에서 실행할 문을 골라주는 일을 해요. if은 주어진 Boolean 표현식이 true로 평가될 때에만 어떤 문을 실행하고, if-else은 Boolean 표현식 하나로 두 코드 경로 중 하나를 고르게 해주죠. switch은 표현식과 패턴 매치 결과에 따라 실행할 문 목록을 선택해요.

if

if 문은 두 가지 형태 중 하나로 쓰여요.

  • else 부분이 있는 if 문은 Boolean 표현식의 값에 따라 두 문 중 하나를 실행해요. 다음 예시를 볼게요.
DisplayWeatherReport(15.0);  // Output: Cold.
DisplayWeatherReport(24.0);  // Output: Perfect!

void DisplayWeatherReport(double tempInCelsius)
{
    if (tempInCelsius < 20.0)
    {
        Console.WriteLine("Cold.");
    }
    else
    {
        Console.WriteLine("Perfect!");
    }
}
  • else 부분이 없는 if 문은 Boolean 표현식이 true로 평가될 때에만 본문을 실행해요. 다음 예시를 볼게요.
DisplayMeasurement(45);  // Output: The measurement value is 45
DisplayMeasurement(-3);  // Output: Warning: not acceptable value! The measurement value is -3

void DisplayMeasurement(double value)
{
    if (value < 0 || value > 100)
    {
        Console.Write("Warning: not acceptable value! ");
    }

    Console.WriteLine($"The measurement value is {value}");
}

여러 조건을 확인하고 싶을 땐 if 문을 중첩해서 쓸 수도 있어요.

DisplayCharacter('f');  // Output: A lowercase letter: f
DisplayCharacter('R');  // Output: An uppercase letter: R
DisplayCharacter('8');  // Output: A digit: 8
DisplayCharacter(',');  // Output: Not alphanumeric character: ,

void DisplayCharacter(char ch)
{
    if (char.IsUpper(ch))
    {
        Console.WriteLine($"An uppercase letter: {ch}");
    }
    else if (char.IsLower(ch))
    {
        Console.WriteLine($"A lowercase letter: {ch}");
    }
    else if (char.IsDigit(ch))
    {
        Console.WriteLine($"A digit: {ch}");
    }
    else
    {
        Console.WriteLine($"Not alphanumeric character: {ch}");
    }
}

이건 나중에 어떤 조건을 먼저 검사하느냐가 순서대로 중요한 if-else if 형태인데요, 위에서부터 차례로 맞는 조건을 찾아가기 때문에 놓치기 쉬운 부분이에요. 중첩된 else if는 실제로는 if 안에 또 if가 들어간 구조와 같아요. 식(expression)이 필요한 자리에서는, 조건 연산자 ?:를 쓰면 Boolean 표현식 값에 따라 두 식 중 하나를 평가할 수 있어요.

switch

switch 문은 match 표현식(pattern match)과의 매치 결과에 따라 실행할 일련의 문을 골라줘요. 다음 예시를 볼게요.

DisplayMeasurement(-4);  // Output: Measured value is -4; too low.
DisplayMeasurement(5);  // Output: Measured value is 5.
DisplayMeasurement(30);  // Output: Measured value is 30; too high.
DisplayMeasurement(double.NaN);  // Output: Failed measurement.

void DisplayMeasurement(double measurement)
{
    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;
    }
}

위 예시의 switch 문은 두 가지 패턴을 사용해요.

[!IMPORTANT] switch 문이 지원하는 패턴에 대한 자세한 내용은 Patterns를 참고하세요.

위 예시는 default 케이스도 보여주고 있어요. default 케이스는 match 표현식이 다른 어떤 케이스 패턴과도 일치하지 않을 때 실행할 문을 지정해줘요. match 표현식이 어떤 케이스 패턴과도 일치하지 않고 default 케이스도 없다면, 제어는 switch 문을 그대로 빠져나가버려요.

switch 문은 match 표현식과 일치하는 case 패턴을 가진 첫 번째 *switch 구역(switch section)*의 문 목록을 실행하는데, 그 구역에 case guard가 있다면 그것도 true로 평가돼야 해요. switch 문은 case 패턴을 텍스트 순서(위에서 아래로)대로 평가해요. switch 문에 도달할 수 없는 case가 있으면 컴파일러가 오류를 내요. 앞의 case가 이미 그 case를 처리하거나, 패턴이 아예 매치될 수 없는 경우에 그런 오류가 발생하지요.

[!NOTE] default 케이스는 switch 문 안 어디에든 올 수 있어요. 위치와 상관없이, default 케이스는 다른 모든 case 패턴이 매치되지 않거나, 어느 switch 구역에서 goto default; 문이 실행될 때만 평가돼요.

하나의 switch 구역에 여러 case 패턴을 지정할 수도 있어요.

DisplayMeasurement(-4);  // Output: Measured value is -4; out of an acceptable range.
DisplayMeasurement(50);  // Output: Measured value is 50.
DisplayMeasurement(132);  // Output: Measured value is 132; out of an acceptable range.

void DisplayMeasurement(int measurement)
{
    switch (measurement)
    {
        case < 0:
        case > 100:
            Console.WriteLine($"Measured value is {measurement}; out of an acceptable range.");
            break;
        
        default:
            Console.WriteLine($"Measured value is {measurement}.");
            break;
    }
}

switch 문 안에서는 한 switch 구역에서 다음 switch 구역으로 제어가 흘러내려갈(fall through) 수 없어요. 이 섹션의 예시들처럼 보통 각 switch 구역 끝에 break 문을 써서 제어를 switch 문 밖으로 넘겨요. 제어를 switch 밖으로 빼내는 데는 return이나 throw 문을 쓸 수도 있어요. fall-through처럼 다른 switch 구역으로 제어를 넘기고 싶을 때는 goto을 사용할 수 있어요.

[!IMPORTANT] 모든 switch 구역break, goto, return 중 하나로 끝나야 해요. 한 switch 구역에서 다음으로 흘러내려가면 컴파일 오류가 나요. 하지만 같은 switch 구역에 여러 switch 레이블을 붙일 수는 있어요. 위 예시의 case < 0:처럼요. 이렇게 의도적으로 설계한 덕분에, 같은 로직을 공유하거나 서로 의존하는 여러 케이스를 한 번에 간결하게 처리할 수 있어요.

식이 필요한 자리에서는 switch을 써서, 표현식과의 패턴 매치에 따라 후보 식 목록 중 하나를 평가할 수도 있어요.

[!IMPORTANT] switch 식switch 문의 차이:

  • switch 문은 코드 블록 안에서 실행 흐름을 제어할 때 쓰여요.
  • switch 식은 주로 값을 반환하거나 할당하는 자리에서 쓰여요. 식 본문 멤버(expression-bodied members) 같은 곳이 대표적이죠.
  • switch 식의 case 구역은 비어 있을 수 없지만, switch 문의 case 구역은 비어 있을 수 있어요.

Case guards

case 패턴만으로는 switch 구역을 실행할 조건을 제대로 표현하지 못하는 경우가 있어요. 그럴 때 case guard를 쓰면 돼요. case guard는 패턴이 매치된 것에 더해 함께 충족돼야 하는 조건인데, 반드시 Boolean 식이어야 해요. 패턴 뒤에 when 키워드를 쓰고 case guard를 지정하면 됩니다. 다음 예시를 볼게요.

DisplayMeasurements(3, 4);  // Output: First measurement is 3, second measurement is 4.
DisplayMeasurements(5, 5);  // Output: Both measurements are valid and equal to 5.

void DisplayMeasurements(int a, int b)
{
    switch ((a, b))
    {
        case (> 0, > 0) when a == b:
            Console.WriteLine($"Both measurements are valid and equal to {a}.");
            break;

        case (> 0, > 0):
            Console.WriteLine($"First measurement is {a}, second measurement is {b}.");
            break;

        default:
            Console.WriteLine("One or both measurements are not valid.");
            break;
    }
}

위 예시는 위치 패턴(positional patterns)관계 패턴을 중첩해서 쓰고 있어요. 두 측정값을 쌍으로 받아서, 둘 다 양수이면서 같은 값이면 a == b 조건까지 만족하는 첫 번째 케이스로, 그게 아니고 둘 다 양수이면 두 번째 케이스로, 그 밖에는 default로 흘러가요. case guard(when)는 패턴이 매치됐더라도 그 추가 조건이 참일 때만 해당 구역을 선택하게 해주는 것이라, 겉보기에 비슷한 케이스들을 세밀하게 나눌 수 있어요.

C# 언어 사양

자세한 내용은 C# 언어 사양의 다음 섹션을 참고하세요.

패턴에 대한 자세한 내용은 C# 언어 사양Patterns and pattern matching 섹션을 참고하세요.

더 알아보기