본문 바로가기
프로그래밍/C#

C# 제네릭 대리자(Generic Delegate)

by bantomak 2024. 3. 26.
반응형

C# 제네릭 대리자(Generic Delegate)란?

대리자 형식은 제네릭(generic) 형식을 매개 변수로 이용할 수 있다. 제네릭을 지원하면, 대리자를 변수로 초기화할 때까지 대리자에 사용할 매개 변수나 반환 형식을 지정을 미룰 수 있다. 즉, 대리자 형식을 정의할 때는 대리자의 매개 변수와 반환 형식을 명시하지 않는다. 다음 코드를 이용해서 좀 더 자세히 알아보자.

 

public partial class Program
{
    private delegate T FormulaDelegate<T>(T a, T b);
}

 

using System;

public partial class Program
{
    private delegate T FormulaDelegate<T>(T a, T b);

    private static int Addint(int a, int b)
    {
        return a + b;
    }

    private static double Adddouble(double a, double b) 
    {
        return a + b;
    }

    static void Main(string[] args)
    {
        FormulaDelegate<int> IntAddDel = Addint;
        FormulaDelegate<double> DoubleAddDel = Adddouble;

        Console.WriteLine("Invoking intAddtion(3, 4)");
        Console.WriteLine($"Result = {IntAddDel(3, 4)}");

        Console.WriteLine("Invoking doubleAddtion(2.2, 3.3)");
        Console.WriteLine($"Result = {DoubleAddDel(2.2, 3.3)}");
    }
}

 

 

int와 double 둘 다 처리가 가능한 제네릭 대리자를 생성하였다.

예제를 통해 하나의 대리자 형식을 이용해서 시그니처가 다른 두 개의 메서드를 참조하는 대리자들을 선언할 수 있다는 것을 알았다. intAddDel 대리자는 매개 변수와 반환 형식이 int인 Addint() 메서드를 참조하며, DoubleAddDel 대리자가 참조하는 메서드의 매개 변수와 반환 형식은 double이다. 대리자가 참조하는 메서드의 데이터 형식이 무엇인지는 대리자를 초기화할 때 꺽쇠(<>)안에 표기한 데이터 형식을 이용해서 정의한다. 다음은 제네릭 데이터 형식을 이용하는 대리자의 초기화 코드의 예다.

 

FormulaDelegate<int> IntAddDel = Addint;
FormulaDelegate<double> DoubleAddDel = Adddouble;

 

이처럼 데이터 형식을 정의함으로써 대리자가 참조 대상 메서드의 데이터 형식과 일치 여부를 확인할 수 있다. 시그니처가 다른 함수들을 호출할 수 있는 것도 이런 구조 덕분이다.

 

지금까지 하나의 제네릭 매개 변수를 가지는 대리자를 살펴봤다. 다음은 대리자 선언 시, 제네릭 매개 변수를 여러 개 사용할 수 있음을 보여주는 예시다.

 

public partial class Program
{
    private delegate void AdditionDelegate<T1, T2>(T1 value1, T2 value2);
}

 

AdditionDelegate 대리자는 형식이 다른 두 개의 매개 변수 T1과 T2를 가지며, 이들은 변수 선언 시에 정의할 데이터 형식을 의미한다. 자세한 예를 들기 위해 서로 다른 형식의 매개 변수를 두 개씩 가지는 AddIntDouble()과 AddFloatDouble() 메서드를 살펴보자.

 

using System;

public partial class Program
{
    private delegate void AdditionDelegate<T1, T2>(T1 value1, T2 value2);

    private static void AddIntDouble(int a, double b)
    {
        Console.WriteLine($"{a} + {b} = {a + b}");
    }

    private static void AddFloatDouble(float a, double b) 
    {
        Console.WriteLine($"{a} + {b} = {a + b}");
    }

    static void Main(string[] args)
    {
        AdditionDelegate<int, double> AddIntDoubleDel = AddIntDouble;
        AdditionDelegate<float, double> AddFloatDoubleDel = AddFloatDouble;

        Console.WriteLine("Invoking AddIntDouble(3, 4.3)");
        AddIntDoubleDel(3, 4.3);

        Console.WriteLine("Invoking AddFloatDouble(1.2f, 2.3)");
        AddFloatDoubleDel(1.2f, 2.3);
    }
}

 

 

이번에는 반환값을 갖는 다중 템플릿 대리자를 만들어보자. 대리자 선언은 다음과 같다.

 

using System;

public partial class Program
{
    private delegate TResult AddAndConvert<T1, T2, TResult>(T1 digit1, T2 digit2);

    private static float AddIntDouble(int a, double b)
    {
        Console.WriteLine($"(int){a} + (double){b} = (float){(float)(a + b)}");
        return (float)(a + b);
    }

    private static int AddFloatDouble(float a, double b) 
    {
        Console.WriteLine($"(float){a} + (double){b} = (int){(int)(a + b)}");
        return (int)(a + b);
    }

    static void Main(string[] args)
    {
        AddAndConvert<int, double, float> AddIntDoubleDel = AddIntDouble;
        AddAndConvert<float, double, int> AddFloatDoubleDel = AddFloatDouble;

        Console.WriteLine("Invoking intDoubleAddConvertToFloat delegate(5, 3.9)");
        Console.WriteLine(AddIntDoubleDel(5, 3.9));

        Console.WriteLine("Invoking floatDoubleAddConvertToInt delegate(4.3, 2.1)");
        Console.WriteLine(AddFloatDoubleDel((float)4.3, 2.1));
    }
}

 

 

다중 템플릿 제네릭 형식의 대리자를 이용하면 상이한 시그니처를 갖는 다양한 메서드에 대응할 수 있다.

 

Action<>과 Func<>

C#은 최대 16개의 매개 변수를 갖는 void 반환 형식의 내장 대리자인 Action을 제공한다. 즉, Action 대리자는 매개 변수를 0~16개 가지면서 값을 반환하지 않는 메서드를 호출할 수 있다. 덕분에 대리자를 선언할 필요성이 대폭 줄어들었으며 손쉬운 메서드 참조가 가능해졌다.

 

다음은 MultiTemplateDelegates의 VoidDelegateInvoke()에 Action 대리자를 적용한 ActionDelegateInvoke() 메서드다.

 

public partial class Program
{
    private static void AddIntDouble(int a, double b)
    {
        Console.WriteLine($"(int){a} + (double){b} = (float){(float)(a + b)}");
    }

    private static void AddFloatDouble(float a, double b)
    {
        Console.WriteLine($"(float){a} + (double){b} = (int){(int)(a + b)}");
    }

    private static void ActionDelegateInvoke()
    {
        Action<int, double> intDoubleAddAction = AddIntDouble;
        Action<float, double> floatDoubleAddAction = AddFloatDouble;

        Console.WriteLine("Invoking intDoubleAddConvertToFloat delegate(5, 3.9)");
        intDoubleAddAction(1, 2.5);

        Console.WriteLine("Invoking floatDoubleAddConvertToInt delegate((float)1.2, 4.3)");
        floatDoubleAddAction((float)1.2, 4.3);
    }
}

 

미리 정의된 Action<> 델리게이트로 교체하였다. 별도로 Delegate를 선언하지 않아도 되서 유용하게 사용이 가능하다.

다음은 TResult를 반환하는 경우를 Func<> 델리게이트로 변경하였다.

 

동일하게 맨 위에 선언되어 있던 Delegate 구문은 이제 더 이상 선언하지 않아도 된다.

 

public partial class Program
{
    private static float AddIntDouble(int a, double b)
    {
        Console.WriteLine($"(int){a} + (double){b} = (float){(float)(a + b)}");
        return (float)(a + b);
    }

    private static int AddFloatDouble(float a, double b) 
    {
        Console.WriteLine($"(float){a} + (double){b} = (int){(int)(a + b)}");
        return (int)(a + b);
    }

    static void Main(string[] args)
    {
        Func<int, double, float> AddIntDoubleFunc = AddIntDouble;
        Func<float, double, int> AddFloatDoubleFunc = AddFloatDouble;

        Console.WriteLine("Invoking intDoubleAddConvertToFloat delegate(5, 3.9)");
        Console.WriteLine(AddIntDoubleFunc(5, 3.9));

        Console.WriteLine("Invoking floatDoubleAddConvertToInt delegate(4.3, 2.1)");
        Console.WriteLine(AddFloatDoubleFunc((float)4.3, 2.1));
    }
}

함께 읽으면 좋은 글

 

C# 대리자(Delegate)에 대해서

대리자(Delegate)란? 특정 매개 변수 목록과 반환 형식이 있는 매서드에 대한 참조를 나타내는 형식 대리자를 인스턴스화할 때 호환되는 매개변수 및 반환 형식을 가지는 모든 메서드와 연결할 수

jettstream.tistory.com

 

C# 미리 정의된 Delegate에 대해서

Action Delegate namespace System { // // 요약: // Encapsulates a method that has no parameters and does not return a value. public delegate void Action(); } Action delegate는 하나 이상의 파라미터를 받아들이고, 리턴 값이 없는(void)

jettstream.tistory.com

출처

 

Functional C# - 예스24

C# 개발자를 위한 함수형 프로그래밍 학습서다. 명령형 프로그래밍 방식과 함수형 프로그래밍을 비교하고, 함수형 프로그래밍을 위한 C#의 언어적 지원과 이를 이용한 실제 구현 예를 살펴보면

www.yes24.com

댓글