숏컷 코드
// 람다 식 기본 형태
Func<int, int> square = x => x * x;
Func<int, int, int> add = (x, y) => x + y;
Action<string> print = msg => Console.WriteLine(msg);
// LINQ와 결합
var evens = numbers.Where(n => n % 2 == 0).ToList();문법
어떤 람다 형태가 있나
| 형태 | 의미 |
|---|---|
x => x * x | 단일 매개변수, 식 본문 |
(x, y) => x + y | 매개변수 여러 개 |
() => DoWork() | 매개변수 없음 |
x => { ...; return v; } | 블록 본문 |
람다 식은 익명 메서드
=> 왼쪽은 매개변수, 오른쪽은 본문입니다. 본문이 식 하나면 값이 반환되고, 여러 줄이 필요하면 블록 { ... } 안에 씁니다.
// 식 본문 람다 (자동으로 반환)
Func<int, bool> isEven = n => n % 2 == 0;
// 블록 본문 람다 (return 명시 필요)
Func<int, string> classify = n =>
{
if (n < 0) return "음수";
if (n == 0) return "영";
return "양수";
};Func vs Action
Func<T>는 반환값이 있는 메서드, Action<T>는 void 메서드를 나타냅니다.
Action : () → void
Action<T> : (T) → void
Func<TResult> : () → TResult
Func<T,TResult> : (T) → TResult
// 마지막 타입 인자가 반환 타입클로저 — 외부 변수를 캡처한다
람다는 자신이 정의된 스코프의 변수를 캡처합니다. 값이 아닌 변수 자체를 캡처하므로, 람다가 호출될 시점의 변수 값이 사용됩니다.
// ❌ 루프 변수를 캡처하는 고전 함정
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
actions.Add(() => Console.WriteLine(i)); // i를 캡처
actions.ForEach(a => a()); // 3, 3, 3 출력 — i가 루프 종료 후 3이 됨
// ✅ 루프마다 새 변수를 만들어 캡처
for (int i = 0; i < 3; i++)
{
int captured = i; // 새 변수
actions.Add(() => Console.WriteLine(captured));
}
actions.ForEach(a => a()); // 0, 1, 2 출력foreach는 반복마다 새 변수를 만들기 때문에 이 문제가 없습니다.
// ❌ 단순 전달인데 불필요한 람다를 한 번 더 감쌐
items.Select(x => Normalize(x));
// ✅ 기존 메서드가 그대로 맞으면 메서드 그룹도 가능
items.Select(Normalize);식 본문 멤버 (expression-bodied member)
람다 문법을 메서드, 프로퍼티, 생성자에도 쓸 수 있습니다.
public class Circle
{
public double Radius { get; }
public Circle(double r) => Radius = r; // 생성자
public double Area => Math.PI * Radius * Radius; // 프로퍼티
public string Describe() => $"반지름 {Radius}"; // 메서드
}체크포인트
| 형태 | 의미 |
|---|---|
x => x * x | 매개변수 1개, 식 반환 |
(x, y) => x + y | 매개변수 여러 개 |
() => DoWork() | 매개변수 없음 |
x => { ...; return v; } | 블록 본문, return 필요 |
Func<int,int> | 입력 int, 반환 int |
Action<string> | 입력 string, 반환 없음 |
주의할 점
클로저가 캡처한 변수의 수명이 길면 예상보다 오래 메모리에 남아 있을 수 있습니다. 특히 람다를 이벤트 핸들러나 정적 필드에 등록하면, 캡처된 객체가 GC 대상이 되지 않아 메모리 누수로 이어질 수 있습니다.
람다 안에 길고 복잡한 로직을 넣으면 디버깅과 재사용이 어려워집니다. 로직이 길어진다면 이름 있는 메서드나 로컬 함수(static ... LocalMethod())로 분리하는 편이 좋습니다.
참고 링크
2 sources