- 대리자
- 일반화 대리자
- 대리자 체인
- 익명 메소드
- 대리자와 이벤트
[ 대리자란 ]
[ 콜백 ]
- 어떤 일을 수행하는 코드 .
- 콜백은 다른 코드에 맡겨져 실행된다 .
- 콜백을 맡아줄 코드는 컴파일 시점이 아닌 프로그램 실행중 결정된다 .
[ 대리자 ]
- 콜백을 맡아 실행하는 일을 대리자가 담당한다 .
- 대리자는 메소드에 대한 참조이다 .
- 대리자에 메소드의 주소를 할당 , 대리자를 호출하면 대리자가 해당 메소드를 호출한다 .
한정자 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]} ");
}
}
}
[ 이벤트 : 객체에 일어난 사건 알리기 ]
[ 이벤트 ]
- 어떤 일이 생겼을 때 이를 알려주는 객체가 필요한 경우 사용하는 것이 이벤트 .
[ 이벤트의 선언과 사용 ]
- 대리자를 선언한다 . 클래스 밖 , 안 상관없다
- 클래스 내에 선언한 대리자의 인스턴스를 event 한정자로 수식해서 선언한다 .
- 이벤트 핸들러를 작성한다 . 위에서 선언한 대리자와 일치하는 메소드면 된다 .
- 클래스의 인스턴스 생성 , 객체의 이벤트에 위에서 작성한 이벤트 핸들러를 등록한다
- 이벤트 발생시 이벤트 핸들러가 호출된다
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 수식시 클래스 외부에서 얼마든지 호출이 가능하다 .
[ 이벤트의 효과 ]
- 이벤트는 견고한 이벤트 기반 프로그래밍에 대한 기대를 가능하게 한다 .
- 예를 들어 , 네트워크 상태 변화에 대한 사건을 알리는 클래스를 작성해 동료에게 준 상황을 가정하자 .
- 이벤트를 객체 외부에서 임의로 호출할 수 있게 된다면 , 동료 프로그래머는 클래스의 객체가 감시하는 실제
네트워크 상태와 상관없이 객체 외부에서 허위로 네트워크 상태 변화 이벤트를 일으킬 수 있다 .
- 이것은 객체의 상태를 바꾸는 것보다 나쁘다 . 객체의 상태를 허위로 나타낼 수 있기 때문이다 .
- 이런 위협은 이벤트로 막을 수 있다 .
[ 결론 ]
- 대리자는 대리자대로 콜백 용도로 사용해야 한다.
- 이벤트는 이벤트대로 객체의 상태 변화나 사건의 발생을 알리는 용도로 구분해 사용해야 한다 .
'C# > 이것이 C#이다' 카테고리의 다른 글
[ 이것이 C#이다 ] Chapter 15 . LINQ (0) | 2024.03.27 |
---|---|
[ 이것이 C#이다 ] Chapter 14 . 람다식 (0) | 2024.03.25 |
[ 이것이 C#이다 ] Chapter 12 . 예외처리 (0) | 2024.03.20 |
[ 이것이 C#이다 ] Chapter 11 . 일반화 프로그래밍 (0) | 2024.03.18 |
[ 이것이 C#이다 ] Chapter 10 . 배열과 컬렉션 그리고 인덱서 (0) | 2024.03.14 |