`switch` 식 - `switch` 키워드로 패턴 일치를 평가하는 식

switch 식 - switch 키워드로 패턴 일치를 평가하는 식

여러 후보 식 중에서 입력 값과 패턴이 맞는 하나를 골라 결과를 내는 게 switch 식이에요. 값을 계산해야 하는 곳에서 바로 switch 같은 동작을 쓰고 싶을 때 아주 유용하죠. 참고로, 문장(statement) 맥락에서 switch 같은 동작을 지원하는 switch 문이 따로 있는데, 그건 선택 문 문서의 switch 절에서 다뤄요.

출처: Microsoft Learn

본문

switch 식이 어떻게 생겼는지 예제부터 볼게요. 아래 코드는 온라인 지도에서 화면 방향을 나타내는 enum 값을 실제 나침반 방향으로 바꿔주는 예시예요.

namespace operators;

public static class SwitchExample
{
    public enum Direction
    {
        Up,
        Down,
        Right,
        Left
    }

    public enum Orientation
    {
        North,
        South,
        East,
        West
    }

    public static Orientation ToOrientation(Direction direction) => direction switch
    {
        Direction.Up    => Orientation.North,
        Direction.Right => Orientation.East,
        Direction.Down  => Orientation.South,
        Direction.Left  => Orientation.West,
        _ => throw new ArgumentOutOfRangeException(nameof(direction), $"Not expected direction value: {direction}"),
    };

    public static void Main()
    {
        var direction = Direction.Right;
        Console.WriteLine($"Map view direction is {direction}");
        Console.WriteLine($"Cardinal orientation is {ToOrientation(direction)}");
        // Output:
        // Map view direction is Right
        // Cardinal orientation is East
    }
}

위 예제에서 switch 식의 기본 구성 요소를 확인할 수 있어요.

  • switch 키워드 앞에 오는 식이 하나 있어요. 위 예제에서는 direction 메서드 매개 변수가 그 역할을 하죠.
  • switch 식 arm(분기)들이 콤마로 구분되어 나열돼요. 각 arm은 패턴(pattern), 생략할 수 있는 case guard, => 토큰, 그리고 으로 이루어져요.

이 예제의 switch 식은 두 가지 패턴을 사용해요.

  • 상수 패턴: Direction 열거형에 정의된 값들을 처리하기 위한 패턴이에요.
  • 무시 패턴: Direction 열거형에 해당하는 멤버가 없는 정수 값(예: (Direction)10)을 처리해요. 이 패턴 덕분에 switch 식이 완전(exhaustive)해져요.

[!IMPORTANT] switch 식이 지원하는 패턴에 대한 자세한 내용과 더 많은 예시는 패턴 문서를 참고하세요.

switch 식의 결과는, 입력 식과 패턴이 맞으면서 (존재한다면) case guard도 true로 평가되는 첫 번째 arm의 식 값이에요. arm들은 텍스트 순서대로 평가된다는 점 기억해두세요.

한 가지 주의할 점이 있어요. 위쪽 arm이 자신이 처리하는 모든 값을 다 일치시키는데도 아래쪽 arm이 선택될 수 없는 상황이 생기면, 컴파일러가 오류를 내요.

Case guard

패턴만으로는 특정 arm의 식을 평가할 조건을 다 표현하지 못할 때가 있어요. 그럴 때 쓰는 게 case guard예요. case guard는 패턴이 일치하는 것과 함께 추가로 만족해야 하는 또 하나의 조건이에요. 이 조건은 반드시 불리언(Boolean) 식이어야 하고, 패턴 뒤의 when 키워드 다음에 지정해요. 아래 예시를 볼게요.

public readonly struct Point
{
    public Point(int x, int y) => (X, Y) = (x, y);
    
    public int X { get; }
    public int Y { get; }
}

static Point Transform(Point point) => point switch
{
    { X: 0, Y: 0 }                    => new Point(0, 0),
    { X: var x, Y: var y } when x < y => new Point(x + y, y),
    { X: var x, Y: var y } when x > y => new Point(x - y, y),
    { X: var x, Y: var y }            => new Point(2 * x, 2 * y),
};

위 예제는 속성 패턴 안에 var 패턴이 중첩된 형태를 사용해요.

완전하지 않은(nonexhaustive) switch

switch 식의 어떤 패턴도 입력 값과 일치하지 않으면, 런타임에서 예외를 던져요. .NET Core 3.0 이상에서는 <xref:System.Runtime.CompilerServices.SwitchExpressionException?displayProperty=nameWithType> 예외가, .NET Framework에서는 <xref:System.InvalidOperationException> 예외가 발생해요. 대부분의 경우 switch 식이 가능한 모든 입력 값을 처리하지 않으면 컴파일러가 경고를 만들어 줘요. 다만 목록 패턴은 모든 입력을 처리하지 않아도 경고를 만들지 않아요.

유니언 형식의 경우, switch 식이 모든 case 형식을 처리하면 완전해요. catch-all arm까지는 필요 없어요. 다만 유니언 Value 속성의 null 상태가 "maybe null"이면, 경고를 피하려면 null도 처리해 줘야 해요. 자세한 내용은 유니언 완전성을 참고하세요.

[!TIP] switch 식이 모든 가능한 입력 값을 확실히 처리하도록 하려면 무시 패턴을 가진 arm을 하나 추가하면 돼요.

C# 언어 사양

더 자세한 내용은 C# 언어 사양switch 절을 참고하세요.

더 알아보기