C#에서는 JavaScript나 Python처럼 직접적으로 클로저(Closure) 개념을 제공하지 않지만, 익명 함수(Anonymous Functions), 람다 표현식(Lambda Expressions), 로컬 함수(Local Functions) 등을 활용하여 클로저와 유사한 동작을 구현할 수 있습니다.
클로저(Closure)란?
클로저는 함수 내부에서 외부 함수의 변수를 캡처하고, 해당 변수를 유지하면서 사용하는 기능을 의미합니다.
즉, 함수가 실행된 후에도 변수를 계속 유지하는 특징이 있습니다.
C#에서는 람다(Lambda) 또는 익명 함수(Anonymous Functions)가 외부 범위의 변수를 캡처하여 유지하는 방식으로 클로저를 구현할 수 있습니다.
1. 람다 표현식으로 클로저 구현
using System;
class Program
{
static Func<int> Counter()
{
int count = 0; // 외부 변수 (Closure 대상)
return () => ++count; // 외부 변수 `count`를 캡처
}
static void Main()
{
var counter1 = Counter();
var counter2 = Counter();
Console.WriteLine(counter1()); // 1
Console.WriteLine(counter1()); // 2
Console.WriteLine(counter2()); // 1 (새로운 클로저)
Console.WriteLine(counter2()); // 2
}
}
2. 익명 메서드(Anonymous Method) 사용
using System;
class Program
{
static Func<int> Counter()
{
int count = 0;
return delegate () { return ++count; };
}
static void Main()
{
var counter = Counter();
Console.WriteLine(counter()); // 1
Console.WriteLine(counter()); // 2
}
}
3. 로컬 함수(Local Function) 활용
C# 7.0부터는 로컬 함수를 활용하여 클로저처럼 동작할 수 있습니다.
using System;
class Program
{
static Func<int> Counter()
{
int count = 0;
int Next() => ++count; // 로컬 함수 (외부 변수 `count`를 캡처)
return Next;
}
static void Main()
{
var counter = Counter();
Console.WriteLine(counter()); // 1
Console.WriteLine(counter()); // 2
}
}
주의할 점: 클로저의 변수 캡처 방식
C#의 클로저는 참조형 변수(Reference)로 캡처됩니다.
이 때문에 예상과 다른 결과가 나올 수도 있습니다.
1. 반복문에서 클로저 사용 시 주의
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<Func<int>> funcs = new List<Func<int>>();
for (int i = 0; i < 3; i++)
{
funcs.Add(() => i);
}
foreach (var func in funcs)
{
Console.WriteLine(func()); // ???
}
}
}
예상: 0, 1, 2
실제 출력: 3, 3, 3
이유
- i는 반복문 내에서 클로저에 의해 캡처되지만, 변수 자체는 참조됩니다.
- 반복문이 끝나면 i = 3이므로 모든 람다가 3을 출력하게 됩니다.
2. 해결 방법: 변수 복사
for (int i = 0; i < 3; i++)
{
int copy = i; // 복사본을 생성하여 캡처
funcs.Add(() => copy);
}
이제 예상대로 0, 1, 2가 출력됩니다.
변수 copy가 새로운 범위를 가지므로 클로저가 개별 값을 유지할 수 있습니다.
Q&A: 자주 묻는 질문
Q: 클로저는 언제 사용하면 좋을까요?
A: 특정 상태를 유지하면서 함수를 실행해야 할 때 유용합니다. 예를 들어, 카운터 기능이나 이벤트 핸들러에서 자주 사용됩니다.
Q: C#에서 클로저는 어떻게 변수를 캡처하나요?
A: C#에서는 변수의 참조(Reference)를 캡처합니다. 따라서 반복문에서 사용 시 주의해야 하며, 필요하면 변수 복사를 통해 해결할 수 있습니다.
Q: 클로저를 사용하면 메모리 문제가 발생할 수 있나요?
A: 클로저가 참조하는 변수가 GC(Garbage Collector)에 의해 수거되지 않을 수 있으므로, 불필요한 클로저 사용은 메모리 누수를 일으킬 가능성이 있습니다. 따라서 사용 후에는 참조를 해제하는 것이 좋습니다.
정리
C#에서는 클로저를 다음과 같이 구현할 수 있습니다.
- 람다 표현식 (()=> 사용)
- 익명 메서드 (delegate 사용)
- 로컬 함수 (Local Function 활용)
주의할 점
- 클로저는 참조(Reference) 를 캡처하므로 반복문에서는 변수 복사를 해야 합니다.
- 클로저는 함수가 종료된 후에도 변수를 유지하는 특징이 있습니다.
C#에서도 클로저 개념을 활용하여 상태를 유지하는 함수를 만들 수 있습니다! 🚀