본문 바로가기

C#/이것이 C#이다

[ 이것이 C#이다 ] Chapter 13 . 대리자와 이벤트

[ 학습 흐름 ]
  1. 대리자
  2. 일반화 대리자
  3. 대리자 체인
  4. 익명 메소드
  5. 대리자와 이벤트

[ 대리자란 ]

[ 콜백 ]

- 어떤 일을 수행하는 코드 .

- 콜백은 다른 코드에 맡겨져 실행된다 .

- 콜백을 맡아줄 코드는 컴파일 시점이 아닌 프로그램 실행중 결정된다 .


[ 대리자 ]

- 콜백을 맡아 실행하는 일을 대리자가 담당한다 .

- 대리자는 메소드에 대한 참조이다 .

- 대리자에 메소드의 주소를 할당 , 대리자를 호출하면 대리자가 해당 메소드를 호출한다 .

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

- 대리자는 메소드에 대한 참조이기에 자신이 참조할 메소드의 반환형식과 매개변수를 명시해야 한다 .

- 대리자는 인스턴스가 아닌 형식이다 .


[ 콜백 구현 하기 ] 

private delegate int MyDelegate(int a,int b);

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

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

MyDelegate Callback;
Callback = new MyDelegate(Plus);
Console.WriteLine(Callback(3,4));

Callback = new MyDelegate(Minus);
Console.WriteLine(Callback(7,5));

- Callback은 반환형식이 int, 매개변수가 (int,int)인 MyDelegate의 인스턴스이다 .

- MyDelegate 생성자를 호출 , Callback 객체를 생성하였고 , 생성자 인수로 Plus() / Minus() 메소드를 사용하였다 .

- 메소드를 호출하듯 Callback을 사용하면 Callback은 현재 자신이 참조하는 주소의 메소드의 코드 실행, 결과를 호출자에 반환한다 .

 


[ 콜백의 과정 ]

- 대리자를 선언한다 .

- 대리자의 인스턴스를 생성한다 . 인스턴스 생성시 대리자가 참조할 메소드를 인수로 넘긴다

- 대리자를 호출한다

using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;

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;
    }
}
class MainApp
{   
    static void Main(string[] args)
    {
        Calculator calculator = new Calculator();
        MyDelegate Callback;

        Callback=new MyDelegate(calculator.Plus);
        Console.WriteLine(Callback(3, 4));

        Callback = new MyDelegate(Calculator.Minus);
        Console.WriteLine(Callback(7, 5));

    }
}

[ 대리자의 사용 ]

[ 대리자의 사용 ]

- 값이 아닌 코드 자체를 매개변수에 넘기고 싶을 수 있다 .

- 배열을 정렬하는 메소드에서 오름차순/내림차순등의 비교루틴을 매개변수에 넣을 수 있다면 편리할것이다 .

- 비교메소드를 참조할 대리자를 매개변수에 받을 수 있도록 정렬 메소드를 작성하면 간편하게 사용 할 수 있다 .

using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;

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

class MainApp
{   
    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.WriteLine($"{array[i]}");

        int[] array2 = { 7,2,8,10,11 };
        Console.WriteLine("Sorting descending ...");
        BubbleSort(array2, new Compare((DescendCompare)));
        for (int i = 0; i < array2.Length; i++)
            Console.WriteLine($"{array2[i]}");
    
    }
}

[ 일반화 대리자 ]

[ 일반화 대리자 ]

- 대리자는 일반화 메소드도 참조 할 수 있다 .

- 위 경우 대리자도 일반화 메소드를 참조할 수 있도록 형식 매개변수를 이용하여 선언되어야 한다 ,

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

[ 일반화 대리자의 사용 ]

using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;

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

class MainApp
{   
    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.WriteLine($"{array[i]}");

        string[] array2 = { "abc","def","ghi","jkl","mno"};
        Console.WriteLine("Sorting descending ...");
        BubbleSort(array2, new Compare<string>((DescendCompare)));
        for (int i = 0; i < array2.Length; i++)
            Console.WriteLine($"{array2[i]}");
    
    }
}

-  모든 수치형식과 string 은 모두 ICompareable을 상속 ,CompareTo 메소드를 구현하고 있다 .

- 해당 메소드는 매개변수가 자신보다 크면 -1, 같으면 0 , 작으면 1을 반환한다 .


[ 대리자 체인 ]

[ 대리자 체인 ]

- 대리자 하나가 메소드 여러 개를 동시에 참조 할 수 있다 .

- 대리자 체인은 하나씩 차례대로 호출한다 .

using System;

namespace DelegateChains;

delegate void Notify(string message);

//Notify 대리자의 인스턴스 EventOccured를 가지는 클래스 Notifier
class Notifier
{
    public Notify EventOccured;
}

class EventListener
{
    string name;
    public EventListener(string name)
    { this.name = name; }
    public void SomethingHappened(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.SomethingHappened;
        notifier.EventOccured += listener2.SomethingHappened;
        notifier.EventOccured += listener3.SomethingHappened;
        notifier.EventOccured("You've got Email");
        Console.WriteLine();

        notifier.EventOccured -= listener2.SomethingHappened;
        notifier.EventOccured("Download Complete");
        Console.WriteLine();

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

        Notify noti1 = new Notify(listener1.SomethingHappened);
        Notify noti2 = new Notify(listener2.SomethingHappened);
        notifier.EventOccured=(Notify)Delegate.Combine(noti1,noti2);
        notifier.EventOccured("Fire");
        Console.WriteLine();

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



    }
}

 


[ 익명 메소드 ]

[ 익명 메소드 ]

- 익명 메소드는 이름이 없는 메소드이다 .

- 선언된 대리자의 인스턴스를 만들고 , 해당 인스턴스가 메소드의 구현이 담긴 코드 블록을 참조한다 .

- 이후 대리자의 인스턴스를 호출하면 참조하고 있는 코드를 실행한다 .

int delegate Calculate(int a,int b);

Caculate cal = delegate (int a,int b)
						{
                        	return a+b;
                        }
cal(3,4);

- 익명 메소드는 참조할 대리자의 형식과 동일한 형식으로 선언되어야 한다 .

- 대리자가 참조할 메소드를 넘겨야 할 때 해당 메소드가 두번 다시 사용할 일이 없다면 익명 메소드를 사용한다 .

using System;
using System.Data;

namespace AnnonymousMethod;
delegate int Compare(int a, int b);

class MainApp
{
    static void BubbleSort(int[]Dataset,Compare compare)
    {
        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 (compare(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 };
        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]} ");    
        }
        Console.WriteLine();

        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]} ");
        }

    }
}

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

[ 이벤트 ]

- 어떤 일이 생겼을 때 이를 알려주는 객체가 필요한 경우 사용하는 것이 이벤트 .


[ 이벤트의 선언과 사용 ]

  1.  대리자를 선언한다 . 클래스 밖 , 안 상관없다
  2.  클래스 내에 선언한 대리자의 인스턴스를 event 한정자로 수식해서 선언한다 .
  3.  이벤트 핸들러를 작성한다 . 위에서 선언한 대리자와 일치하는 메소드면 된다 .
  4.  클래스의 인스턴스 생성 , 객체의 이벤트에 위에서 작성한 이벤트 핸들러를 등록한다 
  5.  이벤트 발생시 이벤트 핸들러가 호출된다
using System;
using System.Data;
using System.Numerics;

namespace EventTest;
// #1 . 대리자를 선언한다 .
delegate void EventHandler(string message);

class MyNotifier
{
    // #2 . 클래스 내에 대리자의 인스턴스를 event 키워드로 수식해서 선언한다 .
    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
{
    // #3 . 이벤트 핸들러를 작성한다 . 이벤트 핸들러는 대리자와 일치해야 한다 .
    static public void MyHander(string message)
    {
        Console.WriteLine(message);
    }
    static void Main(string[] args)
    {
        // # 4 . 클래스의 인스턴스를 생성하고 , 해당 객체의 이벤트에 이벤트 핸들러를 등록한다 .
        MyNotifier notifier = new MyNotifier();
        notifier.SomethingHappened += new EventHandler(MyHander);

        // # 5 . 이벤트 발생시 이벤트 핸들러가 호출된다 .
        for(int i=0;i<30;i++)
            notifier.DoSomething(i);
    }
}

[ 대리자와 이벤트 ]

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

- 이벤트는 public 한정자로 선언되어 있어도 자신이 선언된 클래스 외부에서는 호출 불가하다 .

- 반면 대리자는 public / internal  수식시 클래스 외부에서 얼마든지 호출이 가능하다 .


[ 이벤트의 효과 ]

- 이벤트는 견고한 이벤트 기반 프로그래밍에 대한 기대를 가능하게 한다 .

- 예를 들어 , 네트워크 상태 변화에 대한 사건을 알리는 클래스를 작성해 동료에게 준 상황을 가정하자 .

- 이벤트를 객체 외부에서 임의로 호출할 수 있게 된다면 , 동료 프로그래머는 클래스의 객체가 감시하는 실제

네트워크 상태와 상관없이 객체 외부에서 허위로 네트워크 상태 변화 이벤트를 일으킬 수 있다 .

- 이것은 객체의 상태를 바꾸는 것보다 나쁘다 . 객체의 상태를 허위로 나타낼 수 있기 때문이다 .

- 이런 위협은 이벤트로 막을 수 있다 .


[ 결론 ]

- 대리자는 대리자대로 콜백 용도로 사용해야 한다.

- 이벤트는 이벤트대로 객체의 상태 변화나 사건의 발생을 알리는 용도로 구분해 사용해야 한다 .