본문 바로가기

C# 공부/C# 기본 문법

대리자(Delegate)와 이벤트 - 1 (대리자, 대리자를 사용하는 이유와 상황, 일반화 대리자, 대리자 체인, 익명 메소드)

1. 대리자(Delegate)

콜백(Callback)은 프로그래밍에서 어떤일을 해줄 코드를 두고, 이 코드가 실행할 세부코드는 컴파일 시점이 아닌 실행시점에 부여하는것이다.

또한, 대리자는 메소드에 의한 참조이며, 대리자에 메소드의 주소를 할당한 후 대리자를 호출하면 이 대리자가 메소드를 호출해준다.

//대리자 선언예시
//메소드에 대한 참조이므로 자신이 참조할 메소드의 반환 형식과 매개 변수를 명시해줘야한다.
delegate int MyDelegate(int a, int b)

* 대리자는 인스턴스가 아닌 int, string과 같이 형식(Type)이며, 인스턴스를 따로 만들어야 사용가능하다.

ex)

int Plus(int a, int b){return a+b;}
int Minus(int a, int b){return a-b;}

MyDelegate Callback;

Callback = new MyDelegate(Plus);
Console.WriteLine(Callback(3,4)); //7출력

Callback = new MyDelegate(Minus);
Console.WriteLine(Callback(7,5)); //2출력

- 콜백을 구현과정 요약

1. 대리자를 선언한다.

2. 대리자의 인스턴스를 생성한다. 인스턴스를 생성할 때는 대리자가 참조할 메소드를 매개변수로 넘긴다.

3. 대리자를 호출한다.

 

B. 대리자를 사용하는 이유와 사용하는 상황

"값"이 아닌 "코드" 자체를 매개 변수로 싶은경우에 사용한다.

예를들어, 배열을 정렬하는 메소드를 만들때, 오름차순으로 정렬하도록 할것인지, 내림차순으로 정렬하도록 할것인지, 특별한 계산식을 거쳐서 나오는 결과순으로 정렬하도록 할것인지를 정해야할때, 

비교루틴을 매개변수로 넣을수있도록 하기위해 대리자가 사용된다.

 

Step 1. 대리자를 선언한다.

delegate int Compare(int a, int b);

 

Step 2. Compare 대리자가 참조할 비교 메소드를 작성한다.

static int Ascendcompare(int a, int b)
{
    if(a>b)
        return 1;
    else if(a==b)
        return 0;
    else
        return -1;
}

 

Step 3. 정렬할 배열과 비교 메소드를 참조할 대리자를 매개 변수로 받는 정렬 메소드를 작성한다.

statkc void BubbleSort(int[] DataSet, Compare Comparer)
{
    int i=0;
    int j=0;
    int temp=0;
    
    for(int i=0;i<DataSet.Length-1;i++)
    {
        for(j=0;j<DataSet.Length -(i+1); j++)
        {
            if(Comparer(DataSet[j],DataSet[j+1])>0) //Comparer가 어떻게 구현된 메소드를 참조하고있는가에 따라 
                                                    //정렬 결과가 달라진다.
            {
                temp=DataSet[j+1];
                DataSet[j+1]=DataSet[j];
                DataSet[j]=temp;
            }
        }
    }
}

 

Step 4. 정렬 메소드를 호출하면 원하던 대로 정렬 방식이 분리된 정렬 코드를 얻을 수 있다. 이 코드는 오름차순으로 정렬하도록 되어 있지만, 내림차순으로 정렬하고싶으면 Comparer가 참조할 메소드를 새로 구현해서 BubbleSort를 호출할 때 매개 변수로 넘기기만 하면된다.

int[] array = {3,7,4,2,10};
BubbleSort(array, new Compare(AscendComparer)); //오름차순

//또는 아래 DescendCompare 정의 후 Comparer에 Descend메소드 할당시 array가 내림차순으로 정렬된다.

static int DescendCompare(int a, int b)
{
    if(a<b)
        return 1;
    else if(a==b)
        return 0;
    else
        return -1;
}

BubbleSort(array, new Compare(DescendComparer)); //내림차순

 

C. 일반화 대리자

대리자는 일반화 메소드도 참조할 수 있다.

이 때, 조건으로는 3가지 정도가 있다.

1. 대리자가 형식매개변수 <T>를 가져야한다. 

delegate int Compare<T>(T a, T b);

2. 참조할 메소드도 일반화 메소드여야한다.

static int AscendCompare<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b);
}
//여기서 IComparable<T>를 상속하는이유는, System.Int32, System.Double, System.String은 모두
//IComparable을 상속해서 CompareTo() 메소드를 구현하고있으며
//CompareTo() 메소드는 매개변수가 자신보다 크면 -1, 같으면 0, 작으면 1을 반환한다.
//따라서 AscendCompare() 메소드가 a.CompareTo(b)를 호출하면 원하는대로 오름차순정렬에 필요한 비교를 얻는다.

3. 일반화 대리자를 매개변수로 사용할 메소드가 있다면 형식 매개 변수를 받아들이도록 변경해야한다.

static void BubbleSort<T>(T[] DataSet, Compare<T> Comparer)
{
    ~
}

 

ex)

delegate int Compare<T>(T a, T b);

class MainApp
{
    static int AscendCompare<T>(T a, T b) where T : IComparable<T>
    {
        return a.CompareTo(b);
    }
    
    static int DescendCompare<T>(T a, T b) where T : IComparable<T>
    {
        return a.CompareTo(b) * -1; //오름차순과 결과 반대값 출력
    }
    
    static void BubbleSort<T>(T[] DataSet, Compare<T> Comparer)
    {
        int i, j;
        T temp;

        for (i = 0; i < DataSet.Length; i++)
        {
            for (j = 0; j < DataSet.Length - (i + 1); j++)
            {
                if(Comparer(DataSet[j],DataSet[j+1])>0)
                {
                    temp = DataSet[j];
                    DataSet[j] = DataSet[j + 1];
                    DataSet[j + 1] = temp;
                }
            }
        }
    }
    
    static void Main(String[] args)
    {
        int[] array = { 3, 7, 4, 2, 10 };

        BubbleSort<int>(array, new Compare<int>(AscendCompare<int>));

        foreach (int i in array)
            Console.Write($"{i} ");

        string[] array2 = { "abc", "def", "ghi", "jkl", "mno" };

        BubbleSort<string>(array2, new Compare<string>(DescendCompare<string>));

        foreach (string s in array2)
            Console.Write($"{s} ");
    }
}

 

D. 대리자 체인

대리자 하나가 여러개의 메소드를 참조할 수 있다.

대리자의 인스턴스에 메소드 체인추가시 '+=', ('+','=') 연산자, 'delegate.Combine()' 메소드 를 이용하여 결합할 수 있다.

delegate void ThereIsAFire(string location);

ThereIsAFire Fire = new ThereIsAFire(Call119);
Fire+= new ThereIsAFire(ShotOut); //메소드 추가
Fire+= new ThereIsAFire(Escape); //메소드 추가

* 이 때, 대리자 체인은 여러 개의 콜백을 체인을 따라 차례로 호출한다. 

 

또한, 대리자 삭제시에는 '-=',('-','=') 연산자, 'Delegate.Remove()' 메소드를 사용해 대리자를 끊어낼 수 있다.

사용 예시)

delegate void Notify(string message);

class Notifier //Notify 대리자의 인스턴스 EventOccured를 가지는 클래스 Notifier를 선언
{
    public Notify EventOccured;
}

class EventListener
{
    private string name;
    public EventListener(string name)
    {
        this.name = name;
    }

    public void SomethingHappend(string message)
    {
        Console.WriteLine($"{name}.SomethingHappened : {message}");
    }
}
class MainAPP
{
    static void Main(string[] args)
    {
        Notifier notifier = new Notifier();
        EventListener listener1 = new EventListener("Listener1");
        EventListener listener2 = new EventListener("Listener2");
        EventListener listener3 = new EventListener("Listener3");

        notifier.EventOccured += listener1.SomethingHappend;
        notifier.EventOccured += listener2.SomethingHappend;
        notifier.EventOccured += listener3.SomethingHappend; //+=연산자를 사용한 체인 만들기
        notifier.EventOccured("You've got mail");

        Console.WriteLine();

        notifier.EventOccured -= listener2.SomethingHappend; //-,= 연산자를 사용한 체인 끊기
        notifier.EventOccured("Download complete.");

        Console.WriteLine();

        notifier.EventOccured = new Notify(listener2.SomethingHappend)
                              + new Notify(listener3.SomethingHappend); //+,=연산자를 사용한 체인 만들기
        notifier.EventOccured("Nuclear launch detected");

        Console.WriteLine();

        Notify notify1 = new Notify(listener1.SomethingHappend);
        Notify notify2 = new Notify(listener2.SomethingHappend);

        notifier.EventOccured = (Notify)Delegate.Combine(notify1, notify2); //Delegate.Combine메소드를 이용한 체인 만들기
        notifier.EventOccured("Fire!");

        Console.WriteLine();

        notifier.EventOccured = (Notify)Delegate.Remove(notifier.EventOccured, notify2); //Delegate.Remove메소드를 이용한 체인 끊기
        notifier.EventOccured("RPG!");
    }
}

 

E. 익명 메소드(Anonymouse Method)

대리자가 참조할 메소드를 넘겨야하는데 메소드가 두 번 다시 사용할 일이 없다고 판단되면 사용하는 이름이 없는 메소드이며, delegate 키워드를 사용하여 선언한다.

- 익명메소드의 선언 형식

대리자 인스턴스 = delegate(매개변수_목록)
		{
                //실행코드
                };

 

- 익명메소드 사용 예제

delegate int Calculate(int a, int b);

public static Main()
{
    Calculate Calc;
    
    Calc = delegate(int a, int b) //익명 메소드
    {
        return a+b;
    }
    
    Console.WriteLine($"3+4 = {Calc(3,4)}");
}