람다 캡쳐링 문제
댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!
private Action<Collider>[] OnTriggerEntered;
private Action<Collider>[] OnTriggerExitted;
for (int i = 0; i < datas.Length; i++)
{
OnTriggerEntered[i] = (Collider other) => HandleTriggerEntered(other, i);
OnTriggerExitted[i] = (Collider other) => HandleTriggerExitted(other, i);
}
람다에서 직접 전달받은 인자(Collider other)가 아닌, 외부 변수(i) 를 참조할 때 발생하는 현상으로, 그 변수를 임시 클래스(Display Class)로 만들어 감싸버리는 문제가 있다. 참조방식이므로, 루프를 탈출할 때 i = datas.Length가 대입되어 out-of-range exception이 발생하는 문제가 생긴다.
이를 해결하기 위한 노하우가 람다식 캡쳐, 람다 캡쳐링 등으로 불린다.
for (int i = 0; i < datas.Length; i++)
{
int captured = i; // Lambda Capturing
OnTriggerEntered[i] = (Collider other) => HandleTriggerEntered(other, captured);
OnTriggerExitted[i] = (Collider other) => HandleTriggerExitted(other, captured);
}
i를 복사하여 별도의 변수에 넣는다. 람다식이 별도의 변수를 참조하게 하여, 각 루프 별로 다른 변수를 캡쳐링 하도록 유도한다.
람다식이 스코프를 한참 벗어난 뒤, 스코프에서 정의한 값을 기억해내고 로직을 수행하는 방식이 항상 의문이기는 했다. 아마 예전에 찾아본 적은 있을 텐데, 까먹은 것 같다. 너무 오래 전인 듯. 하도 람다를 안 써버릇해서 신경을 못 썼다.
람다가 이 문제를 해결하는 방법은 변수를 캡쳐링하는 것으로, 해당 외래 변수를 임시 클래스(Display Class)로 만들어 참조해둔다. 물론, 직접 매개변수로 받은 인자는 캡쳐링 할 이유가 없으므로 해당 없다.
이렇게 캡쳐링된 외래 변수를 포함한 람다는 클로저(Closure)라고도 불린다.
… 클로저는 외부의 context를 내부에서 참조한다는 넓은 의미로도 쓰이는 듯하다.
(박싱) 힙 할당을 유발한다. 내 경우의 코드는 상태머신의 State의 생성자에서 실행되었으므로 큰 오버헤드는 없다.
람다를 쓸 일도 많이 없고, 되도록 사용을 피하려고 하는 편이지만, 알아 둘 필요는 있겠다.
모르면 또 한 시간 버릴 테니까…
끗.