반응형

 

 

"대리자(delegate)와 event"

 

 

개발을 하다보면 어떤 일이 발생했을 때 다른 시스템이 반응해야 하는 상황이 자주 생긴다.

 (HP 변경, 점수 변경, 로딩 완료 등)

 

 

예를들어,

Unity에서도 PlayerHP가 HP를 변경할 때 UI, 사운드, 이펙트가 함께 반응해야 하는데

이를 한 클래스에서 모두 직접 호출하기 시작하면 결합도가 올라가고 유지보수가 어려워진다.

 


이때 C#의 대리자(delegate)event를 사용하면, 

함수(메서드)를 값처럼 다루고, 발행/구독 구조로 통신할 수 있어 코드 구조가 훨씬 깔끔해진다.

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

1) 대리자(Delegate)란?


쉽게 말해 “메서드를 담는 변수"다.

(단, 아무 메서드나 담는 것이 아니라 시그니처(매개변수/반환값)가 같은 메서드만 담을 수 있음).

 

 

delegate 정의 예시 코드

public delegate void DamageHandler(int damage);

 

이 타입에는 int를 받고 void를 반환하는 메서드만 담을 수 있다.

 

 

대리자 등록/호출 예시 코드

public class Player
{
    public DamageHandler OnDamaged; // 대리자

    public void TakeDamage(int dmg)
    {
        OnDamaged?.Invoke(dmg); // 등록된 메서드들 호출
    }
}

// 등록(구독)
void PrintDamage(int dmg) => Debug.Log($"Damaged: {dmg}");
player.OnDamaged += PrintDamage;

 

 

+=로 메서드를 등록(구독)할 수 있고

Invoke() 하면 등록된 메서드들이 순서대로 실행된다.

(멀티캐스트 가능)

 

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

2) 그런데 event는 왜 필요할까?


대리자를 public 필드로 열어두면 이런 문제가 생긴다.
(외부 코드가 +=로 등록하는 것뿐만 아니라 외부에서 마음대로 Invoke()로 호출해버릴 수도 있음)

 

즉, “HP 변경 이벤트”를 HP를 관리하는 클래스가 아니라

UI가 멋대로 발행해버리는 상황이 가능해지는데,
이건 설계상 매우 위험하다.

 

1) 이벤트의 의미가 깨진다.

예를들어,

OnHpChanged는 의미상 “HP가 변경되었다”는 사실(Truth) 을 알리는 신호여야 하는데,
UI가 마음대로 Invoke()를 할 수 있으면

실제 hp 값은 다른 값인데

UI가 Invoke()한 값으로 화면에 표시되는“거짓 상태”가 만들어질 수 있다.

 

 

즉, 이벤트가 상태의 진실을 보장하지 못하게 된다.

 

 

2) 시스템 불변식(규칙)이 쉽게 깨진다.

UI가 임의로 이벤트를 발행하면

사망 처리 없이 HP가 0으로 표시되거나, 무적 중인데도 HP 감소 효과가 발생하는

게임 로직의 규칙이 우회될 수 있다.

 

 

3) 호출 경로가 통제되지 않아 디버깅이 어려워진다.

“HP가 갑자기 바뀌었다” 같은 버그가 생겼을 때 원인을 찾기 어려워진다.

 

 

그래서 C#은 event키워드를 제공한다.

 

 

delegate vs event 차이

public Action<int> OnHpChanged;          // 외부에서 Invoke 가능(주의)
public event Action<int> OnHpChanged2;   // 외부에서 Invoke 불가(구독/해지만 가능)

 

 

delegate: 외부에서 +=, -=, Invoke 모두 가능

event: 외부에서 +=, -=만 가능 / Invoke는 클래스 내부만 가능

 

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

4) 그렇다면, 언제 delegate를 쓰고, 언제 event를 쓰나?


함수를 변수처럼 저장해서 직접 호출/조합하고 싶다면
→ delegate/Action 자체를 써도 됨

(단, 접근 제한 설계가 중요)

여러 구독자에게 상태 변화를 알리고, 발행은 한 곳에서만 통제하고 싶다면
→ event를 사용하는 것이 정석

공용 API로 노출할 때는 대부분 event를 권장한다.

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

5) Unity에서 자주 터지는 주의사항

 

구독 해제 누락

OnEnable += 했다면 OnDisable -=로 반드시 을 맞추기
(누락하면 비활성화/파괴 이후에도 호출되거나 참조가 남을 수 있다)

 


예외 처리
이벤트는 등록된 메서드를 순서대로 호출한다.
(한 구독자에서 예외가 터지면 뒤 구독자가 실행되지 않을 수 있으니, 

필요하면 호출 쪽에서 방어적으로 처리한다)

 


람다 등록 시 해제 어려움
player.OnHpChanged += (hp) => {...}; 는 빼기(-=)가 까다로울 수 있다.

(가능하면 메서드로 분리하는 편이 안전하다)

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

 

오늘의 한 줄 요약

 

대리자(delegate)“메서드를 담는 타입”이고,

event는 그 대리자를 “안전한 이벤트(발행/구독) 형태로 노출”하기 위한 장치이다.

 

반응형

+ Recent posts