전체 페이지뷰

2016년 12월 28일 수요일

C# delegate

GUI(Graphic User Interface)같은 프로그래밍을 할 때, Event Driven Programming을 하게 됩니다. 예를 들어서 마우스 클릭 같은 이벤트 말이죠. 그런 이벤트가 일어나면 그 사실을 프로그램에게 알려줄 주체가 필요합니다. 그렇게 이벤트가 일어났음을 Callback 해주는 것이 바로 delegate(대리자)입니다.

Delegate는 특정메소드에 대한 참조를 가지고 있다가 이벤트 발생 시 메소드를 호출해 줍니다.





형식은 메소드와 비슷합니다.

한정자 delegate 반환형식 델리게이트명 ( 매개변수목록 );

실제 예를 보겠습니다.
delegate double MathAction(double num);
cs
사용될 때에는 이 델리게이트의 인스턴스를 생성해서 사용합니다.

이 델리게이트가 참조할 메소드도 선언합니다. 반환형식과 매개변수가 일치해야 합니다.

double Double(double input)
{
    return input * 2;
}
double Half(double input)
{
    return input / 2;
}
cs

이제 델리게이트가 이 메소드들을 참조하려면,

MathAction Callback;

Callback =  new MathAction( Double );
Console. WriteLine( Callback( 2.5 ));

Callback =  new MathAction( Half );
Console. WriteLine( Callback( 4.53 ));

이런 식으로 하면 됩니다.
결국 델리게이트를 만들고, 그 델리게이트의 인스턴스를 생성하고 거기에 메소드를 매개변수로 넘겨 참조하게 만든 후, 델리게이트를 호출하는 것입니다.

합쳐서 보겠습니다.

using System;
namespace Delegate
{
    delegate double MathAction(double num);
    class Calc
    {
        public double Double(double input)
        {
            return input * 2;
        }
        public double Half(double input)
        {
            return input / 2;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Calc c = new Calc();
            MathAction Callback;
            Callback = new MathAction(c.Double);
            Console.WriteLine(Callback(2.5));
            Callback = new MathAction(c.Half);
            Console.WriteLine(Callback(2.5));
        }
    }
}
cs

위의 예를 보시면, 델리게이트에 메소드를 넘겨준 것을 new 연산자로 마치 인스턴스 생성하듯 한 것을 알 수 있습니다. 그런 식으로 특정메소드에 대한 참조를 넘겨주고 필요할 때 그 메소드를 호출하는 것이 아니라 인스턴스를 대신 불러 사용하는 것입니다.

한 가지 예를 더 보겠습니다.

using System;
namespace Delegate
{
    delegate int Compare(int a, int b);
    class Program
    {
        static int AscendCompare(int a, int b)
        {
            if (a > b)
                return 1;
            else if (a == b)
                return 0;
            else
                return -1;
        }
        static int DescendCompare(int a, int b)
        {
            if (a < b)
                return 1;
            else if (a == b)
                return 0;
            else
                return -1;
        }
        static void BubbleSort(int[] DataSet, Compare Comparer)
        {
            int i = 0;
            int j = 0;
            int temp = 0;
            for (i = 0; i < DataSet.Length - 1; i++)
            {
                for (j = 0; j < DataSet.Length - (i + 1); j++)
                {
                    if (Comparer(DataSet[j], DataSet[j + 1]) > 0)
                    {
                        temp = DataSet[j + 1];
                        DataSet[j + 1= DataSet[j];
                        DataSet[j] = temp;
                    }
                }
            }
        }
        static void Main(string[] args)
        {
            int[] array = { 385210 };
            Console.WriteLine("오름차순 정렬");
            BubbleSort(array, new Compare(AscendCompare));
            for (int i=0;i<array.Length;i++)            
                Console.Write("{0} ", array[i]);
            Console.WriteLine("\n내림차순 정렬");
            BubbleSort(array, new Compare(DescendCompare));
            for (int i=0;i<array.Length;i++)
                Console.Write("{0} ", array[i]);
            
        }
    }
}
cs


결과)
오름차순 정렬
2 3 5 8 10
내림차순 정렬
10 8 5 3 2

정렬의 한 방법인 BubbleSort 메소드를 구현해 놓았습니다. 다만 오름차순인가 내림차순인가는 결정하지 않은채, 실제 정렬을 하려고 할때 결정하도록 비교방법을 델리게이트로 넘겨줍니다.


generic delegate


형식 매개변수 <T>를 이용하면 델리게이트도 generic으로 만들 수 있습니다.
위의 정렬 프로그램에서 몇 군데 손을 보려고 합니다.

그러려면 한 가지 더 알아두어야 할 점이 있습니다.

위 프로그램의 경우, 정렬에 쓰인 메소드를 문자에는 적용할수 없어서 IComparable<T>을 상속받아서 CompareTo() 메소드를 이용해야 제네릭을 구현할 수 있다는 점입니다.
CompareTo는 위에서 했던 방식대로 매개변수가 자신보다 크면 -1, 같으면 0, 작으면 1을 반환합니다.

static int AscendCompare<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b);
}
이렇게 말입니다.

이제 전체 코드를 바꿔 보겠습니다.

using System;
namespace Delegate
{
    delegate int Compare<T>(T a, T b);
    class Program
    {
        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 = 0;
            int j = 0;
            T temp;
            for (i = 0; i < DataSet.Length - 1; i++)
            {
                for (j = 0; j < DataSet.Length - (i + 1); j++)
                {
                    if (Comparer(DataSet[j], DataSet[j + 1]) > 0)
                    {
                        temp = DataSet[j + 1];
                        DataSet[j + 1= DataSet[j];
                        DataSet[j] = temp;
                    }
                }
            }
        }
        static void Main(string[] args)
        {
            int[] array = { 385210 };
            Console.WriteLine("오름차순 정렬");
            BubbleSort(array, new Compare<int>(AscendCompare));
            for (int i=0;i<array.Length;i++)            
                Console.Write("{0} ", array[i]);
            Console.WriteLine("\n내림차순 정렬");
            BubbleSort(array, new Compare<int>(DescendCompare));
            for (int i=0;i<array.Length;i++)
                Console.Write("{0} ", array[i]);
            double[] array2 = { 1.52.411.560.54.5 };
            Console.WriteLine("\n오름차순 정렬");
            BubbleSort(array2, new Compare<double>(AscendCompare));
            for (int i = 0; i < array.Length; i++)
                Console.Write("{0} ", array2[i]);
            Console.WriteLine("\n내림차순 정렬");
            BubbleSort(array2, new Compare<double>(DescendCompare));
            for (int i = 0; i < array.Length; i++)
                Console.Write("{0} ", array2[i]);
            string[] array3 = { "x","d","w","a","f" };
            Console.WriteLine("\n오름차순 정렬");
            BubbleSort(array3, new Compare<string>(AscendCompare));
            for (int i = 0; i < array.Length; i++)
                Console.Write("{0} ", array3[i]);
            Console.WriteLine("\n내림차순 정렬");
            BubbleSort(array3, new Compare<string>(DescendCompare));
            for (int i = 0; i < array.Length; i++)
                Console.Write("{0} ", array3[i]);
        }
    }
}
cs

결과)

오름차순 정렬
2 3 5 8 10
내림차순 정렬
10 8 5 3 2
오름차순 정렬
0.5 1.5 2.4 4.5 11.56
내림차순 정렬
11.56 4.5 2.4 1.5 0.5
오름차순 정렬
a d f w x
내림차순 정렬
x w f d a 


델리게이트 체인(대리자 조합)


한 개의 델리게이트가 + 연산자를 이용하여 여러 개의 메소드를 참조하게 할 수 있습니다.
물론 같은 형식의 메소드여야 하겠지요.

델리게이트를 합치고 제거하는 예를 살펴보겠습니다.

using System;
namespace DelegateChain
{
    delegate void CustomDelegate(string s);
    class Program
    {
        static void Hello(string s)
        {
            Console.WriteLine("반갑습니다, {0} 씨.", s);
        }
        static void GoodBye(string s)
        {
            Console.WriteLine("안녕히 계세요, {0} 씨.", s);
        }
        static void Main()
        {
            // 델리게이트의 인스턴스 선언
            CustomDelegate hi, bye, hiAndBye, hiOnlyAgain;
            // Hello와 Goodbye의 델리게이트 객체를 지정
            hi = Hello;
            bye = GoodBye;
            // hi와 bye를 동시에 참조하는 객체 생성
            hiAndBye = hi + bye;
            //hiAndBye로부터 bye참조 제거
            hiOnlyAgain = hiAndBye - bye;
            Console.WriteLine("델리게이트 hi를 invoking");
            hi("Adam");
            Console.WriteLine("델리게이트 bye를 invoking");
            bye("Bob");
            Console.WriteLine("hi와 bye를 합쳐서 invoking");
            hiAndBye("Charlie");
            Console.WriteLine("bye 제거");
            hiOnlyAgain("Denise");
        }
    }
}
cs

결과)
델리게이트 hi를 invoking
반갑습니다, Adam 씨.
델리게이트 bye를 invoking
안녕히 계세요, Bob 씨.
hi와 bye를 합쳐서 invoking
반갑습니다, Charlie 씨.
안녕히 계세요, Charlie 씨.
bye 제거
반갑습니다, Denise 씨.

위의 예처럼 +연산자를 이용하여 합칠수도 있고 Delegate.Combine 메소드를 이용해서 함칠 수도 있습니다.

hiAndBye = hi + bye;

hiAndBye = (CustomDelegate)Delegate.Combine(hi, bye);

두 가지는 같은 의미입니다만 +쪽이 아무래도 훨씬 간단하고 직관적으로도 알아보기 쉬은 듯 합니다.


무명(익명) 메소드


아주 잠깐만 쓰고 말 대리자를 선언하는데 정식으로 명명된 메소드를 써서 객체를 생성하는 방법이 귀찮다고 여겨지면 무명 메소드를 사용하여 대리자를 만들 수 있습니다.
C# 2.0이전에서는 대리자를 사용할 때 무조건 명명된 메소드를 사용해야만 했으나, 2.0부터는 무명 메소드가 도입되었고 3.0부터는 람다도 도입되었습니다.

델리게이트 인스턴스 = delegate(매개변수목록) { //코드; }

의 문법으로 델리게이트 선언과 동시에 { //코드; } 부위에 곧바로 메소드의 내용을 적는 방법입니다. 예를 들어 보겠습니다.

using System;
namespace AnonymousMethod
{
    //  delegate 선언.
    delegate void Printer(string s);
    class TestClass
    {
        // 비교용 명명 메소드.
        static void DoWork(string k)
        {
            System.Console.WriteLine(k);
        }
        static void Main()
        {
            // 무명메소드를 통한 델리게이트 인스턴스 생성.
            Printer p = delegate (string j)
            {
                System.Console.WriteLine(j);
            };
            // 무명델리게이트를 이용.
            p("The delegate using the anonymous method is called.");
            // 명명된 메소드 "DoWork"를 통한 인스턴스 생성.
            p = new Printer(TestClass.DoWork);
            p("The delegate using the named method is called.");
        }
    }
}
cs

Printer p = delegate ( string j)
               {
                   System.Console.WriteLine(j);
               };

와 같이 delegate 뒤의 코드 블럭에 바로 메소드 내용을 작성하면 됩니다.
어떤 경우에 이런 무명메소드를 이용한 대리자가 쓰이는가 하면, 예를 들어 간단한 멀티 스레드 프로그램을 짤 때에 새로운 스레드 내부에서 델리게이트를 사용한다면 굳이 추가적인 정식 메소드를 만들 필요없이 간단히 스레드 내의 코드블럭에서만 작동하는 무명메소드를 만들어 사용할 수 있습니다.


이벤트


특정한 사건(event)이 일어났을 때 알려주는 객체를 만드는 방법을 알아보겠습니다.

먼저 델리게이트를 선언하고, 
이벤트를 처리하는데 쓰일 클래스 내부에 델리게이트의 인스턴스를 event 한정자로 수식하여 선언합니다. 그 후 이벤트 핸들러를 작성하고, 클래스의 인스턴스를 생성한 뒤 이벤트 핸들러를 등록합니다.

말로 쓰니까 도무지 뭔 소린지 모르겠네요. 하나씩 따라가 보겠습니다.

1. 델리게이트 선언
delegate void EventHandler(string message);

2. 클래스 내부에 델리게이트 인스턴스 생성

class Notifier
{
    public event EventHandler SomethingHappened;
    public void DoSomething(int number)
    {
         int temp = number % 10;
         if ( temp != 0 && temp % 3 ==0 )
         {
              SomethingHappened(String.Format("{0} : 짝", number));
         }
    }
}

3. 이벤트 핸들러 작성
class Program
{
    static void MyHandler(string message)
    {
        Console.WriteLine(message);
    }
    //,....
}

4. 클래스 인스턴스 생성, 이벤트 핸들러 등록
static void Main()
{
    MyNotifier notifier = new MyNotifier();
    notifier.SomethingHappened += new EventHandler(MyHandler);
    //...
}

델리게이트와 달리 이벤트는 자신이 속한 클래스 내부에서만 사용 가능합니다. 따라서 외부에서 마음대로 이벤트에 접근할 수가 없습니다.

using System;
namespace EventHandlerTest
{
    delegate void EventHandler(string message);
    class MyNotifier
    {
        public event EventHandler SomethingHappened;
        public void DoSomething(int number)
        {
            int temp = number % 10;
            if (temp != 0 && temp % 3 == 0)
            {
                SomethingHappened(String.Format("{0} : 짝", number));
            }
        }
    }
    class Program
    {
        static void MyHandler(string message)
        {
            Console.WriteLine(message);
        }
        static void Main(string[] args)
        {
            MyNotifier notifier = new MyNotifier();
            notifier.SomethingHappened += new EventHandler(MyHandler);
            for (int i = 0; i < 30; i++)
            {
                notifier.DoSomething(i);
            }
        }
    } 
}
cs

결과)
3 : 짝
6 : 짝
9 : 짝
13 : 짝
16 : 짝
19 : 짝
23 : 짝
26 : 짝
29 : 짝


댓글 없음:

댓글 쓰기