[C# Tip] Switch Expression, 속성 패턴, var 패턴

Data:     Updated:

Category:

태그:

💁‍♂️ Switch 문은 알겠는데 switch expression 은 뭐죠?


🔖 Switch Expression (Switch 패턴 일치식)

혼자 코드를 리팩토링 하다가 갑자기 Switch expression를 보게 되었다.

무려 C# 8.0부터 추가 된 기능이다.

Switch 패턴 일치 식이라고 하나보다.

wow1

🚀 관련 MS Doc

나는 대체로 분기가 3개 이상이 되면 Switch문을 사용 하는데, 각 case의 본문이 같을 경우 뭔가 X를 보고 어디를 안닦은 기분이 들었다.

예를 들어 아래와 같은 코드에서 말이다.

enum Fruit
{
   Apple,
   Banana,
   Orange,
   Grape,
}

public void ComeOnIsThisYourBest()
{
   var _fruitName = Fruit.Apple;
   var num = 0;

   switch (_fruitName)
   {
      case Fruit.Apple:
            num = 1;
            break;
      case Fruit.Banana:
            num = 2;
            break;
      case Fruit.Orange:
            num = 3;
            break;
      case Fruit.Grape:
            num = 4;
            break;
      default:
            throw new ArgumentOutOfRangeException();
   }
}

놀랍게도 Switch 문 내의 각 본문이 동일한 패턴의 코드로 되어 있다면 아래로 쓸떼없이 길었던 Switch 문을 한번에 줄여버릴 수 있다.

  enum Fruit
    {
        Apple,
        Banana,
        Orange,
        Grape,
    }

    public void ThisIsMyBest()
    {
        var _fruitName = Fruit.Apple;

        var num = _fruitName switch
        {
            Fruit.Apple => 1,
            Fruit.Banana => 2,
            Fruit.Orange => 3,
            Fruit.Grape => 4,
            _ => throw new ArgumentOutOfRangeException()
        };
    }

바로 위와 같이 바꾸어 줄 수 있는데 이 것이 바로바로바로바로바로 Switch 패턴 일치식이다.
가독성이 더 있는지는 모르겠지만, 이 정도도 못 알아챈다면 그냥 개발자를 접는 게 어떨까?

가슴에 손을 얹고 생각해보자.

나의 뇌의 용적량은 개미와도 같아서 이렇게 적어 놓고도 분명 잊어버릴 게 분명하기 때문에 간단하게 설명을 적어두어야 겠다.

public void ThisIsMyBest()
{
   var _fruitName = Fruit.Apple;

   // num 에 값을 넣을 건데, _fruitName을 기준으로 switch 문을 쓸거야
   var num = _fruitName switch
   {
      Fruit.Apple => 1,   // 사과면 1이고
      Fruit.Banana => 2,  // 바나나면 2고
      Fruit.Orange => 3,  // 오렌지면 3이고
      Fruit.Grape => 4,   // 포도면 4야
      _ => throw new ArgumentOutOfRangeException() // 그것도 아니다? 그러면 전부 Exception 날려 버려
   }; // 문장의 끝은 항상 세미콜론이야 ㅇㅋ?
}

아주 자세하게 설명을 적어 보았다.

Switch 패턴 일치식의 전제조건은 이름에서 부터 알 수 있다시피 각 case의 본문이 정확하게 패턴화 되어 있어야 한다.

그러고 문서를 내리다가 또 재미있는 걸 찾았는데 Case Guard 라는 녀석이다.

이것을 사용하려면 우리는 속성 패턴과 var 패턴을 알아야만 한다.

최대한 짧고 굵게 알잘딱 설명해보겠다.

🔖 Property Pattern Matching

이건 별거 없다. 만약 내가 Struct 즉 구조체를 정의했는데, 그 구조체를 기준으로 속성에 따라 분기를 나눠서 무언가 하고 싶다.
이럴 때 쓰는게 속성 패턴이다.

백문이불여일견 한번 코드로 설명해본다.

public enum Animals
{
   Tiger,
   Mouse,
   Dog,
   Horse,
}

public struct Animal
{
   public Animals Type { get; set; }
   public string Name { get; set; }
   public int Level { get; set; }
   public int Damage { get; set; }
   public int Armor { get; set; }
   public int Health { get; set; }
   public int Speed { get; set; }

   public Animal(Animals type, string name, int level, int damage, int armor, int health, int speed)
      => (Type, Name, Level, Damage, Armor, Health, Speed) = (type, name, level, damage, armor, health, speed);
}

위와 같은 구조체를 만들어 보았다.
동물은 타입, 이름, 레벨, 데미지, 방어력, 체력, 스피드 속성들을 갖는다.
그리고 나는 지금 Level이 10 이상일 경우, 데미지가 10 이상일 경우, 스피드가 10 이상일 경우 등과 같은 각 속성 마다의 분기를 나누고 싶다.

이럴 때 속성 패턴을 샤샤샥

public Animal GetAnimal(Animal animal)
{
   switch (animal)
   {
      case { Type: Animals.Tiger, Level: 5 }:
            return new Animal(Animals.Tiger, "노말 호랑이", 5, 5, 5, 5, 5);
      default:
            return default;
   }
}

위와 같이 쓸 수 있다.
그리고 코드를 말로 풀어서 설명하면,

인수로 받아온 animalTypeAnimals.Tiger 이고 Level 이 5일 경우 case 본문 실행이다.

🔖 Var Pattern

이어서 var 패턴을 설명하겠다.

   public Animal GetAnimal(Animal animal)
    {
        switch (animal)
        {
            case { Type: Animals.Tiger, Level: 5 }:
                return new Animal(Animals.Tiger, "노말 호랑이", 5, 5, 5, 5, 5);

            case { Type: Animals.Dog, Level: 5, Damage: var damage } when damage < 10: 
                return new Animal(Animals.Tiger, "나약한 강아지", 5, 5, 5, 5, 5);

            default:
                return default;
        }
    }

여기서 나약한 강아지를 보면 Damage: var damage 라고 쓴 부분을 볼 수 있다.
속성 값을 var 변수에 따로 담아야 하는 일이 존재할 경우 사용한다.

위에서는 단지 damage 단일 값을 비교 했지만, 2가지 이상의 속성을 비교해야 할 경우 사용하곤 한다.

마지막으로 이것을 Switch 패턴 일치식으로 변환하면 아래와 같다.

public Animal GetAnimal(Animal animal)
{
   return animal switch
   {
      { Type: Animals.Tiger, Level: 5 } => new Animal(Animals.Tiger, "노말 호랑이", 5, 5, 5, 5, 5),
      { Type: Animals.Dog, Level: 5, Damage: var damage } when damage < 10 => new Animal(Animals.Tiger, "나약한 강아지",
            5, 5, 5, 5, 5),
      _ => default
   };
}

🔖 Case Guard

빙 돌아서 왔는데 그래서 케이스 가드가 무엇이냐?

먼저 케이스 가드는 부울식 즉 Bool 값이어야 한다.

public Animal GetAnimal(Animal animal)
{
   return animal switch
   {
      { Type: Animals.Tiger, Level: 5 } => new Animal(Animals.Tiger, "노말 호랑이", 5, 5, 5, 5, 5),
      { Type: Animals.Dog, Level: 5, Damage: var damage, Armor: var armor } when damage < armor => new Animal(Animals.Tiger, "단단한 강아지", 5, 5, 10, 5, 5),
      _ => default
   };
}

위의 코드의 단단한 강아지 를 보자.
animal 로 받아온 인수의 Type 값이 Animals.Dog 이고 Level 이 5일 경우, Damage 속성을 var 변수의 damage 에 넣고, Armor 속성을 var 변수의 armor 에 넣는다.
그리고 when 케이스 가드로 damage < armor 가 true 면 case 본문을 실행한다.

결론적으로 케이스 가드는 일치하는 패턴과 함께 충족해야 하는 또 다른 조건이다.

🤔 해당 기술에 대해서 느낀점?

만약 case가 패턴화되어 있다면 무조건 쓸 것 같다.
이것도 못 알아 본다면 찾아보고 신 문물을 받아 들여야지 언제까지 구시대 코딩을 할 순 없잖아?

스크립터면 그냥 몰라도 되고, 자신이 개발자라고 말하고 싶다면 찾아서 배워야 겠지

C#님 오늘도 일용할 지식을 주셔서 감사합니다.



이 게시물에는 지극히 주관적인 생각이 포함되어 있습니다. 
오류나 틀린 부분, 또는 수정해야 할 부분이 있다면 언제든지 댓글 혹은 메일로 지적 부탁드립니다.

Top

C Sharp Go to see other posts in the category

Comment