Deligate
대리자우리가 여러 데이터를 각 형식에 맞게 자료형을 담은 것과 같이, 메서드를 담을 수 있는 자료형을 델리게이트라고 한다. 매개변수로 메서드의 동작 자체를 전달할 수 있기 때문에 내용은 짧으면서도 더 직관적인 코드 작성이 가능하다.
delegate
코드 영역에 존재하는 메서드의 주소를 가리키는 참조타입이기 때문에, 변수처럼 선언하여 사용하며 new 키워드로 인스턴스 생성이 가능하다.
public delegate float DeligateMethod1(float left, float right);
public delegate void DeligateMethod2(string str);
public static float Plus(float left, float right)
public static void Message(string message)
static void Main(string[] args)
{
DeligateMethod1 mydel1 = Plus;
DeligateMethod1 mydel2 = Message; // 구문 오류
DeligateMethod2 mydel3 = Message;
}
delegate
선언과 함께 객체를 생성하고 함수를 추가할 때, 반드시 반환형과 매개변수가 일치해야한다. DeligateMethod1
로 생성한 mydel2
에 매개변수가 다른 message
를 저장하려고 하면 구문 오류가 생기는 것을 확인할 수 있다.
Call Back
게임에서 버튼을 눌렀을 때 버튼의 종류에 따라 다른 기능을 수행하도록 만들어보자. 만약 delegate
기능을 몰랐다면 Button 클래스를 생성하고 이를 부모클래스로 하여 버튼의 종류마다 자식클래스를 만들어 상속하였을 것이다. 하지만 이렇게 할 경우 버튼의 종류가 많아질수록 클래스를 계속 생성해야하기에 프로그램에 부담이 될텐데, 이는 Delegate
를 통해 해결할 수 있다.
public class Button
{
public delegate void ClickListener();
public ClickListener clicklistener;
public virtual void Click()
{
if(clicklistener != null)
{
clicklistener();
}
}
}
public class File
{
public void Save()
{
Console.WriteLine("저장합니다.");
}
public void Load()
{
Console.WriteLine("불러옵니다.");
}
}
static void Main(string[] args)
{
File file = new File();
Button saveButton = new Button();
saveButton.clicklistener = file.Save;
Button loadButton = new Button();
loadButton.clicklistener = file.Load;
saveButton.Click(); // 저장하기
loadButton.Click(); // 불러오기
방법은 Button
클래스에 delegate
를 생성하여 여기에 File
클래스의 멤버함수를 추가하는 것이다. saveButton
은 Button
클래스로 생성되었기에 saveButton.clicklistener
에 file.Save
함수를 추가할 수 있는데, 이렇게 하면 saveButton.Click()
를 실행했을 때 file.Save
가 구동된다. 마찬가지로 loadButton.Click()
또한 file.Load
로 연결할 수 있으므로,Button
클래스 하나로 2개의 버튼을 만들 수 있게 된다.
여기서 saveButton.Click();
호출을 통해 file.Save
함수가 구동되는 것을 볼 수 있는데 이렇게 직접적으로 호출되는 것이 아닌, 특정 조건에 반응하여 구동되는 함수를 콜백 함수라고 하며, Delegate
를 활용하여 알람처럼 작동하는 것이 가능하다.
Generic delegate
협업을 하는 상황이라고 가정했을 때 만약 개발자 별로 독자적인 delegate
를 선언하여 함수를 추가하게되면 delegate
이름만 보고 어떤 함수를 묶어놨는지 파악하기도 힘들뿐더러, 마찬가지로 매개변수 자료형과 반환형이 동일한 함수를 묶은건데 다른 delegate
로 선언해서 호환이 불가능하게 되면 굉장히 불편할 것이다. 따라서 호완성을 높이기 위해 C#에는 자체적으로 반환형과 매개변수를 지정한 일반화 델리게이트가 존재한다.
public delegate void MessageDelegate(string str);
public delegate void LogDelegate(string str);
static void Main(string[] args)
{
MessageDelegate stringDelegate = Message;
LogDelegate logDelegate = Message;
stringDelegate = logDelegate; // 구문 오류
Action<string> stringDelegate1 = Message;
Action<string> logDelegate1 = Message;
}
Action<T>
는 반환형이 void인 델리게이트로, T에는 매개변수 자료형을 입력하면 된다. 위처럼 string
을 입력하면 매개변수의 자료형은 string
이고 반환형이 void인 함수를 추가할 수 있는 것이다.
int Plus(int left, int right) { return left + right; }
float Test(int value1, string value2) { return 0; }
Func<int, int, int> func1;
func1 = Plus;
Func<int, string, float> func2;
func2 = Test;
Func<T>
는 반환형과 매개변수의 자료형을 모두 입력받는 델리게이트이다. T에 자료형과 반환형을 입력하고, 반환형을 맨 마지막에 입력하는 특징을 가진다. 위처럼 <int, string, float>
을 입력하면 매개변수의 자료형은 int
와 string
이고 반환형은 float
인 함수를 추가할 수 있는 것이다.
Delegate Chain
static void Main(string[] args)
{
int value = 10;
value += 20;
Action action;
action = Func1; // 델리게이트에 Func1 저장
action += Func2; // 델리게이트에 Func2 추가 저장, 순차적으로 저장
action += Func3;
action += Func2;
action -= Func1; // 델리게이트에 Func1 제거
action -= Func2; // 델리게이트 제거시 나중에 추가 된거 제거(후입선출)
}
Func2
Func3
위에서 action
은 반환형이 void이고 매개변수가 없는 함수를 추가할 수 있는 델리게이트로 조건에 맞는 Func을 +=
로 추가하거나 -=
로 제거할 수 있다. 이때 추가와 제거는 스택구조와 동일하게 순차적으로 이루어진다.
주의사항 : 대입연산자 = 을 쓰게되면 해당 함수로 초기화되기 때문에 대입연산자만 사용하는 것은 굉장히 위험할 수 있다.