캡처와 클로저 ( Capture & Closure )

2025. 11. 15. 00:40·⭐C Sharp/12. 델리게이트

캡처와 클로저 ( Capture & Closure )

C# 에서 람다식이나 익명 함수를 사용할 때 , 바깥에 있는 지역 변수에 접근하는 코드가 종종 등장한다.

int bonus = 10;
Func<int, int> calc = x => x + bonus;

이 때 bonus 는 calc 함수 내부에서 정의된 변수가 아니다.

그럼에도 불구하도 calc 는 bonus 에 접근하고 그 값을 참조한다.

이런 현상을 변수 캡처 ( Capture )라고 한다.

그리고 이런 캡처 기능을 가진 함수 객체를 클로저 ( Closure ) 라고 부른다.

 

 

변수 캡처 ( Capture )

변수 캡처는 람다식이나 익명 함수가 자신이 선언된 위치의 외부 변수를 참조하는 것이다.

변수를 복사해서 저장하는 것이 아니라 , 변수의 참조를 저장한다.

람다 ( 또는 익명 메서드 )가 , 바깥에 있는 지역 변수를 참조하기 위해 그 변수를 "잡아서 저장해 두는 것"

int x = 10;

Action printX = () =>
{
    Console.WriteLine(x);
};

x = 20;
printX();

이 코드는 x 에 대한 복사본을 사용하는 것이 아니라, x 변수의 현재 값을 항상 참조한다.

  • 많은 사람들이 "10이겠지?" 라고 착각하지만
  • 실제 출력은 20 이다.

왜 20이 나올까?

  • 람다는 x의 "값"을 복사해두는 것이 아니다.
  • x 라는 변수 자체를 캡처해서 이후에 x 가 변경되면 클로저 안에서도 변경된 값을 보게 된다.

클로저는 "x 의 스냅샷 ( 값 한 번 )" 이 아니라

"x 라는 박스를 통째로 들고 다닌다" 고 이해하면 편하다.

 

 

 

클로저 ( Closure )

캡처한 변수를 포함하고 있는 함수 객체를 클로저라고 한다.

람다식과 익명 함수는 클로저를 생성할 수 있는 구조를 가지고 있다.

함수 ( 람다 )가, 자신이 만들어질 때 주변에 있던 변수들을 "잡아서 같이 들고 다니는" 기능

Func<int, int> makeAdder(int value)
{
    return x => x + value;
}

이 함수는 내부에 value 라는 지역 변수를 가지고 있으며 반환되는 람다식은 그 value 를 캡처한다.

makeAdder 함수의 실행이 끝나더라도 value 는 클로저 안에서 계속 살아있다.

  • 보통 변수는 스코프 ( Scope ) 를 벗어나면 사라진다.
  • 그런데 클로저가 해당 변수를 캡처 ( Capture ) 하면
    함수가 살아 있는 동안 그 변수도 계속 살아있고
    함수 안에서 읽고 / 쓰기가 가능해진다.

C# 에서는 람다식 , 익명 메서드가 클로저를 많이 사용한다.

 

 

내부에서 어떻게 구현하는지 정리

C# 컴파일러는 캡처가 필요한 경우 , 익명 클래스를 자동으로 생성한다.

해당 클래스는 캡처된 변수들을 필드로 저장하고 , 람다식은 이 익명 클래스의 메서드처럼 처리된다.

▼ 컴파일러는 이런 코드를 보면

int x = 10;
Action a = () => Console.WriteLine(x);

 

▼ 이런 식으로 "숨겨진 클래스"를 자동으로 만든다고 생각하면 된다.

class DisplayClass
{
    public int x;            // 캡처된 변수
    public void Print()
    {
        Console.WriteLine(x);
    }
}

var obj = new DisplayClass();
obj.x = 10;
Action a = obj.Print;
  • x 는 이제 DisplayClass 인스턴스의 필드가 된다.
  • 람다 ( ) => Console.WriteLine( x ) 는 사실 obj.Print 메서드로 바뀐다.
  • 그래서 스코프를 벗어났어도 , obj 가 살아있는 동안 x 도 계속 살아있는 것처럼 보이게 된다.

이게 클로저 + 캡처의 실체라고 보면 된다.

 

 

 

▼ 예시 코드 1

int counter = 0;

Action incr = () =>
{
    counter++;
    Console.WriteLine(counter);
};

incr(); // 1
incr(); // 2
incr(); // 3
  • counter 는 원래 지역 변수
  • 하지만 incr 가 counter 를 캡처했기 때문에
    incr 델리게이트가 존재하는 동안 counter 도 살아있고 값이 누적된다.

 

 

▼ 예시 코드 2

Action[] actions = new Action[3];

for (int i = 0; i < 3; i++)
{
    actions[i] = () => Console.WriteLine(i);
}

foreach (var act in actions)
{
    act();
}

 

▼ 초보자 예상

0
1
2

 

▼ 실제 출력

3
3
3
  • 람다는 매번 새로운 i 를 캡처하는 것이 아니라 루프에 있는 같은 i 변수를 하나 캡처한다
  • for 문이 다 돌고 나면 i 는 3 이 되어있다.
  • actions 안의 모든 람다는 그 동일한 i 를 보고 있으므로 3, 3, 3 을 출력한다.

▼ 올바르게 쓰려면 , 루프 안에서 별도 변수에 복사해서 캡처해야 한다.

for (int i = 0; i < 3; i++)
{
    int temp = i;   // 새로운 박스
    actions[i] = () => Console.WriteLine(temp);
}

 

▼ 출력

0
1
2

 

 

 

캡처 & 클로저의 장점

  1. 상태를 가지는 함수를 만들 수 있다
    함수가 환경 ( 변수 )을 함께 들고 다니면서 일종의 "작은 객체" 처럼 행동한다
  2. 코드를 간결하게 만들 수 있다
    별도 클래스를 정의하지 않고 , 람다만으로 콜백 / 이벤트 핸들러 작성 가능하다
  3. 고차 함수 , 함수형 스타일에 잘 어울린다
    LINQ 이벤트 시스템 , 비동기 로직 등에서 유용하다

 

캡처 & 클로저의 단점

  1. 의도치 않은 변수 공유
    for 루프 예제처럼 "각 람다가 각자 다른 값을 잡을 줄 알았는데 실제로는 하나를 공유" 하는 문제
  2. 생명 주기가 길어진다
    원래는 블록을 벗어나면 사라질 지역 변수가
    클로저에 캡처되는 바람에 힙 객체 안에서 오래 살아남을 수 있다.
    큰 객체를 캡처하면 메모리가 오래 잡혀 있을 수도 있다.
  3. 디버깅이 헷갈릴 수 있다
    값이 언제 바뀌는지 , 어떤 람다가 어떤 변수를 보고 있는지 파악이 어렵다
    "어디서 변경됐지?" 라는 고민을 많이 하게 된다

C# 에서 클로저는 함수형 프로그래밍을 구성하는 중요한 요소이며 , 상태를 함수 안에 캡슐화할 수 있는 수단으로 사용된다.

 

 

 

클로저를 사용하면 좋을 상황

  • 함수가 바깥 변수의 상태를 기억해야 할 때
  • 코드 실행 시점과 함수 정의 시점이 다를 때
  • 비동기 처리나 이벤트 핸들러에서 외부 상태에 접근할 때

 

 

 

정리 

캡처 : 박스 ( 외부 변수 )를 람다 안으로 끌어오는 과정

클로저 : 변수 박스를 통째로 들고 다니는 함수

'⭐C Sharp > 12. 델리게이트' 카테고리의 다른 글

invoke  (0) 2025.10.09
델리게이트 ( Delegate )  (0) 2025.09.28
'⭐C Sharp/12. 델리게이트' 카테고리의 다른 글
  • invoke
  • 델리게이트 ( Delegate )
DevHoChan
DevHoChan
맨땅에서 시작하는 코딩 도전
  • DevHoChan
    Debugging Life
    DevHoChan
  • 전체
    오늘
    어제
    • 분류 전체보기 (374)
      • 🕹️Game Life (1)
      • 🖥️Computer Science (5)
      • 📖TIL (141)
        • 🔥Projects (16)
        • 💡DevTips (5)
        • 🤔발생한 문제와 해결 (5)
        • 🔮Unity Graphics (5)
        • 🎤Interview (3)
        • ✅CodingTest (9)
      • 🚀Game Release (4)
      • 🧊Unity Basic (58)
        • 📌용어 사전 (1)
        • 에디터&인터페이스 (3)
        • 디버그 (1)
        • 라이프사이클 (4)
        • 게임오브젝트 (4)
        • 프리팹 (1)
        • 오브젝트풀링 (4)
        • 애트리뷰트 (2)
        • 트랜스폼 (4)
        • 물리&충돌 (1)
        • 프레임&델타타임 (4)
        • 코루틴&이벤트 (7)
        • 수학&보정함수 (3)
        • 디자인패턴 (9)
        • UGUI (3)
        • 벡터 ( Vector ) (3)
        • 씬 ( Scene ) (2)
        • 데이터 관리 (2)
      • ⭐C Sharp (99)
        • 📌용어 사전 (1)
        • 📌문법 사전 (6)
        • 메모리 관리 (3)
        • 00. 문법 (17)
        • 01. 변수 (3)
        • 02. 자료형 (2)
        • 03. 연산자 (6)
        • 04. 조건문 (2)
        • 05. 반복문 (2)
        • 06. 배열 (3)
        • 07. 메서드(함수) (7)
        • 08. 열거형 (3)
        • 09. 구조체 (2)
        • 10. 참조 (2)
        • 11. 객체 지향 (11)
        • 12. 델리게이트 (3)
        • 13. 디자인 패턴 (7)
        • 14. LINQ (1)
        • 📂▼자료구조 (2)
        • 15-1. 제네릭 (3)
        • 15-2. 배열 (4)
        • 15-3. 리스트 (2)
        • 15-4. 스택과 큐 (2)
        • 15-5. 딕셔너리 해시테이블 (2)
        • 15-6. 트리와 그래프 (3)
      • 📊Algorithm (16)
        • BigO (2)
        • 정렬 (4)
        • 셔플 (2)
        • 탐색 (6)
        • 최적화 (1)
      • 📝Game Design (16)
      • 🤖​AI Tools (12)
        • AI 리뷰 분석 (6)
        • Player2 (0)
        • 3D 모델링 (1)
        • 2D 스프라이트 (0)
        • 이미지 (2)
        • 사운드 (1)
        • 동영상 (1)
        • 문서 (1)
      • 🌍Network (6)
      • 🌱Github (11)
        • 기본 개념 (7)
        • 명령어 (1)
        • 도구 활용 (1)
      • ⚙️Visual Studio (5)
        • 🔧설치 및 환경설정 (2)
        • ⌨️HotKey (1)
        • 🚨디버깅 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    OOP
    gamedesign
    메모리관리
    자료형
    디자인패턴
    CodingTest
    algorithm
    게임디자인
    til
    유니티
    GitHub
    기획
    객체지향
    c#
    csharp
    문법
    게임기획
    자료구조
    unity
    부트캠프
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
DevHoChan
캡처와 클로저 ( Capture & Closure )
상단으로

티스토리툴바