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 |