본문 바로가기

카테고리 없음

클로저(Closure) in C#

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#에서도 클로저 개념을 활용하여 상태를 유지하는 함수를 만들 수 있습니다! 🚀