포스트

C# Delegate (Action, Func, Event)

C# Delegate (Action, Func, Event)

Q


  • 대리자
  • 대리자를 선언하고 사용하는 방법
  • 대리자가 사용되는 이유와 상황
  • 일반화 대리자를 작성하고 사용하는 방법
  • 익명 메소드를 선언하고 사용하는 방법
  • 이벤트를 선언하고 사용하는 방법

Delegate | 대리자


메서드의 참조.
Like 함수 포인터 in C/C++.

메서드를 변수처럼 쓸 수 있게 해준다 !
메서드를 변수에 할당하고, 다른 메서드의 인자로 전달하거나 반환 값으로 사용할 수 있다.

주로 이벤트 처리, 비동기 처리, 콜백 함수를 구현할 때 사용된다.

Delegate 구현

delegate 반환타입 MyDelegate(매개변수); 같은 모양으로 선언한다.
매개변수는 필요한 만큼 선언할 수 있다. (0 ~ 16개)

주의 !!
delegateint, 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)

InvokeBeginInvoke, 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개까지 선언할 수 있다.

그 이상의 매개변수를 사용하거나, refout 한정자로 수식된 매개 변수를 사용해야 하는 경우라면 직접 별도의 대리자를 만들어 써야한다.

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개까지 선언할 수 있고, 마지막 매개변수가 반환 값이 된다.

그 이상의 매개변수를 사용하거나, refout 한정자로 수식된 매개 변수를 사용해야 하는 경우라면 직접 별도의 대리자를 만들어 써야한다.

1
2
Func<int, int, int> myFunc = (a, b) => a + b;
myFunc(1, 2); // 3

Predicate

Func의 특수한 형태로, 반환 값이 booldelegate.
하나의 매개변수를 받아 bool 값을 반환한다.

1
2
Predicate<int> myPredicate = (num) => num > 0;
myPredicate(1); // True

Event | 이벤트


이벤트(사건)를 구현하기 위해 몇 가지 제약을 가진, 래핑된 delegate.

이벤트 | 객체에 일어난 사건 알리기
객체의 상태 변화, 사건의 발생을 다른 객체나 메서드에 알리기 위해 사용된다.
다른 객체나 메서드에서 이벤트를 구독하고, 이벤트가 발생하면 알림을 받아 처리할 수 있다.

Event가 Delegate와 다른 점

  1. event는 외부에서 직접 호출할 수 없다.
    • delegatepublic이나 internal로 수식되어 있으면 가능하다.
  2. delegateinterface 내부에서 사용할 수 있지만, eventinterface 내부에서 사용할 수 없다.
    • eventdelegate의 인스턴스이기 때문이다.

Event 구현

delegateevent 키워드로 수식해서 선언한다.
이때 수식되는 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
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.