대리자(delegate)
말 그대로 대리자이다. 즉, 대리로 함수를 실행 해 주는 것이다.
간단한 예시를 들어 보자.
class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
간단하게 물품 - 가격 정보가 들어 있는 클래스를 만들어 준다.
프로퍼티 형태로 만들어 주었다.
이제, 리스트를 만들어 주어 Product 아이템들을 넣어 준 다음, 정렬을 해 보도록 하자.
static void Main(string[] args)
{
List<Product> products = new List<Product>()
{
new Product() { Name = "닌텐도4 스위치", Price = 350000 },
new Product() { Name = "Ps5", Price = 560000 },
new Product() { Name = "XBox", Price = 480000 }
}; // 리스트에 처음 값을 정의하듯이 List<int> list = new List<int>{1,2,3); 한 것이다. 원소가 클래스가 들어가니 일일히 정의 해 준 것
Console.WriteLine("정렬 전\n=====");
foreach (var item in products)
{
Console.WriteLine(item.Name + "의 가격은 " + item.Price + "원 입니다.");
}
Console.WriteLine("\n");
// 정렬
products.Sort();
Console.WriteLine("정렬 후\n=====");
foreach (var item in products)
{
Console.WriteLine(item.Name + "의 가격은 " + item.Price + "원 입니다.");
}
}
클래스 안에 있는 프로퍼티 값을 통해 비교해야 하기 때문에 그냥 Sort를 사용하면 위와 같이 오류가 생기게 된다.
따라서 Sort 함수 속에 클래스 속에 있는 어느 요소를 가지고 비교 할 것인지 체크하는 익명 대리함수를 만들어 주어야 한다.
코드를 아래와 같이 바꾸어 준다.
static void Main(string[] args)
{
List<Product> products = new List<Product>()
{
new Product() { Name = "닌텐도4 스위치", Price = 350000 },
new Product() { Name = "Ps5", Price = 560000 },
new Product() { Name = "XBox", Price = 480000 }
}; // 리스트에 처음 값을 정의하듯이 List<int> list = new List<int>{1,2,3); 한 것이다. 원소가 클래스가 들어가니 일일히 정의 해 준 것
Console.WriteLine("정렬 전\n=====");
foreach (var item in products)
{
Console.WriteLine(item.Name + "의 가격은 " + item.Price + "원 입니다.");
}
Console.WriteLine("\n");
// 정렬
products.Sort(delegate(Product a, Product b)
{
return a.Price.CompareTo(b.Price); // a의 Price 프로퍼티와 b의 Price 프로퍼티를 비교해서 정렬해라!
});
Console.WriteLine("정렬 후\n=====");
foreach (var item in products)
{
Console.WriteLine(item.Name + "의 가격은 " + item.Price + "원 입니다.");
}
}
Sort 속에 delegate 함수를 넣어 주고 두 개의 원소를 비교하는데, Product 클래스의 Price 프로덕트를 비교하여 정렬 하라고 설정 해 주는 것이다.
이렇게 설정 해 주게 되면
아래와 같이
가격 값에 따라서 오름차순으로 정렬이 된다.
유니티에서의 간단한 활용
위와 같이 리스트에서 활용할 수도 있으며, 대리자는 함수 자체를 대리로 대신 수행하게 할 수도 있다.
한 delegate 별로 하나의 함수만을 수행 하는 것이 아닌, 여러 개의 함수를 체인처럼 연쇄적으로 등록하여 여러 개의 함수를 순서대로 수행하게 할 수도 있다.
유니티에서 살펴 보도록 하자
테스트 코드를 만들고, 부모와 연결 된 OnInIt()에 아래와 같이 적어 준다. (새로 만든다면 Awake()에 적어줘도 된다. -> Awake에 적어주면 base.OnInit()은 빼 주어야 한다.)
public delegate void ThisIsEvent();
protected override void OnInit()
{
base.OnInit();
Debug.Log("testChild's OnInit()");
Debug.Log("setEvent");
ThisIsEvent del = new ThisIsEvent(WriteLog); // WriteLog를 받음
ThisIsEvent secondDel = WriteSecondLog; // 이렇게도 대리자를 설정할 수 있음
del(); // 대리자 실행 (실제는 WriteLog)
secondDel(); // 두 번째 대리자 실행 (실제는 WriteSecondLog)
}
public void WriteLog()
{
Debug.Log("EventLog");
}
public void WriteSecondLog()
{
Debug.Log("Second Event Log");
}
delegate로 대리자(ThisIsEvent())를 정의하고, 대리자 속에 변수에 값 더하듯이 함수 이름을 더해 주면 된다.
단, 중요한 점은 더할 함수와 매개변수 형태를 똑같이 정의해 주어야 한다는 점이다.
예를 들어, 매개변수가 없다면 없이 하고, int 1개면 int 1개, 이렇게 해 주어야 한다.
그리고 대리자를 설정하는 두 가지 방법은 new로 새롭게 생성해 주는 것과, 그냥 더하듯이 해 주는 방법이 있다.
위 코드에서는 둘 다 한 개의 함수씩을 넣어 주었다.
이렇게 설정 해 주면
위와 같이 WriteLog와 WriteSecondLog가 실행됨을 볼 수 있다.
한 개의 대리자에 여러 개의 체인을 걸어 주는 경우
여러 개의 함수를 한 개의 대리자가 순서대로 수행하게 할 수도 있다.
protected override void OnInit()
{
base.OnInit();
Debug.Log("testChild's OnInit()");
Debug.Log("setEvent");
ThisIsEvent del = new ThisIsEvent(WriteLog); // WriteLog를 받음
del += WriteSecondLog; // 대리로 실행 할 함수 더하기
del();
}
del 대리자 한 개에 WriteLog와 WriteSecondLog를 더해 주었다.
del을 실행하면
위와 같이 나오게 된다.
더하면 당연히 빼는 것도 가능하다.
protected override void OnInit()
{
base.OnInit();
Debug.Log("testChild's OnInit()");
Debug.Log("setEvent");
ThisIsEvent del = new ThisIsEvent(WriteLog); // WriteLog를 받음
del += WriteSecondLog; // 대리 목록 추가
del();
del -= WriteSecondLog; // 목록 제거
del(); // 한번 더 실행
}
뒤에 들어간 것부터 순서대로 빼 줘야 한다.
Action 대리자
Action은 using System; 을 가져와야 사용할 수 있다.
그냥 Action으로 만들면 매개변수가 없는 함수만을 대리 리스트에 넣을 수 있다.
하지만, 매개변수가 있는 것도 아래처럼 람다로 넣을 수 있음 (익명함수).
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class testChild : test
{
public delegate void ThisIsEvent();
public event ThisIsEvent SetEvent;
public Action actions; // 대리자
// Start is called before the first frame update
protected override void OnInit()
{
base.OnInit();
Debug.Log("testChild's OnInit()");
Debug.Log("setEvent");
actions += WriteLog;
actions += WriteSecondLog;
actions += () => WriteItem("InputString");
actions();
}
public void WriteLog()
{
Debug.Log("EventLog");
}
public void WriteSecondLog()
{
Debug.Log("Second Event Log");
}
public void WriteItem(string item)
{
Debug.Log(item + "is in.");
}
}
이렇게 출력이 되게 된다.
매개변수가 있는 Action 설정
Action<T> → T 형식의 매개변수를 넣을 수 있음
즉, 매개변수가 있는 대리자를 세팅할 수 있게 됨
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class testChild : test
{
public Action actions; // 대리자
public Action<string> stringActions; // string 매개변수를 가지는 대리자
// Start is called before the first frame update
protected override void OnInit()
{
base.OnInit();
Debug.Log("testChild's OnInit()");
Debug.Log("setEvent");
stringActions += (str) => WriteItem(str);
stringActions("input1"); // 대리자 실행 시에 매개변수도 설정 가능
stringActions("input2");
}
public void WriteItem(string item)
{
Debug.Log(item + "is in.");
}
}
차이점이 있다면, 위에서 람다를 사용했을 때는 Action에 추가 할 때, 매개변수의 값을 정하고, 그 값이 고정되는데 반해 매개변수를 넣을 수 있게 세팅 해 주면 들어 가 있는 놈들(함수들)에게 매개변수를 한 번에 설정 해 줄 수 있으며, 실행할 때 마다 다르게 설정할 수 있게 되는 것이다.
즉, 더 유연성이 생겼음을 알 수 있다.