(C#) 5. 코드의 흐름 제어 (switch)
같은 변수를 값별로 나눌 때 쓰는 switch. C#은 C와 달리 case가 아래로 흘러내리는 걸 컴파일 단계에서 막는다는 점과, 여러 case를 묶는 법·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가 맨 위에 한 번만 나오니 “이 변수로 갈라진다”가 바로 보이고, 어떤 값들을 처리하는지가 세로로 나열되어 눈에 들어온다.
default는 4편의 마지막 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 => "치트키입니다.",
_ => "잘못된 입력입니다."
};
break도 case도 없고 _가 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이나ifenum을 다 처리해도default는 둔다. 정의에 없는 값이 캐스팅으로 들어올 수 있다- 값을 만드는 분기는
switch식이 짧고, 빠뜨린 경우를 컴파일러가 알려준다