C#/수업내용

2020.05.15. 대리자(Delegate) / 이벤트(event)

dev_sr 2020. 5. 17. 19:40

 

1. 대리자

1) 콜백
-대신 어떤 일을 해줄 대리자를 두고, 세부 실행 코드는 컴파일 시점이 아닌 실행 시점에 부여
-대리자는 메소드에 대한 참조 (메서드를 담는 변수라고 생각하면 편함)
-->대리자에 메서드의 주소를 할당
-->대리자 호출 ->대리자 -> 메서드 호출

2) 선언 형식
-한정자 delegate 반환 형식 대리자 이름( 매개 변수_목록);
ex) delegate void Mydelegate( );
ex) delegate int Mydelegate(int a, int b);

3) 대리자는 인스턴스가 아닌 형식, 그래서 인스턴스를 만든 후 메서드를 참조해야함.

 



2. 대리자를 이용한 콜백 구현 과정

 

1) 대리자 선언 (반환타입과 매개변수는 참조할 메서드와 같아야함)

2) 대리자가 참조할 메서드 선언

3) 대리자 인스턴스 생성 및 대리자 호출

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System;
 
namespace Delegate
{
    // 1. 대리자 선언 (반환타입과 매개변수는 참조할 메서드와 같아야함)
    delegate int MyDelegate(int a, int b);
 
    class Calculator
    {
        //2. 대리자가 참조할 메서드 선언
        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 Calc = new Calculator();
            MyDelegate Callback;
 
            //3. 대리자 인스턴스 생성 및 대리자 호출
            Callback = new MyDelegate(Calc.Plus);
            Console.WriteLine(Callback(34));  //7
 
            Callback = new MyDelegate(Calculator.Minus);
            Console.WriteLine(Callback(65));  //1
        }
    }
}
 

 

 

 

 

3. 대리자를 이용한 버블 정렬

 

BubbleSort(array, new Compare(AscendCompare)); 

->대리자를 통해 AscendCompare 메서드를 통째로 BubbleSort메서드에 넘기고 BubbleSort안에서

  Comparer(DataSet[j], DataSet[j + 1]) 로 대리자를 호출하여

  AscendCompare 를 실행하여 반환값 1, 0 , -1을 받는다 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using System;
 
namespace UsingCallback
{
    delegate int Compare(int a, int b);
 
    class MainApp
    {
        static int AscendCompare(int a, int b)
        {
            //Console.WriteLine("AscendCompare a: {0}, b: {1}", a,b);
            //3. 대리자를 통해 넘어온 매개변수를 비교하여 반환값을 주는 메서드 실행
 
            if (a > b)
                return 1;
            else if (a == b)
                return 0;
            else
                return -1;
        }
 
        static int DescendCompare(int a, int b)
        {
 
            //Console.WriteLine("DescendCompare a: {0}, b: {1}", a, 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;
 
            //Console.WriteLine("BubbleSort");
 
            for (i = 0; i < DataSet.Length - 1; i++)
            {
                for (j = 0; j < DataSet.Length - (i + 1); j++)
                {
                    
                    //2. Compare대리자가 참조하는 AscendCompare 메서드 호출
                    // DataSet[j], DataSet[j+1]의 값을 매개변수로 넘긴다.
 
                    //Console.WriteLine("반환되는 값 {0}",Comparer(DataSet[j], DataSet[j + 1])); // -1, 1, 1...
 
                    if (Comparer(DataSet[j], DataSet[j + 1]) > 0)
                    {
                        //4. 대리자를 통해 실행된 메서드의 반환값이 1이면
                        //두 수를 비교해서 더 작은 값의 자리를 앞으로 바꾸는 기능을 수행한다.
 
                        temp = DataSet[j + 1];
                        DataSet[j + 1= DataSet[j];
                        DataSet[j] = temp;
                    }
                }
            }
        }
 
        static void Main(string[] args)
        {
            int[] array = { 374210 };
 
            Console.WriteLine("Sorting ascending...");
 
            //1. BubbleSort 호출 (대리자의 인스턴스 생성 및 메서드 호출)
            
            //BubbleSort(array, new Compare(AscendCompare));
            //와 같다.
            Compare compare = new Compare(AscendCompare);
            BubbleSort(array, compare);
 
            //5. 바뀐 배열 출력
            for (int i = 0; i < array.Length; i++)
                Console.Write($"{array[i]} ");
 
            int[] array2 = { 7281011 };
            Console.WriteLine("\nSorting descending...");
            BubbleSort(array2, new Compare(DescendCompare));
 
            for (int i = 0; i < array2.Length; i++)
                Console.Write($"{array2[i]} ");
 
            Console.WriteLine();
        }
    }
}
 

 

 

 

4. 일반화 대리자를 이용한 버블정렬

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
using System;
 
namespace GenericDelegate
{
    //1. 대리자 일반화 선언
    delegate int Compare<T>(T a, T b);
 
    class MainApp
    {
        //**CompareTo( )**
        // System.Int32, System.Double System.String 등은 모두 CompareTo() 라는 메서드를 제공하는데
        // 이 메서드를 사용하게 되면 간단히 오름차순으로 사용할 수 있다. 그런데~
        // 이걸 일반화 형식에서 사용하려면 IComparable<T>를 상속 받아야함
        // 그래서 IComparable<T>를 상속받는 제약 조건이 생김
 
 
        //2. 대리자가 참조할 일반화 메서드 선언
        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++)
                {       
                        //4. 대리자가 참조하는 메서드 호출
                    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 = { 374210 };
 
            Console.WriteLine("Sorting ascending...");
 
            //3. 일반화 대리자 인스턴스 생성 및 호출
            BubbleSort<int>(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<string>(array2, new Compare<string>(DescendCompare));
 
            for (int i = 0; i < array2.Length; i++)
                Console.Write($"{array2[i]} ");
 
            Console.WriteLine();
        }
    }
}
 

 

 

 

5. 대리자 연결, 연결해제 하기

 

1) += / - =

notifier.EventOccured += listener1.SomethingHappend;

notifier.EventOccured += listener2.SomethingHappend;

notifier.EventOccured -= listener2.SomethingHappend;

 

2) = +  / = -

notifier.EventOccured = new Notify(listener2.SomethingHappend)

                            + new Notify(listener3.SomethingHappend);

notifier.EventOccured = new Notify(listener2.SomethingHappend)

                           new Notify(listener3.SomethingHappend);

 

3) Delegete.Combine / Delegete.Remove

(Notify)Delegate.Combine(notify1, notify2);

(Notify)Delegate.Remove(notifier.EventOccured, notify2);

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
using System;
 
namespace DelegateChains
{
    //1. 대리자 선언
    delegate void Notify(string message);
 
    class Notifier
    {
        //2. Notifier 타입의 대리자 변수 EventOccured 선언
        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)
        {
            //3. 대리자의 인스턴스 생성
            Notifier notifier = new Notifier();
            //Listener 1,2,3이 이름이 된다
            EventListener listener1 = new EventListener("Listener1");
            EventListener listener2 = new EventListener("Listener2");
            EventListener listener3 = new EventListener("Listener3");
 
            //4. 대리자 연결 및 EventListener의 인스턴스가 가지고 있는 메서드를 참조
            notifier.EventOccured += listener1.SomethingHappend;
            notifier.EventOccured += listener2.SomethingHappend;
            notifier.EventOccured += listener3.SomethingHappend;
            //4. 참조하는 메서드 호출
            notifier.EventOccured("You've got mail.");
 
            Console.WriteLine();
 
            //5. EventListener의 인스턴스가 가지고 있는 메서드를 연결 해제
            notifier.EventOccured -= listener2.SomethingHappend;
            notifier.EventOccured("Download complete.");
 
            Console.WriteLine();
 
            //4번 연결식을 풀어쓰기
            notifier.EventOccured = new Notify(listener2.SomethingHappend)
                                  + new Notify(listener3.SomethingHappend);
            notifier.EventOccured("Nuclear launch detected.");
 
            Console.WriteLine();
 
            //4번을 연결식을 더 풀어쓰기
            Notify notify1 = new Notify(listener1.SomethingHappend);
            Notify notify2 = new Notify(listener2.SomethingHappend);
 
            //대리자를 묶을 수 있다.
            //notify1, notify2 대리자를 묶어서 연결하기
            //public static Delegate Combine(Delegate a, Delegate b);
            //Delegate타입을 반환해서 (Notify)타입으로 캐스팅
            notifier.EventOccured =
                (Notify)Delegate.Combine(notify1, notify2);
            notifier.EventOccured("Fire!!");
 
            Console.WriteLine();
 
            //대리자를 뗄 수 있다.
            //notifier 대리자에서 연결된 notify2 연결 해제하기
            //한 대리자에게 연결된 가장 마지막 대리자를 제거한다
            //public static Delegate Remove(Delegate source, Delegate value);
            //Delegate타입을 반환해서 (Notify)타입으로 캐스팅
            notifier.EventOccured =
                (Notify)Delegate.Remove(notifier.EventOccured, notify2);
            notifier.EventOccured("RPG!");
        }
    }
}
 

 

 

 

6. 익명메서드로 대리자 만들기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
using System;
 
namespace AnonymouseMethod
{
    //1. 대리자 선언
    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++)
                {
                    //3. 대리자 호출
                    //BubbleSort를 호출한 시점에서 { }안을 실행시킨다.
                    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 = { 374210 };
 
            Console.WriteLine("Sorting ascending...");
            //2. 대리자 인스턴스 생성, 호출 모두 익명메서드로 진행(lambda)
            //앞서 대리자가 참조했던 메서드를 식 안에 써준다.
            //BubbleSort를 실행하고 BubbleSort안에서 특정 시점에 대리자가 호출되면 
            //{ }안을 실행시킨다
            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 = { 7281011 };
            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();
        }
    }
}
 

 

 

 

7. Event

 

대리자와 비슷해서 대리자에 event라는 한정자를 붙여서 만든다.

 

이벤트 만드는 순서

1) 대리자 선언

2) 선언한 대리자 인스턴스를 event한정자로 수식

3) 이벤트 핸들러 작성 (대리자와 일치하는 메서드 작성)

4) 클래스 인스턴스 생성 후 객체의 이벤트에 이벤트 핸들러 등록 (이벤트를 +=해서 연결시킨다는 말)

5) 이벤트 발생 -> 이벤트 핸들러 호출

 

대리자의 이벤트의 차이점

-이벤트는 public 이라도 클래스 외부에서 호출 불가

-대리자는 public이나 internal인 경우 클래스 외부에서 호출 가능

 

-대리자는 콜백 용도로 사용

-이벤트는 외부에서 변조의 위험이 있을 경우에 사용(객체 상태변화, 사건의 발생 통지)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using System;
 
namespace EventTest
{
    //1. 대리자 선언
    delegate void EventHandler(string message);
 
    class MyNotifier
    {
        //2. 대리자의 인스턴스 EventHandler 에 event를 붙여서 이벤트 선언
        public event EventHandler SomethingHappened;
 
         
        public void DoSomething(int number)
        {
            int temp = number % 10;
 
            if (temp != 0 && temp % 3 == 0)
            {
                //5-2. 전달한 숫자를 3 : 짝 형태로 만들고 (message)
                SomethingHappened(String.Format("{0} : 짝", number));
            }
        }
    }
 
    class MainApp
    {
        //3. EventHandler 가 참조할 MyHandler라는 구조가 동일한 메서드 구현
        static public void MyHandler(string message)
        {
            //5-3. DoSomething에서 만들어진 message를 매개변수로 받는 
            //SomethingHappened 에 연결된 이벤트 핸들러 호출
            //Console.WriteLine("MyHandler message: {0}", message);
            Console.WriteLine(message);
        }
 
        static void Main(string[] args)
        {
            //4-1. 클래스의 인스턴스 생성
            MyNotifier notifier = new MyNotifier(); 
            //4-2. MyNotifier 클래스 notifier 의 이벤트 핸들러 SomethingHappened 에 
            //작성한 이벤트 핸들러 등록(대리자 인스턴스 생성 및 연결)
            notifier.SomethingHappened += new EventHandler(MyHandler);
 
            for (int i = 1; i < 30; i++)
            {
                //5. 이벤트 발생 -> 이벤트 핸들러 호출
                //5-1.여기서 숫자를 전달하고
                notifier.DoSomething(i);
            }
        }
    }
}
 
 

 

 

 

 

참고:

1) 5/15 수업내용

2) 이것이 C#이다 https://www.youtube.com/watch?v=0RHHb4uyUVY