IT/언어

[C#] C#의 Delegate

개발자 두더지 2021. 3. 14. 01:31
728x90

1. Delegate의 개요


 delegate를 한 마디로 말하자면, "함수를 대입할 수 있는 변수"이다. 코드로 설명한다면 더 빨리 와닿을 것 같지만, 그 전에 Delegate형을 선언하는 방법부터 설명하고자 한다. 방금 Delegate는 함수를 대입하는 변수라고 설명했다. 이 말은 즉, 함수에는 "반환값"과 "인수"가 필요하다는 것이다. 아래와 같은 형식으로 선언한다.

delegate 리턴값의데이터형 델리게이트형이름(인수리스트);

 예를 들어, 표준 출력으로 Hello를 표시하는 리턴값이 없고 인수가 없는 SayHello이라는 델리게이트형의 이름으로 선언하는 경우 아래와 같이 작성할 수 있다.

delegate void SayHello();

 더욱 자세한 예를 살펴 보자. 이름을 인수를 넣으면 "Hello! xxx씨"라고 출력되는 함수를 생각해본다면 다음과 같이 수정할 수 있을 것이다.

delegate string SayHello(string name);

 Delegate에 대한 설명은 여기까지 하도록 하고, 실제 코드를 중심으로 자세히 알아보고자 한다. 여기서 작성할 코드는 hoge라는 문자열을 표준 출력으로 표시하는 함수를 Hoge라는 변수에 대입하여 실행하는 코드이다.

class Program
{
    // 먼저, delegate형의 변수를 선언한다. 
    // hoge라는 문자열을 표준출력으로 표시할 뿐이므로, 리턴값이 없는 void형 함수로 정의한다.
    // 마찬가지로 Delegate의 인수도 필요하지 않다.
    public delegate void Delegate();  // (1)
 
    static void Main(string[] args)
    {   
        // hoge라는 변수를 (1)에서 정의한 Delegate형으로 정의한다.
        // 그리고, 그 hoge에 (2)에서 정의한 Hoge라는 함수에 대입한다.
        // 이러한 것이 Delegate이다.
        Delegate hoge = Hoge;
 
        // 아까의 변수를 아래와 같이 실행한다.
        // 변수를 실행한다는 표현이 이상하지만, 이 변수에는 Hoge라는
        // 함수가 대입되어 있으므로, Hoge라는 함수가 실행된다.
        hoge();
 
        Console.ReadLine();
    }   
 
    static void Hoge() { // (2) 
        Console.WriteLine("hoge");
    }   
}

 여기까지가 Delegate에 대한 간단한 개요이다.

 

2. 인수와 반환값이 있는 Delegate


  인수와 반환값이 있는 Delegate에 대해서도 생각해보고자 한다. 아래의 요건을 실현하는 코드를 작성해봤다.

"함수의 인수로 지정한 문자열의 길이를 반환값으로 돌려주는 Deleagate"

 작성해 본 코드는 아래와 같다.

class Program
{
    // 아까와 동일하게 Delegate형의 변수를 선언한다. 
    // 함수의 인수로는 길이를 세고 싶은 대상 문자열을, 반환값으로는 문자열의 길이를 지정할 것이므로
    // 인수는 string, 리턴값은 int형으로 한다. 정의는 아래와 같이 한다.
    public delegate int Delegate(string s); // (1)
 
    static void Main(string[] args)
    {   
        // (1)에서 정의한 Delegate형의 변수countString를 정의한다.
        // 그리고 변수countStirng에, (2)에서 정의한 변수countString를 대입한다.
        Delegate countString = CountString;
 
        // 변수countString를 실행하면, 함수CountString가 실행되어
        // hoge의 문자열의 길이가 표시된다.
        Console.WriteLine(countString("hoge"));
 
        Console.ReadLine();
    }   
 
    static int CountString(string s) // (2)
    {   
        return s.Length;
    } 
}

 인수와 반환값이 없는 Delegate와는 그렇게 차이가 나지 않는다.

 

3. Delegate와 콜백 함수


 그렇다면 언제 Delegate를 사용하는 것일까? 주로 이용되는 예의 하나로는 콜백 함수가 있다. 콜백함수란, 무언가의 처리가 끝난 후에, 호출하고 싶은 처리를 의미하는 것으로, 비동기 처리의 종료 통지등에 사용되는 경우가 많다.

 여기에서는 "Hello!'라는 문자열을 표준 출력으로 표시한 후에 어떠한 처리를 실행하는 콜백함수를 Delegate로써 정의해보도록 하자. 아래의 예는 "Hello!"라는 표준 출력하는 함수의 콜백 함수로 "How are you?"를 표시하는 함수를 정의하고자 한다. 

class Program
{
    // 콜백함수의 Delegate형을 정의한다.
    // 여기에서는 인수와 반환값이 없는 Delegate형을 정의하고 있다.
    public delegate void Callback(); // (1)
 
    static void Main(string[] args)
    {
        // (1)에서 정의한 Delegate형의 변수 sayHowAreYou를 정의한다.
        // 여기서 (2)에서 정의한SayHowAreYou함수를 대입한다.
        Callback sayHowAreYou = SayHowAreYou;
 
        // 콜백함수를 대입한 Delegate형의 변수를
        // SaySomethingAfterHello함수((3)에서 정의)의 인수로 하여 실행한다.
        SaySomethingAfterHello(sayHowAreYou);
 
        Console.ReadLine();
    }
 
    // 콜백함수다. (3)에서 정의한SaySomethingAfterHello함수의
    // 마지막에 실행한다.
    static void SayHowAreYou() // (2)
    {
        Console.WriteLine("How are you?");
    }
 
    // 콜백 함수를 실시하는 함수이다. Hello!!라고 표준 출력된 뒤에
    // 이 함수의 변수로 지정된 콜백함수가 실행된다.
    static void SaySomethingAfterHello(Callback callback) // (3)
    {
        Console.WriteLine("Hello!!");
        callback();
    }
}

 "Hello!"의 표준 출력 후를, Delegate형의 변수를 사용하여 외부에서 부터 주입하는 것이 가능하다. 다음으로는 실제의 프로그램과 가까운 예시를 통해 움직임을 확인해보자.

 아래의 예시는 어떤 URL에 HTTP리퀘스트를 한 후에 돌아오는 HTTP 리스펀스에 대해 임의의 처리를 실시하는 코드이다. 이 "임의의 처리"를 Delegate를 이용해 외부에서부터 주입하고 있다. 여기서는 HTTP의 상태 코드를 반환하고 있다.

class Program
{
    // HTTP리스펀스를 처리하는 함수를 정의하는 Degate형을 만든다.
    // 물론 인수로는 HTTP리스펀스를 나타내는 HttpResponseMessage를 정의한다.
    public delegate void Callback(HttpResponseMessage res); // (1)
 
    static void Main(string[] args)
    {   
        // (1)에서 정의한 Callback형의 Delegate인 변수callback를 정의한다.
        // 변수callback에는 (2)에서 정의한 함수 GetStatusCode를 대입한다.
        Callback callback = GetStatusCode;
 
        // (3)에서 정의한 함수를 호출하고 있다. 제1인수에 지정한 URL액세스에
        // 대응하는 HTTP리스펀스를 제 2인수로 지정한 콜백함수에서 처리하는 것이다.
        HttpRequest("http://www.yahoo.co.jp/",callback);
 
        Console.ReadLine();
    }   
 
    // HTTP리스펀스를 처리하기 위한 콜백함수이다.
    // 여기에서는 HTTP리스펀스의 상태코드를 표준으로 출력하고 있다. 
    static void GetStatusCode(HttpResponseMessage res) // (2)
    {   
        Console.WriteLine(res.StatusCode.ToString());
    }   
 
    // 이 함수는 제 1인수로 지정한 URL에 액세스 할 때의 HTTP리스펀스에 대해,
    // 제2인수에서 지정한 Callback형의 Delegate에서 처리하는 것이다.
    async static void HttpRequest(String url,Callback callback) // (3)
    {   
        using (HttpClient httpClient = new HttpClient())
        {   
            // HttpClient클래스를 사용해 제1인수에 지정한URL에 액세스하여,
            // 그 HTTP리스펀스를 취득한다.
            HttpResponseMessage res = await httpClient.GetAsync(url);
 
            // 두 번째 파라미터로 지정한 Callback형의 Delegate에
            // 아까 취득해낸 HTTP리스펀스를 처리한다.
            callback(res);
        }   
    }   
}

 물론 이 외에도 delegate를 사용해서 여러가지 처리가 가능하다.

 

4. Action형 변수의 이용


Delegate를 사용하는 것이 번거롭게 느껴지는 경우, Action형의 변수를 이용하는 방법이 있다.

class Program
{
    static void Main(string[] args)
    {   
        HttpRequest("http://www.yahoo.co.jp/", GetStatusCode);
 
        Console.ReadLine();
    }   
 
    static void GetStatusCode(HttpResponseMessage res)
    {   
        Console.WriteLine(res.StatusCode.ToString());
    }   
 
    async static void HttpRequest(String url, Action<HttpResponseMessage> callback) // (1)
    {   
        using (HttpClient httpClient = new HttpClient())
        {   
            HttpResponseMessage res = await httpClient.GetAsync(url);
            callback(res);
        }   
    }   
}

 이 소스 코드가 방금 Delegate를 사용한 것과 다른 점은 크게 두 가지이다.

① Delegate형의 변수 정의가 없다는 점

② 그대신 Action형의 변수를 정의하고 있다는 점

 특히 두 번째 특징에 대해 더 자세히 살펴보고자 한다. Delegate를 사용하는 경우와 Action의 사용하는 경우 아래와 같이 코드가 바뀐다.

async static void HttpRequest(String url,Callback callback)
        ↓
async static void HttpRquest(String url, Action&lt;HttpResponseMessage&gt; callback)

 Delegate를 사용하지 않아도, Action형(혹은 Func형)를 사용하여 Delegate와 같은 결과를 얻어 낼 수 있다. 인수나 반환값에 따라 각각 Action, Func를 구분하여 사용해야할 경우가 있다.

인수와 반환값이 없는 경우

Delegate에서는 아래와 같이 작성했지만,

delegate void Hoge();

Action에서는 아래와 같이 쓴다. 

Action hoge;

인수는 있지만, 반환값은 없는 경우

Delegate의 경우는

delegate void Hoge(string fuga);

Action의 경우는 다음과 같다.

Action&lt;string&gt; hoge;

인수는 없지만, 반환값은 있는 경우

Delegate의 경우

delegate int Hoge();

Func에서는 다음과 같이 쓴다.

Func&lt;int&gt; hoge;

인수, 반환값 둘 다 있는 경우

Delegate의 경우

delegate int Hoge(string fuga);

Func의 경우

Func&lt;int,string&gt; hoge;

 

5. 그래도 알아보기 힘든 경우


Delegate를 Action으로 바꾸면서 소스 코드를 읽기 조금 쉬워진감이 있지만, 그래도 아직 어려운 느낌이 있다면 다음의 순서로 코드를 파악하면 된다. 앞서 봤던 동일한 코드를 예로 사용하겠다.

class Program
{
    static void Main(string[] args)
    {   
        HttpRequest("http://www.yahoo.co.jp/", GetStatusCode); // (1)
 
        Console.ReadLine();
    }   
 
    static void GetStatusCode(HttpResponseMessage res) // (2)
    {   
        Console.WriteLine(res.StatusCode.ToString());
    }   
 
    async static void HttpRequest(String url, Action<HttpResponseMessage> callback) // (3)
    {   
        using (HttpClient httpClient = new HttpClient())
        {   
            HttpResponseMessage res = await httpClient.GetAsync(url);
            callback(res);
        }   
    } 

① Main의 함수의 (1)에 적혀있는 HttpRequest함수를 살펴본다.

② (3)에서 실행되는 HttpRequest함수의 정의를 살펴본다.

③ 다시 (1)로 돌아가, 이번에는 HttpRequest함수의 두 번째 파라미터인 GetStatusCode함수의 처리((2)의 함수)를 살펴본다.


참고자료

tech-lab.sios.jp/archives/15318

728x90