재우니의 블로그

 

 

C# 에는 Pattern matching(패턴 일치) 라는 기술이 있습니다. 

특정 속성이 있는 경우 주어진 표현식이 특정 기준 세트와 일치하는지 테스트하는 데 사용됩니다. 따라서 우리가 이야기할 "패턴"은 예상되는 형식에 해당하는 올바른 모양을 가진 코드 표현식에 관한 것입니다.

 

Pattern matching(패턴 일치) 는 두 가지 중요한 키워드 is switch 에 의존 합니다.

 

샘플 예제를 통해 알아보죠.😊😊😊😊😊

using System;

public class Hero { ... }
public class Warrior : Hero { ... }
public class Mage : Hero { ... }

public static class Program
{
    private static void DisplayHero(Hero h)
    {
        if (h is Warrior) {
            Console.WriteLine("Hero is a Warrior");
        }
        else if (h is Mage) {
            Console.WriteLine("Hero is a Mage");
        }
        else {
            Console.WriteLine("Unknown hero type! (or null?)");
        }
    }

    public static void Run()
    {
        Warrior warrior = new Warrior();
        Mage mage = new Mage();
        DisplayHero(warrior);
        DisplayHero(mage);
        DisplayHero(null);
    }

    // output:
    //
    // Hero is a Warrior
    // Hero is a Mage
    // Unknown hero type! (or null?)

}

 

위의 구문을 switch 표현식을 활용하여 일치 match 결과 기반으로 실행해 보죠

 

using System;

public static class Program
{
    private enum LightState
    {
        ON,
        OFF,
        BLINKING
    }

    private static string LightStateMessage(LightState state)
    {
        return state switch {
            LightState.ON => "Tadaa!",
            LightState.OFF => "It's pitch black...",
            LightState.BLINKING => "Uh-oh.",
            _ => "<unknown>",
        };
    }

    public static void Run()
    {
        Console.WriteLine($"ON       => {LightStateMessage(LightState.ON)}");
        Console.WriteLine($"OFF      => {LightStateMessage(LightState.OFF)}");
        Console.WriteLine($"BLINKING => {LightStateMessage(LightState.BLINKING)}");
    }

    // output:
    //
    // ON       => Tadaa!
    // OFF      => It's pitch black...
    // BLINKING => Uh-oh.

}

 

위에 기재된 switch 은 switch 문이 아닌 표현식으로 사용합니다. 

 

몇가지 간단한 예

 

 

null 체크 확인하기

 

is 키워드를 통해 nullable 여부를 체크 가능합니다. 

using System;

public static class Program
{

    private static bool IsInt(int? variable)
    {
        return (variable is int);
    }

    public static void Run()
    {
        Console.WriteLine($"11   => {IsInt(11)}");
        Console.WriteLine($"null => {IsInt(null)}");
    }

    // output:
    //
    // 11   => True
    // null => False

}

 

nullable 변수 값을 받아서 활용하는 방법도 가능합니다. 

is 구문 끝에 변수 i 를 만들어서 원하는 개발 코드를 작성 합니다.

 

using System;

public static class Program
{

    private static int? IncrementNullableInt(int? variable)
    {
        if (variable is int i) return i + 1;
        else return null;
    }

    public static void Run()
    {
        Console.WriteLine($"11   => {IncrementNullableInt(11)}");
        Console.WriteLine($"null => {IncrementNullableInt(null)}");
    }

    // output:
    //
    // 11   => 12
    // null => 

}

 

인터페이스 Type 확인하기

 

is 키워드를 통해 인터페이스 타입 체크한 다음, 이를 변수를 만들어 활용 가능합니다.

 

using System;
using System.Collections;
using System.Collections.Generic;

public static class Program
{

    private static int? Size(IEnumerable sequence)
    {
        if (sequence is Array array) return array.Length;
        else if (sequence is IList list) return list.Count;
        else if (sequence is IDictionary dict) return dict.Count;
        else return null;
    }

    public static void Run()
    {
        int[] arr = new int[] { 1, 2, 3, 4, 5 };
        List<int> list = new List<int>() { 1, 2 };
        Dictionary<int, string> dict = new Dictionary<int, string>()
        { { 1, "a" },
          { 2, "b" },
          { 3, "c" } };
        Console.WriteLine($"Size(arr)  => {Size(arr)}");
        Console.WriteLine($"Size(list) => {Size(list)}");
        Console.WriteLine($"Size(dict) => {Size(dict)}");
        Console.WriteLine($"Size(null) => {Size(null)}");
    }

    // output:
    //
    // Size(arr)  => 5
    // Size(list) => 2
    // Size(dict) => 3
    // Size(null) => 

}

 

열거형 또는 상수와 비교하기

 

열거형 enum 을 통해서 받게 되면 실수로 잘못된 입력값을 받지 않게 가이드 해주기도 합니다.

using System;

public enum ShipStatus
{
    Healthy,
    Damaged,
    Destroyed
}

public class Ship
{
    public ShipStatus status = ShipStatus.Healthy;
    
    public bool CanFly() {
        if (status == ShipStatus.Healthy) {
            return true;
        }
        else if (status == ShipStatus.Damaged) {
            return true;
        }
        else if (status == ShipStatus.Destroyed) {
            return false;
        }
        else {
            return false;
        }
    }
}

public static class Program
{

    public static void Run()
    {
        Ship ship = new Ship();
        Console.WriteLine($"STATUS: {ship.status}. Can fly? {ship.CanFly()}");
        ship.status = ShipStatus.Damaged;
        Console.WriteLine($"STATUS: {ship.status}. Can fly? {ship.CanFly()}");
        ship.status = ShipStatus.Destroyed;
        Console.WriteLine($"STATUS: {ship.status}. Can fly? {ship.CanFly()}");
    }

    // output:
    //
    // STATUS: Healthy. Can fly? True
    // STATUS: Damaged. Can fly? True
    // STATUS: Destroyed. Can fly? False

}

 

switch 명령문을 사용하여 상황을 약간 단순화 할 수도 있습니다.

 

public bool CanFly() {
    switch (status)
    {
        case ShipStatus.Healthy:
            return true;
        case ShipStatus.Damaged:
            return true;
        case ShipStatus.Destroyed:
            return false;
        default:
            return false;
    }
}

 

 

하지만, switch 표현식은 더 나아가 모든 중간(불필요한) 키워드와 중괄호를 완전히 제거합니다 !

 

이는 분명히 값을 직접 반환 할 때만 작동합니다. 주어진 경우에 대해 올바른 결과를 얻기 전에 계산을 수행해야 하는 경우 switch 문이 더 적합할 수 있습니다. 

public bool CanFly() {
    return status switch {
        ShipStatus.Healthy => true,
        ShipStatus.Damaged => true,
        ShipStatus.Destroyed => false,
        _ => false,
    };
}

 

switch 은 conditions 조건에서도 다음과 같은 표현식을 사용할 수도 있습니다 .

 

public class Ship
{
    public int healthpoints = 100;
    
    public bool CanFly() {
        return healthpoints switch {
            > 50 => true,
            _ => false,
        };
    }
}

 

좀 더 복잡한 데이터 사용

 

 

위의 예제는 다양한 분기에 대해 일치하는 입력이 하나뿐이었습니다. 하지만 같은 유형의 두 값을 확인해야 하는 경우에는 어떻게 해야 할가요?

 

 

switch표현식 에서 여러 입력 처리

 

두 개로 분리된 조건에 따라 switch 구문을 사용했습니다.

public class Ship
{
    public ShipStatus status = ShipStatus.Healthy;
    public bool pilotIsHuman;
    
    public bool CanFly() {
        if (pilotIsHuman)
            return status switch {
                ShipStatus.Healthy => true,
                ShipStatus.Damaged => false,
                ShipStatus.Destroyed => false,
                _ => false,
            };
        else
            return status switch {
                ShipStatus.Healthy => true,
                ShipStatus.Damaged => true,
                ShipStatus.Destroyed => false,
                _ => false,
            };
    }
}

 

 

이를 하나로 묶어서 표현한 방법도 존재합니다. 

(true,  ShipStatus.Healthy) => true 의미는 "pilotIsHuman" 값이 true 이고(and) ShipStatus.Healthy 이 true 이면 결과 반환값은 true 를 의미합니다.

public class Ship
{
    public ShipStatus status = ShipStatus.Healthy;
    public bool pilotIsHuman;
    
    public bool CanFly() {
        return (pilotIsHuman, status) switch {
            (true,  ShipStatus.Healthy) => true,
            (true,  ShipStatus.Damaged) => false,
            (true,  ShipStatus.Destroyed) => false,
            (false, ShipStatus.Healthy) => true,
            (false, ShipStatus.Damaged) => true,
            (false, ShipStatus.Destroyed) => false,
            _ => false,
        };
    }
}

 

인스턴스 속성 액세스

 

외부에서 인스턴스의 속성에 액세스하고 식이 switch기본 함수에 있도록 할 수도 있습니다.

 

public class Ship
{
    public ShipStatus status = ShipStatus.Healthy;
    public bool pilotIsHuman;
}

public static class Program
{

    public static void Run()
    {
        Ship ship = new Ship();
        bool canFly = ship switch {
            Ship { pilotIsHuman: true, status: ShipStatus.Healthy } => true,
            Ship { pilotIsHuman: true, status: ShipStatus.Damaged } => false,
            Ship { pilotIsHuman: true, status: ShipStatus.Destroyed } => false,
            Ship { pilotIsHuman: false, status: ShipStatus.Healthy } => true,
            Ship { pilotIsHuman: false, status: ShipStatus.Damaged } => true,
            Ship { pilotIsHuman: false, status: ShipStatus.Destroyed } => false,
            _ => false,
        };
        Console.WriteLine($"Can fly? {canFly}");
    }

    // output:
    //
    // Can fly? True

}

 

폐기된 값, 그리고 null 경우 (Discarded values, and the null case)

 

 

이전 예제에서는 "defalut" 케이스를 처리하고 싶을 때마다 _문자를 사용했습니다. 

이 기호를 폐기 패턴 null 이라고 하며, 표현식의 분기에서 아직 일치(match) 하지 않는(null 포함) 값을 처리하도록 프로그램에 지시하는 방법입니다.

 

 

그러나 거기에 null 케이스를 포함하고 싶지 않다면 어떻게 될까요?

 

{} "빈 중괄호"를 사용하여, 아직 일치하지 않은 non-null value(null 이 아닌 값) 과 일치시킬 수 있습니다.

 

public class Ship
{
    public ShipStatus status = ShipStatus.Healthy;
    
    public float GetSpeed()
    {
        return this switch {
            Ship { status: ShipStatus.Healthy } => 100.0f,
            {} => 50.0f,
            null => throw new NullReferenceException()
        };
    }
}

 

결론

 

Pattern matching(패턴 일치) 는 코드를 더 읽기 쉽게 만드는 데 도움이 될 뿐만 아니라 모든 멋진 유형 검사와 IDE 자동 완성을 사용할 수 있도록 유지하기 때문에 C#의 정말 강력한 기능입니다!

 

 

 

번역 및 발췌

 

https://medium.com/c-sharp-progarmming/a-quick-study-of-the-c-patterns-9d78e224aa50

 

A quick study of the “C# patterns”

Let’s see how to use handy syntactic sugar to run checks and matches on our variables!

medium.com