정신과 시간의 방
카테고리
작성일
2022. 7. 16. 23:55
작성자
risehyun

C#과 유니티로 만드는 MMORPG 게임 개발 시리즈 강의를 수강하며 람다식에 대해 배웠다.

 

1. 람다식을 사용하지 않았을 때

    enum ItemType
    { 
        Weapon,
        Armor,
        Amulet,
        Ring
    }

    enum Rarity
    { 
        Normal,
        Uncommon,
        Rare
    }

    class Item
    {
        public ItemType ItemType;
        public Rarity Rarity;
    }

    static List<Item> _items = new List<Item>(); // 가상의 인벤토리처럼 아이템 목록 역할을 하는 리스트

    // 인벤토리에서 특정 아이템을 찾고 싶을 때 함수 구현(Find 함수를 하나씩 만든 Bad Ver.)
    static Item FindWeapon()
    {
        // 아이템을 쭉 스캔하면서 찾으려는 무기가 있는지 보고, 있으면 바로 그 아이템을 반환
        foreach (Item item in _items)
        {
            if (item.ItemType == ItemType.Weapon)
                return item;
        }
        return null; // 반환이 안되면 없다는 뜻이므로 null 리턴
    }

    // 현재 가지고 있는 희귀 등급의 아이템을 찾고 싶을 때(Bad ver.)
    static Item FindRareItem()
    {
        // 아이템을 쭉 스캔하면서 찾으려는 무기가 있는지 보고, 있으면 바로 그 아이템을 반환
        foreach (Item item in _items)
        {
            if (item.Rarity == Rarity.Rare)
                return item;
        }
        return null; // 반환이 안되면 없다는 뜻이므로 null 리턴
    }

    static void Main(string[] args)
    {
        _items.Add(new Item() { ItemType = ItemType.Weapon, Rarity = Rarity.Normal }); // 아이템을 만들면서 동시에 정보를 넣어 ADD 해주는 문법
        _items.Add(new Item() { ItemType = ItemType.Armor, Rarity = Rarity.Uncommon });
        _items.Add(new Item() { ItemType = ItemType.Ring, Rarity = Rarity.Rare });
    }

    /*
     * 기능을 몇 개 구현하지도 않았는데 너무 많은 함수들이 생김.
     * 그리고 아이템을 찾는 조합이 몇개인지를 미리 예상하기도 힘듦. 즉 무식한 방법!
     * 심지어 지금처럼 foreach로 끝나지 않고 인벤토리 내부를 서치하는 내용이 아주 길어질 수 있기 때문에
     * 그걸 일일이 복붙하는 것은 비효율적임.
     * 
     * => 이런 문제를 해결하기 위해 델리게이트를 사용함
     */

 

2. 같은 내용을 델리게이트를 사용한 버전으로 수정

    enum ItemType
    { 
        Weapon,
        Armor,
        Amulet,
        Ring
    }

    enum Rarity
    { 
        Normal,
        Uncommon,
        Rare
    }

    class Item
    {
        public ItemType ItemType;
        public Rarity Rarity;
    }

    static List<Item> _items = new List<Item>(); // 가상의 인벤토리처럼 아이템 목록 역할을 하는 리스트

    delegate bool ItemSelector(Item item); // 인자로 받은 아이템이 유효한지 아닌지 여부를 리턴해줌

    static bool IsWeapon(Item item)
    {
        return item.ItemType == ItemType.Weapon; // 아이템 타입이 weapon인 경우 true 리턴
    }

    static Item FindItem(ItemSelector selector) // 찾을 아이템의 조건을 매개변수로 넘겨줌
    {
        // 아이템을 쭉 스캔하면서 찾으려는 무기가 있는지 보고, 있으면 바로 그 아이템을 반환
        foreach (Item item in _items)
        {
            if (selector(item)) // 만약에 selector의 조건을 아이템이 통과한다면
                return item; // 아이템을 찾았다는 뜻이므로 리턴해줌
        }
        return null; // 반환이 안되면 조건에 맞는 아이템이 없다는 뜻이므로 null 리턴
    }

    static void Main(string[] args)
    {
        _items.Add(new Item() { ItemType = ItemType.Weapon, Rarity = Rarity.Normal }); // 아이템을 만들면서 동시에 정보를 넣어 ADD 해주는 문법
        _items.Add(new Item() { ItemType = ItemType.Armor, Rarity = Rarity.Uncommon });
        _items.Add(new Item() { ItemType = ItemType.Ring, Rarity = Rarity.Rare });

        Item item = FindItem(IsWeapon);
    }

    /*
     * 여기까지 구현하면 이전 버전에 비해 앞으로 추가할 함수의 양이 확 줄어든다는 장점이 생김.
     * 하지만 그럼에도 아쉬운 점이 있음.
     * 
     * 만약 조건이 20개가 필요하면 IsWeapon과 같은 조건 함수가 20개 필요해지는 것이다.
     * 즉, 불필요하게 코드가 늘어나는 것은 똑같다.
     * 
     * 이 문제를 해결하기 위해서 조건함수를 익명 함수로 처리해보자.
     */

 

3. 익명 함수(=무명 함수)를 이용한 구현

    static void Main(string[] args)
    {
        _items.Add(new Item() { ItemType = ItemType.Weapon, Rarity = Rarity.Normal }); // 아이템을 만들면서 동시에 정보를 넣어 ADD 해주는 문법
        _items.Add(new Item() { ItemType = ItemType.Armor, Rarity = Rarity.Uncommon });
        _items.Add(new Item() { ItemType = ItemType.Ring, Rarity = Rarity.Rare });

        Item item = FindItem(delegate (Item item) { return item.ItemType == ItemType.Weapon; });
    }

    /*
     * IsWeapon 함수를 지우고 1회용 함수 구현을 위해서 익명 함수(=무명 함수, (Anonymous function))를 사용함.
     * 델리게이트를 사용한 2번 코드와 동일한 결과이지만 코드가 더욱 깔끔하게 간략화되었음.
     * 다른 데서도 필요한 함수라면 델리게이트로 만들어야겠지만 1회성 함수라면 이런식으로 처리하는 것이 좋음.
     */

 

4. 익명 함수보다 더 간략화 된 람다식

    static void Main(string[] args)
    {
        _items.Add(new Item() { ItemType = ItemType.Weapon, Rarity = Rarity.Normal });
        _items.Add(new Item() { ItemType = ItemType.Armor, Rarity = Rarity.Uncommon });
        _items.Add(new Item() { ItemType = ItemType.Ring, Rarity = Rarity.Rare });

        // 익명함수 대신 람다로 처리함.  ::: ((입력되는 값) => {반환할 값}); :::
        Item item = FindItem((Item item) => { return item.ItemType == ItemType.Weapon; });
    }

 

5. 일회용 함수 구현 외에도 람다식을 활용하는 법 (람다식 재사용)

ItemSelector selector = new ItemSelector((Item item) => { return item.ItemType == ItemType.Weapon; });

// 이렇게 설정해주면 람다식을 재사용 할 수 있음.
Item item = FindItem(selector);

 

+) 람다식 주제에서 살짝 벗어났기 때문에 추후 델리게이트 항목으로 이동

 

6. 공용화 된 델리게이트 사용

// <기존 델리게이트>
delegate bool ItemSelector(Item item);

// <공용화된 델리게이트>
delegate Return MyFunc<T, Return>(T item); 
// + 델리게이트도 특정 타입이 아닌 일반화된 <T> 자료형을 사용해서 나타낼 수 있다.

이렇게 델리게이트를 선언해놓으면 앞으로 반환 형식 1개, 입력 형식 1개가 있는 타입의 델리게이트는

모두 MyFunc 를 이용해 넘겨 줄 수 있다.

 

아래는 사용 예시이다.

    static List<Item> _items = new List<Item>(); // 가상의 인벤토리처럼 아이템 목록 역할을 하는 리스트

    delegate Return MyFunc<T, Return>(T item);

    static Item FindItem(MyFunc<Item, bool> selector)
    {
        // 아이템을 쭉 스캔하면서 찾으려는 무기가 있는지 보고, 있으면 바로 그 아이템을 반환
        foreach (Item item in _items)
        {
            if (selector(item)) // 만약에 selector의 조건을 아이템이 통과한다면
                return item; // 아이템을 찾았다는 뜻이므로 리턴해줌
        }
        return null; // 반환이 안되면 조건에 맞는 아이템이 없다는 뜻이므로 null 리턴
    }

    static void Main(string[] args)
    {
        _items.Add(new Item() { ItemType = ItemType.Weapon, Rarity = Rarity.Normal }); // 아이템을 만들면서 동시에 정보를 넣어 ADD 해주는 문법
        _items.Add(new Item() { ItemType = ItemType.Armor, Rarity = Rarity.Uncommon });
        _items.Add(new Item() { ItemType = ItemType.Ring, Rarity = Rarity.Rare });

        MyFunc<Item, bool> selector = new MyFunc<Item, bool>((Item item) => { return item.ItemType == ItemType.Weapon; });
		//								-> 여기서 MyFunc 앞의 new는 없어도 똑같이 동작한다.

        // 이렇게 설정해주면 람다식을 재사용 할 수 있음. 
        Item item = FindItem(selector);
    }

 

+) 심화

// <심화>
// 위의 상태로는 인자를 1개 밖에 받을 수 없으므로, 
// 인자가 여러개 필요하다면 아래의 예시처럼 필요한 인자의 갯수만큼 선언 해두면 된다.

delegate Return MyFunc<Return>(); // 반환 형식은 있고, 입력 형식은 필요가 없는 타입의 경우
delegate Return MyFunc<T1, T2, Return>(T1 t1, T2 t2); // 반환이 1개 있고, 인자가 2개 필요한 타입의 경우

// 하지만 c#에는 위에 나와있는 내용처럼 인자 수에 따른 Func<>가 16개 미리 만들어져 있다.
// 즉, 따로 델리게이트를 직접 선언하지 않아도 Func<>를 사용하면 된다.

// 주의할 점은 Func<>는 out으로 TResult가 있는 타입이기 때문에, 
// 만약 void 형식으로 리턴하는 함수가 필요하다면 Action<> 타입 중 하나를 사용해야 한다.

// <결론>
// 반환 타입이 있을 경우 Func를
// 반환 타입이 없을 경우 Action을 사용한다.

// EX) Func<Item, bool> selector = (Item item) => { return item.ItemType == ItemType.Weapon; };
// Item을 인자로 받고, boolean을 반환하기 때문에 Func를 사용함.

 

[정리]

  • 람다식은 익명 함수의 선언을 하나씩 하지 않고 빠르게 만들 수 있도록 해주는 문법이다.
  • 람다식은 1회용 함수 구현에 활용될 수 있다.

 

'C#' 카테고리의 다른 글

220817 Interface (인터페이스)  (0) 2022.08.17
[C#] 일반화(Generic)  (0) 2022.08.16
220816 배열과 컬렉션  (0) 2022.08.16
220718 C# 연습문제 풀이  (0) 2022.07.18
[C#] 문법 정리  (0) 2022.07.17