본문 바로가기

IT/학습

캡쳐 클로저

클로저(Closure)란?

캡처 클로저(Closure)는

 

C#에서 람다 식이나 익명 메서드가 외부 범위의 변수에 접근할 때 생성되는 현상을 말한다.

 

클로저는 이러한 외부 변수들을 "캡처"하여, 람다 식이나 익명 메서드가 실행될 때에도 해당 변수들의 값을 유지할 수 있도록 하는 것 이다.

클로저는 함수 또는 메서드가 자신의 범위(scope) 외부에 있는 변수들을 "캡처"하고, 그 변수들을 참조할 수 있는 기능을 말한다.

 

이렇게 캡처된 변수들은 함수가 실행되는 동안에도 계속 유지된다.

 

이것에 대해 잘못된 사용법을 한번 보자.

 

      mydel mydel = null;

      for (int i = 0; i < 3; i++)
      {
          int localVariable = i; // 외부 변수 캡처
          Console.WriteLine(100);
          mydel = () => { Console.WriteLine(i); ; return localVariable + i; };
      }
      Console.WriteLine("Dsdaf");
      Console.WriteLine(mydel?.Invoke());

 

이 코드의 문제점이 무엇인지 알겠는가?

 

한번에 알아보는 당신! 이 글을 볼필요가 없을것이다.

 

아무튼 결과값을 보면 조금 이해가 쉬울것이다.

 

 

자 보이는가?

 

100이 3번출력된뒤 mydel 호출하기전에 이상한 문자열이 나오고, 그다음에 mydel 안에 넣어둔 console구문과 return 값이 나오는것이.

 

여기서 알아야할것은 간단하다 델리게이트구문은 선언 직후 실행이 되는게 아니라 참조값 즉 주소안에 뭔가 할건데 미리 정의를 해두는 것일뿐, 실행순서만 정의 해둘뿐이지 실행을 하는것이 아닌것이다.

 

그래서 mydel 을 호출하기 전까지 주욱 실행이 된다음 저쪽 델리게이트 내부 구동이 시작되는것이다.

 

그리고 잘못된 예시라고 하지않았는가, 그 이유는 간단하다.

 

for문을 델리게이트 바깥에다 사용해서 이 문법에선 i값과 localVariable이 캡쳐로 사용된건 확실한데, i값 출력이 이상하다.

 

3이 출력된다음 i+localVariable는 5다. 우린 분명 i는 3을 못넘게 했는데 값이 3이 들어간것이다.

 

그와중에 localVariable는 2가 들어갔다.

그 이유는 간단하게도 for문이 다돈 후의 i값이 들어가서 그렇다.

 

그럼 여기서 localVariable 는 뭔데? 싶겠지만 저건 또 for문이 다돌기직전 즉 i가 2일때까지 돌았던 값이 들어가고 for문은 i가 증감식때문에 3인걸 확인한뒤 나와서 그렇다.

 

localVariable가 3이 들어가기전에 for문이 끝난것이다!

 

결론은 간단하다. 저렇게 쓰면 i값이 무조건 for문이 끝나는 값이 들어가니 저런구문은 사용할 꼭 주의바란다.

 

 

 

 

클로저는 주로 람다 식 또는 익명 메서드를 사용할 때 발생하며, 특히 비동기 프로그래밍에서 유용하게 사용됩니다.

예제: C#에서 클로저의 사용

 

using System;

public class Program
{
    public static void Main()
    {
        int outerVariable = 5;
        
        Func<int, int> addOuterVariable = (x) => x + outerVariable;
        
        int result = addOuterVariable(3);
        
        Console.WriteLine(result); // 출력: 8 (3 + 5)
        
        outerVariable = 10;
        result = addOuterVariable(3);
        
        Console.WriteLine(result); // 출력: 13 (3 + 10)
    }
}

동작 원리

  1. 변수 캡처: addOuterVariable 람다 식은 outerVariable을 캡처한다.
  2. 변수 참조: addOuterVariable이 호출될 때마다, 캡처된 outerVariable의 현재 값을 참조하여 계산한다.
  3. 변수 변경: outerVariable의 값이 변경되면, 그 다음 람다 식 호출에서 변경된 값이 사용되는 것이다.

 

저거만 보면 이해하기 힘드니 조금 주석을 달고 조금더 복잡하게 가보자

  
        //Func<int> int가 반환 되는 델리게이트
        //Func<int,int> int가 반환되는 int를 인자로 받는 델리게이트
        Func<int, Func<int, int>> createAdder = null;
                    //▼ 외부 변수 x를 캡처 func<int 의 인자
        createAdder = (x) =>
        {
            int y = 10; // 외부 변수
            Console.WriteLine(x);
            Console.WriteLine(y);

                  //▼ 외부 변수 Z를 캡처 func<int,int> 의 인자
            return (z) => x + y + z;  // 클로저: x와 y를 캡처
        };


        var adder = createAdder(5);
        Console.WriteLine(adder(3)); // 출력: 18 (5 + 10 + 3)

 

어떤 차이가 있는지 알겠는가?

 

이것저것 테스트하다보니 저게 된건데 처음엔   Func<int, Func<int>> 이런식으로 썼었다.

 

그리고 return (z) 이부분이 에러가 나길래 머지?? 하고 자세히보니

 

Func<int> 이렇게 되있는건 리턴값만 가진 델리게이트 였던것이다.

 

즉 반환값이 int인 델리게이트 였는데 난 재귀처럼 한번 더 타고 들어가보고 싶었는데, 에러가 난것이다.

 

아무튼 역시나 이렇게 테스트를 해봐야 뭐가 잘못알고있었는지 알수가 있는것같다..

 

이것만으로 되질 않으니 더 알아봐야겠지만... 아무튼 혼자연습한건 이정도이다.

 

참고로 이 코드에서 createAdder 가 몇번 도는거야? 하고 의문이 든사람에겐 두번이라고 말해주겠다.

 

 

  • createAdder(5)가 호출될 때:
    • 이 호출은 createAdder 함수의 본체인 람다 식 (x) => { ... }를 실행하고, x 값을 5로 캡처한다.
    • 내부에서 int y = 10;를 선언하고, (z) => x + y + z라는 람다 식을 반환.
    • 이 시점에서 x는 5, y는 10으로 캡처.
  • adder(3)가 호출될 때:
    • 이 호출은 createAdder(5)의 반환 값인 (z) => x + y + z 람다 식을 실행한다.
    • z 값을 3으로 전달.
    • 람다 식은 x + y + z 계산을 수행하여 5 + 10 + 3 = 18을 반환.

이런식이다.

 

따라서 두 번 실행된 것이고,

처음은 createAdder 함수 자체가 실행될 때,

두 번째는 반환된 adder 함수가 실행될 때 인것이다.

 

나 자신도 완벽하게 이해했다곤 말은 못하겠다.

 

대충 알긴하겠다만... 일단 써보면서 추가 첨삭이 있을수도 있으니 잘못된 정보가 있으면 제보부탁드립니다..

 

 

 

'IT > 학습' 카테고리의 다른 글

framework, engine, library 차이점  (1) 2025.01.02
SOILD 원칙  (0) 2024.12.31
Delegate와 Event  (0) 2024.12.20
[C#] DFS 와 BFS  (0) 2024.12.20
링크드 리스트  (1) 2024.12.19