50. 정적 지역 함수(static local function) vs 람다
난이도 중내 리뷰 · C#
해설 · C#
해설 — 정적 지역 함수(static local function) vs 람다
난이도: 중상
요약
람다가 지역 변수를 캡처하면 컴파일러가 클로저 클래스(힙 객체)를 만들어 그 변수를 필드로 끌어올린다(hoisting). 그래서 캡처한 변수는 참조로 공유되어 정의 이후의 변경도 보인다. 정적 지역 함수는 캡처가 금지되어(=클로저 할당 없음) 일반 정적 메서드 처럼 컴파일되고, 캡처 없는 람다는 한 번 만들어 정적 필드에 캐시된다.
실행 결과(예측)
lambda factor=20
Mul=12
square=25
0 1 2
핵심 개념 / 단계별
- (a) 캡처 람다 → 클로저 할당:
factor를 캡처하는 람다는 컴파일러가 생성한 디스플레이 클래스(예:<>c__DisplayClass)의 인스턴스에factor를 필드로 저장한다.lambda델리게이트는 그 인스턴스를 가리킨다. 따라서factor = 20은 같은 필드를 바꾸고, 호출 시20이 출력된다(값 복사가 아니라 참조 캡처). - (b) 정적 지역 함수 → 할당 없음:
static int Mul(...)은 바깥 변수를 캡처할 수 없으므로(‘static’이 이를 컴파일 타임에 강제) 클로저가 필요 없다. 일반 (정적) 메서드로 낮춰지고, 직접 호출은 델리게이트 할당도 없다. 인라인·최적화에 유리. - (c) 캡처 없는 람다 → 1회 캐시:
x => x*x는 아무것도 캡처하지 않아 컴파일러가 정적 필드에 델리게이트 인스턴스를 한 번만 만들어 캐시한다(호출마다 재할당 없음). - (d) 반복문 캡처: 반복마다 새 지역 변수
copy를 캡처하므로 각 클로저가 자신만의copy를 갖는다 →0 1 2. 만약 루프 변수i를 직접 캡처했다면(그리고 하나의 변수를 공유했다면) 흔한 함정처럼3 3 3이 될 수 있다. C# 5+ 의foreach변수는 반복마다 새 변수라 안전하지만,for의i는 하나의 변수라 여기서는 일부러copy를 도입했다.
흔한 오해·함정
- "람다는 항상 힙 할당" — 아니다. 캡처가 없으면 캐시되고, 캡처가 있으면 할당된다.
캡처 대상이
this(인스턴스 멤버 접근)여도 클로저(또는this참조)가 생긴다. - 정적 지역 함수 +
Span/인자 전달로 핫패스의 클로저 할당을 제거하는 것이 흔한 최적화. 필요한 값은 캡처 대신 매개변수로 넘긴다. - 부지불식 캡처: 람다 안에서 인스턴스 필드/메서드를 쓰면
this를 캡처해 예상치 못한 수명 연장(메모리 누수)·할당이 생긴다.static람다(C# 9+)로 캡처를 금지해 방지 가능. - LINQ 체인의 람다가 반복적으로 캡처를 할당하면 tight loop 에서 GC 압력이 된다.
면접 포인트
- 캡처 유무가 할당을 좌우한다: 캡처 없음=캐시, 캡처=클로저 인스턴스, static 지역함수=할당 없음.
- 캡처는 값 복사가 아니라 변수 hoisting(참조 공유) — 정의 후 변경이 보인다.
- 핫패스에서
static지역 함수/static람다 + 매개변수 전달로 GC 압력 제거.