김선영
한세대학교 22학번 컴퓨터공학과
한세대학교 22학번 컴퓨터공학과
📌 C# 이벤트·델리게이트·멀티캐스트 델리게이트 완전 이해하기
이 문서는 C#에서 자주 쓰이는 이벤트(event) 와 델리게이트(delegate)의 동작 원리, 그리고 내부적으로 어떻게 콜백이 가능해지는지 정리한 기술 노트입니다.
#️⃣ 1. 이벤트 구독하면 왜 콜백이 될까?
이벤트는 “특정 순간에 발생하는 신호”이고, 구독자는 그 신호를 듣고 있다가 반응합니다.
button.OnClick += HandleClick;
이 구조가 좋은 이유:
느슨한 결합(Decoupling) 이벤트를 만드는 쪽은 “신호를 발행”할 뿐, 누가 듣는지 알 필요 없음.
확장성 구독자 추가·제거가 자유로움.
동시 반응 가능 여러 구독자가 하나의 이벤트에 동시에 반응 가능.
#️⃣ 2. 그럼 콜백은 어떻게 가능한가?
핵심은:
델리게이트(delegate)는 “함수 주소(Function Pointer)”를 저장할 수 있는 타입이다.
구독하면 내부적으로 이런 일이 일어남:
함수의 주소가 델리게이트 객체에 저장됨
이벤트가 발생하면
OnClick?.Invoke();
를 통해 저장된 함수들을 모두 호출함
즉 “Invoke()”는 구독한 함수 포인터 목록을 순회하며 하나씩 호출하는 동작이다.
#️⃣ 3. 이벤트가 호출되는 시점은 누가 정하나?
발행자(publisher) 코드가 정한다.
버튼 예시:
if (isClicked)
OnClick?.Invoke();
이 순간이 바로 “이벤트 발생 시점”.
이벤트는 외부에서 호출할 수 없고, 발행자만 호출할 수 있게 되어 있다.
#️⃣ 4. 델리게이트 vs 이벤트 차이
구분 델리게이트 이벤트
외부 호출 가능? 가능함 (위험) 불가능 외부 할당 가능? 통째로 변경 가능 +=, -=만 가능 목적 함수 포인터 안전한 콜백 시스템
이벤트는 델리게이트를 안전하게 감싼 문법적 보호막이다.
#️⃣ 5. 일반 함수 호출과 Invoke 호출의 차이
✔ 일반 함수 호출
→ 컴파일 타임에 호출할 함수가 결정됨 → “주소가 정해진 곳으로 바로 점프”
✔ 델리게이트 Invoke
→ 런타임에 저장된 함수 포인터 리스트를 순회하며 호출 → 즉, 동적 호출 방식
요약하면:
일반 함수 호출 = 단일 정적 호출 델리게이트 Invoke = 동적 함수 목록 순회 호출
#️⃣ 6. 🔥 멀티캐스트 델리게이트 내부 구조
멀티캐스트 델리게이트는 기본적으로 아래 정보를 담는 객체다:
📌 MulticastDelegate 내부 필드(개념)
Delegate
├─ target // 메서드를 호출할 객체 (this)
├─ method // 실제 메서드 주소(Method Pointer)
├─ _invocationList // 멀티캐스트일 경우 저장되는 델리게이트 배열
└─ _invocationCount // invocationList 크기
📌 단일 델리게이트 구조
[Delegate]
target -> 객체 or null
method -> 함수 주소
_invocationList -> null
📌 멀티캐스트 구조 (A += B += C)
[Delegate]
target -> 첫 메서드의 대상 객체
method -> 첫 메서드 주소
_invocationList -> [A, B, C] // 델리게이트 배열
_invocationCount -> 3
📌 Invoke() 동작 방식
Invoke() 는 내부적으로 다음 순서를 수행한다:
_invocationList 를 가져온다
저장된 델리게이트들을 순서대로 반복
각 델리게이트의 method + target 조합을 실행
foreach(delegate d in _invocationList)
{
call d.method on d.target
}
즉, Invoke가 “마법”이 아니라 함수 포인터 배열을 돌며 하나씩 호출하는 메서드일 뿐이다.
#️⃣ 7. ⭐ 연산은 얼마나 줄어드는가? (성능 관점)
멀티캐스트 델리게이트는 연산을 줄이는 기술이 아니다. 오히려 약간 더 비싸다.
왜 연산이 줄지 않을까?
둘 다 해야 하는 실제 작업은 동일하다:
A 호출
B 호출
C 호출
즉 필수 호출 개수 자체는 변하지 않음 → 호출은 3번 그대로.
직접 호출
A();
B();
C();
멀티캐스트 델리게이트
handlers?.Invoke(); // 내부에서 A, B, C 각각 호출
→ 둘 다 최종적으로 함수는 똑같이 3번 실행됨.
그런데 Invoke가 조금 더 비싸다
Invoke 내부에서 이런 작업을 추가로 수행하기 때문:
null 체크
_invocationList 접근
리스트 길이 확인
함수 주소 + 대상 객체 잡기
루프 돌면서 개별 호출
즉:
직접 호출이 더 빠르고, Invoke는 약간의 루프 + 구조체 조회 비용이 더 든다.
#️⃣ 8. 성능 상관 있냐? 언제 문제 되냐?
대부분의 이벤트:
OnClick
OnDie
SceneLoaded
OnLevelUp
→ 프레임당 자주 호출되지 않음 → 성능 걱정할 필요 없음
주의해야 할 상황은:
매 프레임 수만 번 이상 콜백 발생
델리게이트 목록을 계속 만들고 다시 붙였다가 떼는 경우
초핫 루프(Physics, AI, Pathfinding 핵심 루프)에 이벤트 사용
이런 극단적인 경우만 고려하면 됨.
#️⃣ 9. 결론 요약
Invoke는 마법이 아니라 “함수 포인터 리스트 순차 호출”
일반 함수 호출보다 약간 느리지만 구조적 이점이 압도적으로 크다
이벤트는 “안전한 델리게이트 시스템”
성능 문제는 극단적으로 자주 호출할 때만 고려