델리게이트 : Delegate
- Delegate = 대리자
- 함수 포인터 = 함수 객체(Pred=FunctionPointer)
- 함수의 주소값을 가지고 함수를 대신 호출
- 선언된 델리게이트는 클래스
- 참조형(Reference) 타입
주소 값 연산 수행
_myDelegate(델리게이트 변수)는 MyDelegate(인스턴스)의 주소를
MyDelegate(인스턴스)는 TestFunction(함수)의 주소를 가지고
참조하고, 참조해서 호출되는 과정
델리게이트 선언
- 함수의 반환타입이 델리게이트의 반환타입과 동일한지 확인
- 함수의 매개변수 목록 델리게이트의 매개변수 목록과 동일한지 확인
- 1, 2 모두 성립 시, 델리게이트에 할당 가능
접근제한자 delegate 반환 형식 식별자(매개변수);
public delegate void MyDelegate();
public MyDelegate _myDelegate;
public void TestFunction()
{
Debug.Log("Test");
}
public delegate int MyDelegate2(int num);
public MyDelegate2 _myDelegate2;
public int TestFunction2(int num)
{
return num;
}
델리게이트 사용법
- new를 통한 델리게이트 객체 생성
- Pred를 활용한 델리게이트 객체 생성
- 델리게이트 변수(=함수객체(Pred))에 ()를 통한 실행
- 델리게이트 변수에 연결된 모든 함수 객체가 실행
public void Start()
{
_myDelegate = new MyDelegate(TestFunction);
_myDelegate = TestFunction;
_myDelegate();
}
델리게이트 의의
- 그냥 함수 호출하면 되는데 뭐하러 델리게이트 변수 만들어서 함수 객체 할당하고
- 요약: 그지랄을 왜 해야 허냐?
- 델리게이트 = 타입
- 이유: 타입은 함수의 1. 매개변수 or 2. 반환형식에 넣을 수 있음
- 요약: 델리게이트는 함수 객체(Pred=Function Pointer)처럼 사용된다는 것
- 델리게이트를 인자로 전달하거나 반환 값으로 리턴하면 매우 유용하기 떄문에 사용
TestFunc();
public class DelegateTest2 : MonoBehaviour
{
public delegate void TestDelegate();
TestDelegate _testDelegate;
private void Start()
{
_testDelegate = TargetF;
}
void Do(TestDelegate del)
{
del();
}
void TargetF()
{
Debug.Log("TargetF");
}
}
public class DelegateTest3 : MonoBehaviour
{
public delegate void DelegateTest();
public DelegateTest _testDelegate;
private void Start()
{
DelegateTest result = Do();
}
DelegateTest Do()
{
return _testDelegate = TargetF;
}
void TargetF()
{
Debug.Log("TargetF");
}
}
델리게이트 콜백
- 콜백(Call Back): 함수를 참조한 후, 나중에 호출하는 것
- 호출을 뒤에 하고, 참조 및 수정을 먼저하는 것을 의미
- 델리게이트와 콜백을 결합하면, 불필요한 코드 작성을 방지할 수 있음
- 의존적이지 않고, 유연한 코드 작성이 가능
class Player
{
public enum Buff { None, Buff1, Buff2, }
public Buff _buff;
public void BuffCheck(Buff buff)
{
if (buff == Buff.Buff1) NoneBuff();
else
{
if (buff == Buff.Buff1) Buff1();
if (buff == Buff.Buff1) Buff2();
}
}
public void Attack(Buff buff)
{
BuffCheck(buff);
Debug.Log("Attack");
}
void NoneBuff() { }
void Buff1() { Debug.Log("Buff1"); }
void Buff2() { Debug.Log("Buff2"); }
}
private void Start()
{
Player player = new Player();
player._buff = Player.Buff.Buff1;
player.Attack(player._buff);
}
class Player
{
private delegate void BuffDelegate();
private BuffDelegate _buffDelegate;
public enum Buff { None, Buff1, Buff2, }
private Buff _buff;
public Buff _Buff {
get { return _buff; }
set
{
if (_buff == value) return;
_buff = value;
if (_buff == Buff.Buff1) _buffDelegate = Buff1;
else if (_buff == Buff.Buff2) _buffDelegate = Buff2;
else if (_buff == Buff.None) _buffDelegate = NoneBuff;
}
}
public void Attack()
{
_buffDelegate();
Debug.Log("Attack");
}
void NoneBuff() { }
void Buff1() { Debug.Log("Buff1"); }
void Buff2() { Debug.Log("Buff2"); }
}
private void Start()
{
Player player = new Player();
player._Buff = Player.Buff.Buff1;
player.Attack();
}
델리게이트 체인 : Delegate Chain
- 하나의 델리게이트가 여러 함수를 동시에 참조 가능
- 델리게이트 연산 규칙
- 연산(덧셈/뺄셈)은 항상 뒤에서 발생한다.
- 델리게이트로 들어오는 함수 객체(Pred)에는 순서가 존재
- 동일한 함수라도 다른 함수로 처리된다.
- 예시
del = A + A + B + C + A + B
(출력): A A B C A B
del - A - B - A
(출력): A + B + C
- Delegate.Combine(Delegate1, Delegate2)을 사용하는 구버전(사용X)
- [방법4]를 애용하자
0. 델리게이트 대입(=)
- 델리게이트에 특정 함수 포인터를 할당한다.
- [주의] 기존에 연결된 델리게이트들을 없앨 수 있으므로 주의하자.
public delegate void TestDelegate();
private TestDelegate testDel;
testDel += Test1;
testDel += Test2;
testDel += Test3;
testDel = Test1;
1. 델리게이트 덧셈(+)
- A 연결 -> B 연결 -> C 연결된 Delegate 실행 시
- A 실행 -> B 실행 -> C 실행
public delegate void TestDelegate();
private TestDelegate _testDelegate;
void Chain1() { Debug.Log("Chain1"); }
void Chain2() { Debug.Log("Chain2"); }
void Chain3() { Debug.Log("Chain3"); }
private void Start()
{
TestDelegate test1 = new TestDelegate(Chain1);
TestDelegate test2 = new TestDelegate(Chain2);
TestDelegate test3 = new TestDelegate(Chain3);
_testDelegate = Delegate.Combine(test1, test2) as TestDelegate;
_testDelegate = Delegate.Combine(_testDelegate, test3) as TestDelegate;
_testDelegate.Invoke();
_testDelegate += Chain1;
_testDelegate += Chain2;
_testDelegate += Chain3;
_testDelegate = new TestDelegate(Chain1)
+ new TestDelegate(Chain2)
+ new TestDelegate(Chain3);
_testDelegate.Invoke();
_testDelegate += new TestDelegate(Chain1);
_testDelegate += new TestDelegate(Chain2);
_testDelegate += new TestDelegate(Chain3);
_testDelegate.Invoke();
_testDelegate += Chain1;
_testDelegate += Chain2;
_testDelegate += Chain3;
_testDelegate.Invoke();
}
1. 델리게이트 뺄셈(-)
- A 연결 -> B 연결 -> C 연결된 Delegate에 뺄샘 수행 시
- C 제거 -> B 제거 -> A 제거
private delegate void TestDelegate();
private TestDelegate testDelegate;
void Chain1() { Debug.Log("Chain1"); }
void Chain2() { Debug.Log("Chain2"); }
void Chain3() { Debug.Log("Chain3"); }
private void Start()
{
testDelegate = Chain1;
testDelegate += Chain2;
testDelegate += Chain3;
testDelegate -= Chain2;
testDelegate -= Chain3;
testDelegate.Invoke();
}
델리게이트 실전 사용 예시
- 싱글톤을 활용한 참조
- 장점: 직관적
- 단점: 코드간 의존성이 높아지고, 코드가 길어진다.
IEnumerator CoDie()
{
isDead = true;
anim.SetInteger("Stat", 2);
DelegateUIManager.instance.AddScore(transform.position);
DelegateItemManager.instance.DropItem(transform.position);
DelegateParticle.instance.PlayDieParticle(transform.position);
yield return new WaitForSeconds(0.5f);
Destroy(gameObject);
}
- 델리게이트를 활용한 참조
- 장점: 코드간 의존성이 낮아지고, 코드가 짧고, 간결해짐
- 단점: 코드 흐름을 파악하기 어려워짐
IEnumerator CoDie()
{
isDead = true;
anim.SetInteger("Stat", 2);
dieDelegate(transform.position);
yield return new WaitForSeconds(0.5f);
Destroy(gameObject);
}
- DelegateItemManager.cs
- Delegate 연산(+)을 사용해 체인 연결
private void Start()
{
if(instance == null)
{
instance = this;
}
else
{
Destroy(instance);
}
#region
AddDelegate();
#endregion
}
#region
public void AddDelegate()
{
enemy.dieDelegate += DropItem;
}
#endregion
private void Start()
{
if (instance == null)
{
instance = this;
}
else
{
Destroy(instance);
}
num = 0;
text.text = num.ToString();
#region
AddDelegate();
#endregion
}
#region
public void AddDelegate()
{
enemy.dieDelegate += AddScore;
}
#endregion
private void Start()
{
if(instance == null)
{
instance = this;
}
else Destroy(instance);
#region
AddDelegate();
#endregion
}
#region
public void AddDelegate()
{
enemy.dieDelegate += PlayDieParticle;
}
#endregion
public void PlayDieParticle(Vector3 pos)
{
particle.transform.position = pos;
particle.Play();
}
델리게이트 이벤트 : Delegate Event
- 이벤트(Event): 사건이 발생했음을 알리는 것 or 용도
- event 키워드로 델리게이트 선언
public delegate void TestDelegate();
public event TestDelegate testEvent;
- 외부에서 대입 불가
- 외부에서 호출 불가
- 내부에 델리게이트 대입문을 포함해 제작
- 내부에 델리게이트 호출 함수를 포함해 제작
public class TestDelegate{
public delegate TestEvent();
public event TestEvent testEvent;
public void StartEvent(){
testEvent = Test;
testEvent.Invoke();
}
}
public class EvternalClass{
private void Start(){
TestDelegate testDelegate = new TestDelegate();
testDelegate.StartEvent();
public void Test() { Debug.Log("Test"); }
}
}
이벤트가 필요한 이유
- 이벤트는 외부에서 델리게이트 사용을 불가능하게 만든다.
- 이는 객체의 삭제를 다룰 때, 본인이 삭제해야지, 타인이 삭제하면 문제가 발생할 수 있기 때문
- 즉, 객체 삭제 = 본인 안