
객체 지향 프로그래밍을 하다 보면, 한 객체에서 어떤 일이 발생했을 때,
그 사실을 다른 여러 객체에 알려줘야 하는 상황이 정말 많습니다.
"플레이어의 게임 캐릭터가 레벨업을 했습니다!
이 사실을 UI 관리자에게 알려서 화면에 '레벨업!' 이펙트를 띄우고,
사운드 관리자에게 알려서 축하 효과음을 재생해야 해!"
이때 Player클래스가 UIManager와 SoundManager를 직접 알고 있다면 어떻게 될까요?
코드가 서로 단단히 얽혀서(Tight Coupling), 나중에 파티클 효과를 추가하려면
Player클래스 코드를 또 수정해야 합니다. 이런 문제를 해결하기 위해
C#은 이벤트(Event)라는 발행-구독(Publish-Subscribe) 패턴을 제공합니다.
이번 글에서는 C# 이벤트의 핵심 원리를 파헤치고, 이것이 어떻게 객체들을
느슨하게 연결하여 유연하고 확장할 수 있는 코드를 만드는지 알아보겠습니다!
이벤트를 이해하는 가장 좋은 방법은 '유튜브 채널'을 떠올리는 것입니다.
Player클래스)UIManager, SoundManager)OnLevelUp)
가장 중요한 점은, 유튜버는 자신의 구독자가 누구인지, 전혀 알 필요가 없다는 것입니다.
이 덕분에 새로운 구독자가 생기거나 기존 구독자가 떠나도, 유튜버는 영향받지 않습니다.
이것이 바로 느슨한 결합(Loose Coupling)의 핵심입니다.
"멀티캐스트 대리자 기능이랑
event키워드는 똑같은 거 아닌가요?"
C#에서 이벤트의 내부 메커니즘은 멀티캐스트 대리자를 기반으로 합니다.
하지만 그냥 public대리자를 사용하는 것과 event키워드를 붙이는 것에는
안정성에서 매우 크게 차이 납니다.
만약 Player클래스에 public대리자를 그냥 선언하면 어떤 문제가 생길까요?
public class Player
{
// 그냥 public 대리자 (나쁜 예!)
public Action OnLevelUp;
public void LevelUp()
{
// ... 레벨업 로직 ...
OnLevelUp?.Invoke(); // 구독자들에게 알림
}
}
Player클래스 외부의 아무 코드나player.OnLevelUp()을 호출해서 가짜 레벨업 알림을 보낼 수 있습니다.player.OnLevelUp = null;코드를=할당으로 덮어쓰기 가능)event 키워드의 안정성event키워드는 대리자에게 두 가지 강력한 안전장치를 걸어줍니다.
public class Player
{
// event 키워드로 안전하게 보호!
public event Action OnLevelUp;
// ...
}
OnLevelUp이벤트는 Player클래스 내부에서만Invoke)할 수 있습니다. 외부에서는 절대 불가능합니다.+=(구독)와 -=(구독 취소) 연산자만 사용할 수 있습니다.=연산자로 구독자 목록을 통째로 덮어쓰는 행위가 원천적으로 차단됩니다.이제 Player, UIManager, SoundManager를 이용해 레벨업 알림 시스템을 구축해 봅시다.
[코드]
// 1. 게시자(Publisher) 클래스 구현
public class Player
{
// 이벤트 선언
public event Action OnLevelUp;
private int _level = 1;
public void GainExperience(int amount)
{
Console.WriteLine("플레이어가 경험치를 얻었습니다.");
// 경험치 획득 후 레벨업 조건을 만족했다고 가정
_level++;
Console.WriteLine($"레벨 업! 현재 레벨: {_level}");
// 이벤트 발생! (게시)
// OnLevelUp이 null이 아닐 때만 호출 (구독자가 한 명이라도 있을 때)
OnLevelUp?.Invoke();
}
}
// 2. 구독자(Subscriber) 클래스 구현
public class UIManager
{
// 이벤트 핸들러(EventHandler) 메서드
// 이벤트가 발생했을 때 실행될 실제 로직
public void OnPlayerLevelUp_ShowEffect()
{
Console.WriteLine("[UI] 레벨업 이펙트가 화면에 표시됩니다!");
}
}
public class SoundManager
{
public void OnPlayerLevelUp_PlaySound()
{
Console.WriteLine("[Sound] 빰빠밤~ 레벨업 축하 사운드가 재생됩니다!");
}
}
// 3. 이벤트 연결 및 실행
class Program
{
static void Main()
{
Player player = new Player();
UIManager ui = new UIManager();
SoundManager sound = new SoundManager();
// 이벤트 구독!
// Player의 OnLevelUp 이벤트에 각 관리자의 메서드를 연결(+=)
player.OnLevelUp += ui.OnPlayerLevelUp_ShowEffect;
player.OnLevelUp += sound.OnPlayerLevelUp_PlaySound;
// 이벤트 발생의 트리거가 되는 행동 실행
player.GainExperience(100);
}
}
[실행 결과]
플레이어가 경험치를 얻었습니다.
레벨 업! 현재 레벨: 2
[UI] 레벨업 이펙트가 화면에 표시됩니다!
[Sound] 빰빠밤~ 레벨업 축하 사운드가 재생됩니다!
Player는 UIManager나 SoundManager의 존재를 전혀 모르지만,
레벨업을 하자 구독을 신청했던 두 객체의 메서드가 모두 실행되었습니다!
이벤트는 C#에서 객체 간의 상호작용을 설계하는 가장 중요하고 기본적인 패턴입니다.
| 개념 | 역할 | 설명 |
|---|---|---|
| 게시자 | 이벤트 소유 및 발생 | "사건 발생!"을 알리는 객체 |
| 구독자 | 이벤트 처리 | 알림을 받고 특정 동작을 수행하는 객체 |
| 이벤트 핸들러 | 이벤트 발생 시 실행될 메서드 | 구독자가 제공하는 실제 처리 로직 |
event 키워드 | 대리자 보호 | 외부에서의 임의 호출 및 덮어쓰기를 방지하는 안전장치 |