[C#] 9.1 대리자

대리자는 알고리즘을 개체화하여 인자로 전달할 때 사용되는 형식입니다. 이에 대리자
를 정의할 때는 알고리즘에 필요한 인자와 리턴 형식을 명시하여 정의합니다.

delegate [리턴 형식] [대리자 형식 이름] ( [입력 인자 리스트] );


개발자가 대리자 형식을 정의하면 컴파일러는 MulticastDelegate를 파생한 클래스를 만
들어줍니다

결국 개발자가 대리자를 정의하면 .NET 어셈블리에는 MulticastDelegate를 기반으로 파
생한 클래스가 만들어지는 것입니다.

//delegate int DemoDele(int a, int b);를 컴파일러가 전개한 클래스
class DemoDele : MulticastDelegate //MulticastDelegate에서 파생
{
    public DemoDele(object obj, IntPtr method); //obj:개체, method:멤버 메서드
    IAsyncResult BeginInvoke(int a,int b,AsyncCallback async_callback,object obj);
    int EndInvoke(IAsyncResult iar);
    int Invoke(int a, int b); //동기식 호출
};

대리자 개체를 생성할 때는 시그니쳐가 같은 메서드를 입력 인자로 생성합니다. 그리고
대리자 선언문에서 시그니쳐가 같은 메서드로 초기화해도 대리자 개체를 생성합니다

//대리자 개체 생성
void Example()
{
    DemoDele dele1 = new DemoDele(SAdd);
    DemoDele dele2 = new DemoDele(Add);
    DemoDele dele3 = Add;
}
static int SAdd(int a, int b)
{
    return a + b;
}
int Add(int a, int b)
{
    return a + b;
}

대리자 개체는 이름이 없는 메서드를 이용하여 생성할 수도 있습니다.

void Example()
{
    DemoDele dele = delegate(int a, int b){ return a + b; };
}

대리자 개체는 메서드처럼 사용할 수 있으며 생성할 때 입력 인자로 전달한 메서드를 수
행합니다. 대리자 개체를 메서드처럼 사용하면 내부적으로는 Invoke 메서드를 호출합니
다. 실제로 Invoke 메서드를 호출해도 되며 이 둘의 차이는 없습니다.
▶ 무명 대리자 개체 생성

class Program
{
    static void Main()
    {
        DemoDele dele = Add;
        Console.WriteLine("메서드처럼 호출:{0}", dele(2, 3));
        Console.WriteLine("Invoke 메서드 호출:{0} ", dele.Invoke(2, 3));
    }
    static int Add(int a, int b)
    {
        return a + b;
    }
}
▶ 실행 결과
메서드처럼 호출:5
Invoke 메서드 호출:5

그리고 대리자는 서로 더하거나 빼기를 통해 수행할 메서드를 추가하거나 뺄 수 있습니
다. 이는 기반 형식인 MulticastDelegate에서 제공하는 기능을 상속받기 때문이며 여러
메서드를 수행하는 대리자의 결과는 맨 마지막에 수행한 메서드의 결과입니다.
▶ 대리자의 더하기와 빼기

static void Main()
{
    DemoDele dele1 = Add;
    DemoDele dele2 = Sub;
    DemoDele dele3 = dele1 + dele2; // 두 개의 대리자 결합
    Console.WriteLine("{0}", dele3(4, 2));
    dele3 = dele3 - dele2; //대리자 빼기
    Console.WriteLine("{0}", dele3(4, 2));
}
static int Add(int a, int b)
{
    Console.Write ("Add ");
    return a + b;
}
static int Sub(int a, int b)
{
    Console.Write("Sub ");
    return a + b;
}
▶ 실행 결과
Add Sub 6
Add 6

대리자 개체의 BeginInvoke 메서드를 이용하면 수행할 메서드를 비동기적으로 호출할 수 있습니다. 하지만 BeginInvoke 메서드로 비동기로 대리자를 호출하려면 대상 메서드가 하나여야 합니다.

BeginInvoke 메서드를 호출할 때는 대리자 선언에 명시한 입력 인자와 비동기 작업이 완료 시에 수행할 종료 콜백 메서드와 종료 콜백 메서드에 전달할 인자를 전달할 수 있습니다. 종료 콜백 메서드의 시그니쳐는 리턴 형식이 void이며 입력 인자로 IAsyncResult입니다.

▶ 종료 콜백 메서드의 예

static void EndSum(IAsyncResult iar)

그리고 종료 콜백 메서드에서는 EndInvoke 메서드를 호출하여 비동기로 대리자를 호출한 작업을 정상적으로 종료하세요. 종료 콜백 메서드의 입력 인자로 전달된 IAsyncResult 개체를 참조 연산으로 AsyncResult 개체를 참조하여 AsyncDelegate 속성으로 대리자 개체를 가져올 수 있습니다. 그리고 대리자 개체의 EndInvoke 메서드를 호출하면 작업 결과를 반환받아 사용할 수 있습니다.

▶ 종료 콜백 메서드의 예

static void EndSum(IAsyncResult iar)
{
    AsyncResult ar = iar as AsyncResult;
    DemoDele dele = ar.AsyncDelegate as DemoDele;
    Console.WriteLine("수행 결과:{0}",dele.EndInvoke(iar));
}

종료 콜백 메서드에 null을 인자로 전달할 때는 BeginInvoke에서 전달받은 IAsyncResult 개체의 IsCompleted 속성을 확인하여 요청한 비동기 작업이 완료되었는지를 확인할 수 있습니다. 그리고 EndInvoke 메서드를 호출하여 비동기 작업을 종료할 수 있으며 작업 결과를 반환받아 사용할 수 있습니다.

▶ 비동기로 대리자 호출

using System;
using System.Runtime.Remoting.Messaging;
using System.Threading;
namespace Ex_delegate
{
    delegate int DemoDele(int a,int b);
    class Program
    {
        static void Main()
        {
            DemoDele dele = Sum; //Sum을 대상으로 하는 대리자 생성
            dele.BeginInvoke(1, 5, EndSum, "TEST"); //비동기로 대리자 호출
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Main:{0}", i);
                Thread.Sleep(100); //테스트를 위해 0.1초 멈추게 하였음
            }
            Console.ReadLine();
        }
        static int Sum(int a, int b)
        {
            int sum = 0;
            for (; a <= b; a++)
            {
                sum += a;
                Console.WriteLine("Sum:{0}", a);
                Thread.Sleep(100); //테스트를 위해 0.1초 멈추게 하였음
            }
            return sum;
        }
        static void EndSum(IAsyncResult iar)
        {
            object obj = iar.AsyncState; //BeginInvoke의 마지막 전달 인자를 얻어옴
            
            AsyncResult ar = iar as AsyncResult;
            DemoDele dele = ar.AsyncDelegate as DemoDele;//대리자 개체 참조
            Console.WriteLine("전달받은 인자:{0} 수행 결과:{1}",obj,dele.EndInvoke(iar));
        }
    }
}

▶ 실행 결과

Main:0
Sum:1
Sum:2
Main:1
Main:2
Sum:3
Main:3
Sum:4
Sum:5
Main:4
전달받은 인자:TEST 수행 결과:15

예제 코드는 비동기 작업이 있어서 실제 수행 결과는 다양하게 나옵니다.