클로저(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)
}
}
동작 원리
- 변수 캡처: addOuterVariable 람다 식은 outerVariable을 캡처한다.
- 변수 참조: addOuterVariable이 호출될 때마다, 캡처된 outerVariable의 현재 값을 참조하여 계산한다.
- 변수 변경: 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 |