정리가 필요한 카테고리(추후 정리)/C#,Unity

C#] Delegate(델리게이트, 대리자) - 1

TwinParadox 2017. 2. 1. 00:30
728x90

### Delegate


Delegate type은 그 형식의 인스턴스를 호출할 수 있는 종류의 메서드를 정의함.

좀 더 구체적으로 이 형식은 그런 메서드의 반환 형식과 매개변수 형식들을 정의함.

delegate는 전통적인 프로그래밍 언어에서의 callback과 유사한 측면이 있다. 일반적으로 이 callback은 C의 함수 포인터 같은 지연 호출 수단을 일컫는 용어로 쓰임.


반환 형식이 int, int 형식의 매개변수 하나를 받는 모든 메서드에 사용 가능한 delegate type 정의한 예.


delegate int Transformer (int x);

class Test
{
	static void Main()
	{
		Transformer t = Square;
		int result = t(3);
		Console.WriteLine(result);
	}
	static int Sqaure (int x) => x * x;
}


delegate 인스턴스는 말 그대로 호출자의 대리인 역할을 함.

호출자가 대리자를 호출하면 대리자가 대상 메서드를 대신 호출하고 이런 간접 호출에 의해서 호출자와 대상 메서드 사이의 결합이 끊어짐.


엄밀히 말해서 상기 코드에서 대리자 인스턴스를 생성 시기에 괄호 쌍과 인수들 없이 함수 이름만 지칭한 것은 하나의 메서드가 아닌 메서드 그룹을 지정한 것이며, 이것이 오버로딩 되어 있는 경우, C#은 해당 delegate 형식의 서명에 기초해 적절한 버전을 선택함.




## Delegate를 이용한 플러그인 메서드 작성


delegate 변수에 메서드를 배정하는 연산은 실행 시점에서 일어남.

delegate는 플러그인(plug-in) 메서드를 구현하기 좋은 수단으로 사용.

아래는 이 경우에 대한 예제 소스



public delegate int Transformer (int x);

class Util
{
	public static void Transform(int[] values, Transformer f)
	{
		for(int i=0;i x * x;
}




## Multicast Delegate


모든 delegate 인스턴스에는 multicast 능력이 있음.

하나의 인스턴스가 하나의 대상 메서드가 아니라 여러 대상 메서드를 지칭할 수 있음.

기존 delegate 인스턴스에 +, += 연산자를 이용해 새로운 대상 메서드 추가 가능.

-, -=연산자는 좌변의 delegate에서 우변의 메서드를 제거하는 방식임.

이런 연산자 활용에 따라서, null인 delegate 변수에 +, += 호출도 가능하며, 해당 변수에 메서드를 배정하는 것과 동일하며, 메서드가 하나 뿐인 변수에 -=를 호출하는 것은 null 배정과 동일함.


delegate의 경우 immutable(불변이) 객체로, +=, -= 연산자는 실제로 새로운 delegate 인스턴스가 생성된 후 그것이 기존 변수에 배정되는 방식으로 작동함.


대부분의 경우 반환형이 void인 대상 메서드들에 쓰여, 드문 경우지만, 만약 multicast delgate의 반환형이 void가 아니면, 호출자는 마지막으로 호출된 메서드가 돌려준 값을 받게 되며, 마지막 이전 메서드들도 호출되긴 하나, 반환값을 폐기됨.


모든 delegate 형식은 암묵적으료 System.MulticastDelegate를 상속하고, MulticastDelegate 자체는 System.Delegate를 상속함. C#은 delegate에 대한 +, -, +=, -= 연산을 System.Delegate 클래스의 정적 Combine, Remove 메서드들도 바꾸어 컴파일함.



SomeDelegate d = Method1;
d += Method2;

d = d + Method2;

SomeDelegate d = null;
d += Method1; // d = Method1





## 인스턴스 메서드, 정적 메서드


인스턴스 메서드를 delegate 인스턴스에 등록하는 경우, 그 메서드를 제대로 호출하기 위해서 delegate 인스턴스는 그 메서드에 대한 참조 뿐 아니라 그 메서드가 속한 인스턴스에 대한 참조도 기억해야함.

System.Delegate 클래스의 Target 속성이 이를 나타내는데, 정적 메서드의 경우 이 속성이 null이 됨.



public delegate void ProgressReporter (int percentComplete);

class Test
{
	static void Main()
	{
		X x = new X();
		ProgressReporter p = x.InstanceProgress;
		p(99);
		Console.WriteLine(p.Target == x); // T
		Console.WriteLine(p.Method); // void InstanceProgress(Int32);
	}
}

class X
{
	public void InstanceProgress (int percentComplete) => Console.WriteLine(percentComplete);
}




## 제네릭 Delegate 형식


public delegate T Transform (T arg);

public class Util
{
	public static void Transform (T[] values, Transformer t)
	{
		for(int i = 0; i < values.Length; i++)
			values[i] = t(values[i]);
	}
}

class Test
{
	static void Main()
	{
		int[] values = {1,2,3};
		Util.Transform(values, Square);
		foreach(int i in values)
			Console.Write(i + " ");
	}
	static int Square (int x) => x * x;
}


Delegate 형식에 제네릭 형식 매개변수를 둘 수도 있으며, 메서드가 임의의 형식에 대해 작동할 수 있게 함.






## 표준 Func, Action Delegate


제네릭 delegate를 이용하면, 임의의 반환 타입과 임의의 개수의 매개변수들을 가진 그 어떤 메서드에도 작동할 정도로 일반적인 delegate 형식들만 몇 개만 작성해 재사용하는 것이 가능함.

System namesapce 에 정의된 Func, Action delegate가 이러한 대리자로, 둘의 선언은 아래와 같음.


delegate TResult Func  ();
delegate TResult Func  (T arg);
delegate TResult Func  (T1 arg1, T2 arg2);
/* ... */
delegate void Action ();
delegate void Action   (T arg);
delegate void Action  (T1 arg1, T2 arg2);
/* ... */



극도로 일반적인 delegate로, 이전 예제에 나온 Transformer delegate를 다음처럼 T 형식의 인수 하나를 받고 형식의 값을 돌려주는 Func delgate로 대신할 수 있음. 실무에서는 이런 delegate로 해결되지 않는 유일한 시나리오는 ref, out 매개변수들과 포인터 매개변수 뿐임.


.Net Framework 2.0 이전에는 Func, Action delegate가 없었음.(제네릭 자체가 존재하지 않음.) Func와 Action 대신 커스텀 delegate 형식을 사용하는 것은 이런 이유로 인함.

728x90

'정리가 필요한 카테고리(추후 정리) > C#,Unity' 카테고리의 다른 글

C#] MySQL 사용하기  (0) 2017.02.16
C#] 제네릭(Generic) - 2  (0) 2017.02.02
C#] 구조체(Structure)  (0) 2017.01.27
C#] 제네릭(Generic) - 1  (0) 2017.01.24
C#] 열거형(Enum Type)  (60) 2017.01.23