[C#] Delegate - 1

이정석·2024년 3월 3일

CSharp

목록 보기
16/22

Delegate

C#에서는 함수를 변수처럼 다루기 위해 Delegate라는 기능을 제공하는데 함수의 반환형과 매개변수 형식을 하나의 타입으로 저장할 수 있다.

Delegate를 사용하는 방법은 아래와 같이 사용할 수 있다.

  // delegate 반환타입 이름(매개변수, ...) 구조
  delegate void FUNC(int arg);

  class Program
  {
      public static void foo(int arg)
      {
          Console.WriteLine($"foo: {arg}");
      }

      public static void Main(string[] args)
      {
          FUNC f = foo;
          f.Invoke(10);   // f(10)도 가능
      }
  }

Delegate를 지정하면 컴파일러는 자동으로 하나의 클래스를 생성하게 되는데 위의 상황이라면 FUNC라는 클래스가 자동으로 생성된다.

	class FUNC : System.MulticastDelegate
    {
    	// ...	
    }

원래 클래스를 생성할 때는 new 키워드를 이용해서 생성해야 하지만 일반 대입식으로 표현해도 컴파일러가 자동으로 new로 변환해준다.

    public static void Main(string[] args)
    {
        FUNC f1 = new FUNC(foo);
        f1.Invoke(10);

        FUNC f2 = foo;
        f2(20);
    }

Delegate는 결국 데이터 타입이고 하나의 클래스로 표현 가능하며 위의 예제에서는 FUNC가 이에 해당하는 것이다.

1. static method

static method에 Delegate를 사용하려면 어떻게 해야할까? 간단하다. static method는 객체 생성이 필요가 없기 때문에 바로 static 함수를 등록하면 된다.

반면, instance method는 객체를 생성하고 생성한 객체의 메소드를 등록해주어야 한다.

  class Test
  {
      public static void static_method(int arg) {  }
      public void instance_method(int arg) { }
  }

  class Program
  {
      public static void static_method(int arg) { }
      public void instance_method(int arg) { }

      public static void Main(string[] args)
      {
          // static 메소드는 바로 등록 가능
          FUNC f1 = Test.static_method;
          FUNC f2 = Program.static_method;

          // instance 메소드는 객체를 생성하고 해야함
          Test t = new Test(); Program p = new Program();
          FUNC f3 = t.instance_method;
          FUNC f4 = p.instance_method;
      }
  }

Delegate의 사용을 객체 인스턴스 함수의 내부에서 사용할 경우 this 키워드를 사용하면 객체의 호출 없이 등록이 가능하다.

    public static void Main(string[] args)
    {
        Program p = new Program();
        p.foo();
    }

    public void foo()
    {
        FUNC f1 = this.instance_method;
        FUNC f2 = instance_method;
    }

2. 익명 메소드

이름 그대로 C#에서 제공하는 기능 중 하나로 이름 없는 메소드를 의미한다. 함수 명 없이 구현만 하는 구조로 함수의 이름 없이 아래와 같은 구조를 가진다.

delegate (매개변수, ...) { 구현부 }

익명 메소드를 Delegate에 사용할 때, 반환타입과 매개변수의 형식이 동일해야 사용이 가능하다. 익명 메소드를 Delegate에 사용한 예제 코드는 아래와 같다.

    public static void Main(string[] args)
    {
        FUNC f1 = delegate (int a, int b) { return a + b; };
        FUNC f2 = delegate (int a, int b) { return a - b; };

        Console.WriteLine(f1(10, 20)); // 30
        Console.WriteLine(f2(10, 20)); // -10
    }

결국 Delegate는 스태틱 메소드, 인스턴스 메소드, 익명 메소드, 람다 표현식와 같은 여러 함수들을 사용할 수 있다.

3. Delegate Chain

Delegate에는 Chain이라는 기능이 존재하는데 Delegate를 서로 연결하듯이 다룰 수 있는 특징이 있다.

  class Test
  {
      public static int Method1()
      {
          Console.WriteLine("Method1");
          return 1;
      }

      public static int Method2()
      {
          Console.WriteLine("Method2");
          return 2;
      }

      public static int Method3()
      {
          Console.WriteLine("Method3");
          return 3;
      }

      public static int Method4()
      {
          Console.WriteLine("Method4");
          return 4;
      }
  }

  delegate int FUNC();

  class Program
  {
      public static void Main(string[] args)
      {
          FUNC f1 = Test.Method1;
          FUNC f2 = Test.Method2;
          FUNC f3 = Test.Method3;

          FUNC f4 = (FUNC)Delegate.Combine(f1, f2, f3);
          // Method1
          // Method2
          // Method3
          f4();
      }
  }

위의 코드에서 f4가 Delegate Chain으로 3개의 Delegate가 연결된 새로운 Delegate에 해당한다. f4를 실행하면 Chain된 순서대로 Method1, Method2, Method3가 실행된다.

Combine 함수는 Delegate를 Chain하는 것이기 때문에 매개변수로 메소드가 들어갈 수 없음에 주의해야 한다.

Chain하는 또다른 방법으로 +, += 연산자가 있다. Delegate에 +=으로 Chain이 가능하고 정의할 때도 +를 통해 한번에 여러개의 Delegate를 표현할 수 있다.

    public static void Main(string[] args)
    {
		// ...
        FUNC f4 = (FUNC)Delegate.Combine(f1, f2, f3);
        f4();

        FUNC f5 = f1 + f2 + f3; // f1 + Test.Method2 + f3
        f5();
    }

마지막으로 Delegate는 string처럼 Immutable하다는 점이 있다. 기존의 Delegate뒤에 +=로 새로운 Delegate를 이어 붙이는 형식이 아닌 Delegate가 붙어있는 새로운 Delegate를 생성해 대입하는 방식으로 동작한다.

4. 반환값

Chain된 여러개의 Delegate가 있을 때 반환값과 타입은 마지막으로 Chain된 Delegate를 따르게 된다. 예를들어, 아래 코드의 출력값은 4가 된다.

    public static void Main(string[] args)
    {
        FUNC multi = Test.Method1;
        multi += Test.Method2;
        multi += Test.Method3;
        multi += Test.Method4;
        Console.WriteLine(multi()); // 4
    }

Chian된 Delegate를 얻기 위해서 GetInvocationList 함수를 사용할 수 있는데 반환 타입은 Delegate[]이기 때문에 순회하면서 사용할 때 형변환이 필요하다.

    public static void Main(string[] args)
    {
		// ...
        Delegate[] arr = multi.GetInvocationList();

        foreach(Delegate d in arr)
        {
            FUNC f = (FUNC)d;
            Console.WriteLine(f());
        }
    }
profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글