대리자란?
상현이가 비서에게 사장님이 돌아오면 전화 해달라고 메모를 전달했다.
비서는 사장님 방에 상현이가 준 메모를 놓았고 사장님이 돌아와서 방에 있는 메모를 확인하고 상현이한테 전화한다.
메모를 비서에게 전달하는 행위는 상현이가 작성한 메모(callback)를 다른 코드에 맡겨 대신 실행하도록 요청하는 것이다. 사장님이 메모를 확인하고 상현이한테 전화를 하는 행위는 이벤트(callback)이 발생했다는 의미이다.
C# 에서 callback을 맡아 실행하는 것을 대리자가 담당한다.
대리자는 해당 메소드를 참조하는 방식으로 대리자에 메소드의 주소를 할당한 후 대리자를 호출하면 이 대리자가 메소드를 호출한다.
대리자 사용방법
1. 대리자 선언
delegate [반환타입] 대리자이름 (매개변수 목록)
delegate int Mydelegate (int a, int b);
2. 대리자가 참조할 메소드
주의할 점 : 반환타입과 매개변수는 대리자의 반환타입과 매개변수 목록과 동일해야 한다.
int Plus(int a,int b) {
return a + b;
}
int Minus(int a,int b) {
return a - b;
}
3. 대리자가 참조할 메소드를 주소를 넘긴다
Mydelegate callbackA = new Mydelegate(Plus);
Console.WriteLine(callbackA(3, 2));
Mydelegate callbackB = new Mydelegate(Minus);
Console.WriteLine(callbackB(3, 2));
callbackA 호출하면 callbackA 대리자가 참조하고 있는 주소인 Plus() 메서드를 실행하고,
callbackB 호출하면 callbackB 대리자가 참조하고 있는 주소인 Minus() 메서드를 실행한다.
대리자는 왜? 그리고 언제 사용되는가?
값이 아닌 "코드" 자체를 매개변수에 넘기고 싶은 경우 사용된다.
예를 들어 배열을 정렬하는 메소드를 만들 때 오름차순으로 정렬할지 아니면 내림차순으로 정렬할지 정해지지 않은 이러한 고민은 대리자를 통해 상황에 따라 사용자가 직접 정렬 방식을 선택 할 수 있도록 자유성을 줄 수 있다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Test
{
internal class Program
{
delegate int Compare(int a, int b);
static int AscendCompare(int a, int b) {
if (a > b)
return 1;
else if(a == b)
return 0;
else
return -1;
}
static int DescendCompare(int a, int b)
{
if (a < b)
return 1;
else if (a == b)
return 0;
else
return -1;
}
// compare 파라미터에 사용자가 선택한 정렬 메소드의 주소가 들어감
static void BubbleSort(int[] DataSet, Compare compare) {
int i, j, temp = 0;
for (i = 0; i < DataSet.Length - 1; i++)
{
for (j = 0; j < DataSet.Length - (i + 1); j++)
{
if(compare(DataSet[j], DataSet[j+1]) > 0){
temp = DataSet[j + 1];
DataSet[j + 1] = DataSet[j];
DataSet[j] = temp;
}
}
}
}
static void Main(string[] args)
{
int[] array = { 3, 7, 4, 2, 10 };
Console.WriteLine("오름차순 정렬 선택");
BubbleSort(array, new Compare(AscendCompare));
for(int i=0; i<array.Length; i++)
Console.Write($"{array[i]} ");
int[] array2 = { 3, 7, 4, 2, 11 };
Console.WriteLine("\n내림차순 정렬 선택");
BubbleSort(array2, new Compare(DescendCompare));
for (int i = 0; i < array2.Length; i++)
Console.Write($"{array2[i]} ");
}
}
}
사용자가 선택한 정렬방식에 따라 오름차순 정렬인 경우 대리자에 오름차순에 대한 메소드를 들어가 오름차순으로 정렬되고 내림차순 정렬을 선택하면 내림차순에 대한 메소드가 들어가 내림차순으로 정렬된다.
만약에 첫번째 값은 고정한 오름차순을 구현하라고 요구사항이 들어오면 메소드만 따로 만들고 대리자 객체를 생성해서 붙이면 되기 때문에 모듈화가 용이하다.
대리자 체인
하나의 대리자가 여러 개의 메소드를 동시에 참조할 수 있음
대리자 호출시에는 대리자 체인에 결합된 순서대로 호출된다.
public class Test
{
// Action : 반환타입이 없는 대리자
public Action OnstartClicked;
void Action1()
{
Debug.Log("Action1");
}
void Action2()
{
Debug.Log("Action2");
}
void Start()
{
// 함수를 등록할 때는 +를 사용
OnstartClicked += Action1;
OnstartClicked += Action2;
OnstartClicked(); // 등록된 함수 Action1(), Action2() 모두 호출됨
// 더이상 함수를 사용하지 않을 때 -를 사용해서 대리자 체인에서 제거
OnstartClicked -= Action2;
}
}
namespace System
{
public delegate void Action();
}
이벤트
알람 시계처럼 특정 시간이 되었을 때 이를 알려주거나, 사용자가 버튼을 클릭했을 때 이를 알려주는 객체를 만들 때 사용하는 것이 이벤트이다. 이벤트의 동작 원리는 대리자와 거의 비슷함
/*
UI에 어떠한 이벤트가 발생했을 때 어떠한 로직을 수행할 핸들러 이벤트
*/
public class UI_EventHandler : MonoBehaviour, IBeginDragHandler, IDragHandler
{
public Action<PointerEventData> OnBeginDragHandler = null;
public Action<PointerEventData> OnDragHandler = null;
public void OnBeginDrag(PointerEventData eventData)
{
Debug.Log("드래그 시작");
if (OnBeginDragHandler != null)
OnBeginDragHandler.Invoke(eventData); // 등록된 이벤트 핸들러 호출
}
public void OnDrag(PointerEventData eventData)
{
// transform.position = eventData.position;
Debug.Log("드래그 중..");
if (OnDragHandler != null)
OnDragHandler.Invoke(eventData); // 등록된 이벤트 핸들러 호출
}
}
public class UI_Btn
{
// 익명 메소드 형태로 이벤트 핸들러를 만들어서 OnDragHandler 이벤트가 발생시 호출할 이벤트 핸들러로 등록시킴
evt.OnDragHandler += ((PointerEventData data) => { evt.gameObject.transform.position = data.position; });
}
이벤트와 대리자가 다른 점은 바로 이벤트를 외부에서 직접 사용할 수 없다는 데 있다.
이벤트는 public 한정자로 되어 있어도 자신이 선언된 클래스 외부에서 호출이 불가능하다.
그래서 이벤트의 주 사용용도는 객체 내부의 상태 변화나 사건의 발생을 알리는 용도로 사용하고 대리자는 콜백의 용도로 사용한다.
'프로그래밍 언어' 카테고리의 다른 글
C# reflection (0) | 2024.02.28 |
---|---|
객체지향 프로그래밍(OOP) 개념정리 (0) | 2024.01.25 |
연결 리스트(C++) (0) | 2023.08.29 |
행맨 (0) | 2022.05.25 |
분할구현 (0) | 2022.04.26 |