CodeGym /행동 /C# SELF /이벤트 구독과 핸들러 작성

이벤트 구독과 핸들러 작성

C# SELF
레벨 52 , 레슨 2
사용 가능

1. 이벤트 구독 문법

작업 관리 앱을 생각해봐: 작업이 완료되면 이벤트가 발생하고, 핸들러는 예를 들어 알림을 보내거나 UI를 업데이트하거나 로그를 남길 수 있어. C#은 이벤트 구독을 명시적이고 안전하게 해줘: 어떤 핸들러가 특정 이벤트에 반응하는지 분명히 표시할 수 있어.

C#에서 이벤트 구독은 파티에 손님으로 이름 올리는 것과 거의 같아:

publisher.MyEvent += HandlerMethod;

여기서 publisher는 이벤트 MyEvent를 선언한 객체고, HandlerMethod는 이벤트가 발생했을 때 호출될 메서드야.

간단한 예제로 살펴보자. 클릭 수를 세는 애플리케이션이 있다고 가정해보자:

public class Clicker
{
    public event Action Clicked;

    public void Click()
    {
        // 무언가 클릭됨!
        Clicked?.Invoke();
    }
}

이건 Clicked라는 "발행자"야. 이제 핸들러를 구독해보자:

Clicker clicker = new Clicker();

void OnClicked()
{
    Console.WriteLine("버튼이 눌렸어!");
}

clicker.Clicked += OnClicked;

// 코드 어딘가에서
clicker.Click();
// → "버튼이 눌렸어!"

어떻게 동작하냐면? "클릭" 이벤트에 우리 메서드 OnClicked를 추가했고, 클릭이 발생할 때마다 이 메서드가 호출돼.

2. 이벤트 핸들러: 종류와 선언 방법

이벤트 핸들러는 이벤트가 발생했을 때 호출되는 메서드야. 시그니처는 이벤트의 델리게이트 타입과 일치해야 해. 예를 들어 이벤트가 public event Action Clicked; 로 선언되어 있다면, 핸들러는 매개변수와 반환값이 없는 메서드여야 해.

Action용 핸들러

void OnClicked() 
{
    Console.WriteLine("이벤트 발생 (Action)!");
}

표준 EventHandler용 핸들러

고전적인 EventHandler를 사용하면 핸들러는 두 개의 매개변수를 받지: 발신자(object sender)와 이벤트 데이터(EventArgs e):

public class Alarm
{
    public event EventHandler AlarmRaised;

    public void RaiseAlarm()
    {
        AlarmRaised?.Invoke(this, EventArgs.Empty);
    }
}

Alarm alarm = new Alarm();

void AlarmHandler(object sender, EventArgs e)
{
    Console.WriteLine("알람이 울렸어!");
}

alarm.AlarmRaised += AlarmHandler;
alarm.RaiseAlarm();

익명 메서드와 람다식

C#은 구독 시점에 익명 함수나 람다식을 직접 사용할 수 있어:

clicker.Clicked += () => Console.WriteLine("또 다른 클릭!");

이벤트에 인자가 있으면 조금 더 복잡해질 수 있어:

alarm.AlarmRaised += (sender, e) =>
{
    Console.WriteLine($"Alarm raised by: {sender}");
};

4. 유용한 뉘앙스

구독과 구독 해제: 중요한 점들

현실에서는 파티에 손님이 너무 많아지거나 누군가 집에 가고 싶어할 수 있어. 델리게이트도 마찬가지: 핸들러는 추가(구독)할 수 있고 제거(구독 해제)할 수 있어:

// 구독
publisher.MyEvent += MyHandler;

// 구독 해제 (더 이상 필요 없을 때)
publisher.MyEvent -= MyHandler;

왜 중요하냐면? 특히 큰 애플리케이션에서 구독 해제를 하지 않으면 핸들러가 "떠 있는" 상태가 되어 가비지 컬렉터가 객체를 정리하지 못해서 메모리 누수가 생길 수 있어.

여러 핸들러

하나의 이벤트에 원하는 만큼 핸들러를 구독할 수 있어. 추가된 순서대로 호출돼.

clicker.Clicked += () => Console.WriteLine("첫 번째 핸들러!");
clicker.Clicked += () => Console.WriteLine("두 번째 핸들러!");

clicker.Click();
// → 첫 번째 핸들러!
// → 두 번째 핸들러!

런타임에 핸들러를 구독하거나 해제하는 것도 가능해서 유연하게 설계할 수 있어.

실제 시나리오에서 왜 중요한가

  • UI (Windows Forms, WPF, WinUI, MAUI): 버튼 클릭, 텍스트 변경 같은 것들이 이벤트야.
  • FileSystemWatcher: 폴더에 새 파일이 생겼을 때 알림을 받음.
  • 비동기 작업: 파일 다운로드 완료, 작업 진행률 업데이트 등.
  • 플러그인 시스템: 별도 모듈들이 메인 앱의 이벤트를 구독함.

이벤트 모델은 확장 가능한 아키텍처를 만들게 해줘: 기본 코드를 바꾸지 않고도 새 모듈을 추가해서 기존 이벤트에 구독할 수 있어.

구독 예시

이벤트 시그니처 구독 예시 핸들러 예시
event Action
ev += Handler;
void Handler() { ... }
event Action<int>
ev += (x) => { ... };
void Handler(int x) { ... }
event EventHandler
ev += Handler;
void Handler(object s, EventArgs e) { ... }
event EventHandler<CustomArgs>
ev += Handler;
void Handler(object s, CustomArgs e) { ... }

5. 흔한 실수와 함정

실수 #1: 핸들러 시그니처 불일치.
이벤트가 event Action<int>로 선언되어 있는데 매개변수 없는 메서드를 구독하려 하면 컴파일 오류가 날 거야. 항상 메서드가 이벤트의 시그니처와 일치하는지 확인해.

실수 #2: 람다에서 변수 캡처.
람다로 구독할 때 주변 스코프의 변수를 캡처할 수 있어(클로저). 구독 후에 그 변수가 바뀌면 핸들러는 변경된 값을 보게 돼서 예상치 못한 동작이 발생할 수 있어.

실수 #3: 초기화되지 않은 객체의 이벤트에 구독.
객체가 생성되기 전에 그 객체의 이벤트에 구독하면 NullReferenceException이 발생할 위험이 있어. 구독하기 전에 객체가 사용 가능한 상태인지 확인해.

실수 #4: 같은 핸들러를 여러 번 구독.
같은 핸들러를 여러 번 구독하면 그만큼 여러 번 호출돼. 버그는 아니지만 의도치 않은 중복 호출을 만들어서 놀랄 수 있어.

2
과제
C# SELF, 레벨 52, 레슨 2
잠금
여러 구독자가 있는 이벤트
여러 구독자가 있는 이벤트
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION