본문 바로가기

C# 프로그래밍/문법 개념

[C#] 델리게이트 (Delegate)

반응형

델리게이트란 무엇인가?

델리게이트(대리자)는 메서드를 참조를 나타내는 형식이다. 다시 말해 델리게이트의 포인터는 메서드의 주소를 가리킨다. 그러나 ms공식 문서에 따르면 c++ 포인터와는 다르게 객체지향이라서 인스턴스 및 메서드를 모두 캡슐화한다고 되어있다. 델리게이트는 주로 메서드를 다른 메서드의 매개변수로 전달하는 데에 쓰인다.

 

대리자는 크게 다음과 같은 역할을 수행한다.

  1. 메서드를 메서드의 매개변수로 전달할 수 있다.
  2. 콜백 메소드를 정의할 수 있다.
  3. 여러 개의 대리자를 연결하여 실행할 수 있다. 델리게이트 체인(delegate chain)이라고도 한다.

그리고 이러한 기능은 람다식을 사용하여 간단하게 정의가 가능하다.


왜 사용하는가?

가장 큰 이유는 위에서 언급한 것처럼 메서드 자체를 매개변수로 전달하기 위해서이다.

 

그러나 이 외에도 event와 함께 사용하여 콜백 함수로 사용할 수 있고 delegate chain을 통해 여러 함수를 한 번에 호출하는 데에 사용할 수 있다.


어떻게 사용할 수 있는가?

 

다음 예제 소스코드를 보자.

public class Test0202 : MonoBehaviour
{
    //델리게이트의 기본형태이다.
    public delegate void JDelegate(int a, string b);

    void Start()
    {
        //델리게이트에 참조할 함수를 넣어서 인스턴스화 한다.
        JDelegate j1 = PrintBisA;

        //만약 함수가 일회성이라면 무명메소드를 사용하여 작성할 수 있다.
        JDelegate j2 = delegate (int _a, string _b) { Debug.Log(_b + " is " + _a); };

        //람다식을 사용하면 더 간편하게 사용이 가능하다.
        JDelegate j3 = (int a1, string b1) => Debug.Log(a1 + " from " + b1);
        
        //델리게이트 체인을 통해서 여러 함수를 동시에 실행할 수 있다.
        j2 += PrintBisA;
        j2.Invoke(3, "Cool");
    }

    public void PrintBisA(int _a, string _b)
    {
        Debug.Log(_b + " is " + _a);
    }
}

델리게이트는 함수의 주소를 참조하기 때문에 반드시 함수를 넣어주어야 한다.

만약 한번 사용하고 버릴 함수라면 무명 메서드를 사용할 수도 있다. 그러나 이것마저 줄여서 람다식으로 정의가 가능하다.

 

델리게이트 체인은 += 연산자로 추가할 수 있으며, 연결된 함수들을 모두 실행한다. 이때 Invoke 함수를 사용하게 되면 마지막으로 추가된 함수만 반환 값을 가져서 반환한다.


유니티에서 어떻게 활용할 수 있는가?

우리는 델리게이트가 매개변수로써 활용되고 결과적으로는 함수의 역할을 한다는 것을 이용해야 한다. 함수는 매개변수가 있고 특정 반환 값이 존재한다. 다시 말해서 델리게이트(대리자)를 사용하면 특정 타입의 반환 값을 자유롭게 매개변수로 전달할 수 있다는 것이다.

 

다음 예시를 살펴보자.

public class Diary
{
    public string title;
    public int month;
    public int day;
    public int temperature;
    public int humidity;
    public int moneySpent;
}

해당 클래스는 하루 일기의 정보를 담고 있다. 제목을 제외하면 int가 대부분인데 만약 배열이나 리스트로 저장된 일기의 필드 정보를 빼내 오려면 어떻게 해야 할까?

 

다음은 일기의 각 필드의 최고값을 가져오는 예제이다.

private string GetHighestSpentInDiary(List<Diary> diary)
{
    int maxValue = 0;
    string title = "";

    for (int i = 0; i < diary.Count; i++)
    {
        if (diary[i].moneySpent > maxValue)
        {
            maxValue = diary[i].moneySpent;
            title = diary[i].title;
        }
    }
    return title;
}

private string GetHighestTemperatureInDiary(List<Diary> diary)
{
    int maxValue = 0;
    string title = "";

    for (int i = 0; i < diary.Count; i++)
    {
        if (diary[i].temperature > maxValue)
        {
            maxValue = diary[i].temperature;
            title = diary[i].title;
        }
    }
    return title;
}
    
    //...

이렇게 하면 아주 간단하게 각 필드의 최고값에 해당하는 title을 가져올 수 있다. 그러나 너무 비효율적이다...

함수의 재사용성이라고는 찾아볼 수 없다. 이때 delegate를 사용하면 된다.

 

public List<Diary> myDiary = new List<Diary>();

public delegate int DiaryDelegate(Diary perDay);

private string GetHighestValueInDiary(List<Diary> diary, DiaryDelegate diaryDelegate)
{
    int maxValue = 0;
    string title = "";

    for (int i = 0; i < diary.Count; i++)
    {
        if (diaryDelegate(diary[i]) > maxValue)
        {
            maxValue = diaryDelegate(diary[i]);
            title = diary[i].title;
        }
    }
    return title;
}

public void PrintHottestDay()
{
    Debug.Log(GetHighestValueInDiary(myDiary, a => a.temperature));
}

public void PrintHumiditestDay()
{
    Debug.Log(GetHighestValueInDiary(myDiary, a => a.humidity));
}

대리자를 매개변수로 넣어주게 되면 얻고자 하는 필드 변수를 람다식으로 넣어주기만 하면 된다.

 

대리자를 사용하면 함수를 여러 개 만드는 방법보다 더 뛰어난 재사용성을 갖는다.

반응형