포스트

(C#) 5. 코드의 흐름 제어 (switch)

같은 변수를 값별로 나눌 때 쓰는 switch. C#은 C와 달리 case가 아래로 흘러내리는 걸 컴파일 단계에서 막는다는 점과, 여러 case를 묶는 법·switch 식까지 정리했다.

(C#) 5. 코드의 흐름 제어 (switch)

if 를 늘어놓는 대신

4편의 코드는 choice 하나를 계속 비교했다. 이렇게 같은 변수를 값별로 나누는 경우에 쓰는 문법이 따로 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;

namespace Switch
{
    class Program
    {
        static void Main(string[] args)
        {
            int choice = 0; // 0:가위, 1:바위, 2:보, 3:치트키

            switch (choice)
            {
                case 0:
                    Console.WriteLine("가위입니다.");
                    break;
                case 1:
                    Console.WriteLine("바위입니다.");
                    break;
                case 2:
                    Console.WriteLine("보입니다.");
                    break;
                case 3:
                    Console.WriteLine("치트키입니다.");
                    break;
                default:
                    Console.WriteLine("다 실패했습니다.");
                    break;
            }
        }
    }
}

if/else if로 쓸 때보다 나은 점이 두 개 있다. 비교 대상인 choice가 맨 위에 한 번만 나오니 “이 변수로 갈라진다”가 바로 보이고, 어떤 값들을 처리하는지가 세로로 나열되어 눈에 들어온다.

default4편의 마지막 else에 해당한다. 어디에 써도 문법상 통과하지만 관례상 맨 아래에 둔다.

break 를 빠뜨리면 컴파일이 안 된다

C를 먼저 봤다면 여기서 놀란다. C에서는 break를 안 쓰면 아래 case로 그대로 흘러내린다. 그게 의도일 때도 있지만 대부분은 실수였고, 찾기도 어려운 버그였다.

C#은 이걸 아예 막는다.

1
2
3
4
5
6
7
8
switch (choice)
{
    case 0:
        Console.WriteLine("가위입니다.");
        // break 가 없으면 컴파일 에러
    case 1:
        // ...
}

CS0163: 한 case 레이블에서 다른 case 레이블로 제어를 이동할 수 없습니다가 뜬다. 실수가 실행 시점이 아니라 빌드 시점에 잡힌다.

break 대신 return이나 throw로 끝내도 된다. 그 자리에서 흐름이 끝나는 게 확실하면 통과한다.

여러 값을 묶을 때

흘러내림을 막았으니 “0과 1을 같이 처리”는 어떻게 하나 싶었는데, 본문이 비어 있는 case는 이어 붙일 수 있다.

1
2
3
4
5
6
7
8
9
10
11
switch (choice)
{
    case 0:
    case 1:
    case 2:
        Console.WriteLine("정상 입력입니다.");
        break;
    case 3:
        Console.WriteLine("치트키입니다.");
        break;
}

case 0:case 1: 사이에 실행할 코드가 없으니 흘러내릴 코드도 없다. 그래서 허용된다. 결국 C#이 막는 건 “코드를 실행하고 나서 아래로 새는 것”뿐이다.

정말로 코드를 실행한 뒤 다른 case로 가고 싶으면 goto case를 쓴다. 의도를 명시적으로 적게 만든 것이다.

1
2
3
4
5
6
case 0:
    Console.WriteLine("가위입니다.");
    goto case 3;
case 3:
    Console.WriteLine("치트키입니다.");
    break;

써본 적은 거의 없다. 이게 필요해 보이면 대개 공통 부분을 함수로 빼는 게 나았다.

문자열도 된다

C에서는 정수와 문자만 됐는데 C#은 string도 받는다.

1
2
3
4
5
6
7
8
9
switch (command)
{
    case "attack":
        break;
    case "move":
        break;
    default:
        break;
}

case가 많아지면 컴파일러가 해시 기반으로 처리해서, if를 길게 늘어놓는 것보다 빠르다. 정수도 값이 촘촘하면 점프 테이블로 바꿔서 비교 없이 한 번에 찾아간다. case가 서너 개일 때는 차이가 없지만, 수십 개짜리 상태 기계에서는 의미가 있다.

case에 올 수 있는 건 컴파일 타임에 정해지는 상수뿐이다. 변수는 못 쓴다.

1
case someVariable:   // 컴파일 에러

if로 갈아타거나, C# 7 이상이면 뒤에 나오는 when 절을 쓴다.

enum 과 같이 쓰기

4편 마지막에 만든 enum을 여기 넣으면 제일 잘 맞는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Choice { Scissors, Rock, Paper, Cheat }

switch (choice)
{
    case Choice.Scissors:
        Console.WriteLine("가위입니다.");
        break;
    case Choice.Rock:
        Console.WriteLine("바위입니다.");
        break;
    case Choice.Paper:
        Console.WriteLine("보입니다.");
        break;
    case Choice.Cheat:
        Console.WriteLine("치트키입니다.");
        break;
}

case Choice. 까지만 치면 편집기가 남은 후보를 보여준다. enum에 값을 추가했을 때 어디를 고쳐야 하는지도 찾기 쉽다.

다만 enum을 다 처리했다고 default를 빼면 안 된다. enum은 내부적으로 int라서 정의에 없는 값도 들어올 수 있다.

1
Choice c = (Choice)99;    // 컴파일도 되고 실행도 된다

네트워크로 받은 값을 캐스팅하는 경우 실제로 이런 값이 온다. default에 로그를 남겨두면 이상한 값이 들어온 걸 알 수 있다.

switch 식

C# 8부터 값을 고르는 용도의 짧은 형태가 생겼다.

1
2
3
4
5
6
7
8
string text = choice switch
{
    Choice.Scissors => "가위입니다.",
    Choice.Rock     => "바위입니다.",
    Choice.Paper    => "보입니다.",
    Choice.Cheat    => "치트키입니다.",
    _               => "잘못된 입력입니다."
};

breakcase도 없고 _default 역할을 한다. 값을 만들어 변수에 담는 경우에는 이쪽이 훨씬 짧다.

좋은 점이 하나 더 있다. 모든 경우를 처리했는지 컴파일러가 확인한다. _를 빼고 빠뜨린 값이 있으면 경고가 나온다. 문장 형태의 switch는 이 검사를 안 해준다.

조건을 덧붙이려면 when을 쓴다. 상수만 올 수 있다는 제약이 여기서 풀린다.

1
2
3
4
5
6
7
string grade = hp switch
{
    <= 0            => "사망",
    < 30            => "위험",
    var h when h > 100 => "과회복",
    _               => "정상"
};

언제 if 를 쓰나

switch한 변수를 값으로 나눌 때 쓰는 것이다. 조건이 서로 다른 변수를 보거나 범위가 겹치면 if가 맞다.

1
2
if (hp <= 0) { }
else if (mp < 10 && !hasPotion) { }

이걸 switch로 억지로 바꾸면 오히려 읽기 어려워진다. switch가 어울리는지 판단하는 기준은 “switch (여기)에 넣을 변수가 하나로 정해지는가”였다.

정리하면

  • switch는 한 변수를 값별로 나눌 때 쓴다. 비교 대상이 맨 위에 한 번만 나온다
  • C#은 case가 아래로 흘러내리는 걸 컴파일 에러로 막는다. break, return, throw 중 하나로 끝내야 한다
  • 본문이 빈 case는 이어 붙여 여러 값을 묶을 수 있다. 의도적인 흘러내림은 goto case
  • case에는 컴파일 타임 상수만 온다. 변수 조건은 when이나 if
  • enum을 다 처리해도 default는 둔다. 정의에 없는 값이 캐스팅으로 들어올 수 있다
  • 값을 만드는 분기는 switch 식이 짧고, 빠뜨린 경우를 컴파일러가 알려준다
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.