[고급문법 익히기] 13.대리자와 이벤트

0

이것이 C#이다

목록 보기
23/26

13.1 대리자란?

출장을 나온 상현은 사장님께 보고드릴것이 있어서 회사에 전화를 했지만 비서가 대신 전화를 받았습니다. 그리고 상현은 비서에게 사장님이 시간될 때 상현에게 전화 해달라는 메모를 맡기죠.

이 이야기에서 메모를 맡기는 행위(부탁)을 callback이라고 부르는데, 이 콜백은 프로그래밍에서도 사용하게 됩니다.
'전화 주세요'메모처럼 어떤 일을 수행하는 코드, 즉 콜백을 작성하고 다른 코드에 이 콜백을 맡겨 실행하게 합니다.
여기서 콜백을 맡아줄(대리자가 실행할) 코드는 컴파일 시점이 아닌 프로그램 실행중에 결정됩니다.

C#에서는 콜백을 맡아 실행하는 일을 '대리자'가 담당합니다.
대리자(delegate)는 누군가의 일을 대신해주는것을 전문으로 하는 사람을 의미합니다.
대리자는 메소드의 주소를 할당한 후 대리자를 호출하면 이 대리자가 메소드를 호출해줍니다.

  • 코드(메소드)를 대신 실행하는 객체
  • 단, 대리자가 실행할 코드는 컴파일 시점이 아닌 실행 시점에 결정

13.1.1 대리자 선언과 사용

  • delegate 키워드를 이용하여 선언

  • 메소드를 호출하듯 사용(즉, 인수를 입력하고 결과를 반환받음)

  • 메소드와 같이 대리자 또한 매개변수 목록과 반환 형식을 가짐

    • 한정자 delegate 반환형식 델리게이트이름 ( 매개변수_목록 );
delegate int MyDelegate(int a, int b);	//대리자 MyDelegate 선언

한가지 알아 둘 것이 있습니다. 대리자는 인스턴스가 아닌 형식(Type)입니다. 다시 말해 MyDelegate는 int, string과 같은 형식이며 "메소드를 참조하는 그 무엇"을 만들려면 MyDelegate의 인스턴스를 따로 만들어야 한다는 말입니다.

이번에는 대리자가 참조할 메소드를 선언해보겠습니다.
이 메소드들의 반환 형식과 매개변수는 대리자의 반환 형식과 매개변수를 따라야합니다.

int Plus(int a, int b)
{
	return a + b;
}

int Minus(int a, int b)
{
	return a - b;
}

Plus()와 Minus()메소드 모두 MyDelegate 대리자의 반환 형식과 매개 변수를 따르고 있습니다.
그럼 이 메소드를 MyDelegate가 참조하도록 해보겠습니다.

MyDelegate Callback;					//앞에서 만든 대리자형식(MyDelegate)으로 callback 참조를 만듭니다.

//대리자의 인스턴스를 만들때도 new연산자가 필요합니다.
Callback = new MyDelegate( Plus );		// MyDelegate 대리자의 인스턴스를 만들어 Plus를 참조하도록 합니다.
Console.WriteLine( Callback(3,4) );		// 7 출력

Callback = new MyDelegate( Minus );
Console.WriteLine( Callback(7,5) );		// 2 출력

Callbsck은 반환 형식이 int, 매개변수가 (int,int)인 MyDelegate 대리자의 인스턴스입니다.
MyDelegate()생성자를 호출해서 Callback 객체를 생성했습니다. 그리고 생성자 인수는 각각 Plus()메소드와 Minus 메소드를 사용했습니다.
MyDelegate() 대리자에 Plus() 메소드를 인수로 넘기면 Callback은 Plus() 메소드를 참조하고, Minus 메소드를 넘기면 Minus() 메소드를 참조합니다. 이때 메소드를 호출하듯 Callback을 사용하면 자신이 참조하는 주소에 있는 메소드의 코드를 실행하고 그 결과를 호출자에게 반환합니다.

이제까지 설명했던 대리자를 이용하여 콜백을 구현하는 과정을 요약해봅시다.

  1. 대리자를 선언한다.
    delegate int MyDelegate(int a, int b);
  2. 대리자의 인스턴스를 생성한다. 인스턴스를 생성할 때는 대리자가 참조할 메소드를 인수로 넘긴다.
    MyDelegate Callback;
    Callback = new MyDelegate( Plus );
  3. 대리자를 호출한다.
    Callback(3,4)

대리자의 예제 프로그램을 만들어봅시다.

namespace Delegate
{
    delegate int MyDelegate(int a, int b);      //대리자 선언

    class Calculator
    {
        public int Plus(int a, int b)           //대리자는 인스턴스 메소드 참조할 수 있고
        {
            return a + b;
        }

        public static int Minus(int a, int b)	//정적 메소드도 참조할 수 있습니다.
        {
            return a - b;
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            Calculator Calc = new Calculator();
            MyDelegate Callback;

            Callback = new MyDelegate(Calc.Plus);
            Console.WriteLine( Callback(3, 4) );    //메소드를 호출하듯 대리자를 사용하면, 
                                                    //참조하고 있는 메소드가 실행됩니다.
            Callback = new MyDelegate(Calc.Minus);
            Console.WriteLine(Callback(7, 5));
        }
    }
}

13.2 대리자는 왜, 그리고 언제 사용하나요?

프로그래밍을 하다보면 '값'이 아닌 '코드'자체를 매개변수에 넘기고 싶을 때가 많습니다.
예를 들어서 배열을 정렬하는 메소드를 만든다고 생각했을때,
여러분은 이 메소드가 오름차순으로 정렬하도록 하겠습니까,내림차순으로 정렬하도록 하겠습니까, 아니면 특별한 계산식을 거쳐서 나오는 결과순으로 정렬하도록 만들겠습니까?
어떻게 이 메소드가 정렬을 수행할 때 사용하는 비교 루틴을 매개변수에 넣을 수 있을까요?

이 방법이 바로 대리자입니다.

대리자는 메소드에 대한 참조이므로, 비교 메소드를 참조할 대리자를 매개변수에 받을 수 있도록 정렬 메소드를 작성해 놓으면 우리의 고민은 해결됩니다.

  • 대리자는 '값' 대신 '코드'를 매개변수로 넘기고싶을때 사용할 수 있다.

  • 다양한 메소드참조할 대리자매개변수로 받을 수 있도록 메소드를 작성하고
    메소드 인수로 메소드를 넘기면 코드 참조가 가능하다.

이런 예제를 만들어보겠습니다.

namespace UsingCallback
{
    delegate int Compare(int a, int b);

    internal class Program
    {
        //Compare 대리자가 참조할 비교 메소드를 작성 합니다.
        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 temp = 0;

            for (int i = 0; i < DataSet.Length-1; i++)
            {
                for (int j = 0; j < DataSet.Length - (i + 1); j++)
                {
                    //Comparer가 어떤 메소드를 참조하는가에 따라
                    //정렬 결과가 달라집니다.
                    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();
        }
    }
}

출력
Sorting ascending...
2 3 4 7 10
Sorting descending...
11 10 8 7 2

정렬메소드를 호출하면 우리가 원하던 대로 정렬 방식이 분리된 정렬 코드를 얻을 수 있습니다.

위 BubbleSort처럼 대리자가 참조할 여러가지 메소드를 호출할 때 인수로 넘기면 됩니다.

13.3 일반화 대리자

대리자는 보통의 메소드뿐 아니라 일반화 메소드도 참조 할 수 있습니다.
이 경우에는 대리자도 일반화 메소드를 참조 할 수 있도록 형식 매개변수를 이용하여 선언되어야 합니다.

일반화 대리자 선언 방식은 다음과 같습니다.

delegate int Compare<T> (T a, T b);	//일반화 대리자 선언

또한 Compare대리자를 매개변수로 사용하는 Bubble() 메소드도 형식 매개변수를 받아 들이도록 작성해야 합니다.

//형식 매개 변수가 추가되었습니다.
static void BubbleSort<T> (T[] DataSet, Compare<T> comparer)
{
   int temp = 0;

   for (int i = 0; i < DataSet.Length-1; i++)
   {
       for (int j = 0; j < DataSet.Length - (i + 1); j++)
       {
           //Comparer가 어떤 메소드를 참조하는가에 따라
           //정렬 결과가 달라집니다.
           if (Comparer(DataSet[j], DataSet[j + 1]) > 0)
           {
               temp = DataSet[j + 1];
               DataSet[j + 1] = DataSet[j];
               DataSet[j] = temp;
           }
        }
    }
}	

대리자도 참조할 메소드 구현이 없으면 아무런 소용이 없습니다.
일반화 대리자를 지원하는 비교 메소드를 구현해보겠습니다.
다음은 AscendCompare()의 일반화 버전입니다.

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

위 코드는 설명이 좀 필요할것 같습니다.
먼저 형식 매개변수가 추가된것은 잘 이해하고 있을것이라 생각하고, 형식 매개변수의 제약 조건 where T에 대해서도 아실거라 생각합니다. (현재 조건은 "인수가 IComparable< T >를 상속받은 데이터 타입만 매개변수로 넘길수 있다" 입니다.)

문제는 IComparable< T >를 상속하는 a 객체의 CompareTo()메소드를 호출해서 그 결과를 반환하는것이 이해가 안 될 겁니다.

사실은 System.Int32(int), System.Double(double)을 비롯한 모든 수치 형식과 System.String(string)은 모두 IComparable을 상속해서 CompareTo() 메소드를 구현하고있습니다.

이들 모두의 CompareTo() 메소드는 매개변수가 자신보다 크면 -1, 같으면 0, 작으면 1을 반환합니다. 그래서 a.CompareTo(b)를 호출하면 오름차순 정렬에 필요한 비교 결과를 얻을 수 있습니다.

앞에서 했던 예제를 일반화 버전으로 업그레이드 해보겠습니다.

namespace GenericDelegate
{
    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)
        {
            T temp;

            for (int i = 0; i < DataSet.Length - 1; i++)
            {
                for (int j = 0; j < DataSet.Length - (i + 1); j++)
                {
                    //Comparer가 어떤 메소드를 참조하는가에 따라
                    //정렬 결과가 달라집니다.
                    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)
        {
            string[] array = { "abc", "Abc", "ABc", "ABC", "def","abcdefg", "a", "A", "c", "C", "b", "B", " " };
            Console.WriteLine("오름차순 정렬합니다(작은 수가 앞으로)...");
            BubbleSort(array, new Compare<string>(AscendCompare));

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

            int[] array2 = {1,2,3,4,5};
            Console.WriteLine("\n내림차순 정렬합니다(큰숫자가 앞으로)...");
            BubbleSort(array2, new Compare<int>(DescendCompare));

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

            Console.WriteLine();
        }
    }
}

출력
오름차순 정렬합니다(작은 수가 앞으로)...
a A abc Abc ABc ABC abcdefg b B c C def
내림차순 정렬합니다(큰숫자가 앞으로)...
5 4 3 2 1

13.4 대리자 체인(Delegate Chains)

대리자는 메소드의 참조라고 했는데,대리자에는 재밌는 속성이 있습니다.
그건 바로 대리자 하나가 여러 개의 메소드를 동시에 참조할 수 있다는 것입니다.

예를 들어 다음과 같이 대리자와 대리자 형식에 맞춘 메소드를 몇개 선언 해봅시다.

delegate void TrereIsAFire( string location );

void Call119( string location )
{ 
	Console.WriteLine($"소방서죠? 불이 났어요! 주소는 {location}입니다."); 
}

void ShotOut( string location )
{ 
	Console.WriteLine($"피하세요! {location}에 불이 났어요"); 
}

void Escape( string location )
{ 
	Console.WriteLine($"빨리 {location}에서 나갑시다!"); 
}

이렇게 선언한 메소드 들은 ThereIsAFire 대리자의 인스턴스가 자신들을 동시에 참조할 수 있도록 다음과 같이 += 연산자를 이용하여 결합할 수 있습니다. 한편 특정 메소드를 끊을때는 -= 연산자를 이용합니다.

ThereIsAFire Fire = new ThereIsAFire(Call119);
Fire += new ThereIsAFire(ShotOut);
Fire += new ThereIsAFire(Escape);

Fire("롯데타워");	//대리자 호출

이렇게 결합해놓은 Fire 대리자는 다음과 같이 한 번만 호출하면 자신이 참조하고 있는 Call119(),ShotOut(),Escape() 메소드를 모두 호출합니다.

대리자 체인은 여러개의 콜백을 동시에(엄밀히는 차례대로 호출합니다.) 호출해야 할때 유용합니다.

대리자 체인을 만드는 또 다른 방법은 이런 방법이 있습니다.

//방법 2
ThereIsAFire Fire = new ThereIsAFire(Call119)
					+ new ThereIsAFire(ShotOut)
                    + new ThereIsAFire(Escape);
                    
//방법 3 (Delegate.Combine()메소드 사용)                  
ThereIsAFire Fire = Delegate.Combine(
					new ThereIsAFire(Call119),
					new ThereIsAFire(ShotOut),
                    new ThereIsAFire(Escape));

대리자의 다양한 모양이 있으니 예제를 통해 알아보도록 하겠습니다.

namespace DelegateChains
{
    delegate void Notify(string message);

    class Notifier
    {
        public Notify EventOccured;
    }

    class EventListener
    {
        private string name;
        public EventListener(string name)
        {
            this.name = name;
        }

        public void SomethingHappend(string message)
        {
            Console.WriteLine($"{name}.SomethingHappened : {message}");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Notifier notifier = new Notifier();
            EventListener listener1 = new EventListener("Listener1");
            EventListener listener2 = new EventListener("Listener2");
            EventListener listener3 = new EventListener("Listener3");

            notifier.EventOccured += listener1.SomethingHappend;
            notifier.EventOccured += listener2.SomethingHappend;
            notifier.EventOccured += listener3.SomethingHappend;
            notifier.EventOccured("You've got mail.");

            Console.WriteLine();

            notifier.EventOccured -= listener2.SomethingHappend;
            notifier.EventOccured("Download complete.");

            Console.WriteLine();

            notifier.EventOccured = new Notify(listener2.SomethingHappend)
                                  + new Notify(listener3.SomethingHappend);
            notifier.EventOccured("Nuclear launch detected.");

            Console.WriteLine();

            Notify notify1 = new Notify(listener1.SomethingHappend);
            Notify notify2 = new Notify(listener2.SomethingHappend);

            notifier.EventOccured =
                (Notify)Delegate.Combine(notify1, notify2);
            notifier.EventOccured("Fire!!");

            Console.WriteLine();

            notifier.EventOccured =
                (Notify)Delegate.Remove(notifier.EventOccured, notify2);
            notifier.EventOccured("RPG!");
        }
    }
}

출력
Listener1.SomethingHappened : You've got mail.
Listener2.SomethingHappened : You've got mail.
Listener3.SomethingHappened : You've got mail.

Listener1.SomethingHappened : Download complete.
Listener3.SomethingHappened : Download complete.

Listener2.SomethingHappened : Nuclear launch detected.
Listener3.SomethingHappened : Nuclear launch detected.

Listener1.SomethingHappened : Fire!!
Listener2.SomethingHappened : Fire!!

Listener1.SomethingHappened : RPG!

13.5 익명 메소드

익명 메소드(Anomymous Mwthod)란 이름 없는 메소드를 말합니다.
대리자를 선언후에 대리자의 인스턴스를 만들고 이 인스턴스가
메소드의 구현이 담겨있는 코드 블록(익명메소드)을 참조시킵니다.
이때 대리자의 인스턴스를 호출하면 자신이 참조하고 있는 코드를 실행하게 됩니다.

delegate int Calculate(int a, int b);	//대리자를 선언후에 대리자의 인스턴스를 만들고 

public static void Main()
{
	Calculate Calc;
    
    Clac = delegate (int a, int b)		//대리자 인스턴스에
    		{							//메소드의 구현이 담겨있는 코드 블록(익명메소드)을 참조시킵니다.
            	return a+b;
            }
            
    Console.WriteLine($"3+4 :{Calc(3,4)}");
}
  • 다른 코드 블록에서 재사용될 일이 없는 이름없는 메소드
  • 익명 메소드는 자신을 참조할 대리자의 형식과 동일한 형식으로 선언되어야 합니다.
  • 익명 메소드 구현 절차
    1. 사전정의된 대리자 형식(반환형식,매개변수)에 맞춰 익명 메소드 작성
    2. 익명 메소드 정의시 delegate 키워드로 시작하며, 정의된 코드는 대리자 참조에 할당
    3. 익명 메소드를 할당받은 대리자 참조 호출

다음은 익명 메소드를 이용한 버블 정렬 프로그램 예제입니다.

using System;

namespace AnonymouseMethod
{
    delegate int Compare(int a, int b);

    class MainApp
    {
        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, 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();
        }
    }
}

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

프로그래밍을 하다보면 어떤 일이 발생했을 때 이를 알려주는 객체가 필요한 경우가 있습니다.
알람시계처럼 특정 시간이 되었을때나, 사용자가 버튼을 클릭했을 때 이를 알려주는 객체 말입니다.
이런 객체를 만들 때 사용하는 것이 바로 이벤트(event) 입니다.

  • C#에서 객체의 사건을 표현하는 형식

  • 이벤트 처리기(Event Handler): 이벤트 발생시 실행되는 메소드

  • 이벤트는 외부에서 직접 호출할 수 없음. 객체의 은닉성 표현을 위해

  • 동작 원리는 대리자와 유사

  • 대리자를 event 한정자로 수식하여 선언

13.6.1 이벤트를 선언하고 사용하는 방법

  1. 대리자를 선언합니다. 이 대리자는 클래스 밖에 선언해도 되고 안에 선언해도 됩니다.
delegate void EventHandler(string message);
  1. 클래스 내에 선언한 대리자의 인스턴스를 event 한정자로 수식해서 선언합니다.
class MyNotifier
{
	public event EventHandler SomethingHappened; //EventHandler = (1.)에서 선언한 대리자
    
    public void DoSomething(int number)
    {
    	int temp = number % 10;
        
        //number가 3,6,9로 끝나는 값이 될때마다 이벤트 발생
        if(temp != 0 && temp % 3 ==0)
        {
        	SomthingHappened(String.Format($"{number} : 짝"));
        }
    }
}
  1. 이벤트 핸들러를 작성 합니다. 이벤트 핸들러는 ( 1 )에서 선언한 대리자와 형식이 일치하는 메소드면 됩니다.
class MainApp
{
	static public void MyHandler(string message)
    {
    	Console.WriteLine(message)
    }
}
  1. 클래스의 인스턴스를 생성하고 이 객체의 이벤트에 ( 3 )에서 작성한 이벤트 핸들러를 등록합니다.
  2. 이벤트가 발생하면 이벤트 핸들러가 호출됩니다.
class MainApp
{
	static public void MyHandler(string message)
    {
    	Console.WriteLine(message);
    }
    
    static void Main(string[] args)
    {
    	MyNotifier notifier = new MyNotifier();
        notifier.SomethingHappened += new EventHandeler(MyHandeler);	
        // ↑ SomethingHappend 이벤트에 MyHandler() 메소드를 이벤트 핸들러로 등록합니다.
        
        for(int i = 1; i < 30; i++)
        {
        	notifier.DoSomething(i);			//이벤트 발생시 호출됩니다.
        }
    }
}

위에서 살펴봤던 예제 프로그램입니다.

using System;

namespace EventTest
{
    delegate void EventHandler(string message);

    class MyNotifier 
    {
        public event EventHandler SomethingHappened;
        public void DoSomething(int number)
        {
            int temp = number % 10;

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

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

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

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

출력
3 : 짝
6 : 짝
9 : 짝
13 : 짝
16 : 짝
19 : 짝
23 : 짝
26 : 짝
29 : 짝

컴파일러는 알려주지 않는 이벤트 처리기 없는 이벤트

이벤트는 이벤트 처리기를 등록하지 않아도 컴파일 에러가 발생하지 않습니다.
덕분에 프로그래머들은 초기화 하지 않은 이벤트를 쉽게 놓치곤 합니다. 이런 문제 때문에 이벤트를 선언하면 항상 비어있는 익명메소드를 미리 초기화 해두는 방법을 쓰는 프로그래머도 있습니다.
최악의 경우 프로그램이 다운되는것은 막을 수 있으니까요.

13.7 대리자와 이벤트의 차이점

앞에서 살펴봤듯이, 이벤트는 대리자에 event 키워드로 수식해서 선언한 것에 불과합니다.
언뜻 봤을 때는 둘의 차이가 별로없는 것 같은데, C#팀은 왜 이벤트를 언어에 추가했을까요?

이벤트와 대리자의 가장 큰 차이점은 바로 이벤트를 외부에서 직접 사용할 수 없다는 데 있습니다.

이벤트는 public 한정자로 선언되어도 자신이 선언된 클래스 외부에서는 호출이 불가능합니다.
반면에 대리자는 public이나 internal로 수식되어 있으면 클래스 외부에서라도 얼마든지 호출이 가능합니다.

예를 들어 네트워크 상태 변화에 대한 사건을 알리는 클래스를 작성해서 동료에게 줬다고 생각해 봅시다.
이벤트를 객체 외부에서 임의로 호출할 수 있다면 동료 프로그래머는 여러분이 작성한 클래스의 객체가 감시하는 실제 네트워크 상태와 상관없이 객체외부에서 허위로 네트워크 상태 변화 이벤트를 일으킬 수 있게 됩니다.

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

delegate void EventHandler(string message);

class MyNotifier
{
	public event EventHandler SomethingHappened; //public으로 선언한 event
}

class MainApp
{
	static void Main()
    {
    	MyNotifier notifier = new MyNotifier();
        notifier.SomethingHappened("테스트"); 	//에러! 이벤트는 객체 외부에서 
        										//직접 호출할 수 없습니다.
    }
}

0개의 댓글