Home Full Site
C# delegate 쉽게 이해하기

C# delegate는 약간 낯선 개념이고 경우에 따라 어렵게 느껴질 수 있다. 여기서 그 기초 개념이 무엇인지 자세히 집어보도록 하자.

다음과 같은 하나의 함수가 있다고 가정하자.
(주: 여기서의 함수는 개념적으로 메서드와 동일한 의미로 가정)

void RunA(int i) { ... }

이 함수는 정수 하나를 파라미터로 받아들인다. 이 함수를 호출하기 위해서는 아래와 같이 정수를 메서드 파라미터로 넘기면 된다.

int j = 123;
RunA(j);

좀 더 복잡한 경우로서 다음과 같이 클래스 객체를 넘기는 경우를 생각해 볼 수 있다.

void RunB(MyClass c) { ... }

이 경우 MyClass는 위의 int와 같은 Built-in 타입이 아니므로 개발자가 그 타입을 어딘가에서 정의해 주어여야 한다 (예를 들어 아래와 같이).

class MyClass{ int id; //필드 string name; public void Display() { } //메서드}

이 함수(RunB)를 호출하기 위해서는 아래와 같이 클래스 인스턴스를 만들어 이를 메서드 파라미터로 넘기면 된다.

MyClass c = new MyClass();
RunB(c);

그런데 위의 2가지 케이스를 자세히 보면, 파라미터로서 정수 혹은 객체 즉 어떤 "데이타"를 메서드에 보내고 있는 것을 알 수 있다.

그러면, 이러한 통상적인 개념의 물리적 "데이타" 말고, 어떤 "메서드" 자체를 함수(메서드)의 파라미터로 전달할 수 있을까?

(주: 사실 "추상적 개념으로" 클래스 객체는 데이타(필드)와 행위(메서드)를 함께 포함하고 있는 것이고, 이를 메서드 파라미터로 보낼 수 있다는 것은, 클래스의 일부인 메서드만을 보낼 수 있음을 (보낼 수 있게 만들 수 있음을) 의미하기도 한다.)

Delegate는 이렇게 메서드를 다른 메서드로 전달할 수 있도록 하기 위해 만들어 졌다. 아래 그림에서 보이듯이, 숫자 혹은 객체를 메서드의 파라미터로써 전달할 수 있듯이, 메서드 또한 파라미터로서 전달할 수 있다. (주: 복수 개의 메서드들도 전달 가능) Delegate는 메서드의 입력 파라미터로 피호출자에게 전달될 수 있을 뿐만 아니라, 또한 메서드의 리턴값으로 호출자에게 전달 수도 있다.

Delegate 개념 이해하기

예를 들어, 다음과 같은 함수를 가정해 보자. 여기서 MyDelegate가 델리게이트 타입이라고 가정하면, 이 함수는 다른 어떤 메서드를 Run() 메서드에 전달하고 있는 것이다.

void Run(MyDelegate method) { ... }

그러면 델리게이트 타입 MyDelegate은 어떻게 정의하는가? 위의 클래스 예제(MyClass)의 경우 C# 키워드 class 를 사용하여 해당 클래스를 정의하였는데, 델리게이트 타입을 정의하기 위해선 특별한 C# 키워드 delegate 를 사용한다.

delegate int MyDelegate(string s);

델리게이트 정의에서 중요한 것은 입력 파리미터들과 리턴 타입이다. 만약 어떤 메서드가 이러한 델리게이트 메서드 원형(Prototype)과 일치한다면, 즉 입력 파리미터 타입 및 갯수, 리턴 타입이 동일하다면 그 메서드는 해당 델리게이트에서 사용될 수 있다.

델리게이트 정의는 마치 하나의 함수(메서드)를 정의하는 Prototype 선언식처럼 보이는데, 사실 내부적으로 이 선언식은 컴파일러에 의해 특별한 클래스로 변환된다.
(주: C# 컴파일러는 위의 delegate 정의를 읽어, System.MulticastDelegate 클래스로부터 파생된 MyDelegate 클래스를 생성하게 된다. 따라서 delegate는 메서드를 전달하기 위해 메서드 메타정보를 내부에 갖고 있는 특별한 종류의 Wrapper 클래스라 볼 수 있다. 그러면, 델리게이트 생성시 C# delegate 키워드 말고 직접 System.MulticastDelegate 클래스로부터 파생된 클래스를 만들 수 있을까? 이는 불가능하다. System.MulticastDelegate 클래스는 특별한 .NET 클래스로 Base클래스로 사용될 수 없다.)
이렇게 C# delegate 식을 클래스가 아닌 함수 선언식처럼 정의하게 한 것은 내부의 복잡한 설계를 숨기고 '메서드를 전달하는 본연의 의도'를 더 직관적으로 표현하기 위한 것으로 볼 수 있다.

델리게이트가 이렇게 정의된 후에는 클래스 객체를 생성한 것과 비슷한 방식으로 new를 써서 델리게이트 객체를 생성한다.
(주: delegate는 결국 클래스이므로 클래스 객체 생성과 같은 방식을 사용한다)

델리게이트를 다른 메서드에 전달하는 방식은 델리게이트 객체를 메서드 호출 파라미터에 넣으면 된다. 이는 메서드를, 좀 더 정확히는 그 메서드 정보를 갖는 Wrapper 클래스의 객체를, 다른 메서드의 입력 파라미터로 전달하는 것이 된다.

// int StringToInt(string s) { ... }MyDelegate m = new MyDelegate(StringToInt);Run(m);

전달된 델리게이트로부터 실제 메서드를 호출하는 것은 어떻게 하는가? 이는 델리게이트 객체의 .Invoke() 메서드나 .BeginInvoke() 메서드를 써서 호출한다. 예를 들어, m 이라는 델리게이트 객체를 전달 받았을 경우, 아래와 같이 Invoke() 메서드를 호출한다. 만약 메서드에 입력파라미터가 있을 경우, 이를 Invoke() 안에 추가한다.

i = m.Invoke("123");

또 다른 메서드 호출방법으로 C# 프로그래머들이 더 애용하는 방식은, .Invoke 메서드명을 생략하고 다음과 같이 직접 함수처럼 사용하는 방법이다. 이 방식은 마치 메서드를 직접 호출하는 느낌을 주므로 더 직관적이다.

i = m("123");

참고로, 아래 예제는 위의 설명을 종합한 간단한 delegate 샘플이다.

예제

class Program
{
    static void Main(string[] args)
    {
        new Program().Test();
    }

    // 델리게이트 정의
    delegate int MyDelegate(string s);

    void Test()
    {
        //델리게이트 객체 생성
        MyDelegate m = new MyDelegate(StringToInt);

        //델리게이트 객체를 메서드로 전달
        Run(m);
    }

    // 델리게이트 대상이 되는 어떤 메서드
    int StringToInt(string s)
    {
        return int.Parse(s);
    }

    // 델리게이트를 전달 받는 메서드
    void Run(MyDelegate m)
    {
        // 델리게이트로부터 메서드 실행
        int i = m("123");

        Console.WriteLine(i);
    }
}


© csharpstudy.com