Home Full Site
C# 8 : switch expression (switch 식)

switch문 (switch statement)은 각 case 별로 값을 체크하여 분기하게 된다. switch식 (switch expression)은 기존의 case 블럭들을 보다 간결하게 식(expression)으로 표현한 것으로, 기존의 case, break, default 등과 같은 키워드를 사용하지 않고 각 case를 '(패턴/값) => (수식)' 와 같은 expression 으로 표현한다. 아래 예제는 Shape 객체를 받아들여 각 도형별 면적을 구하는 코드를 예시한 것이다. switch식은 switch문과 달리 switch식 앞에 변수명(shape)을 적게 되고, 각 case 블럭은 case/break 등을 쓰지 않고 => 을 사용하여 expression을 표현한다. 첫번째 식은 shape 가 null값인지 체크하는 것으로 만약 null이면 0을 리턴한다. 두번째 식은 shape가 Line 타입인지 체크해서 라인의 경우 면적이 없으므로 0을 리턴한다. 서번째 식은 shape가 Rectangle 타입인지 체크해서 사각형인 경우 너비와 높이를 곱한 값을 리턴한다. 마지막에 _ 은 switch문의 default와 같은 것으로 상단의 모든 경우가 아닌 경우를 표현한 것이다. 한가지 주의할 점은 switch식에서 => 다음 부분은 statement가 아니고 expression 이라는 점이다. 따라서, return r.Width * r.Height; 과 같이 쓰지 않고 r.Width * r.Height와 같이 쓴다.

예제

static double GetArea(Shape shape)
{
    // C# 8.0 switch expression
    double area = shape switch
    {
        null        => 0,
        Line _      => 0,
        Rectangle r => r.Width * r.Height,
        Circle c    => Math.PI * c.Radius * c.Radius,
        _           => throw new ArgumentException()
    };
    return area;
}



C# 8 : Property Pattern (속성 패턴) I

C# 8.0에서 도입된 Property Pattern (속성 패턴)은 객체의 속성을 사용하여 패턴 매칭을 할 수 있도록 한 것이다. 속성 패턴을 사용하면 복잡한 switch문을 보다 간결하게 switch식으로 표현할 수 있다. 아래 예제는 Customer 객체의 속성에 따라 요금(fee)을 판별하는 switch식으로서 Customer 클래스의 여러 속성들(예: cust.IsSenior, cust.Level)을 사용하여 여러 케이스별로 요금을 다르게 책정하게 된다. 속성 패턴은 하나 이상의 속성을 사용할 수 있는데, 예를 들어 아래 4번째 케이스는 Level 속성과 IsMinor 속성을 함께 사용하고 있으며, Level이 A이고 IsMinor가 false 인 경우를 다루고 있다.

예제

public decimal CalcFee(Customer cust)
{
    // Property Pattern (속성 패턴)
    decimal fee = cust switch
    {
        { IsSenior: true } => 10,
        { IsVeteran: true } => 12,
        { Level: "VIP" } => 5,
        { Level: "A", IsMinor: false} => 10,
        _ => 20
    };
    return fee;
}



C# 8 : Property Pattern (속성 패턴) II

객체의 속성을 체크하는 Property Pattern (속성 패턴)은 또한 아래와 같이 '객체 is {속성패턴}' 과 같은 형식을 사용할 수 있다. 이는 그 객체가 해당 속성값을 가지고 있는지를 체크하는 것이다.

C# 8.0 에서는 Nested 속성인 경우 계속 괄호 안에서 내포 속성을 길게 적어 주어야 하는데, 이는 C# 10에서 간소화 되었다.


예제

class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

class Address
{
    public string Country { get; set; } = "Korea";
    public string City { get; set; } = "Seoul";
}

class Program
{
    static void Main(string[] args)
    {
        var p = new Person { Name = "Kim", Address = new Address()};

        // Property pattern
        if (p is { Name: "Kim"})
        {
            Console.WriteLine("I am Kim");
        }

        // C# 8 Property pattern (nested property).
        // C# 10에서는 { Address.City : "Seoul" }와 같이 표현 가능
        if (p is { Address: { City: "Seoul"} })
        {
            Console.WriteLine("I live in Seoul");
        }
    }
}



C# 8 : Tuple Pattern (튜플 패턴)

하나의 변수가 아닌 복수 개의 변수들에 기반한 패턴 매칭을 위해 튜플 패턴 (Tuple Pattern)을 사용할 수 있다. 예를 들어, 신용도와 부채수준에 따른 신용한도를 산출하기 위해 2개의 변수(신용도과 부채수준)를 받아들이게 되는데, 아래 예제는 이렇게 복수 개의 변수들을 패턴 매칭에 사용하는 튜플 패턴을 예시한 것이다. 여기서 GetCreditLimit() 메서드는 creditScore와 debtLevel을 받아들여 이 두개의 값을 사용하여 적절한 크레딧 한도를 산출해서 리턴하는 것이다.

예제

static int GetCreditLimit(int creditScore, int debtLevel)
{
    // Tuple Pattern (튜플 패턴)
    var credit = (creditScore, debtLevel) switch
    {
        (850, 0) => 200, //max credit score with no debt
        var (c, d) when c > 700 => 100,
        var (c, d) when c > 600 && d < 50 => 80,
        var (c, d) when c > 600 && d >= 50 => 60,
        _ => 40
    };
    return credit;
}

static void Main(string[] args)
{
    int creditPct = GetCreditLimit(650, 30);
    Console.WriteLine(creditPct);
}



C# 8 : Positional pattern (위치 패턴)

만약 어떤 타입이 Deconstructor 를 가지고 있다면, Deconstructor로부터 리턴되는 속성들을 그 위치에 따라 패턴 매칭에 사용할 수 있는데, 이를 Positional pattern (위치 패턴)이라 한다.

예를 들어, 아래 예제에서 Point 클래스는 Deconstruct() 메서드를 가지고 있는데, 이 Deconstructor는 X, Y 속성을 순서대로 리턴하고 있다. 이 Point 객체를 입력 파라미터로 받아들이는 사분면() 메서드는 Point 객체(point)의 Deconstructor에서 리턴되는 속성들을 순서대로 받아 각 값들을 사용하게 된다. Deconstructor에서 리턴되는 속성들은 var (x, y)로 전달되고, 이 x, y 값의 범위를 when 조건으로 비교하여 몇사분면에 있는지를 판단하게 된다.


예제

class Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) => (X, Y) = (x, y);
    public void Deconstruct(out int x, out int y) =>
        (x, y) = (X, Y);
}

static string 사분면(Point point)
{
    //Positional pattern (위치 패턴)
    string quad = point switch
    {
        (0, 0) => "원점",
        var (x, y) when x > 0 && y > 0 => "1사분면",
        var (x, y) when x < 0 && y > 0 => "2사분면",
        var (x, y) when x < 0 && y < 0 => "3사분면",
        var (x, y) when x > 0 && y < 0 => "4사분면",
        var (_, _) => "X/Y축",
        _ => null
    };
    return quad;
}

static void Main(string[] args)
{
    var p = new Point(-5, -2);
    string q = 사분면(p);
    Console.WriteLine(q); // 3사분면
}



C# 8 : Recursive pattern (재귀 패턴)

C# 8.0에서 패턴은 다른 서브패턴(sub pattern)들을 포함할 수 있고, 한 서브패턴은 내부에 또 다른 서브패턴들을 포함할 수 있는데, 이러한 것을 재귀 패턴(Recursive pattern)이라 한다. 아래 예제는 변수 p가 Student 클래스인지 체크해서 만약 Student 타입이면 다시 속성 패턴을 사용해서 Graduated 속성이 false 인지(constant pattern)를 체크하고, 그리고 Name 속성이 non-null string 인지(type pattern)를 체크해서 non-null이면 Name 속성을 name 변수에 넣고 이를 yield return 하게 된다. 참고로 'Name: string name' 패턴은 Name 속성이 string 타입인지 체크하는데, 만약 Name is string에서 Name 속성이 null 이면 Name is string 이 false가 된다. 좀 더 보편적인 표현으로 'x is T y' 에서 x가 null 이면 항상 패턴매칭이 false 가 된다.

예제

IEnumerable<string> GetStudents(List<Person> people)
{
    foreach (var p in people)
    {
        // Recursive pattern (재귀 패턴)
        if (p is Student { Graduated: false, Name: string name })
        {
            yield return name;
        }
    }
}



© csharpstudy.com