# 델리게이트(Delegate)

 해석하면 '대리인'이라는 뜻의 델리게이트는 C++의 포인터처럼 메서드를 안전하게 캡슐화하는 형식입니다.
즉, 메소드를 대신해서 호출하는 역활을 하는 메소드를 참조하는 변수입니다.
특정 메소드를 처리할 때 그 메소드를 직접 호출해서 실행시켜야 했지만, 델리게이트를 사용하면 그 메소드를 대신하여 호출할 수 있습니다.

# 사용방법
(1) 델리게이트 타입 선언.
    메소드 타입은 매개변수와 반환타입에 의해 결정됩니다.
    델리게이트 타입도 그 메소드와 동일한 매개변수, 반환 타입으로 선언해주면 됩니다. 
(2) 델리게이트 인스턴스화하여 메서드 전달.

(3) 델리게이트 호출.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace DelegateTest
{
    class Program
    {
        delegate void Del(string message);
        public static void DelegateMethod(string message)
        {
            System.Console.WriteLine(message);
        }
        static void Main(string[] args)
        {
            // 명명된 메서드 전달.
            Del handler = DelegateMethod;
            // 무명 메서드 전달.
            Del handler2 = delegate (string message)
                            { System.Console.WriteLine(message); };
            // 람다식 활용.
            Del handler3 = (message) => System.Console.WriteLine(message);
            handler("Hello World");
            handler2("Bye World");
        }
    }
}
cs

# 콜백 메서드
인스턴스화 된 델리게이트는 매개 변수로 전달하거나 속성에 할당할 수 있습니다.
따라서, 메서드가 델리게이트를 매개 변수로 허용하여 매개 변수로 받은 델리게이트를 나중에 호출할 수 있습니다. 

이러한 방식을 비동기 콜백이라고 부르며, 콜백 메서드를 구현할 때, 델리게이트의 가치를 볼 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace Calculater
{
    delegate int Calc(int a, int b);
 
    class MainApp
    {
        public static void Calculator(int a, int b, Calc cb)
        {
            Console.WriteLine(cb(a, b));
        }
 
        public static int Plus(int a, int b) { return a + b; }
        public static int Minus(int a, int b) { return a - b; }
 
        static void Main(string[] args)
        {
            Calc plusCalc = new Calc(Plus);
            Calc MinusCalc = new Calc(Minus);
 
            Calculator(1122, plusCalc);  // 33 출력
            Calculator(3322, MinusCalc); // 11 출력
        }
    }
}
cs



# 델리게이트 체인

델리게이트는 둘 이상의 메서드를 호출할 수 있습니다. 이러한 호출을 멀티캐스트라고 합니다.

델리게이트에 메서드 목록을 추가하려는 경우, 더하기 또는 더하기 대입 연산자('+' 또는 '+=')를 사용합니다. 

메서드 제거도 가능한데, 제거하는 경우엔 감소 또는 감소 대입 연산자('-' 또는 '-=')를 사용하면 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
namespace Multicast
{
    delegate void PrintText();
 
    class MainApp
    {
        public static void First() { Console.Write("첫번째 "); }
        public static void Second() { Console.Write("두번째 "); }
        public static void Third() { Console.Write("세번째 "); }
 
        static void Main(string[] args)
        {
            PrintText MultiHandler;
 
            MultiHandler = new PrintText(First);
            MultiHandler += Second;
            MultiHandler += Third;
 
            Console.WriteLine("메서드 추가");
            MultiHandler();
 
            MultiHandler -= First;
            MultiHandler -= Third;
 
            Console.WriteLine("\n\n메서드 제거");
            MultiHandler();
        }
    }
}
cs



    [ 콘솔 출력 결과 ]


    메서드 추가

    첫번째 두번째 세번째


    메서드 제거

    두번째





delegate 관련 Effective C# : https://loveme-do.tistory.com/14


참   고 : https://mrw0119.tistory.com/19?category=585887

           https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/delegates/using-delegates

nameof( ) 연산자를 적극 활용하라.



[ Content ]

# nameof( )

: C# 6.0에 추가된 개념으로, 임의의 기호(형식, 멤버, 변수 등)의 이름에 해당하는 문자열을 돌려줍니다.

: 심볼의 이름을 평가하며, 타입, 변수, 인터페이스, 네임스페이스에 대하여 사용할 수 있습니다.

: 완전히 정규화된 이름을 사용할 수도 있지만, 정규화되지 않은 이름도 제한 없이 사용할 수 있습니다.

다만, 제네릭 타입을 사용할 경우에는 부분적으로 제작이 있어 모든 타입 매개 변수를 지정한 닫힌 제네릭 타입만을 사용할 수 있습니다. 

. 열린 타입 (ex. Dictionary<Tkey, Tvalue>) : 아직 결정되지 않은 타입 파라미터가 존재.

. 닫힌 타입 (ex. Dircionary <string, int>) : 모든 파라미터가 정해진 경우를 말함.

: 항상 로컬 이름을 문자열로 반환합니다. 완전 정규화된 이름(ex. System.Int.MaxValue)을 사용하더라도 항상 로컬 이름(MaxValue)을 반환합니다.


# nameof( ) 연산자의 장점!

이름이나 문자열 식별자에 의존하는 간단한 라이브러리들이 많이 사용됩니다. 이러한 방식이 간편할진 몰라도 그에 준하는 추가 비용이 발생합니다. 대표적인 단점이 바로 타입 정보를 손실한다는 것입니다. 이로 인해 타입 정보를 활용한 기능을 제공하는 개발 도구의 도움을 받지 못하며, 정적 타입 언어의 주요 장점을 상실하는 문제가 있습니다.


nameof( ) 연산자를 활용하면 이러한 문제를 해결할 수 있습니다. 

해당 문자열을 직접 지정하는 것에 비해, 정적 형식 검사가 일어난다는 장점이 있습니다. 

Visual Studio에서는 기호 참조를 이해할 수 있기 때문에, 해당 기호의 이름을 바꾸면 그에 대한 모든 참조의 이름도 바뀝니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
public string Name
{
    get{ return name; }
    set
    {
        if (value != name)
        {
            name = value;
            PropertyChanged?.Invoke(this,
            new PropertyChangedEventArgs(nameof(Name)));
        }
    }
}

cs


# nameof( ) 활용

: 대부분의 예외 타입은 매개 변수의 이름 자체를 생성자의 매개 변수로 취합니다. 

 이 때, nameof( )로 활용하면 효율적으로 작성할 수 있습니다.


1
2
3
4
5
6
public static void ExceptionMessage(object thisCantBeNull)
{
    if (thisCantBeNull == null)
        throw new ArgumentNullException(nameof(thisCantBeNull),
                "We told you this can't be null");
}
cs

 -> 타이핑 오류를 방지할 수 있습니다.

 -> 차후 리팩토링에 유연한 구조를 만들어 줍니다. 


: 열거형 문자열 이름을 가져올 때 활용할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
public class NameOfOperation
{
    enum MyEnum { START, SECOND, THIRD, END };
 
    void WriteEnum()
    {
        Console.WriteLine(MyEnum.SECOND.ToString());
 
        Console.WriteLine(nameof(MyEnum.SECOND));
    }
}
cs

Console.WriteLine(MyEnum.Second.ToString( ));

-> .Net이 enum 값을 보유하고 런타임에 이름을 찾으면 실제 상대적으로 느립니다.

Console.WriteLine(nameof(MyEnum.Second));

-> nameof()를 사용하면 .NET은 컴파일 타임에 열거형 이름을 문자열로 바꿔줍니다.




[ Digression ]

 

  확실히 직접 문자열을 넣어 하드코딩하는 경우 잦은 실수가 발생합니다. 이러한 실수를 미연에 방지할 수 있고, 작업을 진행하다보면 이전 코드들을 리팩토링하는 과정에서 효율적으로 대체할 수 있는 것 같습니다. 프로젝트에서도 보편적으로 예외 타입을 생성할 때 nameof( )를 사용하여 해당 심볼을 출력해주고 있습니다.




참 고 : Effective C# 

    https://code.i-harness.com/ko-kr/q/1e3a41c

문화권별로 다른 문자열을 생성하려면 FormattableString을 사용하라.



[ Content ]

# FormattableString
    . C# 6.0에서 추가된 클래스입니다.

    . 문자열 보간 기능의 결과로 생성되는 반환값으로 문자열일 수도 있지만, FormattableString을 상속한 타입일 수도 있습니다.

    . FormattableString은 문자열의 조립을 돕는 기능이 있어, 문화권과 언어를 지정하여 문자열을 생성하는데 활용할 수 있습니다.


1
2
3
4
5
6
7
 
String s = $ "It's the {DateTime.Now.Day} of the {DateTime.Now.Month} month";
 
var vs = $"It's the {DateTime.Now.Day} of the {DateTime.Now.Month} month"
 
FormattableString fs = $"It's the {DateTime.Now.Day} of the {DateTime.Now.Month} month";
 
cs


    . var로 선언하면 위 변수 vs는 string 객체가 될 수도 있겠지만, FormattableString을 상속한 타입의 객체가 될 수도 있습니다.


# 활용


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Globalization;
 
namespace GlobalString
{
    class Program
    {
        static void Main(string[] args)
        {
            FormattableString fs = $"{Math.PI}";
            Console.WriteLine(fs);
            Console.WriteLine(ToGerman(fs));
        }
 
        public static string ToGerman(FormattableString src)
        {
            return string.Format(
                CultureInfo.CreateSpecificCulture("de-De"),
                src.Format,
                src.GetArgument(0));
        }
    }
}
cs


    : 위 예제의 결과는 다음과 같이 출력됩니다. 


3.1415

3,1415


     미국식  표기법인 '.'이 변환되어 ','로 출력되는 것을 볼 수 있습니다.



[ Digression ]

 

  사실 큰 필요성을 느끼진 못했습니다. 실제로는 로컬라이징을 위해 문자를 그대로 사용하기보다는 키 값을 이용하여 국가에 맞게 대응하는 데이터를 뿌려주도록 작업합니다. 그러나 대응 영역은 어디까지나 문자에 대한 대응이고, 위의 예제와 같이 기호에 대한 언어 문화가 적용될 필요가 있다면 유용할 것 같습니다. 




참 고 : Effective C# 

    https://ryo511.info/archives/3981

+ Recent posts