패턴 매칭

황현중·2025년 12월 11일
  • 선언 패턴 / var 패턴 / discard(_) 패턴
  • 위치(포지셔널) 패턴 – 튜플 / Deconstruct / record
  • 논리 패턴 – and, or, not
  • 리스트 패턴(List Pattern) 응용
  • 재귀(Recursive) 패턴 – 패턴들을 섞어서 쓰기
  • when 가드와 패턴의 조합
  • foreach + 튜플/패턴 활용

1. 선언 패턴, var 패턴, discard(_) 패턴

1-1. 선언 패턴 (Declaration Pattern)

패턴 매칭의 가장 기본 형태는 “타입 검사 + 캐스팅”을 한 줄로 합친 선언 패턴이다.

if (obj is string s)
{
    // s는 이미 string으로 캐스팅된 상태
    Console.WriteLine(s.ToUpper());
}
  • obj is string s의 의미
    • objstring 타입이면 true
    • 동시에 s라는 이름의 지역 변수에 캐스팅된 결과를 넣어줌

예전에는

if (obj is string)
{
    string s = (string)obj;
    ...
}

이렇게 타입 체크와 캐스팅을 따로 했지만, 선언 패턴 덕분에 한 줄로 줄일 수 있다.

1-2. var 패턴

var 패턴은 “어떤 값이든 받되 이름만 붙이고 싶다”는 느낌으로 사용할 수 있다.

if (obj is var x)
{
    // 항상 true, obj를 x라는 이름으로 받는 패턴
    Console.WriteLine(x);
}

이렇게만 보면 굳이 쓸 이유가 별로 없는데, 다른 패턴과 섞을 때 “중간 값에 이름 붙이기” 용도로 쓸 수 있다.

if (n is > 0 and < 100 and var inRange)
{
    Console.WriteLine($"범위 안 숫자: {inRange}");
}
  • n is > 0 and < 100으로 0 < n < 100 범위를 체크
  • and var inRange로 그 값을 inRange라는 이름으로 꺼내 사용

즉, 조건 검사 + 별칭(alias) 부여 용도로 쓰는 패턴이다.

1-3. discard(_) 패턴

_는 “이 값은 굳이 쓰지 않을 거라서 버리겠다”는 의미의 패턴이다.

(int x, int y, int z) point = (1, 2, 3);

if (point is (_, _, 3))
{
    Console.WriteLine("세 번째 값이 3이다");
}
  • (_, _, 3) → 첫 번째, 두 번째 값은 신경 안 쓰고, 세 번째가 3인지 여부만 확인

가장 익숙한 예는 switchdefault 역할이다.

return code switch
{
    200 => "OK",
    400 => "Bad Request",
    _   => "Unknown"
};

_는 “나머지 모든 경우”를 의미하는 패턴이며, 값을 버리는 패턴(discard)이라고 이해하면 된다.


2. 위치(포지셔널) 패턴 – 튜플 / Deconstruct / record

위치(포지셔널) 패턴은 튜플이나 Deconstruct를 제공하는 타입을 “좌표”처럼 다룰 때 사용하는 패턴이다.

2-1. 튜플과 위치 패턴

(int x, int y) p = (10, 0);

string Describe((int x, int y) point) => point switch
{
    (0, 0)      => "원점",
    (_, 0)      => "X축 위",
    (0, _)      => "Y축 위",
    var (a, b)  => $"일반 점 ({a}, {b})"
};
  • (0, 0) : x == 0, y == 0 일 때
  • (_, 0) : y == 0이고 x는 어떤 값이든
  • (0, _) : x == 0이고 y는 어떤 값이든
  • var (a, b) : 나머지 모든 경우 → 튜플 요소를 a, b로 꺼냄

2-2. Deconstruct를 가진 클래스/struct에도 사용 가능

튜플뿐 아니라, Deconstruct 메서드를 가진 타입도 위치 패턴으로 매칭할 수 있다.

class Range
{
    public int Start { get; }
    public int End   { get; }

    public Range(int start, int end) => (Start, End) = (start, end);

    public void Deconstruct(out int start, out int end)
        => (start, end) = (Start, End);
}

string Describe(Range r) => r switch
{
    (0, 0)    => "빈 구간",
    (0, > 0)  => "0 이상 양수 구간",
    var (s, e) => $"[{s} ~ {e}] 구간"
};

여기서 (0, > 0) 같은 부분은 “위치 패턴 + 관계 패턴(> 0)”이 섞인 형태다.


3. 논리 패턴: and, or, not

논리 패턴은 기존의 &&, ||, !와 비슷한 역할을 하지만, 패턴 문법 안에서 자연스럽게 섞어서 쓸 수 있는 버전이다.

if (n is >= 0 and <= 100)
{
    // 0~100 사이
}

if (n is < 0 or > 100)
{
    // 범위 밖
}

if (obj is not null)
{
    // null 아님
}

예를 들어 ERP 느낌으로 상태와 금액을 같이 볼 수도 있다.

if (order is { Status: "CLOSE" or "CANCEL", Amount: > 0 })
{
    Console.WriteLine("종료/취소 상태지만 금액은 0보다 큼");
}
  • Status: "CLOSE" or "CANCEL" → 두 상태 중 하나면 매칭
  • Amount: > 0 → 금액이 0보다 큰 경우만

이렇게 패턴만으로도 꽤 복잡한 조건을 표현할 수 있다.


4. 리스트 패턴(List Pattern) – 배열/리스트의 “모양”으로 매칭

C# 11부터 추가된 리스트 패턴은 배열/리스트의 형태로 매칭하는 기능이다.

int[] arr = { 1, 2, 3, 4 };

string Describe(int[] xs) => xs switch
{
    []                => "빈 배열",
    [0]               => "0 하나만",
    [1, 2, 3]         => "1,2,3 딱 세 개",
    [1, ..]           => "1로 시작",
    [.., 4]           => "4로 끝",
    [_, _, _, _]      => "길이 4",
    [1, .. var rest]  => $"1로 시작 + 나머지 {rest.Length}개",
    _                 => "그 외"
};
  • [] → 빈 배열
  • [0] → 요소가 하나이고 그 값이 0
  • [1, 2, 3] → 정확히 1,2,3 세 개일 때
  • [1, ..] → 첫 번째가 1이고 뒤에는 뭐든
  • [.., 4] → 마지막이 4
  • [1, .. var rest] → 1로 시작하고, 나머지를 rest로 받아서 사용

ERP 예를 살짝 섞어 보면:

// 특정 일자의 입출고 기록이 [입고, 출고, 출고] 순인지 체크
string CheckIoPattern(string[] iogbns) => iogbns switch
{
    ["I", "O", "O"] => "입고 후 2번 출고 패턴",
    ["I", .., "O"]  => "입고로 시작해서 출고로 끝나는 패턴",
    ["O", ..]       => "출고로 시작하는 특이 패턴",
    _               => "일반 패턴"
};

리스트 패턴을 한 번 익혀두면 시간 순서, 상태 변경 패턴 같은 것도 꽤 직관적으로 표현할 수 있다.


5. 재귀(Recursive) 패턴 – 패턴끼리 섞어서 쓰기

C# 패턴의 재미는 여러 패턴들을 섞어서 쓸 수 있다는 점이다.
속성 패턴, 확장 속성 패턴, 관계 패턴, 논리 패턴 등을 한 줄에 모아서
“조건 스펙”을 딱 표처럼 표현할 수 있다.

class Order
{
    public string Status { get; set; }
    public decimal Amount { get; set; }
    public Customer Customer { get; set; }
}

class Customer
{
    public string Grade { get; set; }  // A, B, C 등급
}

string Describe(Order o) => o switch
{
    { Status: "CLOSE", Customer.Grade: "A", Amount: > 1_000_000m }
        => "고액 프리미엄 종료 주문",

    { Status: "OPEN", Customer.Grade: "A" or "B" }
        => "우수 고객 진행 중 주문",

    { Status: "OPEN", Amount: <= 0 }
        => "금액 없는 진행 주문",

    null
        => "null 주문",

    _
        => "일반 주문"
};

예를 들어 첫 번째 패턴:

{ Status: "CLOSE", Customer.Grade: "A", Amount: > 1_000_000m }
  • 속성 패턴: Status, Amount
  • 확장 속성 패턴: Customer.Grade
  • 관계 패턴: Amount: > 1_000_000m
  • 이 모든 걸 한 줄로 조합한 형태

기존에 if/else로 길게 늘어졌던 조건문을 switch + 패턴 조합으로 정리하면 “조건 정의서”를 코드로 옮겨 놓은 것처럼 깔끔하게 보이는 효과가 있다.


6. 패턴 + when 가드

when 가드는 패턴 뒤에 “추가 필터”를 붙이고 싶을 때 사용한다.

string Describe(int n) => n switch
{
    var x when x < 0      => "음수",
    var x when x == 0     => "0",
    var x when x % 2 == 0 => "양수 짝수",
    _                     => "양수 홀수"
};

패턴으로 대략적인 범위를 잡고, 세부 조건은 when으로 필터링할 수도 있다.

string Describe(Order o) => o switch
{
    { Status: "CLOSE" } when o.Amount == 0
        => "금액 0인 종료 주문",

    { Status: "CLOSE" }
        => "일반 종료 주문",

    _
        => "기타"
};

이런 식으로 “같은 패턴” 안에서 금액이 0인 경우와 아닌 경우를 when 가드로 나눠서 표현할 수 있다.


7. foreach와 튜플/패턴

foreach에서도 튜플 분해와 패턴을 같이 사용할 수 있다.

7-1. 튜플 분해

var users = new List<(string Name, int Age)>
{
    ("A", 10),
    ("B", 20),
    ("C", 30)
};

foreach (var (name, age) in users)
{
    Console.WriteLine($"{name} / {age}");
}

각 튜플 요소를 name, age로 바로 꺼내 쓸 수 있어 user.Name, user.Age보다 더 가볍게 데이터를 다룰 수 있다.

7-2. 튜플 + 패턴 매칭

foreach (var (name, age) in users)
{
    var grade = age switch
    {
        < 13 => "어린이",
        < 20 => "청소년",
        _    => "성인"
    };

    Console.WriteLine($"{name}는 {grade}");
}

튜플로 받은 데이터를 바로 switch 식 + 패턴으로 분류해서 코드를 간단하고 읽기 좋게 구성할 수 있다.


8. 전체 정리

C# 패턴 매칭을 머릿속에 이렇게 정리해 두면 편하다.

8-1. 어디서 쓰는가?

  • if (x is 패턴)
  • switch (x) { case 패턴: ... }
  • x switch { 패턴 => 결과, ... } (switch 식)

8-2. 어떤 패턴들이 있는가?

  • 선언 패턴: is string s
  • var 패턴: is var x
  • discard 패턴: _
  • 상수 패턴: is 0, case "CLOSE":
  • 관계 패턴: <, >, <=, >=
  • 논리 패턴: and, or, not
  • 속성 패턴: { Prop: ..., Other: ... }
  • 확장 속성 패턴: { A.B: ..., A.C: ... }
  • 위치(포지셔널) 패턴: (x, y), Deconstruct
  • 리스트 패턴: [], [1, ..], [.., 4], [1, .. var rest]
  • when 가드: 패턴 뒤에 추가 조건

0개의 댓글