Deff_Dev

[Unity/C#] 패턴 매칭 본문

Unity(유니티)/유니티 공부

[Unity/C#] 패턴 매칭

Deff_a 2024. 4. 13. 21:37

패턴 매칭이란 ?

  • switch문, switch 식, is 연산자를 이용하여 어떤 식이 특정 패턴(형태)과 일치하는지 검사하는 것이다.
  • 패턴 매칭을 이용하면 장황하고 거추장스러운 분기문을 간결하고 읽기 쉬운 코드로 대체할 수 있다.

식 (Expression) → 패턴 매칭 → 결과 (bool)


식(Expression) 이란 ?

  • 코드에서 단일 결과값을 만들어낼 수 있는 연산자와 연산자의 조합을 한다.
 // 식
 a = 123;
 c = typeof(int);
 d = a + 52;
 
 b = int; // int는 값이 아니므로 식 X

선언 패턴

  • 주어진 식이 특정 형식 ( int, string … )과 일치하는지를 평가한다.
  • 만약 주어진 식과 형식이 일치한다면, 선언 패턴은 식을 해당 형식으로 변환한다.

식이 주어진 형식과 일치하는지 테스트 → 테스트가 성공하면 식을 주어진 형식으로 변환

 

is 연산자를 이용한 예제

    private void Start()
    {
        object foo = 23;

        // foo가 int 형식인 경우 foo를 int 형식으로 변환하여 bar에 할당한다.
        if(foo is int bar)
        {
            Debug.Log(bar);
        }
    }

foo가 int 형식 일 경우 bar 변수가 if 블록 안에 생성되고 23이 출력되지만, int 형식이 아닐 경우 if 문은 실행되지않는다. (bar 변수 생성 X)


형식 패턴

  • 선언 패턴과 거의 같은 방식으로 동작하지만, 변수 생성 없이 형식 일치 여부만 확인한다.

is 연산자를 이용한 예제

    private void Start()
    {
        object foo = 23;

        // foo가 int 형식인 경우 foo 출력
        if(foo is int)
        {
            Debug.Log(foo);
        }
    }

 

switch 식을 이용한 예제

using UnityEngine;
using System;

public class Junior{}
public class Senior{}
public class Leader{}
public class Master{}

public class TestManager : MonoBehaviour
{
    
    private int CalculateFee(object visitor)
    {
        return visitor switch
        {
            Junior => 100,
            Senior => 500,
            Leader => 1000,
            _ => throw new ArgumentException(
                $"{visitor.GetType()} 직급은 없습니다 !! ")
        };
    }
    private void Start()
    {
        Debug.Log($"주니어 직급의 임금은 {CalculateFee(new Junior())}");
        Debug.Log($"시니어 직급의 임금은 {CalculateFee(new Senior())}");
        Debug.Log($"리더 직급의 임금은 {CalculateFee(new Leader())}");
        Debug.Log($"마스터 직급의 임금은 {CalculateFee(new Master())}");
    }
}


상수 패턴

  • 식이 특정 상수와 일치하는지를 검사한다.
  • 정수, 문자열, null, enum 등 모든 상수와 매칭할 수 있다.

문자열에 대한 상수 패턴 매칭 예제

using UnityEngine;
using System;

public class TestManager : MonoBehaviour
{
    private int GetCountryCode(string nation)
    {
        switch (nation)
        {
            case "KR":
                return 82;
            case "US":
                return 1;
            case "UK":
                return 44;
            default:
                throw new ArgumentException($"{nation} 국가 코드는 존재하지 않습니다.");
        }
    }

    private void Start()
    {
        Debug.Log($"{GetCountryCode("KR")}");
        Debug.Log($"{GetCountryCode("US")}");
        Debug.Log($"{GetCountryCode("UK")}");
        Debug.Log($"{GetCountryCode("CN")}");
    }
}


프로퍼티 패턴

  • 식의 속성이나 필드가 패턴과 일치하는지를 검사한다.
  • 입력된 식이 int, double 같은 기본 데이터 형식이 아닌 경우에 유용하게 사용할 수 있다.
using UnityEngine;

public class Car
{
    public string Model { get; set; }
    public int Year { get; set; }
}
public class TestManager : MonoBehaviour
{
    private string GetNickname(Car car)
    {
        if (car is Car { Model: "Mustang", Year: 1967 })
        {
            return "Fastback";
        }
        else
        {
            return "Unknown";
        }
    }
    private void Start()
    {
        Debug.Log(GetNickname(new Car() {Model ="Mustang", Year = 1967}));
        Debug.Log(GetNickname(new Car() {Model ="Mustang", Year = 1999}));
    }
}

관계 패턴

  • , >=, ==, !=, <, <=와 같은 관계 연산자를 이용하여 입력받은 식을 상수와 비교한다.
    private void Start()
    {
        int score = 60;

        bool isPass = score switch
        {
            < 60 and > 0 => false,
            < 100 and >= 60 => true,
            _ => false
        } ;

        Debug.Log(isPass);
    }

논리 패턴

  • 패턴과 패턴을 패턴 논리 연산자 (and, or, not)을 조합해서 하나의 논리 패턴으로 만들 수 있다.
using UnityEngine;

public class OrderItem
{
    public int Amount { get; set; }
    public int Price { get; set; }

}
public class TestManager : MonoBehaviour
{
    float GetPrice(OrderItem orderItem) => orderItem switch
    {
        OrderItem { Amount: 0 } or OrderItem { Price: 0 }
            => 0.0f,
        OrderItem {Amount: >= 100} and OrderItem {Price: >= 10000}
            => orderItem.Amount * orderItem.Price *  0.8f,
        not OrderItem {Amount: < 100} // not은 조건의 반대의 경우 -> 수량이 100 이상이고 price가 10000미만일 때
            => orderItem.Amount * orderItem.Price * 0.9f,
        _ => orderItem.Amount * orderItem.Price,
    };

    private void Start()
    {
        Debug.Log(GetPrice(new OrderItem() {Amount = 0, Price = 10000}));
        Debug.Log(GetPrice(new OrderItem() {Amount = 100, Price = 10000}));
        Debug.Log(GetPrice(new OrderItem() {Amount = 100, Price = 9000}));
        Debug.Log(GetPrice(new OrderItem() {Amount = 1, Price = 1000}));
    }
}

괄호 패턴

  • 소괄호 ()로 패턴을 감싼다.
  • 보통 논리 패턴으로 여러 패턴을 조합한 뒤 이를 새로운 패턴으로 만드는 경우에 사용한다.
    private void Start()
    {
        object age = 30;

        if (age is (int and > 19))
        {
            Debug.Log("성인");
        }
    }

위치 패턴

  • 식의 결과를 분해하고, 분해된 값들이 내장된 복수의 패턴과 일치하는지를 검사한다.
  • 위치 패턴 안에 내장되는 패턴에는 형식 패턴, 상수 패턴 등 어떤 패턴이든 올 수 있다.
  • 분해된 값들과 내장된 패턴의 개수, 순서가 일치해야 한다는 점을 주의해야 한다.
using UnityEngine;

public class Audience
{
    public bool IsCitizen { get; set; }
    public int Age { get; set; }

    public Audience(bool isCitizen, int age)
    {
        IsCitizen = isCitizen;
        Age = age;
    }

    
    // Deconstruct() : 값을 반환할 때 쓰는 함수
    public void Deconstruct(out bool isCitizen, out int age)
    {
        isCitizen = IsCitizen;
        age = Age;
    }
}
public class TestManager : MonoBehaviour
{
    int CalculateFee(Audience audience) => audience switch
    {
        (true, < 19) => 100,
        (true, _) => 200, // 내국인 20살 이상
        (false, < 19) => 200,
        (false, _) => 400, // 외국인 20살 이상
    };

    private void Start()
    {
        Audience a1 = new Audience(true, 10);
        Debug.Log($"내국인 : {a1.IsCitizen}, 나이 : {a1.Age}, 요금 : {CalculateFee(a1)}");
		
        // Deconstruct() 호출 후 a1의 IsCitizen, Age 값을 튜플 변수에 저장
        var (isCitizen, age) = a1;
        Debug.Log($"내국인 : {isCitizen}, 나이 : {age}");
        
        Audience a2 = new Audience(false, 33);
        Debug.Log($"내국인 : {a2.IsCitizen}, 나이 : {a2.Age}, 요금 : {CalculateFee(a2)}");
    }
}

 

여기서 Deconstruct 메소드는 C# 7.0부터 도입된 새로운 함수로, 메서드를 클래스 내에 정의하면 해당 클래스의 인스턴스를 분해하여 여러 개의 값을 한 번에 out 매개변수로 통해 반환할 수 있는 함수이다.

 

튜플과 함께 사용되며 객체의 다양한 인스턴스 값을 간편하게 추출하는 데 유용하다.


var 패턴

  • null을 포함한 모든 식의 패턴 매칭을 성공시키고, 그 식의 결과를 변수에 할당한다.
  • 어떤 식의 결과를 임시 변수에 할당한 뒤 추가적인 연산을 수행하고자 할 때 유용하게 사용할 수 있다.
    private void Start()
    {
        // Player 태그를 가진 GameObject를 var형 임시 변수에 할당
        var player = GameObject.FindGameObjectWithTag("Player");
        if (player != null) // GameObject를 찾았을 때
        {
            // Player GameObject를 찾았을 때 실행됨
            player.GetComponent<Player>().Move();
        }
    }

무시 패턴

  • var 패턴처럼 모든 식과의 패턴 일치 검사를 성공시키지만 var 패턴과 다르게 is 식에서는 사용할 수 없고, switch 식에서만 사용할 수 있다.
  • ‘모든 식’을 매칭할 수 있기 때문에 switch 문의 default 케이스와 비슷한 용도로 사용한다.
using UnityEngine;
using System;

public class TestManager : MonoBehaviour
{
    private int GetCountryCode(string nation)
    {
        int code = nation switch
        {
            "KR" => 82,
            "US" => 1,
            "UK" => 44,
            // 무시 패턴 매칭
            _ => throw new ArgumentException($"{nation} 국가 코드는 존재하지 않습니다.")
        };

        return code;
    }

    private void Start()
    {
        Debug.Log($"{GetCountryCode("KR")}");
        Debug.Log($"{GetCountryCode("US")}");
        Debug.Log($"{GetCountryCode("UK")}");
        Debug.Log($"{GetCountryCode("CN")}");
    }
}

 

 

그 이외에 목록 패턴도 존재하지만 유니티는 C# 8.0까지 지원하기 때문에 list patterns (C# 11.0)를 지원하지않는다.

 

 

스스로 공부하면서 정리한 내용으로 틀린 내용이 있을 수도 있습니다. 고쳐야할 점이 있거나 이해가 안되는 부분이 있으시다면 댓글 달아주세요 !