5 분 소요

이 글은 한빛미디어의 이것이 C#이다를 보고 헷갈리는 부분 위주로 정리한 글입니다.

대리자 delegate 란?

  • delegate는 메소드를 대신 호출해주는 대리인으로써 한마디로 말하면, delegate는 메소드에 대한 참조이다.

delegate의 기본 형태

delegate의 기본 형태는 다음과 같다.

// 기본 형태
한정자 delegate 반환_형식 대리자_이름(매개변수_목록);

// 예시
public delegate int MyDelegate(int a, int b);

delegate는 메소드에 대한 참조이기 때문에 자신이 참조할 메소드의 반환 형식매개변수를 명시해 주어야함. 그렇게 떄문에 delegate는 메소드와 매우 유사한 모양을 가지고 있음.

여기서 한가지 알아두어야 할 것은 delegate는 인스턴스(객체)가 아닌 형식(Type)인 점이다.
즉, int, string과 같은 형식이며 메소드를 참조하는 그 무엇을 만드려면 인스턴스화 해줘야한다.

delegate int MyDelegate(int a); 
static void Main(string[] args)
{
    MyDelegate delegateCallBack; // 대리자 변수 생성
}


이렇게 인스턴스화된 delegate 객체는 메소드들의 주소를 참조 할 수 있다.

MyDelegate delegateCallBack = new MyDelegate(MyMethod); // 메소드 참조

Console.WriteLine(delegateCallBack(123456)); // 대리자 호출 즉, delegate가 참조하는 함수 실행 // 123456

int MyMethod(int a) // 메소드
{
    //...
    return a;
}


image

지금까지의 과정 순서는 다음과 같다.

  1. 대리자를 선언한다.
  2. 대리자의 인스턴스를 생성, 인스턴스를 생성할 떄는 대리자가 참조할 메소드를 인수로 넘긴다.
  3. 대리자를 호출한다.

delegate는 왜, 언제 사용하나?

함수를 사용할 때 매개변수로 을 넘기는 것 아니라 함수 자체를 넘길 수 있다.

다음 코드의 경우 오름차순 비교 함수와 내림차순 비교 함수 두가지가 있다.
이 둘과 같은 인자값을 갖는 delegate를 인자값으로 갖는 BubbleSort함수에
어떤 함수를 넣어주냐에 따라 다른 결과가 나온다.

delegate int Compare(int a, int b);
internal class Program
{

    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;
    }

    static void BubbleSort(int[] DataSet, Compare Comparer)
    {
        int i = 0;
        int j = 0;
        int temp = 0;

        for (i = 0; i < DataSet.Length -1; i++)
        {
            for (j = 0; j < DataSet.Length - (i + 1); j++)
            {
                if (Comparer(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("Sorting ascending..");
        BubbleSort(array, new Compare(AscendCompare));

        for (int i = 0; i < array.Length; i++)
            Console.Write($"{array[i]} ");

        int[] array2 = { 7, 2, 8, 10, 11 };
        Console.WriteLine("\nSorting descending...");
        BubbleSort(array2, new Compare(DescendCompare));

        for (int i = 0; i < array2.Length; i++)
            Console.Write($"{array2[i]} ");

        Console.WriteLine();
    }
}


일반화 대리자

대리자는 일반화(generic)형식의 메소드도 참조할 수 있다.

delegate int Compare<T>(T a, T b); 


위에서 예시로든 코드를 일반화 대리자로 바꾸면 다음과 같다.

이때 여기서 보이는 CompareTo는
모든 수치 형식과 string이 IComparable을 상속해서 구현하고있는 메소드로써
자신보다 큰 값이 인자로 들어오면 -1 , 같으면 0, 작으면 1을 반환한다.

delegate int Compare<T>(T a, T b);
internal class Program
{

    static int AscendCompare<T>(T a, T b) where T : IComparable<T>
    {
        return a.CompareTo(b);
    }

    static int DescendCompare<T>(T a, T b) where T : IComparable<T>
    {
        return a.CompareTo(b) * -1;
    }

    static void BubbleSort<T>(T[] DataSet, Compare<T> Comparer)
    {
        int i = 0;
        int j = 0;
        T temp;

        for (i = 0; i < DataSet.Length -1; i++)
        {
            for (j = 0; j < DataSet.Length - (i + 1); j++)
            {
                if (Comparer(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("Sorting ascending..");
        BubbleSort(array, new Compare<int>(AscendCompare));

        for (int i = 0; i < array.Length; i++)
            Console.Write($"{array[i]} ");

        string[] array2 = { "abc", "def", "ghi", "jkl", "mno" };
        Console.WriteLine("\nSorting descending...");
        BubbleSort(array2, new Compare<string>(DescendCompare));

        for (int i = 0; i < array2.Length; i++)
            Console.Write($"{array2[i]} ");

        Console.WriteLine();
    }
}


대리자 체인

대리자는 여러개의 메소드를 동시에 참조할 수 있다.

+=를 통해 메소드를 여러개 넣어주면 된다.

internal class Program
{
    delegate void ThereIsAFire(string location);

    static void Call119(string location)
    {
        Console.WriteLine("소방서죠? 불났어요! 주소는 {0}", location);
    }
    static void ShotOut(string location)
    {
        Console.WriteLine("피하세요! {0}에 불이 났어요!", location);
    }
    static void Escape (string location)
    {
        Console.WriteLine("{0}에서 나갑시다!", location);
    }

    static void Main(string[] args)
    {
        ThereIsAFire Fire = new ThereIsAFire(Call119);
        Fire += new ThereIsAFire(ShotOut);
        Fire += new ThereIsAFire(Escape);

        Fire("우리집");
        //소방서죠? 불났어요! 주소는 우리집
        //피하세요! 우리집에 불이 났어요!
        //우리집에서 나갑시다!
    }
}


-=을 통해 체인을 끊어내는 것 역시 가능하다.

익명 메소드

익명 메소드는 이름이 없는 메소드를 뜻한다.

익명 메소드는 다음과 같이 선언한다.

대리자_인스턴스 = delegate (매개변수_목록)
                {
                    // 실행하려는 코드 ...
                }


예시는 다음과 같다.

delegate int Calculate(int a, int b);

public static void Main()
{
    Calculate Calc;
    Calc = delegate (int a, int b)
    {
        return a + b;
    };

    Console.WriteLine("3 + 4 : {0}", Calc(3, 4));
}


익명 메소드는 자신이 참조할 대리자의 형식과 동일해야 한다.
반환 형식매개변수 형식, 개수 같은 것들을 모두 맞춰줘야한다.

사용 예시

t

internal class Program
{
    delegate int Compare<T>(T a, T b);
    static void BubbleSort<T>(T[] DataSet, Compare<T> Comparer)
    {
        int i = 0;
        int j = 0;
        T temp;

        for (i = 0; i < DataSet.Length - 1; i++)
        {
            for (j = 0; j < DataSet.Length - (i + 1); j++)
            {
                if (Comparer(DataSet[j], DataSet[j + 1])> 0)
                {
                    temp = DataSet[j + 1];
                    DataSet[j + 1] = DataSet[j];
                    DataSet[j] = temp;
                }
            }
        }
    }
    public static void Main(string[] args)
    {
        int[] array = { 3, 7, 4, 2, 10 };

        Console.WriteLine("Sorting ascending...");
        BubbleSort<int>(array, delegate (int a, int b) // 익명 메소드
        {
            if (a > b)
                return 1;
            else if (a == b)
                return 0;
            else
                return -1;
        });

        for (int i = 0; i < array.Length; i++)
            Console.Write($"{array[i]} ");

        int[] array2 = { 7, 2, 8, 10, 11 };
        Console.WriteLine("\nSorting descending...");
        BubbleSort(array2, delegate (int a, int b) // 익명 메소드
        {
            if (a < b)
                return 1;
            else if (a == b)
                return 0;
            else
                return -1;
        });

        for (int i = 0; i < array2.Length; i++)
            Console.Write($"{array2[i]} ");
        
        Console.WriteLine();
    }
}


event 이벤트 : 객체에 일어난 사건 알리기

다음은 이벤트를 이용하여 3의 배수마다 호출을 하는 예시이다

delegate void EventHandler(string message);

class MyNotifier
{
    public event EventHandler SomethingHappend;

    public void DoSomething(int number)
    {
        int temp = number % 10;

        if (temp != 0 && temp % 3 == 0)
        {
            SomethingHappend(String.Format("{0} : 짝", number));
        }
    }
}

internal class Program
{
    static public void MyHandler(string message)
    {
        Console.WriteLine(message);
    }

    public static void Main(string[] args)
    {
        MyNotifier notifier = new MyNotifier();
        notifier.SomethingHappend += new EventHandler(MyHandler);

        for (int i = 0; i < 30; i++)
        {
            notifier.DoSomething(i);
        }
    }
}


사실 이벤트는 대리자의 한 종류로 대리자와 똑같이 사용된다.

유일한 차이로는 이벤트는 public으로 선언되어도 외부에서 직접 호출될 수 없다는 점이다.

public static void Main(string[] args)
{
    MyNotifier notifier = new MyNotifier();
    notifier.SomethingHappend += new EventHandler(MyHandler);
    notifier.SomethingHappend("test"); // event는 이곳에서 호출될 수 없음

    for (int i = 0; i < 30; i++)
    {
        notifier.DoSomething(i);
    }
}


따라서 대리자는 대리자대로 콜백 용도로 사용하고,
이벤트는 이벤트대로 객체의 상태 변화나 사건의 발생을 알리는 용도로 구분하여 사용해야함

태그:

카테고리:

업데이트:

댓글남기기