본문 바로가기

Library/.NET Framework with C#

C#의 delegate

C#에서의 delegate는 C/C++의 함수 포인터와 쓰임새와 비슷하지만, 구조적인 면에서는 STL의 Functor와 닮은 꼴이며, 패턴으로 설명하면 Observer 패턴과 유사하다고 할 수 있다.

그런데, 재미있는 것은 delegate를 사용하려면, 반드시 최소한 하나 이상의 delegate로 선언된 것과 같은 메서드를 등록해주어야 한다는 것이다. 다음과 같은 코드를 보자.

class DelegateTest
{
    public delegate void ShowMessageHandler();

    public void GeneralMessage()
    {
        System.Console.WriteLine("General Message");
    }

    public void LogMessage()
    {
       System.Console.WriteLine("Log Mesage");
    }

    public static void Main()
    {
        DelegateTest dt = new DelegateTest();
        ShowMessageHandler showmsg = new ShowMessageHandler(dt.GeneralMessage);
        showmsg();
    }
}

이 코드는, 리턴값은 void이고 시그내처 역시 void 형태인 delegate를 등록한다.showmsg()를 통한 메서드 호출은, 당연히 dt.GeneralMessage()를 호출할 것이다. 여기서 이상한 것은, 최초에 showmsg에 값을 할당할 때 += 연산자를 사용할 수 없다는 점이다. += 연산자를 사용해서 값을 할당하려면, 컴파일러 에러가 발생한다. 즉, C/C++ 식으로 이해하자면 텅 비어있는 v-table에 어떠한 함수 포인터도 등록할 수 없다는 것인데.. 이것으로 본다면, 일단 delegate는 실제의 인스턴스가 할당되어야 한다는 결론이 나온다. 이것은 쓰레기 값에 접근하여 정의되지 않은 동작을 하는 것을 방지하기 위한 것으로 보인다. 일단, 인스턴스가 생성되었다면 그 다음에는 += 연산자를 사용하여 계속 메서드를 등록할 수 있다. 이런 특징들은 C#의 delegate가 단순히 함수 포인터가 아니라, 함수자와 Observer 패턴을 섞어놓은 듯한 느낌을 준다.

showmsg += new ShowMessageHandler(dt.LogMessage);

물론, GeneralMessage()나 LogMessage()가 static으로 선언되어 있다면 DelegateTest를 인스턴스화 할 필요없이 바로 메서드를 등록할 수 있을 것이다. 물론, 이런 방어전략에도 불구하고 showmsg에서 -= 연산자를 사용하여 showmsg를 아무런 의미없는 값으로 만들어 버릴 수 있다.. 즉,  Main()에서

showmsg -= new ShowMessageHandler(dt.GeneralMessage);
showmsg -= new ShowMessageHandler(dt.LogMessage);
showmsg();

위와 같은 코드는 정상적으로 컴파일되지만, 런타임 에러를 발생시킨다. 프로그래밍의 오랜 격언대로, 사용자가 정말 멍청하게 사용하는 것은 막을 수 없다.