[C#] Delegate - 콜백, 체인


Delegate(델리게이트)

Delegate의 사전적 의미는 ‘대리인’이다. c++의 포인터와 비슷하게 생각하면 된다.

이 대리인이라는 것은 내가 할 일을 다른 사람에게 위임하는 것이다. 프로그래밍에서도 이런 대리인 역할을 해주는게 존재하는데, 그게 Delegate이다.

델리게이트는 메소드를 대신해서 호출하는 역할을 한다. 즉 메소드의 대리인 역할을 하는 것이다. 원래는 특정 메소드를 처리할 때 그 메소드를 직접 호출해서 실행시켜야 했지만, 델리게이트를 사용하면 그 메소드를 대신해서 호출할 수 있다.

Delegate 선언, 참조

Delegate로 특정 메소드를 대신 호출하려면

  1. 해당 메소드와 동일한 타입의 델리게이트 타입을 선언
  2. 선언한 델리게이트 타입으로 델리게이트 변수를 생성하고, 생성한 델리게이트 변수에 해당 메소드를 참조시킨다.(포인터와 유사하다)

먼저 동일한 타입의 델리게이트 타입을 선언한다.

// delegate 리턴타입 이름(매개 변수);
delegate void typeA(void); // void funcA(void)
delegate void typeB(int); // void funcB(int)
delegate float typeC(float); // float funcC(float)

타입으로 델리게이트 변수를 생성해서 그 변수에 메소드를 참조시킨다.

// 변수 생성
typeA delegate_a;
typeB delegate_b;
typeC delegate_c;

// 메소드 참조
delegate_a = new typeA(funcA);
delegate_b = new typeB(funcB);
delegate_c = new typeC(funcC);

이 방법을 적용하여 덧셈과 뺄셈을 하는 코드를 작성해보자.

namespace Sample {
  delegate int DelegateType(int a, int b);
  
  class MainApp {
    public static int Plus(int a, int b){ return a + b; }
    public static int Sub(int a, int b){ return a - b; }
    
    static void Main(string[] args){
      DelegateType delegateVar;
      
      delegateVar = new DelegateType(Plus);
      int sum = delegateVar(11, 12);
      Console.WriteLine("11 + 12 - {0}", sum);
      
      delegateVar = new DelegateType(Sub);
      int sub = delegateVar(11, 12);
      Console.WriteLine("11 - 12 - {0}", sub);
    }

  }
}

Callback method

위의 더하기 빼기 코드를 보면 한 델리게이트 변수에 한 함수를 참조하게 하고 다음에 바로 참조 위치를 바꿔버린다. 그런데 이렇게 델리게이트를 쓰는 것은 정말 의미가 없다. 메소드를 직접 호출하면 되고, 굳이 델리게이트를 쓸 이유가 없어지는 것이다.

델리게이트는 콜백 메소드를 구현할 때 진가를 발휘한다.

콜백(Callback)이란 A라는 메서드를 호출할 때 B라는 메서드를 넘겨줘서 A가 B 메서드를 호출하도록 하는 것이고, 이때 A를 콜백 메서드라고 한다.

구조를 보면 A 메서드 안에 Delegate를 선언하고, 이 Delegate로 다른 함수를 호출할 수 있는데 이게 B 메서드를 참조하는 것이다.

예제는 아래와 같다.

namespace Sample {
  delegate int DelegateType(int a, int b);
  
  class MainApp {
  
    public static void Calculator(int a, int b, DelegateType delegateVar){
      Console.WriteLine(delegateVar(a,b));
    }
    
    public static int Plus(int a, int b){ return a + b; }
    public static int Sub(int a, int b){ return a - b; }
    
    static void Main(string[] args){
      DelegateType plus = new DelegateType(Plus);
      DelegateType sub = new DelegateType(Sub);
      
      Calculator(11, 12, plus);
      Calculator(11, 12, sub);
    }
  }
}

정말 간편하다. 계산기 함수를 호출할때마다 원하는 연산(메소드)를 넘겨줘서 계산기가 기능을 하도록 하고 있다.

Delegate 일반화

위에서는 Delegate의 타입을 선언할 때 참조할 수 있는 메서드 타입을 지정했지만, 이를 일반화하면 타입과 상관 없이 메소드를 참조할 수 있다.

namespace Sample {
  delegate T DelegateType<T>(T a, T b);
  
  class MainApp {
  
    public static void Calculator<T>(T a, T b, DelegateType<T> delegateVar){
      Console.WriteLine(delegateVar(a,b));
    }
    
    public static int PlusInt(int a, int b){ return a + b; }
    public static float PlusFloat(float a, float b){ return a + b; }
    
    static void Main(string[] args){
      DelegateType plusInt = new DelegateType(PlusInt);
      DelegateType plusFloat = new DelegateType(PlusFloat);
      
      Calculator(11, 12, plusInt);
      Calculator(1.1f, 2.2f, plusFloat);
    }
  }
}

Delegate Chain

위에서는 하나의 델리게이트가 하나의 메소드를 참조할 수 있게 했다. 그런데 델리게이트는 여러개의 메소드도 참조할 수 있다.

아주 간편한게, 아래와 같이 +, += 연산자로 새로운 메소드를 추가해주면 된다.

DelegateType delegateVar;
delegateVar = new DelegateType(func1);
delegateVar += func1;
delegateVar += func2;

이제 델리게이트를 호출하면 참조된 메소드들을 차례대로 호출한다. 이렇게 하나의 델리게이트에 여러 메소드를 연결시키는 것을 델리게이트 체인이라고 한다.

반대로 델리게이트에 연결된 메소드를 끊고 싶으면 -=를 해주면 된다.

namespace Sample {
  delegate void delType();
  
  class MainApp {
  
    public static void func1() { Console.Write("first"); }
    public static void func2() { Console.Write("second"); }
    public static void func3() { Console.Write("third"); }
    
    static void Main(string[] args){
      delType delVar;
      
      delVar = new delType(func1);
      delVar += func2;
      delVar += func3;
      
      delVar();
      
      delVar -= func1;
      delVar -= func2;
      
      delVar();
    }
  }
}

출처 및 참고