CodeGym /행동 /C# SELF /람다식으로 event 및 callbac...

람다식으로 event 및 callback 처리하기

C# SELF
레벨 50 , 레슨 3
사용 가능

1. 소개

C#(그리고 .NET 전반)에서 이벤트는 객체들끼리 소통하는 한 방법이야. 예를 들어 버튼이 있는데, 그 버튼을 누르면 뭔가 실행되도록 하고 싶을 때가 있지. 이벤트 처리를 위해 별도의 메서드를 선언하고 그 메서드를 이벤트에 명시적으로 구독시키는 방식으로 할 수도 있어... 하지만 간단한 로직이라면 굳이 파일 여기저기에 메서드를 흩어놓고 싶지 않을 때가 많아.

람다식이 이런 경우에 딱 맞아 — 이벤트에 구독하는 자리에서 바로 반응을 작성할 수 있게 해줘. 최소한의 의례로 최대의 효율을 얻을 수 있어.

콜백(callback)도 마찬가지야: 어떤 메서드에 나중에 실행할 로직을 넘겨주고 싶을 때가 있는데, 람다식은 그걸 간단하고 직관적으로 만들어줘. 필요한 로직을 바로 호출 지점에 기술하면 돼.

이벤트와 콜백: 핵심만 짧게, 람다가 왜 필요한가

이벤트는 객체에 붙은 “표지판” 같은 거야: “여기 누군가 구독해서 어떤 일이 일어날 때 실행할 수 있어”.

Callback (역호출)은 “너에게 함수를 줄게, 필요할 때 그걸 호출해줘”라는 개념이야. 전화번호 남기고 나중에 연락받는 것과 유사해.

델리게이트와 이벤트를 처음 배울 때 다음 같은 문법을 봤을 거야:

button.Click += MyButtonClickHandler;

여기서 MyButtonClickHandler는 어딘가에 선언된 특정 메서드야. 하지만 로직이 단순하면 굳이 메서드를 하나 더 만들고 싶지 않을 때가 있지.

그럴 때 람다가 등장해:

button.Click += (sender, args) => { Console.WriteLine("버튼이 눌렸어!"); };

간결하고 바로 이해 가능하지!

2. 이벤트 핸들러로서의 람다: 간단한 것부터 흥미로운 것까지

간단한 형태: 전구 깜빡이면 콘솔에 출력

가정해보자, Button 클래스가 있고, 우리는 그 Click 이벤트에 반응하고 싶어.

// 이런 버튼이 있다고 생각해보자:
public class Button
{
    public event EventHandler? Click;

    public void SimulateClick() // 예제용으로: 프로그래밍 방식으로 "클릭" 발생시킴
    {
        // 누군가 구독했다면 핸들러들을 호출
        Click?.Invoke(this, EventArgs.Empty);
    }
}

이제 이 버튼을 사용해보자:

var button = new Button();

// 람다로 버튼 이벤트에 구독해보자!
button.Click += (sender, args) =>
{
    Console.WriteLine("야호! 버튼이 눌렸어!");
};

button.SimulateClick(); // => 야호! 버튼이 눌렸어!

설명:
=> 뒤에 있는 게 당신의 “미니 함수” 본문이며, 이벤트가 발생하면 호출될 거야.
— 핸들러의 생애주기에 대해 고민할 필요 없어: 핸들러는 당신의 구독(+=)이 유지되는 동안 살아있어. 중요: 람다가 외부 변수를 캡처하고 있고 이벤트에서 언구독하지 않으면 메모리 누수가 발생할 수 있어.

변수를 캡처하는 람다 핸들러

int clickCount = 0;

button.Click += (sender, args) =>
{
    clickCount++;
    Console.WriteLine($"버튼이 이미 {clickCount}번 눌렸어!");
};

button.SimulateClick(); // => 버튼이 이미 1번 눌렸어!
button.SimulateClick(); // => 버튼이 이미 2번 눌렸어!

무슨 일이 일어나나:
람다가 외부의 clickCount 변수를 "캡처"해서 호출들 사이에 그 값을 기억하고 있어.

3. callbacks에 람다식 적용하기

콜백은 어떤 코드가 나중에 당신의 함수를 호출해야 할 때 자주 쓰여 — 예를 들어 긴 작업이 끝났을 때 호출하는 식으로.

람다를 callback으로 전달하기

어떤 메서드가 델리게이트나 람다를 인수로 받는다고 해보자:

void DoWork(Action callback)
{
    Console.WriteLine("작업을 시작합니다...");
    // 작업 흉내내기
    System.Threading.Thread.Sleep(500); // (실제 앱에서는 이렇게 하지 마, 블로킹이야!)
    callback(); // 작업 후 callback 호출
}

DoWork(() => Console.WriteLine("작업 완료!"));

출력:
먼저 콘솔에 "작업을 시작합니다..."가 찍히고, 그다음에 "작업 완료!"가 찍혀.

파라미터가 있는 람다도 전달할 수 있어:

void Calculate(int a, int b, Action<int> onResult)
{
    int sum = a + b;
    onResult(sum);
}

Calculate(5, 8, result => Console.WriteLine($"결과: {result}"));

4. 람다 사용 시 흔한 실수들

이벤트에서 언구독하지 않음

람다로 이벤트에 구독하면 보통 “핸들러 이름”이 없어서 나중에 간편하게 언구독하기가 어려워:

button.Click += (s, e) => Console.WriteLine("...");
button.Click -= ??? // 어떻게 이 람다를 전달하지? 저장해두지 않았다면 불가능해.

언구독이 필요하다면 람다를 변수에 저장해라:

EventHandler handler = (s, e) => Console.WriteLine("...");
button.Click += handler;
// ...그리고 나중에
button.Click -= handler;

이건 수명이 긴 객체에 특히 중요해: 언구독하지 않으면 참조가 남아 메모리 누수를 유발할 수 있어.

루프 변수 캡처

클래식한 문제: 루프 내부에서 이벤트를 구독할 때:

for (int i = 0; i < 3; i++)
    button.Click += (s, e) => Console.WriteLine(i); // 모든 람다가 동일한 i를 "기억"할 수 있음

최신 C#에서는 foreach에 대한 동작이 수정된 경우가 있지만, for에서는 여전히 미묘한 점이 있을 수 있어 — 캡처된 변수에 항상 주의하자!

각 람다가 자기만의 값을 기억하게 하고 싶으면 로컬 복사본을 만들어라:

for (int i = 0; i < 3; i++)
{
    int localI = i;
    button.Click += (s, e) => Console.WriteLine(localI);
}
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION