본문 바로가기

C# 일기

18. 대리자 (Delegate)

대리자(Delegate)란, 정해진 매개변수와 반환타입이 일치하는 메서드에 대한 참조를 저장하여, 해당 메서드를 호출하는 기능을 한다.

 

예를들어, 정수 두개를 매개로 받아 서로 더한 정수값을 반환하는 int Sum(int a, int b) 와,

정수 두개를 매개로 받아 서로 곱한 정수값을 반환하는 int Mul(int a, int b)가 있다고 가정해보자면,

 

두 메서드 모두 반환값이 int로 동일하고, 매개변수로 두개의 정수를 입력받기 때문에, 해당 메서드를 하나의 대리자로 호출할 수 있다.

 

대리자의 선언 및 참조저장방식은 다음과 같다.

 

public int Sum(int a, int b) { return a + b; }
public int Mul(int a, int b) { return a * b; }

public delegate int Calculate(int a, int b);

static void Main(string[] args)
{
   Calculate calSum = new Calculate(Sum);
      
   Calculate calMul = Mul;
   
   Console.WriteLine(calSum(3, 4));
   Console.WriteLine(calMul(3, 4));
   
   // 출력값 : 7, 12
}

 

먼저, 접근제한자 뒤에 delegate라는 키워드를 통해 대리자임을 알려준다.

 

그 다음, 대리자로 받고싶은 메서드의 매개변수와 반환값을 동일하게 설정해준다.

 

여기서는, int값을 반환하고, 두개의 정수를 매개변수로 받는 함수 둘을 대리자로 받고자 하기 때문에,

 

int Calculate(int a, int b);로, 반환값인 int와 두 정수 매개변수 int a, int b를 설정해주었다.

 

 

 

 

 

 

이것으로 대리자의 선언은 끝이다. 이제 이 대리자를 통해 매개변수가 정수 두개이며 정수값을 반환하는 모든 종류의 메서드를 저장하는 것이 가능하다.

 

대리자를 통해 메서드의 참조를 저장하는 법은 다음과 같다.

 

우선, 다른 함수에서 대리자의 인스턴스를 생성한다.

 

인스턴스를 생성한 후, 인스턴스에 메서드를 대입하거나, 인스턴스와 동시에 메서드를 대입할 수 있다. 

 

위에서는 인스턴스 생성 후 메서드를 대입하는 방식으로 calSum을, 인스턴스와 동시에 메서드를 대입하는 방식으로 calMul을 인스턴스했다.

 

이렇게 메서드 참조가 완료된 대리자는 해당 메서드처럼 사용이 가능하다.

 

정확히는, 대리자 호출 시, 대리자에 참조된 메서드를 호출하는 것이다.

 

 

 

 

 

 

그렇다면 왜 대리자를 사용해야할까?

 

그냥 메서드를 포함한 객체를 인스턴스 시켜서 해당 객체의 메서드를 호출하면 안될까?

 

물론  가능하지만, 대리자는 호출하길 원하는 메서드르를 갖고있는 객체를 일일히 인스턴스 시키지 않아도, 대리자를 인스턴스시키기만 해도 부합하는 모든 종류의 메서드를 저장하는 것이 가능하기 때문에 특정 상황에서 유리하다.

 

예를들어, 위 처럼 두 정수를 매개로 받아, 정수값을 반환하는 메서드를 갖고있는 객체가 10개 이상이고, 이 메서드를 모두 호출해야한다면 10개의 객체를 인스턴스해야 할 것이다.

 

하지만 대리자로 메서드의 참조를 저장하면 그러지 않아도 된다.

 

그럼 대리자는 대리자하나의 여러개의 메서드를 저장하는 것이 가능하다는 얘기일까?

 

 

 

 

이 질문의 답은 Yes이다. 하지만 제약이 있다.

 

대리자는 여러 메서드를 참조하여 그 메서드 모두 호출시킬 수 있지만, 순서대로 호출한다. 

 

이는 콜백의 구현을 위해 만들어진 대리자의 역할에 충실한 것이다.

 

그렇다면 콜백은 무엇일까?

 

콜백은 다른 메서드의 매개변수로 전달되어지는 메서드. 혹은 메서드 호출 이후 호출되는 메서드를 말한다. 

 

대리자는 다른 함수에 인스턴스 되어, 메서드를 참조하고, 매개로 전달시, 대리자를 전달하는 것이 가능하기 때문에, 전자의 경우에 부합한다.

 

메서드를 변수처럼 매개로 전달받는 행위를 가능케한다는 것이다.

 

위에서 Console.WriteLine에 대리자를 대입하여 반환값을 출력한 것 처럼 말이다.

 

두번째로는 메서드 호출 이후 호출되는 메서드에 대해서다.

 

대리자는 여러개의 메서드를 저장하고, 호출할 때 저장된 순서대로 메서드를 호출한다.

 

따라서 대리자에 순서대로 실행하길 원하는 메서드를 순서대로 저장하고 해당 대리자를 한번만 호출하면 그 순서대로 메서드들이 호출되는 것이다.

 

따라서 후자의 경우에도 부합한다.

 

이번엔, 대리자에 여러 메서드를 어떻게 넣는지, 그렇게 저장된 대리자를 호출하면 어떤 결과가 나오는지 알아보자.

 

 

using System;

namespace Favor
{
   class Student
   {
      public void Print1(string s) { Console.Write($"{s}학생 입실 "); }
      public void Print2(string s) { Console.Write($"{s}학생 외출 "); }
      public void Print3(string s) { Console.Write($"{s}학생 퇴실 "); }
   }

   internal class Program
   {
      public delegate void delPrint(string s);
      
      static void Main(string[] args)
      {
         Student s = new Student();
         delPrint dp = s.Print1("A");
         
         s.Print2("B");
         dp += s.Print2;
         
         s.Print3("C");
         dp += s.Print3;
         
         Console.WriteLine();
         
         delPrint dp2 = s.Print1;
         
         dp2 += s.Print2;
         dp2 += s.Print3;
         
         dp2.Invoke("D");
         
         // 출력값 : A학생 입실 B학생 외출 C학생 퇴실
         //          D학생 입실 D학생 외출 D학생 퇴실 
         
      }
   }
}

 

+= 연산자를 통해 대리자에 메서드를 누적하여 저장할 수 있다.

 

이렇게 저장된 메서드들은 먼저 저장된 순서가 제일 먼저 출력된다.

 

저장될 때 마다 A, B, C 처럼 따로 저장할 수 있고,  D처럼 메서드를 모두 저장한 다음, 대리자의 메서드를 호출하는 함수인 Invoke를 호출할 때, 매개변수를 한꺼번에 전달할 수도 있다.

 

 

 

대리자에 메서드를 저장할 때, 반환값과 매개변수의 자료형을 일치시키지 않는다면. 혹은, 매개변수의 수를 맞추지 않는다면 저장되지 않으니 주의하여야한다. 

 

 

 

 

대리자를 사용한다면, 필요한 객체들의 메서드를 원하는 대로 저장하여 호출할 수 있으며, 콜백기능을 구현하여 순서대로 호출할 필요가 있는 메서드들을 호출하거나, 매개변수로 메서드를 넘겨주는 등 여러 기능들을 구현할 수있다.  

'C# 일기' 카테고리의 다른 글

19 - 1. 이벤트 VS 대리자  (0) 2024.03.11
19. 이벤트(Event)  (0) 2024.03.11
17. 예외처리(Exception Handling)  (0) 2024.03.11
16. 일반화 (Generic <T>)  (0) 2024.03.08
15. 연산자 오버로딩  (0) 2024.03.08