본문 바로가기
개발/Unity 내일배움캠프 TIL

C# 패턴 일치 (Pattern Matching)

by 석시 2023. 9. 1.



패턴 일치?

패턴 일치라는 것은 어떤 대상이 특정한 특징, 즉 패턴을 가지고 있는지 테스트하는 것이다.

패턴 일치를 찾아보고 느낀 것이 설명이 뭔가 모호하다 해야할까나?

뭔가 설명해주는데 말이 어렵다.

그 이유는 패턴 일치라는 것이 추상적 의미이기 때문이다.

최대한 직관적이게 설명하자면 객체 컬렉션으로부터 내 조건에 맞는 객체들만 추출해내기 위해 객체에다 그 조건을 검사하는 것이다.

보통은 == 연산자를 사용하여 조건을 체크할 것이다.

우리가 평소 nullable에서 하던 null 검사도 패턴일치이다.

패턴 일치의 종류

패턴 일치의 종류는 세 가지가 있다.

  1. 상수 패턴 (Const Pattern)
  1. 선언 패턴 (Declaration Pattern)
  1. var 패턴 (var Pattern)

1. 상수 패턴 (Const Pattern)

상수 패턴은 특정 상수값, 즉 지원되는 리터럴과 일치 비교를 한다.

  • 각종 숫자형 (int, float 등등)
  • string, char
  • bool
  • enum
  • const
  • null

해당 자료형의 리터럴과 일치하는지 여부를 검사하는 것이다.

상수 패턴의 is

초기의 C#에서는 주로 ==를 사용했다.

하지만 C# 7.0 부터 is에 기능이 추가되면서 is를 상수 패턴으로 사용이 가능해졌다.

// numeric
var intVal = int.MaxValue;
Console.WriteLine($"{nameof(intVal)} {intVal is 1000000}");

2. 선언 패턴 (Declaration Pattern)

선언 패턴은 그 객체가 어떤 특정 클래스인지, 상속된 자식 클래스인지, 인터페이스를 상속 받았는지 등의 검사를 하는 것이다.

타입 패턴 (Type Pattern)이라고도 한다.

선언 패턴은 다음의 패턴들을 체크한다.

  • 값의 타입이 T
  • 값의 타입이 T를 상속 (class)
  • 값의 타입이 T를 구현 (interface)
  • 값의 타입이 T로 암시적 참조 변환을 지원
  • 값의 타입이 nullable value T? 일때 T 혹은 null
  • 값의 타입이 boxing 혹은 unboxing을 통해 T 타입으로 변경 가능

선언 패턴의 is

선언 패턴 역시 C# 7.0부터 기능이 추가된 is를 이용해 테스트가 가능하다.

object[] data = { 1, null, 10, new Circle(5), new Person("Lee"), "" };

foreach (object item in data)
{
    if (item is Circle circ)  // type pattern
    {
        WriteLine(circ.Radius);
    }
}

3. var 패턴 (var Pattern)

선언 패턴에서 그 타입만 var로 바꾼 것이다.

어라, 그러면 is var는 항상 참이지 않은가?

그래서 var 패턴은 신기한 것을 쓴다.

switch를 이용한 패턴 일치

여기서 switch는 분기를 위한 문이 아니다.

이 역시 C# 7.0 부터 패턴 일치를 위해 추가된 기능의 switch이다.

foreach (var shape in shapes)
{
    switch (shape)
    {
        // const pattern
        case null:
            WriteLine("Skip");
            break;

        // type pattern
        case Circle c:
            WriteLine($"원: {c.Radius * c.Radius * Math.PI}");
            break;
        case Rect r when r.Width == r.Height:
            WriteLine($"정사각형: {r.Width * r.Width}");
            break;
        case Rect r:
            WriteLine($"사각형: {r2.Width * r2.Height}");
            break;

        default:
            WriteLine("모르는 모양");
            break;
    }
}

C# 8.0 이후의 switch 패턴 일치

static int GetSourceLabel<T>(IEnumerable<T> source) => source switch
{
    Array array => 1,
    ICollection<T> collection => 2,
    _ => 3,
};

코드가 골때린다.

switch를 이용한 패턴 일치를 통해 해당 패턴에 맞는 값을 =>를 이용해 골라서 반환해줄 수가 있다.

사용 예를 좀더 첨부하겠다.

public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
    1 => 12.0m,
    2 => 20.0m,
    3 => 27.0m,
    4 => 32.0m,
    0 => 0.0m,
    _ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),
};

public record Point(int X, int Y);

static Point Transform(Point point) => point switch
{
    var (x, y) when x < y => new Point(-x, y),
    var (x, y) when x > y => new Point(x, -y),
    var (x, y) => new Point(x, y),
};

static void TestTransform()
{
    Console.WriteLine(Transform(new Point(1, 2)));  // output: Point { X = -1, Y = 2 }
    Console.WriteLine(Transform(new Point(5, 2)));  // output: Point { X = 5, Y = -2 }
}

public decimal CalculateDiscount(Order order) =>
order switch
{
    { Items: > 10, Cost: > 1000.00m } => 0.10m,
    { Items: > 5, Cost: > 500.00m } => 0.05m,
    { Cost: > 250.00m } => 0.02m,
    null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
    var someObject => 0m,
};

이외에도 무궁무진으로 활용할 수 있다.

C# 9.0, C# 10.0 까지도 패턴 일치 관련 기능이 추가가 되었다.

못다루는 패턴과 C# 기능에 대해선 다음 문서들을 참고하는 것이 좋겠다.

패턴 일치 개요 - C# 가이드
C#의 패턴 일치 식에 대한 자세한 정보
https://learn.microsoft.com/ko-kr/dotnet/csharp/fundamentals/functional/pattern-matching

패턴 - is 및 switch 식을 사용한 패턴 일치입니다.
'is' 및 'switch' 식에서 지원하는 패턴에 대해 알아봅니다. 'and', 'or' 및 'not' 연산자를 사용하여 여러 패턴을 결합합니다.
https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/operators/patterns


Uploaded by N2T