람다식을 제대로 이해하려면, 먼저 delegate(델리게이트) 개념을 알아야 합니다.
이 글은 C#의 delegate개념부터 Action, Func, Predicate, event 키워드에 대해 설명하는 글입니다.
public delegate void MyDelegate(int number);
<사용하는 법>
public delegate void MyDelegate();
void PrintA() => Debug.Log("A");
void PrintB() => Debug.Log("B");
void Start()
{
MyDelegate myFunc;
myFunc = PrintA; // 마치 값을 대입하듯 메서드를 담는다
myFunc(); // A 출력
myFunc = PrintB; // 다른 메서드로 교체 가능
myFunc(); // B 출력
}
delegate 변수는 해당 delegate와 시그니처가 같은 메서드들을 값처럼 대입하고 바꿀 수 있다.
비유하자면 delegate는 메서드를 넣을 수 있는 틀 같은 것이다. 틀에 맞는 모양의 메서드만 들어갈 수 있다.
public class GameManager : MonoBehaviour
{
public UIManager uiManager;
public SoundManager soundManager;
public void PlayerDied()
{
uiManager.ShowGameOver(); // 직접 호출
soundManager.PlayDeathSound(); // 직접 호출
}
}
Delegate를 사용한 예시
public class GameManager : MonoBehaviour
{
public delegate void PlayerDeathHandler();
public event PlayerDeathHandler OnPlayerDied;
public void PlayerDied()
{
OnPlayerDied?.Invoke(); // 외부에 알림만 함
}
}
public class UIManager : MonoBehaviour
{
public GameManager gameManager;
void Start()
{
gameManager.OnPlayerDied += ShowGameOver; // 이벤트에 콜백 등록
}
void ShowGameOver()
{
Debug.Log("Game Over UI 표시");
// 실제 UI 처리
}
}
public class SoundManager : MonoBehaviour
{
public GameManager gameManager;
void Start()
{
gameManager.OnPlayerDied += PlayDeathSound;
}
void PlayDeathSound()
{
Debug.Log("죽음 사운드 재생");
// 실제 사운드 재생
}
}
public delegate void OnClickAction;
public class ButtonExample : MonoBehaviour
{
public OnClickAction onClick;
void Start()
{
onClick = PrintMessage; // 메서드 대입
onClick(); // 실행
}
void PrintMessage()
{
Debug.Log("버튼이 눌렸습니다!");
}
}
익명 메서드는 이름 없이 정의된 일회성 함수이다.
주로 한 번만 사용할 간단한 작업을 정의할 때 유용하다.
using UnityEngine;
public class Test : MonoBehaviour
{
delegate void MyDelegate();
void Start()
{
MyDelegate onClick = delegate { Debug.Log("익명 메서드 실행!"); };
onClick();
}
}
using UnityEngine;
public class Test : MonoBehaviour
{
// 델리게이트 타입 선언
delegate void MyDelegate();
void Start()
{
// 람다식으로 델리게이트 할당
MyDelegate onClick = () => { Debug.Log("람다식 실행!"); };
// 호출
onClick();
}
}
이렇게 하면 코드가 더 짧고 읽기 쉬워진다. 람다는 간단한 함수 표현식을 더욱 직관적으로 작성할 수 있도록 돕는 문법적 요소이다.
얘네들은 델리게이트의 특정 형태(제네릭 버전)이다.
Action은 반환값이 없는(void) 메서드를 담을 수 있는 제네릭 delegate 타입이다.
즉, 어떤 동작만 수행하고, 결과를 돌려주지 않아도 되는 경우에 주로 사용된다.
예시
Action sayHello = () => Debug.Log("Hello!");
sayHello();
Action<int> printScore = score => Debug.Log($"Score: {score}");
printScore(100); // 출력: Score: 100
Func는 반환값이 있는 delegate 타입이다.
가장 마지막 제네릭 타입이 반환형이고, 그 앞에 오는 타입들은 매개변수 타입이다.
예시
Func<int, int> square = x => x * x;
Debug.Log(square(4)); // 출력: 16
Func<string, int> getLength = str => str.Length;
Debug.Log(getLength("Unity")); // 출력: 5
Predicate는 반환형이 bool이고, 매개변수가 1개인 delegate 타입이다.
즉, 조건 판별용 함수를 담을 때 사용한다.
예시
Predicate<int> isEven = x => x % 2 == 0;
Debug.Log(isEven(10)); // 출력: True
| 구분 | Action | Func | Predicate | 일반 delegate |
|---|---|---|---|---|
| 리턴 타입 | void (아무것도 반환 안 함) | 반환값 있음 (제네릭으로 지정) | bool만 반환 | 직접 지정 가능 |
| 매개변수 | 0개 ~ 여러 개 | 0개 ~ 여러 개 | 정확히 1개 | 원하는 대로 정의 가능 |
| 제네릭 기반 | O | O | O | X 직접 선언해야 함 |
| 예시 | Action<int> | Func<int, string> | Predicate<string> | public delegate void MyDel(int x); |
| 사용 편의성 | O 자주 씀 | O 자주 씀 | O 조건 검사할 때 유용 | 처음부터 직접 설계 가능 |
event는 delegate 앞에 붙여 외부에서 직접 이벤트를 호출하지 못하게 막고,
해당 이벤트가 발생하는 시점을 해당 클래스로 제한하는 역할을 한다.
public class HealthSystem
{
// 이벤트 선언
public event Action OnDeath;
public void Die()
{
Debug.Log("죽었습니다.");
OnDeath?.Invoke(); // 이벤트를 내부에서만 발생시킴. 외부에서는 이벤트를 실행할 수 없음.
}
}
event의 핵심은 외부에서 이벤트를 구독하고 해지할 수 있지만, 이벤트를 발동시키는 책임은 해당 클래스로 제한된다는 점이다. 이로 인해 외부에서 OnDeath = null;처럼 이벤트를 null로 변경하거나 직접 Invoke를 호출하는 일이 불가능하다.
여러 메서드를 덧붙여 하나의 델리게이트로 묶는 방법을 제공한다.
public delegate void Notify();
Notify notify = () => Debug.Log("A");
notify += () => Debug.Log("B");
notify(); // 출력: A\nB
이처럼 델리게이트는 여러 메서드를 등록하고 호출할 수 있기 때문에 멀티캐스트 델리게이트라고 불린다.
-> 메서드를 따로 정의해서 추가/제거하는 것이 안전하다.
void PrintA() => Debug.Log("A");
Notify notify = PrintA; // 메서드를 델리게이트에 직접 할당
notify += PrintA; // 메서드를 덧붙임
notify -= PrintA; // 메서드를 안전하게 제거
Delegate는 메서드를 변수처럼 다루는 방법이다.
이를 통해 메서드를 동적으로 연결하고 실행할 수 있다.
Delegate는 다양한 상황에서 효율적이고 유연한 이벤트 처리를 가능하게 합니다. 특히 Action, Func, Predicate는 자주 사용되는 Delegate 유형이다.
Event는 Delegate에 대한 안전장치로, 외부에서 Delegate를 잘못 수정하거나 호출하는 것을 방지해준다.
Unity에서는 콜백 구조나 비동기 처리에 필수적인 개념으로, Delegate를 적극적으로 활용하여 유연한 코드 구조를 만들 수 있다.