전체 코드
enum ItemType { Weapon, Armor, Amulet, Ring }
enum Rarity { Normal, Uncommon, Rare }
class Item
{
public ItemType ItemType;
public Rarity Rarity;
}
class Program
{
static List<Item> _items = new List<Item>();
static Item FindItem(Func<Item, bool> selector)
{
foreach (Item item in _items)
{
if (selector(item))
return item;
}
return null;
}
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) => item.ItemType == ItemType.Weapon);
Console.WriteLine(item?.ItemType);
}
}
🔔 람다식이란?
람다식(Lambda Expression)은 일회용 함수를 간단하게 표현할 수 있는 C# 문법입니다.
- 이름 없는 함수(익명 함수)를 간단하게 정의
- 함수 포인터처럼 사용 가능
- 대리자(Delegate) 또는 이벤트(Event)에 등록할 때 유용
❓ 람다식이 필요한 이유
아이템 리스트에서 특정 조건에 맞는 아이템을 찾는 함수를 만든다고 가정해봅시다.
기존 방식
static Item FindWeapon()
{
foreach (Item item in _items)
{
if (item.ItemType == ItemType.Weapon)
return item;
}
return null;
}
- 조건마다 새로운 함수를 계속 만들어야 함 (무기, 갑옷, 반지 등등)
- 조건 추가할 때마다 함수 폭발 (N가지 조건 = N개의 함수)
🔥 더 나은 방법 - 조건을 "함수"로 넘기기
delegate bool ItemSelector(Item item);
static Item FindItem(ItemSelector selector)
{
foreach (Item item in _items)
{
if (selector(item))
return item;
}
return null;
}
- 아이템 찾기 로직은 FindItem 하나로 통합
- 조건 검사 함수만 따로 작성
static bool IsWeapon(Item item) => item.ItemType == ItemType.Weapon;
Item item = FindItem(IsWeapon);
😵💫 여전히 문제
- 조건 함수(IsWeapon, IsArmor 등)를 매번 정의해야 함
- 조건이 단순한 경우, 함수까지 만드는 게 너무 번거로움
💡 해결책 - 람다식으로 한 방에 해결
Item item = FindItem((Item item) => { return item.ItemType == ItemType.Weapon; });
- 함수를 따로 만들 필요 없이, 조건 검사 로직을 즉석에서 정의
- 필요할 때 바로 작성하고, 함수 정의 없이 일회용 함수로 사용
📥 람다식 기본 문법
| 형태 | 예시 |
|---|
| 입력과 본체 분리 | (매개변수) => { 본문 } |
| 매개변수 타입 생략 가능 | item => item.ItemType == ItemType.Weapon |
| return 생략 가능 | x => x + 1 |
| 매개변수 2개 | (a, b) => a + b |
| 매개변수 없을 때 | () => Console.WriteLine("Hello") |
📦 람다식 + Func/Action 조합
✅ Func
- 반환값이 있는 경우
- 이미 C#에 기본 제공되는 제네릭 대리자 (직접 delegate 선언 필요 없음)
Func<Item, bool> selector = item => item.Rarity == Rarity.Rare;
✅ Action
- 반환값이 없는 경우
- 함수에 전달할 매개변수만 필요할 때 사용
Action<Item> printer = item => Console.WriteLine(item.ItemType);
✅ Func vs Action vs Predicate
| 타입 | 설명 | 예시 |
|---|
| Func | 입력 O, 반환 O | Func<int, int, int> (두 수 더하기) |
| Action | 입력 O, 반환 X | Action<string> (문자열 출력) |
| Predicate | 입력 O, 반환 bool | Predicate<Item> (조건 검사) |
🌐 예제: Func 활용
Func<Item, bool> isRare = item => item.Rarity == Rarity.Rare;
Item rareItem = FindItem(isRare);
🌐 예제: Action 활용
Action<Item> printItem = item => Console.WriteLine($"{item.ItemType}, {item.Rarity}");
_items.ForEach(printItem);
📊 정리 표 - 익명 함수 vs 람다식 vs 일반 함수
| 방식 | 설명 | 예시 |
|---|
| 일반 함수 | 이름 있는 함수로 재사용 목적 | bool IsWeapon(Item item) |
| 익명 함수 | 이름 없이 즉석 생성 | delegate(Item i) { return i.ItemType == ItemType.Weapon; } |
| 람다식 | 가장 간결하고 유연한 형태 | item => item.ItemType == ItemType.Weapon |
📌 람다식 사용 패턴
| 상황 | 패턴 |
|---|
| 리스트 검색 | FindItem(item => item.Rarity == Rarity.Rare) |
| 리스트 정렬 | items.Sort((a, b) => a.Rarity.CompareTo(b.Rarity)) |
| 이벤트 핸들러 등록 | button.Click += (s, e) => Console.WriteLine("클릭!") |
🏷️ 람다식과 클로저(Closure)
람다식은 외부 변수를 캡처해서 사용할 수 있음.
int threshold = 100;
Func<int, bool> isBig = x => x > threshold;
threshold는 람다식 외부에 있지만, 내부에서 사용 가능 = 클로저