[C# 기초] 9. 델리게이트와 이벤트(Delegate, Event)


메소드도 하나의 변수로 다룰 수 있게 해주는 델리게이트와 이벤트기반 프로그래밍(Event-driven programming)을 가능하게 해주는 이벤트에 대해 배웁니다.

델리게이트 (Delegate)


델리게이트는 메소드에 대한 참조입니다. 다시 말해서 델리게이트도 하나의 데이터 형식으로 인스턴스 메소드, 정적메소드 모두 참조 가능합니다. 델리게이트는 값이 아닌 ‘코드’ 자체를 넘기고 싶을때 사용할 수 있습니다.

예를 들어, 어떤 데이터들을 정렬해야할 때 다양한 정렬조건(오름차순, 내림차순, 중앙값으로부터 가까운 순 정렬 등)이 있을 수 있습니다. 이 때 원하는 정렬조건에 대한 메소드만 건네주기만 하면 됩니다.

사용 방법:

한정자 delegate 반환형식 델리게이트이름 (매개변수_목록);

예제 코드:

class Program
{
    delegate int Compare(int number1, int number2);

    // 오름차순 정렬
    static int AscendCompare(int number1, int number2)
    {
        if (number1 > number2)
            return 1;
        else if (number1 == number2)
            return 0;
        else
            return -1;
    }

    // 내림차순 정렬
    static int DescendCompare(int number1, int number2)
    {
        if (number1 < number2)
            return 1;
        else if (number1 == number2)
            return 0;
        else
            return -1;
    }

    static void ExchangeData(ref int number1, ref int number2)
    {
        int Temporary=number1;
        number1 = number2;
        number2 = Temporary;
    }

    static void BubbleSort(int [] Data , Compare Comparer)
    {
        for (int j = 0; j < Data.Length; j++)
        {
            for (int i = 0; i < Data.Length - 1 - j; i++)
            {
                if (Comparer(Data[i], Data[i + 1]) > 0)
                {
                    ExchangeData(ref Data[i], ref Data[i + 1]);
                }
            }
        }
    }

    static void Main(string[] args)
    {
        int[] DataSet = new int[]{ 10, 5, 2, 9, 8, 1, 6, 3, 2, 5 };

        BubbleSort(DataSet, AscendCompare);
        Console.WriteLine("==Sorted by Ascend==");
        foreach(int num in DataSet)
        {
            Console.Write(" {0}", num);
        } // 1 2 2 3 5 5 6 8 9 10
        
        Console.WriteLine(Environment.NewLine+"==Sorted by Descend==");
        BubbleSort(DataSet, DescendCompare);
        foreach (int num in DataSet)
        {
            Console.Write(" {0}", num);
        } // 10 9 8 6 5 5 3 2 2 1
    }
}

델리게이트 체인(Delegate Chain)과 익명메소드(Anonymous Method)


델리게이트 하나가 여러개의 메소드를 동시에 참조할 수 있습니다. 체인처럼 델리게이트를 연달아 등록하게 되면 참조된 함수들을 순서대로 호출해줍니다. 이를 델리게이트 체인(Delegate Chain) 이라고 합니다. 델리게이트를 등록할때는 += 연산자 또는 Delegate.Combine() 메소드를 이용할 수 있고, 체인을 끊고 싶을 때는 -= 연산자 또는 Delegate.Remove() 메소드를 이용하면 됩니다.

익명메소드는 말그대로 이름이 없는 메소드로 델리게이트가 참조할 메소드가 필요한데 이 메소드가 다시 사용할 일이 없다고 판단될때 사용할 수도 있습니다.

익명 메소드 사용 방법

delegate (매개변수_목록)
{
	//실행코드
}

예제 코드:

class Program
{
    delegate void DelegateChains();

    static void Print1()
    {
        Console.WriteLine("Welcome");
    }
    static void Print2()
    {
        Console.WriteLine("To");
    }
    static void Print3()
    {
        Console.WriteLine("C#");
    }

    static void Main(string[] args)
    {
        Console.WriteLine("==체인 연결==");
        DelegateChains chaincall = new DelegateChains(Print1);
        chaincall += Print2;
        chaincall += Print3;
        chaincall(); // Welcome, to, C#

        Console.WriteLine("== 체인 끊기 ==");
        chaincall -= Print1;
        chaincall -= Print3;
        chaincall +=
            delegate()
            {
                Console.WriteLine("Anonymous Method call");
            };
        chaincall(); // To , Anonymous Method Call
    }
}

이벤트 (Event)


프로그래밍을 할때 보통 순차적으로 진행되고는 했습니다. 하지만 어떤 일이 생겼을 때 이를 알려주는 객체가 필요할 때가 있습니다. 이 객체를 만들 때 사용하는 것이 이벤트(Event) 입니다. 이벤트는 어떤 일이 일어났을 때, 그 때 실행되는 코드입니다.

이벤트기반 프로그래밍(Event-driven programming)은 마우스를 움직이고, 클릭하고, 키보드를 입력하는 등 사용자가 명령하는 것에 대해서 프로그램이 그에 맞는 반응을 하는 것처럼 이벤트 기반으로 만들어진 프로그래밍 방식입니다.

이벤트는 단순히 델리게이트를 event 한정자로 수식해서 만들 수 있습니다. 과정은 다음과 같습니다.

  1. 델리게이트를 선언합니다. (이 델리게이트는 클래스 안과 밖 아무곳에나 선언해도 됩니다.)
  2. 클래스내에 1번에서 선언한 델리게이트의 인스턴스를 event 한정자로 수식하여 선언합니다.
  3. 이벤트 핸들러를 작성합니다. 이벤트 핸들러는 1번에서 선언한 델리게이트와 일치하는 메소드여야 합니다.
  4. 클래스의 인스턴스를 생성하고 이 객체의 이벤트에 3번에서 작성한 이벤트 핸들러를 등록합니다.
  5. 이벤트가 발생하면 이벤트 핸들러가 호출됩니다.

예제 코드:

class Program
{
    delegate void EventHandler(string message);

    class Notifier
    {
        public event EventHandler SomethingHappened;

        public void DoSomething(int number)
        {
            if (number == 5)   // 숫자가 5일때 이벤트가 발생한다고 생각합니다.
            {
                SomethingHappened("이벤트 발생! (number 값:}" + number.ToString());
            }
        }
    }

    static void Main(string[] args)
    {
        Notifier notifier = new Notifier();
        notifier.SomethingHappened += MyHandler;  // 발생시킬 이벤트를 등록한다.

        for (int i = 0; i < 30; i++)  // 프로그램이 진행된다.  ( 대기상태에서 숫자 5일때 등록한 이벤트가 발생하게 된다. )
            notifier.DoSomething(i);
        // 이벤트 발생 ! <number 값:> 5
    }

    static void MyHandler(string message)
    {
        Console.WriteLine(message); 
    }
}

이벤트 처리기에 등록하지 않아도 컴파일 에러가 발생하지 않습니다. 따라서 이벤트를 만들었을 때 초기화를 하지 않으면, 이벤트가 발생해도 아무런 일이 일어나지 않은 것처럼 나오기 때문에 항상 초기화하는 습관을 갖는 것이 좋습니다.

사실 이벤트는 델리게이트에 event를 수식해서 선언한 것에 불과합니다. 하지만 이벤트가 델리게이트와 가장 큰 차이점은 이벤트는 외부에서 직접 사용할 수 없다는 데 있습니다. 이벤트는 public 한정자로 선언되어 있어도 자신이 선언되어 있는 클래스 외부에서는 호출이 불가능합니다. 이는 이벤트 기반 프로그래밍을 할 때 안정성을 추구할 수 있게 합니다.

예를 들어 네트워크 상태변화에 대한 사건을 알리는 클래스를 만들었다고 하면, 클래스 내부에서 객체를 감시하며 네트워크 상태를 체크하지만, 클래스 외부에서 네트워크 상태 변화 이벤트를 줄 수 있다면, 이미 네트워크 상태는 신뢰할 수 없게 됩니다.

따라서 델리게이트는 콜백용도로 사용하고, 이벤트는 객체상태변화나 사건의 발생을 알리는 용도로 사용해야 합니다.


이벤트에 대해 익숙해지면 Windows Form과 같은 이벤트 기반 프로그래밍이 실제로 어떻게 구현되었는지를 이해하실 수 있습니다.