내일배움캠프 30일차 TIL
📌 델리게이트란?
델리게이트(delegate)는 C#에서 메서드를 변수처럼 다룰 수 있게 해주는 메서드 참조 타입 (특정 시그니처(매개변수, 반환값)를 가진 메서드를 참조할 수 있는 타입) 이다.
델리게이트는 특정 메서드들을 나중에 호출하기 위해 저장하거나, 외부에서 전달받아 실행할 수 있게 해주는 기능이다.
델리게이트의 목적
| 목적 | 설명 |
| 메서드를 변수처럼 전달 | 메서드를 파라미터로 전달해 콜백 구현 가능 |
| 동적으로 실행할 로직 결정 | 상황에 따라 실행할 메서드를 유동적으로 바꿀 수 있음 |
| 느슨한 결합 | 호출하는 쪽과 실행하는 쪽의 의존도를 낮춤 (OCP 등 원칙 만족) |
| 이벤트 시스템 구현 | 버튼 클릭 등 Unity 이벤트 연결에 필수 |
델리게이트의 주요 사용처
| 사용처 | 예시 |
| 콜백 함수 | 비동기 처리 후 onComplete() 호출 |
| 전략 패턴 구현 | 공격 방식, 이동 방식 등을 런타임에 변경 |
| 이벤트 시스템 | Unity의 onClick, onDie, onScoreChanged |
| LINQ/람다 함수 | Where(x => x > 0)에서 Func<T, bool> 델리게이트 사용 |
| 상태 전환 로직 | 상태에 따라 다른 행동을 연결해서 실행 |
| 의존성 주입(Delegate Injection) | 호출할 로직을 외부에서 주입받음 |
기본 구조
// 1. 델리게이트 선언
public delegate void MyDelegate(string message);
// 2. 델리게이트에 연결할 함수 정의
void SayHello(string msg)
{
Debug.Log("Hello: " + msg);
}
// 3. 델리게이트 인스턴스 생성 및 함수 등록
MyDelegate del = SayHello;
// 4. 호출
del("Unity!");
SayHello 함수 자체를 변수처럼 넘기고 나중에 실행할 수 있다.
C#의 델리게이트는 내부적으로 MulticastDelegate 클래스를 상속받고 있다. 즉, 여러 메서드를 체인으로 묶어 실행할 수 있다.
Delegate[] list = myDelegate.GetInvocationList();
이렇게 등록된 메서드들을 배열로 가져와 개별 실행하거나 제거할 수 있다. delegate는 참조 타입이고, 불변 객체(immutable) 이다. +=나 -=는 내부적으로 새 객체를 반환한다.
왜 사용하나요?
| 상황 | 델리게이트로 해결 가능 |
| 버튼 클릭 시 특정 함수 실행 | 이벤트 연결 |
| 타이머가 끝났을 때 특정 함수 실행 | 콜백 함수 전달 |
| 코드 흐름을 유연하게 제어 | 외부에서 행동 지정 |
| 이벤트 기반 구조 구현 | 옵저버 패턴 |
간단 예제
버튼 클릭 이벤트 (델리게이트 활용)
public delegate void ClickAction();
public class ButtonHandler : MonoBehaviour
{
public ClickAction onClick;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
onClick?.Invoke(); // 등록된 함수 실행
}
}
}
public class GameLogic : MonoBehaviour
{
public ButtonHandler button;
void Start()
{
button.onClick = MyClickEvent;
}
void MyClickEvent()
{
Debug.Log("버튼이 눌렸습니다!");
}
}
여러 함수 등록도 가능 (Multicast Delegate)
MyDelegate del = SayHello;
del += SayBye;
del("Player"); // 두 함수 모두 실행됨
델리게이트는 +=, -=로 여러 함수 연결과 해제가 가능.
유니티에서의 활용 예
1. 공격이 끝났을 때 콜백 실행
public class Enemy : MonoBehaviour
{
public Action onDeath;
public void Die()
{
Debug.Log("죽었습니다");
onDeath?.Invoke();
}
}
public class GameManager : MonoBehaviour
{
public Enemy enemy;
void Start()
{
enemy.onDeath = OnEnemyDeath;
}
void OnEnemyDeath()
{
Debug.Log("적이 죽었으니 점수 +100");
}
}
2. 커스텀 이벤트 시스템 구현
public static class GameEvent
{
public static Action onGameStart;
public static Action onGameOver;
}
// GameManager.cs
void Start()
{
GameEvent.onGameStart?.Invoke();
}
// UIManager.cs
void OnEnable()
{
GameEvent.onGameStart += ShowStartUI;
}
void OnDisable()
{
GameEvent.onGameStart -= ShowStartUI;
}
델리게이트의 종류
1. 일반 델리게이트
public delegate void MyDelegate(string msg);
MyDelegate del = ShowMessage;
del("Hello");
2. Action, Func, Predicate (기본 델리게이트)
- Action : 반환값 없음
- Func : 반환값 있음
- Predicate<T> : Func<T, bool>과 동일
Action<string> print = Debug.Log;
Func<int, int, int> add = (a, b) => a + b;
Predicate<int> isEven = x => x % 2 == 0;
→ 이들은 델리게이트 선언 없이도 편하게 쓸 수 있다.
델리게이트 체인이란?
델리게이트에 +=로 여러 개의 함수를 연결하면, 등록한 순서대로 차례대로 실행된다.
public delegate void MyDelegate();
void A() { Debug.Log("A"); }
void B() { Debug.Log("B"); }
void C() { Debug.Log("C"); }
void Start()
{
MyDelegate del = A;
del += B;
del += C;
del(); // 실행 순서: A → B → C
}
반환값이 있는 경우 (Func<T>)
Func<int> del = A;
del += B;
del += C;
int result = del(); // C()의 반환값만 남음
Func<T>의 경우에도 모든 함수는 실행되지만, 마지막 함수의 반환값만 유효
예)
int A() { Debug.Log("A"); return 1; }
int B() { Debug.Log("B"); return 2; }
int C() { Debug.Log("C"); return 3; }
// del 실행 → A, B, C 호출됨 → result = 3
주의: 중복 등록도 됨.
del += A;
del += A;
del += A;
이러면 A()가 3번 실행. 의도하지 않은 중복 호출이 생길 수 있으니 주의.
순서 변경하고 싶으면?
델리게이트 체인은 직접 순서 변경이 불가능하고, -=로 제거한 후 다시 +=로 등록해야 한다.
del -= A;
del += A; // 가장 마지막으로 이동
체인 호출 순서 정리
| 연산 | 효과 |
| del += A | 체인 뒤에 추가됨 |
| del -= A | 체인에서 제거됨 (여러 번 등록돼 있으면 가장 마지막 것 1개만 제거) |
| Invoke() | 등록된 순서대로 차례로 실행됨 |
Unity에서의 실용 예
1. 버튼 클릭 이벤트 처리
public Action onClick;
void Start()
{
onClick += PlaySound;
onClick += ShowPopup;
onClick += SaveProgress;
}
public void OnButtonClick()
{
onClick?.Invoke();
}
void PlaySound() => Debug.Log("버튼 사운드 재생");
void ShowPopup() => Debug.Log("팝업 표시");
void SaveProgress() => Debug.Log("진행 상황 저장");
2. 플레이어가 죽었을 때 실행할 일들
public static Action onPlayerDead;
void Start()
{
onPlayerDead += ShowGameOverScreen;
onPlayerDead += StopEnemySpawning;
onPlayerDead += SaveHighScore;
}
public void PlayerDie()
{
Debug.Log("플레이어 사망 처리");
onPlayerDead?.Invoke();
}
- 게임 오버 화면 표시
- 적 생성 중단
- 점수 저장
모두 사망 시 동시에 실행되어야 하므로 델리게이트 체인으로 관리하기 좋다.
3. 게임 시작 처리
public static Action onGameStart;
void Awake()
{
onGameStart += LoadMap;
onGameStart += SpawnPlayer;
onGameStart += StartMusic;
}
public void StartGame()
{
Debug.Log("게임 시작!");
onGameStart?.Invoke();
}
모듈화된 초기화 로직을 여러 곳에서 나눠서 관리하고 싶을 때 유용하다.
4. 충돌 이벤트에서 여러 시스템 반응 처리
public Action onPlayerHit;
void Start()
{
onPlayerHit += ShowDamageEffect;
onPlayerHit += PlayHitSound;
onPlayerHit += ReduceHP;
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.collider.CompareTag("Enemy"))
{
onPlayerHit?.Invoke();
}
}
- 이펙트 재생
- 사운드 재생
- HP 감소
다양한 시스템이 동시에 반응해야 할 때 델리게이트 체인이 깔끔하게 처리해준다.
델리게이트 체인이 특히 유용한 상황 정리
| 상황 | 이유 |
| 여러 시스템이 동시에 반응해야 할 때 | 호출 순서대로 메서드를 연결할 수 있음 |
| 특정 이벤트에 쉽게 구독/해제하고 싶을 때 | +=, -=으로 유연하게 관리 가능 |
| UI, 사운드, 이펙트 등 분리된 처리 | 하나의 델리게이트로 중앙에서 실행 가능 |
| 시스템 간 의존성을 줄이고 싶을 때 | 이벤트 기반으로 모듈화 가능 (결합도 ↓) |
실전 사용 패턴
1. 콜백(callback) 함수
void LoadData(Action onComplete)
{
// 데이터 로딩 중...
onComplete?.Invoke();
}
2. 전략 패턴
public delegate void AttackStrategy();
AttackStrategy attack;
void SetAggressive() => attack = () => Debug.Log("강공격!");
void SetDefensive() => attack = () => Debug.Log("방어 모드!");
void DoAttack() => attack?.Invoke();
3. LINQ / 람다 함수
델리게이트는 C#의 LINQ 문법에서도 광범위하게 사용
List<int> nums = new List<int> { 1, 2, 3, 4 };
var evenNums = nums.Where(x => x % 2 == 0);
여기서 x => x % 2 == 0 은 Func<int, bool> 델리게이트
실전에서 자주 겪는 실수 & 주의점
| 실수 | 해결 방법 |
| 등록만 하고 해제 안 함 | OnDisable/OnDestroy에서 -= 해제 |
| NullReferenceException | ?.Invoke() 또는 if (del != null) 체크 |
| static 이벤트 해제 누락 | 반드시 수동으로 -= |
| 체인에 중복 메서드 등록 | GetInvocationList()로 확인 가능 |
Unity에서 잘 쓰이는 패턴 요약
| 패턴 | 사용 예시 |
| Action 이벤트 | public static event Action onGameOver; |
| 콜백 전달 | LoadScene("Map", OnLoaded); |
| 델리게이트 체인 | onDie += PlaySound; onDie += ShowEffect; |
| 전략 교체 | 상태 기계(State Machine)처럼 공격 방식 교체 |
델리게이트 사용 시 주의할 점
1. null 체크 꼭 하기
델리게이트는 구독된 메서드가 없으면 null이다. Invoke() 하기 전에 null 여부를 확인하지 않으면 NullReferenceException이 발생한다.
// ❌ 예외 발생 가능
onEvent.Invoke();
// ✅ 안전한 호출
onEvent?.Invoke();
2. +=, -= 로 구독 관리 필수
델리게이트는 구독한 메서드를 누적하다. += 만 쓰고 -=로 해제하지 않으면, 같은 함수가 여러 번 실행되거나 메모리 누수가 생기는 문제가 발생한다.
void OnEnable()
{
GameEvent.onStart += MyMethod;
}
void OnDisable()
{
GameEvent.onStart -= MyMethod; // 꼭 해제!
}
유니티에서는 OnEnable/OnDisable 또는 Start/OnDestroy에 짝지어 등록 & 해제하는 게 일반적
3. 델리게이트 안에 익명 함수 쓰면 해제 불가
람다나 익명 메서드는 같은 코드라도 참조가 달라서 -=로 해제되지 않는다.
// ❌ 이건 해제 안 됨!
GameEvent.onStart += () => Debug.Log("시작!");
GameEvent.onStart -= () => Debug.Log("시작!"); // 다른 참조
// ✅ 이름 있는 함수는 해제 가능
GameEvent.onStart += MyMethod;
GameEvent.onStart -= MyMethod;
4. 이벤트 대상이 파괴되면 예외 위험
델리게이트는 객체가 파괴되어도 참조를 유지한다.
특히 MonoBehaviour 객체가 Destroy() 되었는데 연결된 델리게이트를 호출하면 문제가 발생할 수 있다.
if (target != null)
onEvent?.Invoke(); // 또는 조건문으로 안전하게 처리
5. 너무 많은 함수 구독 = 추적 어려움
델리게이트는 디버깅이 어렵고, 누가 어디서 어떤 함수를 구독했는지 추적이 힘들 수 있다. 디버깅을 위해선 구독자 수 추적이나 로깅, 또는 UnityEvent 사용도 고려하는 것이 좋음!
6. static 델리게이트는 특히 조심
static 델리게이트는 전역 상태처럼 동작하므로, 한 번 구독하면 앱이 꺼질 때까지 남아 있을 수 있다.
- 불필요한 참조 유지
- 중복 호출
- 참조 해제 안 되는 메모리 누수
→ 꼭 -=로 해제하거나 Clear() 로 초기화!
7. 멀티캐스트(delegate chain) 결과값은 마지막 하나만 반환
Func<int> del = A;
del += B;
int result = del(); // B의 결과만 반환됨
여러 함수가 연결돼 있어도 Func<T>는 마지막 함수의 리턴값만 유효. Action은 리턴값이 없어서 이런 걱정은 없다.
왜 델리게이트 등록 해제가 중요할까?
예를 들어,
구독만 하고 해제하지 않은 경우
public class Enemy : MonoBehaviour
{
void OnEnable()
{
GameManager.OnPlayerDeath += Die;
}
void OnDisable()
{
GameManager.OnPlayerDeath -= Die;
}
void Die()
{
Debug.Log("Enemy가 죽었습니다.");
}
}
이 구조에서는 OnEnable()에서 구독하고, OnDisable() 또는 OnDestroy()에서 해제해주는 게 매우 중요하다.
해제하지 않으면 어떤 문제가 생길까?
| 문제 | 설명 |
| 메모리 누수 | GameManager가 Enemy를 계속 참조하고 있어 GC가 수거하지 못함 |
| NullReferenceException | 이미 Destroy된 객체의 메서드를 호출하려고 해서 에러 발생 |
| 중복 호출 | 재구독 시 같은 메서드가 여러 번 등록되면 이벤트가 중복 실행됨 |
| 의도치 않은 동작 | 죽은 오브젝트가 살아있는 척 이벤트에 반응하는 등 버그 발생 |
언제 해제해야 할까?
OnEnable() → OnDisable() 또는 Start() → OnDestroy() 구조를 많이 사용한다.
Unity에서 안전한 구조
void OnEnable()
{
Player.OnJump += PlayJumpSound;
}
void OnDisable()
{
Player.OnJump -= PlayJumpSound;
}
또는,
void Start()
{
Player.OnJump += PlayJumpSound;
}
void OnDestroy()
{
Player.OnJump -= PlayJumpSound;
}
예외: static 이벤트는 더 조심!
public static event Action OnGameOver;
- 이 이벤트는 앱이 꺼질 때까지 살아있음.
- 구독 객체가 Destroy되어도 여전히 참조됨.
- 필수적으로 해제하지 않으면 메모리 문제 심각해짐.
⚙️ 델리게이트 vs 이벤트
| 항목 | 델리게이트 | 이벤트 (event) |
| 호출 위치 | 어디서든 호출 가능 | 선언한 클래스 내부에서만 호출 가능 |
| 보안성 | 낮음 (외부에서 호출 가능) | 높음 (외부에선 구독만 가능) |
| 주 사용처 | 전략 패턴, 콜백 | UI 이벤트, 게임 상태 이벤트 등 |
2025.04.16 - [TIL] - 델리게이트(Delegate)와 이벤트(Event)
델리게이트(Delegate)와 이벤트(Event)
내일배움캠프 8일차 TIL📌 델리게이트 & 이벤트 왜 두 개념을 같이 보는걸까?델리게이트는 이벤트의 기반이기 때문! 델리게이트는 메서드를 참조할 수 있는 데이터 타입이고, 이벤트는 이 델리
vvin39.tistory.com
🎯 마무리 요약
델리게이트는 단순히 메서드를 넘겨주는 기능을 넘어서, 전략 패턴, 이벤트 시스템, 비동기 처리, 람다 활용, 코드 모듈화에 있어서 핵심적인 역할을 한다. Unity에서는 Action, Func, event 키워드를 자연스럽게 사용하면서 구독 / 해제를 적절히 관리하면 안정적인 코드 흐름을 만들 수 있다.

'TIL' 카테고리의 다른 글
| 전략 패턴과 SRP로 인벤토리 리팩토링 (0) | 2025.05.22 |
|---|---|
| [Unity] ImageType (1) | 2025.05.21 |
| [Unity] Raycast (0) | 2025.05.19 |
| 객체지향 설계의 5가지 원칙 - SOLID (0) | 2025.05.16 |
| Unity 협업 중 프리팹 충돌 및 .meta 파일 관련 트러블 슈팅 (1) | 2025.05.15 |