본문 바로가기

C#(ASP.NET)

delegate(대리자)


델리게이트(Delegate)는 C의 함수 포인터와 비슷한 역할을 한다. 즉, 자기 자신이 실제로 하는 일은 없고, 단지 자기가 가리키고 있는 메서드(함수)를 호출하는 역할을 하는 것이다.

결국 델리게이트(Delegate)는 함수에 대한 참조를 저장 하는 것인데 이렇게 함수에 대한 참조를 가리키고 있다는 것으로 인해 불가능한 작업등이 가능해진다. 예를 들면 Delegete를 다른 함수의 인자로 넘겨주게 되면 그 함수는 델리게이트가 보내 주는 함수의 참조를 이용하여 실행 시점에 호출될 함수를 결정 할 수 있는 것이다.

대리자의 선언은 일반 함수의 선언과 비슷하나 함수 본문이 없고 delegate 라는 키워드를 사용한다는 점이 다르다. 델리게이트는 쓰레드와 이벤트에서 주로 이용 할 수 있다는 것은 참고로 알아 두자.

아래의 코드를 보도록 하자…

int i = int.Parse(“3746”);

일반적으로 대부분의 메서드들이 인자 값을 받아 어떤 일 처리를 하게 된다. 위의 코드에서 Parse()메서드는 string형의 인자를 받아 int형으로 바꾸어 주는 역할을 하는데, 대부분의 메서드들이 인자를 받는 이유는 왜일까?

메서드들이 인자를 받는 이유는 처리해야 할 값을 runtime시에만 알 수 있기 때문이다. 예를 들어 숫자를 정렬하는 메서드를 만들었다고 가정하자  사용자가 숫자 몇에서 몇까지를 정렬하기를 원하는지 알 수가 없다. 따라서, 나중에 그 값을 입력 받도록 처리 하기 위해 인자를 쓰게 되는 것이다.
Delegate도 이와 같은 개념으로 생각하시면 되며 단지 다른 점이 있다면, value형인 매개변수 대신에 메서드를 인자로 받는다는 것이다. 그런데, 메서드를 인자로 받아야 할 상황들이 있을까요? 잠시 후, 예제를 보면 그런 상황들이 있다는 것을 알게 될 것이다.

Delegate 선언 문법은 다음과 같다.

①delegate ②void ③VoidOperation(④uint X);

①은 delegate임을 선언, ②는 리턴형 ③은 delegate의 사용자 정의 ④는 입력 받는 인자의 형 및 변수 이다. 사용형식은 일반 메서드를 선언하는 것과 아주 흡사 한데 다른 점이 있다면 delegate라는 키워드를 먼저 쓰는 것과 구현부({ … })가 없다는 것이다. 구현부가 없는 이유는 이미 설명 드린 것과 같이 delegate 자신은 아무것도 하지 않고, 자신이 참조하고 있는 메서드(함수)만 호출해 주기 때문 이다.
Delegate를 선언하는 것은 새로운 클래스를 생성하는 것과 같다. 형식은 데이터 타입처럼 쓰이지만, 실제로 System.Delegate 클래스에서 파생되는 새로운 객체를 생성한다.


아래의 예제를 보자.[예제1]
using System;
public class Delegate1
{
        private delegate string GetString();

        public static void Main(string[] args)
        {
                int x = 30;
                GetString myMethod = new GetString(x.ToString);
                Console.WriteLine("문자열 : {0}", myMethod());
        }
}

Delegate를 사용할 때 주의할 점은 delegate가 참조하는 메서드(여기서는 ToString) 의 인자 및 리턴형이 선언된 delegate와 같아야 한다는 것이다. 예제 1에서도 볼 수 있듯이, ToString() 메서드는 인자가 없고, string형을 리턴 한다. 따라서 선언된 delegate도 인자가 없이 string형을 리턴 하도록 선언되어 있지만 delegate는 참조하는 메서드가 어떤 타입이든상관 하지 않는다. static이든 메서드(함수) 객체이든 간에 관계없다. 즉, 참조하는 메서드의 인자형, 개수, 리턴 타입만 같으면 문제가 없다는 것 이다.

[예제2]
using System;

class MathOperation
{
        public static double MultipleByTwo(double value)
        {
                return value * 2;
        }

        public static double Square(double value)
        {
                return value * value;
        }
}

delegate double DoubleOp(double x);

class DelegateTest2
{
        static void Main(string[] args)
        {
                //Delegate를 배열로 지정
                DoubleOp[] operation =
                        {
                                new DoubleOp(MathOperation.MultipleByTwo),
                                new DoubleOp(MathOperation.Square)
                        };
                
                for(int i=0; i                 {
                        Console.WriteLine("operation[{0}] 호출:", i);
                        CallDelegate(operation[i], 5.0);  //Delegate를 다른 메소드의 인자로 넘긴다.
                        Console.WriteLine();
                }
        }

        static void CallDelegate(DoubleOp func, double value)
        {
                //넘겨받은 Delegate를 실행 한다.
                double ret = func(value);
                Console.WriteLine("입력된 값은 {0}이고 결과는 {1} 이다", value, ret);
        }
}

위의 예제에서 CallDelegate라는 메서드를 호출할 필요 없이 곧바로 delegate가 참조하는 메서드를 실행 하려면 다음과 같이하면 된다.

operations[i](2.0);

Delegate가 꼭 필요 한 경우

아래와 같은 버블정렬이 있다고 하자.

[예제3]
for (int i=0; i {
     for (int j=0; j      {
          ①if (j > i) // 문제의 소지가 있는 부분
          {
               int temp = sortArray[i]; // i 와 j 바꾸기
               sortArray[i] = sortArray[j];
               sortArray[j] = temp;
          }
     }
}

만약 이 버블정렬의 데이터가 위와 같은 숫자가 아니라 객체라면 어떻게 될까? 현재와 같은 구조에선 곤란하다. 이는 사용자가 어떤 식으로 클래스나 구조체 등을 정의해 놓았는지 알 수가 없기 때문이다. 사용자에게 여러분이 만든 버블정렬 컴포넌트를 판매한다고 가정한다면, 각 사용자를 위해 다른 프로그램을 만든다는 건 말이 안 되는 이야기 이다.이럴 때 필요한 것이 바로 delegate다!!

[예제4]
using System;
//우리가 배포하는 버블정렬 컴포넌트
class BubbleSorter
{
        //RunTime에 수행할 함수를 isConvert 라는 Delegate를 통해서 받는다.
        static public void Sort(object [] sortArray, CompareOp isConvert) //①
         {
                 for (int i=0; i                  {
                         for (int j=0; j                          {
                                 if (isConvert(sortArray[i], sortArray[j])) //②
                                  {
                                          object temp = sortArray[i];
                                          sortArray[i] = sortArray[j];
                                          sortArray[j] = temp;
                                  }
                         }
                 }
         }
}

//사용자 정의 클래스
class Employee
{
        private string name;
        private decimal salary;

        public Employee(string name, decimal salary)
        {
                this.name = name;
                this.salary = salary;
        }

        public override string ToString()
        {
                return string.Format(name + ", {0:C}", salary);
        }

        //사용자 정의 클래스 비교 메서드
        public static bool isConvert(object obj1, object obj2) //③
         {
                 Employee e1 = (Employee) obj1;
                 Employee e2 = (Employee) obj2;
                 return (e1.salary > e2.salary) ? true : false;
         }
}

delegate bool CompareOp (object lhs, object rhs);

class DelegateClass
{
        static void Main(string[] args)
        {
                Employee [] employees =
                                        {
                                        new Employee("순돌이", 5000),
                                        new Employee("순딩이", 1500),
                                    new Employee("차돌이", 3000),
                                        new Employee("공돌이",1000)
                                        };
                CompareOp EmployeeCompareOp = new CompareOp(Employee.isConvert);
                BubbleSorter.Sort(employees, EmployeeCompareOp);

                for (int i=0; i                         Console.WriteLine(employees[i].ToString());
        }
}



①을 보시면 우리가 배포할 버블정렬 컴포넌트를 다시 편집한 것을 보실 수 있는데 , 컴포넌트는 두 개의 인자를 받는다, 첫번째는 정렬하게 될 object(즉, 모든 데이터 타입을 받을 수 있습니다. Object는 가장 상위클래스이기 때문이다.) 타입의 데이터이고, 두번 째 인자는 CompareOp delegate 이다. 다시 정리하면, object형 데이터와 delegate를 인자로 받는 버블정렬 컴포넌트 인 것이다.

②에서는 이전 소스코드에서 문제가 되었던 부분을 수정한 것으로 단지 숫자만 비교할 수 있게 만든 것이 아니라 delegate를 통해서 사용자 정의 비교 메서드를 호출하여 값을 비교하는 것이다. 여기서는 isConvert라는 delegate를 사용하고 있는 것이다.

③은 사용자가 정의한 Employee라는 클래스를 비교하는 메서드

④에서는 CompareOp delegate가 ③에서 정의한 사용자 정의 비교 메서드를 참조하도록 하고 있다.

⑤에서는 employees라는 배열 데이터와 EmployeeCompareOp라는 delegate를 Sort() 메서드의 인자로 넘겨 정렬하고 있다.

위의 예제를 보시면, 이제 우리가 만든 버블정렬 컴포넌트는 어떤 상황에도 문제없이 사용될 수 있다는 것을 알 수 있다. 버블정렬 컴포넌트의 핵심기술(?)은 두 데이터의 값을 비교하는 것인데, 이 부분을 delegate로 바꿈으로써, 사용자가 정의할 수 있도록 만든 것이다. 그 만큼 컴포넌트 유연성 혹은 확장성이 확대된 것이다.

우리는 delegate도 배열로 선언되어 사용될 수 있음을 이전전 예제에서 보았다. 하지만, 꼭 배열로 선언하지 않아도 하나의 delegate에 여러 개의 메서드를 넣을 수가 있는데 다음과 같이 쓸 수 있다.

delegate void DoubleOp(double value);

class MainEntryPoint
{
     static void Main(string[] args)
     {
          DoubleOp operations = new DoubleOp(MathOperations.MultiplyByTwo);
          operations += new DoubleOp(MathOperations.Square);

여러분이 원하시면, 다음과 같이 하는 것도 똑 같은 효과를 낸다.

DoubleOp operation1 = new DoubleOp(MathOperations.MultiplyByTwo);
DoubleOp operation2 = new DoubleOp(MathOperations.Square);
DoubleOp operations = operation1 + operation2;


[출처] 다음카페 개발자를 위하여