C# Delegate (Action, Func, Event)
Q
- 대리자
- 대리자를 선언하고 사용하는 방법
- 대리자가 사용되는 이유와 상황
- 일반화 대리자를 작성하고 사용하는 방법
- 익명 메소드를 선언하고 사용하는 방법
- 이벤트를 선언하고 사용하는 방법
Delegate | 대리자
메서드의 참조.
Like 함수 포인터 in C/C++.
메서드를 변수처럼 쓸 수 있게 해준다 !
메서드를 변수에 할당하고, 다른 메서드의 인자로 전달하거나 반환 값으로 사용할 수 있다.
주로 이벤트 처리, 비동기 처리, 콜백 함수를 구현할 때 사용된다.
Delegate 구현
delegate 반환타입 MyDelegate(매개변수);
같은 모양으로 선언한다.
매개변수는 필요한 만큼 선언할 수 있다. (0 ~ 16개)
주의 !!
delegate
는 int
, string
같은 형식(Type)이므로,
“메소드를 참조하는 그 무엇”을 만들려면 인스턴스를 따로 만들어야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 선언
delegate void MyDelegate(string msg);
// 정의
void Method1(string msg) => Console.WriteLine(msg);
// 인스턴스 생성
MyDelegate myDelegate = new MyDelegate(Method1);
// 호출
myDelegate("Hello, World!");
// Output :
// Hello, World!
Delegate Why?
“값”이 아닌 “행동”, “코드” 자체를 전달하고 싶을 때가 있다.
예를 들어 배열을 정렬하는 메서드를 만들 때,
오름차순으로 정렬할지, 내림차순으로 정렬할지, 특별한 계산식을 쓸지..
정렬을 수행할 때 사용하는 비교 루틴을 매개 변수로 넣을 수 있다면?
이런 고민은 메소드를 사용하는 프로그래머가 알아서 결정할 수 있다.
Delegate 일반화
보통 메소드 뿐만 아니라, 일반화 메서드도 참조할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 선언
// IComparable: 비교 가능한 타입 (수치형, 문자열)
// IComparable.CompareTo(): 비교 메서드 (1, 0, -1)
delegate T MyDelegate<T>(T a, T b) where T: IComparable;
// 정의
int Add(int a, int b) => a + b;
float Add(float a, float b) => a + b;
// 인스턴스 생성
MyDelegate<int> myDelegate1 = Add;
MyDelegate<float> myDelegate2 = Add;
// 호출
int result1 = myDelegate(1, 2);
float result2 = myDelegate(1.0f, 2.0f);
// Output :
// 3
// 3.0
Delegate 체인
하나의 delegate
에 여러 메서드를 할당할 수 있다.
그렇게 할당한 여러 메서드를 한번에 호출할 수 있다. (멀티캐스트)
호출 순서는 할당된 순서대로 호출된다.
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
// 선언
delegate void MyDelegate(string msg);
// 정의
void Method1(string msg) => Console.WriteLine(msg);
void Method2(string msg) => Console.WriteLine(msg);
// 인스턴스 생성
MyDelegate myDelegate;
// 1. 할당 (+=) / 해제 (-=)
myDelegate += Method1;
myDelegate += Method2;
// 2. 할당 (+) / 해제 (-)
myDelegate = Method1 + Method2;
// 3. 할당 (Delegate.Combine) / 해제 (Delegate.Remove)
myDelegate = (MyDelegate) Delegate.Combine(
new MyDelegate(Method1),
new MyDelegate(Method2));
// 호출
myDelegate("Hello, World!");
// Output :
// Hello, World!
// Hello, World!
Delegate 익명 메소드 / 무명 함수 활용
익명 메소드와 무명 함수를 사용할 수 있다.
참고로 익명 메소드는 C# 2.0에서, 무명 함수는 C# 3.0에서 도입되었다.
무명 함수가 편리하지만, 하위 호환성을 유지하기 위해 두 가지 방법을 모두 지원하고 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 선언
delegate void MyDelegate(string msg);
// 인스턴스 생성
MyDelegate myDelegate;
// 익명 메소드 할당
myDelegate = delegate (string msg) { Console.WriteLine(msg); };
// 무명 함수 할당
myDelegate += (string msg) => { Console.WriteLine(msg); };
// 호출
myDelegate("Hello, World!");
// Output :
// Hello, World!
// Hello, World!
Delegate 비동기 처리 (Invoke/BeginInvoke)
Invoke
나 BeginInvoke
, EndInvoke
를 이용해 비동기 처리를 할 수 있다.
쓸 수는 있지만 오래된 방식이므로, 비동기 처리는 Task
를 쓰는 것이 더 바람직하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// https://www.csharpstudy.com/Threads/async-delegate.aspx
// 선언 및 인스턴스 생성
Func<int, int, int> work = GetArea;
// 정의
int GetArea(int height, int width)
{
int area = height * width;
return area;
}
// 비동기 처리
IAsyncResult asyncRes = work.BeginInvoke(10, 20, null, null);
int result = work.EndInvoke(asyncRes);
일반화된 Delegate
일일이 delegate
를 정의하는 것이 번거롭다면,
Action
, Func
, Predicate
등을 사용할 수 있다.
Action
delegate
의 일반화로, 반환 값이 없는(void
) 메서드를 참조할 수 있는 delegate
.
매개변수는 0 ~ 16개까지 선언할 수 있다.
그 이상의 매개변수를 사용하거나, ref
나 out
한정자로 수식된 매개 변수를 사용해야 하는 경우라면 직접 별도의 대리자를 만들어 써야한다.
1
2
3
4
5
Action<string> myAction = (msg) => Console.WriteLine(msg);
myAction("Hello, World!"); // Hello, World!
Action<int, int> myAction = (a, b) => a + b;
myAction(1, 2); // 3
Func
delegate
의 일반화로, 반환 값이 있는(void
제외) 메서드를 참조할 수 있는 delegate
.
매개변수는 0 ~ 16개까지 선언할 수 있고, 마지막 매개변수가 반환 값이 된다.
그 이상의 매개변수를 사용하거나, ref
나 out
한정자로 수식된 매개 변수를 사용해야 하는 경우라면 직접 별도의 대리자를 만들어 써야한다.
1
2
Func<int, int, int> myFunc = (a, b) => a + b;
myFunc(1, 2); // 3
Predicate
Func
의 특수한 형태로, 반환 값이 bool
인 delegate
.
하나의 매개변수를 받아 bool
값을 반환한다.
1
2
Predicate<int> myPredicate = (num) => num > 0;
myPredicate(1); // True
Event | 이벤트
이벤트(사건)
를 구현하기 위해 몇 가지 제약을 가진, 래핑된 delegate
.
이벤트 | 객체에 일어난 사건 알리기
객체의 상태 변화, 사건의 발생을 다른 객체나 메서드에 알리기 위해 사용된다.
다른 객체나 메서드에서 이벤트를 구독하고, 이벤트가 발생하면 알림을 받아 처리할 수 있다.
Event가 Delegate와 다른 점
event
는 외부에서 직접 호출할 수 없다.delegate
는public
이나internal
로 수식되어 있으면 가능하다.
delegate
는interface
내부에서 사용할 수 있지만,event
는interface
내부에서 사용할 수 없다.event
는delegate
의 인스턴스이기 때문이다.
Event 구현
delegate
를 event
키워드로 수식해서 선언한다.
이때 수식되는 delegate
는 클래스 밖에 선언해도 되고 안에 선언해도 된다.
event
는 선언한 delegate
의 인스턴스이다.
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
// delegate 선언 (클래스 밖에 선언해도 된다.)
delegate void MyDelegate(string msg);
// Publisher
class MyClass
{
// 이벤트 선언 (MyDelegate의 인스턴스)
public event MyDelegate MyEvent;
// 구독 & 구독 해제
public void Subscribe(MyDelegate myDelegate) => MyEvent += myDelegate;
public void Unsubscribe(MyDelegate myDelegate) => MyEvent -= myDelegate;
// 이벤트 발생 (이벤트는 클래스 내부에서만 호출 가능)
public void OnEvent(string msg) => MyEvent?.Invoke(msg);
}
// Subscriber
class Program
{
static void Main()
{
MyClass myClass = new MyClass();
myClass.Subscribe(MyMethod);
myClass.OnEvent("Hello, World!");
// 아래처럼 이벤트를 외부에서 직접 호출 할 수 없다.
// myClass.MyEvent?.Invoke("Hello, World!");
}
static void MyMethod(string msg) => Console.WriteLine(msg);
}
1
2
// Static Event을 써보는 것도 고려. (특히 Singleton Pattern)
public static event EventHandler MyEvent;
1
2
3
4
5
6
7
8
// 구독하는 Delegate의 이름은 On + 이벤트 이름으로 짓는 것이 관례
public void OnMyEvent(string msg) => MyEvent?.Invoke(this, new MyEventArgs(msg));
// 이벤트를 발생시키는 메서드는 Raise + 이벤트 이름으로 짓는 것이 관례
public void RaiseMyEvent(string msg) => OnMyEvent(msg);
// 이벤트 핸들러의 이름은 이벤트 이름 + EventHandler로 짓는 것이 관례
public void MyEventHandler(object sender, MyEventArgs e) => Console.WriteLine(e.Message);
메모
- callback을 구현하는 방법.
- delegate는 영어로 “대리인”, “사절” 이라는 뜻이 있다.
delegate
를 이용해 함수형 프로그래밍의 일급 객체로서의 함수 개념을 구현할 수 있다.delegate
(event
)를 이용해 이벤트, 옵저버 패턴을 구현할 수 있다.- ‘이것이 C#이다.’, ‘MSDN’
- EventEvent-Driven-Programming
- Lambda-Expression