본문 바로가기

Library/.NET Framework with C#

C# delegate

C/C++은 실행 시점에서 동작을 결정하기 위해서 함수 포인터를 사용한다. C++은 실행 시점에서의 동작을 정하기 위해 가상 함수와 같은 매커니즘을 사용하지만, 근본적으로는 함수 포인터를 사용한다.

그렇다면, C#에서는 어떤가? C#은 포인터라는 개념을 직접적으로 지원하지 않기 때문에 함수에 대한 주소를 얻어낸다는 것은 불가능하다. 따라서, 언뜻 생각하기에  Win32 API에서의 콜백 함수 호출은 문제가 있어보인다. 윈도우 메세지는 LRESULT 리턴 형식과 WPARAM, LPARAM이라는 두개의 32비트 파라미터를 가지며, 이 파라미터의 내용은 부호없는 정수 형식인 윈도우 메세지 내용에 따라 달라지기 때문이다. 다형성은, 함수의 시그내처를 보고 결정되기 때문에, 윈도우 메세지를 해석해서 동작을 결정해야 하는 이런 방식은 다형성과 어울릴 수 없다.

C#에서는, 함수 포인터와 비슷한 기능을 제공하기 위해 delegate라는 기능을 지원한다. 이것은 사실 함수 포인터와 다른 점이 없지만, 훨씬 타입에 대해 안전하며, 추가 기능이 있다는 점에서 STL의 함수자(Functor)와 더 비슷하다.

이 기능을 사용하기 위해서는 일단 delegate로 개체가 선언되어야 하며, 이 delegate 개체의 인자는 처리를 위임할 메서드와 동일한 시그내처이다. 그리고 C#의 재미있는 특징으로, delegate로 선언된 개체에 +, += 연산자를 사용하여 메서드를 추가해줄 수도 있다. 이 이야기는, 결국 어떤 기본 클래스가 기본 처리 클래스로 지정되어 있다면, delegate 개체를 사용하여 이 기본 클래스에 대한 대리자로 선언하고, 여기에 자신의 임의의 처리 루틴을 추가하거나 기존 메서드를 교체할 수 있다는 말이 된다. 이것을  C++ 방식으로 설명하면, C++의  v-table을 임의로 수정할 수 있다는 말과 비슷할 것이다. (훨씬 타입 안정적으로)

이런 특징은, C#이 윈도우 프로그래밍에서 필수적인 이벤트 처리에 어떻게 대응하는가에 대한 답이 된다. 윈도우 이벤트들은 이미 기본 클래스 타입을 이용하여 기본 동작이 지정되어 있으며, 새로운 이벤트 핸들러를 작성하고자 하는 경우, 이 기본 클래스에 대한 delegate, 즉 대리자를 설정하고 이 대리자에  +, +=, -, -=과 같은 연산자를 사용하여 핸들러를 작성하게 된다. 그리고, 동일한 시그내처를 가지는 복수의 핸들러를 추가할 수도 있는데, 이것을 multicast delegate라 한다. 만약, 동일한 핸들러를 복수개로 지정했을 때, 그 메서드를 대리자를 통해 호출했다면 어떻게 될까? 결과는, 등록된 순서대로 모두 호출한다.

꼭 이벤트 핸들러가 아니더라도, delegate라는 기능은 함수를 실행 시점에서 타입 안정적으로 호출 할 수 있게 해준다.