주녘공부일지

[C#] 대리자 (Delegate - Action, Func) 본문

C#/Definition, Etc

[C#] 대리자 (Delegate - Action, Func)

주녘 2023. 11. 10. 20:16
728x90

대리자 (Delegate)

어떤 메서드를 호출하는 방법을 담은 객체

- C의 함수 포인터 같은 지연 호출 수단 등과 같이 프로그래밍 언어에서 말하는 Callback과 유사

- 제네릭 대리자 형식으로 선언할 수도 있으며, 제네릭 형식 매개변수를 둘 수도 있음

https://godgjwnsgur7.tistory.com/115

 

[C#] 제네릭 (Generic)

제네릭 (Generic) - 서로 다른 형식들에 대해 재사용할 수 있는 코드를 작성하기 위해 사용하는 메커니즘 - 형식 안정성을 높이고 캐스팅과 박싱을 줄이기 위한 수단이 됨 (일반화, 특수화) + 제네릭

godgjwnsgur7.tistory.com

 ex)  대리자 인스턴스, 호출, 대리자를 이용한 플러그인 메서드

    // 제네릭 대리자 형식
    public delegate T Transformer<T>(T arg);

    class Util
    {
        // 제네릭 형식 매개변수로 변환 플러그인 메서드를 받아 배열의 값을 변경하는 메서드 
        public static void Transform<T>(T[] values, Transformer<T> t)
        {
            for (int i = 0; i < values.Length; i++)
                values[i] = t(values[i]);
        }
    }

    class Test
    {
        static int Square(int x) => x * x;

        static void Main()
        {
            Transformer t = Square; // 대리자 인스턴스 생성 = new Transformer(Square)
            int result = t(3); // 대리자 호출 = t.Invoke(3)
            Console.WriteLine(result); // 9

            int[] values = { 1, 2, 3 };
            Util.Transform(values, Square); // Square 메서드를 적용
            foreach (int i in values)
                Console.Write(i + " "); // 1 4 9
        }
    }

1) 다중 캐스트 대리자

하나의 대리자 인스턴스는 여러 개의 대상 메서드들을 지칭 가능

- 대리자 인스턴스에 +=나 -= 연산자를 이용해 메서드를 추가, 제거 할 수 있음

- 만약, 반환 형식이 void가 아닐 경우엔 마지막 호출 메서드의 반환 값을 받게 됨

- '+=' 연산자로 수행되는 메서드 추가는 같은 메서드라고 해도 중복이 가능하므로 주의

+ 대리자는 불변이 객체로, +=나 -= 연산 시에 새로운 대리자 인스턴스가 생성되어 기존 변수에 배정

SomeDelegate d = SomeMethod1;
d.Invoke() // SomeMethod1 실행

d += SomeMethod2;
d.Invoke(); // SomeMethod1, SomeMethod2 실행

d -= SomeMethod1;
d.Invoke(); // SomeMethod2 실행

d -= SomeMethod2; // d는 null이 되며, Invoke 호출 시 예외가 던져짐

+ 메서드 대상이 동일한 대리자 인스턴스들은 서로 같다고 간주되는데, 다중 캐스트 대리자는 같은 대상 메서드들이 같은 순서로 등록되어 있어야 서로 같다고 간주됨

2) 제네릭 대리자 형식 ( 표준 Func 대리자, Action 대리자 )

https://godgjwnsgur7.tistory.com/115

 

[C#] 제네릭 (Generic)

제네릭 (Generic) - 서로 다른 형식들에 대해 재사용할 수 있는 코드를 작성하기 위해 사용하는 메커니즘 - 형식 안정성을 높이고 캐스팅과 박싱을 줄이기 위한 수단이 됨 (일반화, 특수화) + 제네릭

godgjwnsgur7.tistory.com

- 임의의 반환 형식과 임의의 개수의 매개변수들을 가진 그 어떤 메서드에도 작동할 정도로 일반적인 대리자 형식들 몇 개만 작성하여 재사용하는 것이 가능

 ex) System 이름공간에 정의된 Func 대리자들과 Action 대리자들

delegate TResult Func<out TResult>();
delegate TResult Func<in T, out TResult>(T arg);
delegate TResult Func<in T1, T2, out TResult>(T1 arg1, T2 arg2);
// ... 이와 비슷한 선언들이 T16까지 이어짐

delegate void Action();
delegate void Action<in T>(T arg);
delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
// ... 이와 비슷한 선언들이 T16까지 이어짐

 ex) 이전 예제의 Transformer 대리자를 T 형식의 인수를 하나 받아 같은 형식의 값을 돌려주는 Func 대리자로 대신함

pbulic static void Transform<T> (T[] values, Func<T, T> transformer)
{
    for(int i = 0; i < values.Length; i++)
        values[i] = transformer(values[i]);
}

- 실무에서 이 대리자들로 해결되지 않는 경우는 ref, out, 포인터 매개변수 뿐이라고 할 수 있음

3) 대리자의 호환성 ( 반변성, 공변성, 가변성 )

- 모든 대리자 형식은 다른 모든 대리자 형식과 호환되지 않음 ( 서명이 같더라도 호환되지 않음 )

delegate void D1();
delegate void D2();

D1 d1 = Method1;
D2 d2 = d1; // 컴파일 시점 오류

D2 d2 = new D2(d1); // 가능한 코드

1. 매개변수 호환성 ( 반변성, contravariance )

- 대리자의 매개변수 형식이 대상 메서드의 매개변수 형식보다 더 구체적일 수 있음

- 오직 참조 변환에 대해서만 작동함

 ex) 대리자 반변성 예제 - string 형식 인수로 StringAction 인스턴스 호출

delegate void StringAction(string s);

class Test
{
    static void Main()
    {
        StringAction sa = new StringAction(ActOnObejct);
        sa ("hello"); // string -> object 암묵적 상향 캐스팅
    }

    static void ActOnObejct(object o) => Console.WriteLine(o); // hello
}

2. 반환 형식 호환성 ( 공변성, covariance )

- 대리자는 대상 메서드의 반환 형식보다 더 구체적인 형식을 돌려줄 수 있음

 ex) 대리자 공변성 예제 ( 대리자의 반환 형식은 공변 )

delegate object ObjectRetriever();

class Test
{
    static void Main()
    {
        ObejctRetriever o = new ObjectRetriever(RetrieveString);
        object result = o();
        Console.WriteLine(result); // hello
    }
    
    static string RetrieveString() => "hello";
}

제네릭 대리자 형식 매개변수의 가변성

제네릭 대리자 형식을 정의할 때에는 다음과 같은 관행을 따르는 것이 좋음

- 반환 값에만 쓰이는 형식 매개변수는 공변으로 지정 (out 수정자)

- 매개변수에만 쓰이는 형식 매개변수는 반변으로 지정 (in 수정자)

 -> 형식들 사이의 상속 관계에 대한 형식 변환이 자연스럽게 일어남

delegate TResult Func<out TResult>(); // 형식 매개변수 TResult는 공변
delegate void Action<in T>(T arg); // 형식 매개변수 T는 반변

 ex) 따라서 다음과 같은 용법이 허용

Func<string> x = ...;
Func<object> y = x;

Action<object> x = ...;
Action<string> y = x;

 

참고도서) C# 6.0 완벽가이드

728x90