C# - 캡쳐(Capture), 클로져(Closure) - feat React.useState

2025. 9. 23. 20:29·C#

델리게이트(delegate)는 메서드를 참조할 수 있는 타입이다.

그런데 실제 Unity 같은 환경에서 델리게이트를 남용하다 보면, 사라졌어야 할 객체가 여전히 참조된 채 메모리에 남아 기이하게 동작하는 경우가 생긴다.

이는 대부분 캡쳐(변수 포착, Capture) 와 클로져(Closure, 포착된 변수를 계속 보존하는 환경) 의 문제 때문이라고 하는데

이 두가지에 대해 자세히 알아보자.

 


 

캡쳐(Capture)

람다식이 외부의 지역 변수를 가져다가 내부에서 사용하는 것을 말한다.

int count = 0;
Action myAction = () =>
{
    count++; // 외부 변수 count를 가져다가 사용 (캡처 발생)
    Console.WriteLine(count);
};

myAction(); // 1
myAction(); // 2

 

이 코드에서 원래 count는 Main의 지역 변수인데, myAction이 참조하면서 함께 살아남게 된다.

즉 람다가 외부 변수를 복사해 두는 게 아니라 참조를 보존한다.

 

 

클로져(Closure)

이렇게 캡쳐된 변수를 실행 환경과 함께 묶어 지속적으로 사용 가능한 상태를 말한다.

  • 캡쳐는 외부 변수를 가져온 행위
  • 클로져는 그 변수를 묶어 계속 쓸 수 있는 실행 환경

이라고 요약할 수 있다.

Func<int> MakeCounter()
{
    int count = 0; // 지역 변수
    return () =>
    {
        count++;
        return count;
    };
}

var counter1 = MakeCounter();
Console.WriteLine(counter1()); // 1
Console.WriteLine(counter1()); // 2

var counter2 = MakeCounter();
Console.WriteLine(counter2()); // 1 (다른 클로저)

 

여기서 count는 원래라면 MakeCounter 함수가 끝날 때 사라져야 하지만, 람다가 캡쳐한 덕분에 메모리 어딘가에 살아남는다.

 

근데 이 동작, React를 사용했던 사람이라면 뭔가 익숙하다 느낄 수 있다.

바로 useState 다.

 

React 컴포넌트 함수는 호출 될 때마다 새로 실행된다.

그런데도 useState로 선언한 값은 렌더링이 반복돼도 사라지지 않고 유지된다.

이는 내부적으로 useState가 컴포넌트 함수 밖(React 엔진 내부)에 저장소를 두고, 그 저장소를 클로져로 참조하고 있기 때문이다.

즉, 클로져가 없다면 함수형 컴포넌트는 렌더링 할 때마다 state를 잃어버릴 것이다.

 

C#에서 갑자기 자바스크립트 이야기?

하지만 참을 수 없다 나온 김에 코드로 한번 살펴보자.

// useState 흉내내보기
function createUseState() {
  let state; // 외부 저장소 (클로저에 의해 유지됨)

  function useState(initialValue) {
    if (state === undefined) {
      state = initialValue; // 처음 실행될 때만 초기화
    }

    function setState(newValue) {
      state = newValue;
    }

    return [state, setState];
  }

  return useState;
}

const useState = createUseState();

function MyComponent() {
  const [count, setCount] = useState(0);

  console.log("현재 카운트:", count);
  setCount(count + 1);
}

MyComponent(); // 현재 카운트: 0
MyComponent(); // 현재 카운트: 1
MyComponent(); // 현재 카운트: 2

 

state 라는 변수가 함수 바깥에서 선언되어 있고, useState 함수가 이를 클로져로 캡쳐해서 계속 접근 가능한 형태다.

그렇기 때문에 MyComponent가 여러번 실행돼도 state 값은 유지되는 것이다.


근데 이게 뭐가 문제??

Unity 같은 게임 엔진에는 델리게이트 / 이벤트 에 람다를 등록하면서 클로져를 생성하다 보면, GC가 수거해야 할 객체가 메모리에 남는 문제가 발생할 수 있다.

void Start()
{
    int score = 0;
    SomeEvent += () => Debug.Log(score); 
    // score 변수를 캡처 → 클로저 생성
}

 

score는 원래 Start가 끝나면 생명주기를 다해 사라져야 할 지역 변수다.

하지만 이벤트에 등록된 람다가 score를 캡쳐했기 때문에, 해당 오브젝트가 파괴되어도 클로져는 이벤트 체인에 남아있을 수 있다.

결과적으로 score는 메모리에 살아남아 GC가 회수하지 못하고, 의도치 않은 오류를 발생시킬 위험을 남긴다.

 

 

해결 방법

람다 대신 명시적 메서드 사용

void Start()
{
    SomeEvent += PrintScore;
}

void PrintScore() => Debug.Log(score);

 

이벤트 해제 철저히

void OnDestroy()
{
    SomeEvent -= PrintScore;
}

 

클로져 캡쳐 회피

루프 안에서 변수를 캡쳐하는 경우는 특히 위험하기 때문에 복사본을 만들어서 캡쳐하는 것으로 회피할 수 있다.

for (int i = 0; i < 3; i++)
{
    int copy = i; // 복사본
    buttons[i].onClick += () => Debug.Log(copy);
}

 

 

 

요약 정리

C#에서 캡처(Capture)는 람다나 익명 메서드가 외부 변수를 참조하는 것을 의미하고, 클로저(Closure)는 이렇게 캡처된 변수를 실행 환경과 함께 묶어 생명주기를 연장하는 구조를 말한다. Unity에서는 이 메커니즘을 잘못 이해하면 사라져야 할 객체가 이벤트 체인에 계속 남아 메모리 릭이나 예기치 못한 버그를 유발할 수 있다. 따라서 이벤트 해제, 명시적 메서드 사용, 루프 내 변수 복사 같은 관리가 반드시 필요하다.

'C#' 카테고리의 다른 글

C# - record  (0) 2025.09.23
C# - StringBuilder  (0) 2025.09.23
C# - 델리게이트 (Delegate)  (0) 2025.09.22
C# - 인터페이스 문제 풀이  (0) 2025.09.22
C# - 인터페이스 (Interface)  (0) 2025.09.21
'C#' 카테고리의 다른 글
  • C# - record
  • C# - StringBuilder
  • C# - 델리게이트 (Delegate)
  • C# - 인터페이스 문제 풀이
ellug
ellug
Web / App / Game Dev
  • ellug
    dev.ellug
    ellug
  • 전체
    오늘
    어제
    • 분류 전체보기 (95)
      • Javascript (React, Next) (1)
      • C# (66)
      • Graphics (1)
      • Unity (25)
      • 프로그래머스 (0)
        • Lv.0 (0)
        • Lv.1 (0)
        • Lv.2 (0)
        • Lv.3 (0)
      • AI Agent (0)
      • StableDiffusion (0)
      • ETC (1)
      • 잡담 (0)
      • 유머 (0)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Queue
    unity
    firebase
    스택
    inputsystem
    stack
    delegate
    게임
    Design Pattern
    최적화
    유니티
    useState
    콘솔
    큐
    추상 클래스
    class
    C#
    Abstract
    클래스
    list
    console
    function
    Network
    Photon
    interface
    알고리즘
    네트워크
    터미널
    struct
    자료구조
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
ellug
C# - 캡쳐(Capture), 클로져(Closure) - feat React.useState
상단으로

티스토리툴바