Quick Reference
람다는 => 왼쪽에 매개변수, 오른쪽에 식 또는 문 블록을 쓰는 익명 함수입니다. 같은 문법도 받는 API가 Func인지 Expression<Func<...>>인지에 따라 즉시 실행할 코드인지 분석·변환할 식인지 달라집니다.
Action line = () => Console.WriteLine();
Func<int, int> square = x => x * x;
Func<int, int, int> add = (left, right) => left + right;
Func<int, string> sign = value =>
{
return value >= 0 ? "non-negative" : "negative";
};- 반환값이 없으면
Action, 있으면 마지막 type argument가 반환 타입인Func를 씁니다. - 외부 변수를 쓰면 값이 아니라 변수를 캡처합니다. 오래 사는 callback에 큰 객체나
this를 캡처하지 않습니다. - 비동기 callback은
Action이 아니라Func<..., Task>로 받아 완료·예외·취소를await합니다. - 캡처가 필요 없는 lambda에는
static을 붙여 우발적인 capture를 컴파일 오류로 만듭니다.
문법과 대상 타입
식 본문 lambda는 식의 결과를 반환하고, 문 블록 lambda는 return과 여러 문을 쓸 수 있습니다. 매개변수가 없으면 (), 하나면 괄호를 생략할 수 있으며, 여러 개거나 명시 타입을 쓰면 괄호가 필요합니다.
Func<double, double> cube = x => x * x * x;
Func<int, int, bool> equal = (int left, int right) => left == right;
Func<string, string> normalize = text =>
{
string trimmed = text.Trim();
return trimmed.ToUpperInvariant();
};lambda 자체에는 독립적인 타입이 없습니다. 대상 delegate 또는 expression tree가 매개변수·반환 타입을 정하므로, target type이 없는 단독 lambda는 일반적으로 변수에 바로 둘 수 없습니다.
사용자 정의 delegate는 ref/out 매개변수처럼 Action·Func가 표현하지 못하는 시그니처나, 호출 계약에 이름이 필요할 때 선택합니다.
캡처와 비동기 callback
람다는 바깥 지역 변수의 현재 값을 복사하는 것이 아니라 그 변수를 공유합니다. for의 반복 변수는 루프 하나가 공유하므로, 각 반복 값을 저장할 새 지역 변수가 필요합니다. C# 5 이후 foreach 반복 변수는 반복마다 새로 만들어지지만, 가변 외부 상태를 캡처하는 비용은 여전히 같습니다.
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
int captured = i;
actions.Add(() => Console.WriteLine(captured));
}
actions.ForEach(action => action()); // 0, 1, 2
int offset = 10;
Func<int, int> addOffset = static value => value * 2;
// static lambda는 offset을 캡처할 수 없습니다.비동기 작업을 받는 API는 호출자가 결과를 기다릴 수 있도록 Task를 계약에 포함합니다.
static async Task RunAsync(
Func<CancellationToken, Task> work,
CancellationToken cancellationToken)
{
await work(cancellationToken);
}
await RunAsync(async cancellationToken =>
{
await Task.Delay(100, cancellationToken);
}, cancellationToken);async lambda를 Action에 넣으면 async void가 됩니다. 이벤트 handler처럼 void 시그니처가 고정된 경우를 제외하면, 호출자가 예외와 완료를 관찰할 수 없어 피해야 합니다.
Delegate와 expression tree
Func<int, int> execute = x => x * x;
Expression<Func<int, int>> describe = x => x * x;
int value = execute(4); // 지금 코드 실행
string text = describe.ToString(); // 식 구조를 보관Enumerable.Select는 보통 delegate를 받아 메모리에서 실행합니다. Queryable.Select는 expression tree를 받아 provider가 SQL 같은 다른 형식으로 번역할 수 있습니다. 같은 lambda 문법이라도 provider가 메서드 호출을 번역하지 못하면 실행할 수 없으므로, expression tree에는 간단하고 번역 가능한 식을 둡니다.
문 블록 lambda와 async lambda는 expression tree로 변환할 수 없습니다. 자세한 열거·실행 시점은 LINQ 기본에서 다룹니다.
자주 틀리는 부분
이벤트·타이머·정적 컬렉션에 등록한 lambda는 구독 해제 전까지 캡처한 객체를 계속 참조할 수 있습니다. handler를 해제해야 한다면 inline lambda 대신 제거에 쓸 delegate 인스턴스나 이름 있는 메서드를 보관하세요.
길어진 lambda는 이름 있는 메서드나 local function으로 옮겨 계약·테스트·예외 처리를 드러내는 편이 낫습니다.
참고 링크
2 sources