1) 함수를 저장하기위한 타입
void OnCollision(Action action)
{
action(); // 인수로 받은 함수를 호출
}
→ 프레임워크 혹은 라이브러리가 프로그래머의 함수를 호출(콜백)하기 위해서는 위 예시와 같이 호출할 함수를 인수로 넘길 수 있어야하며, 이는 함수를 저장할 수 있는 알맞은 타입이 존재함을 의미합니다. 함수를 저장하기위한 타입이 바로 대리자(Delegate)입니다.
2) 직접 대리자를 정의할 수도 있지만, Action과 Func 같은 미리 정의된 대리자들을 사용하는 것을 권장합니다.
3) 대리자는 참조타입입니다.
delegate void SomeDelegate();
delegate int SomeDelegate2();
delegate(키워드) 저장할 함수의 반환 타입 대리자의 이름(저장할 함수의 매개변수 리스트);
SomeDelegate someDelegate = Foo;
SomeDelegate2 someDelegate2 = Boo;
void Foo()
{
Console.WriteLine(nameof(Foo));
}
int Boo() => 1;
→ 함수의 식별자(이름)을 대리자에 대입합니다.
SomeDelegate someDelegate = Foo();
→ 위와 같이 호출 연산자를 붙이지 않도록 유의합니다.
delegate void SomeDelegate();
SomeDelegate someDelegate = Boo; // void 타입 함수를 저장하는 대리자에 int 타입 함수 저장하려는 경우
→ 정의 내린 타입과 다른 형태의 함수를 저장하려는 경우 컴파일 오류가 발생합니다.
SomeDelegate someDelegate = Foo;
someDelegate += Boo;
someDelegate -= Foo;
void Foo()
{
Console.WriteLine(nameof(Foo));
}
void Boo()
{
Console.WriteLine(nameof(Boo));
}
1) += 연산자를 통해 여러개의 함수를 하나의 대리자에 저장할 수 있습니다.
2) -= 연산자를 통해 대리자에 저장된 함수를 제거할 수 있습니다.
// 대리자에 추가하지 않은 함수를 제거하려는 예시
SomeDelegate someDelegate = Foo;
someDelegate -= Boo;
someDelegate.Invoke();
void Foo()
{
Console.WriteLine(nameof(Foo));
}
void Boo()
{
Console.WriteLine(nameof(Boo));
}
// 1. 호출연산자 사용
someDelegate();
// 2. Invoke 메소드 사용
someDelegate.Invoke();
→ 대리자를 통해 함수를 간접적으로 호출할 수 있습니다.
SomeDelegate someDelegate;
someDelegate();
→ 위와 같이 대리자를 초기화하지 않고 호출연산자를 사용하는 경우, NullReferenceException이 발생합니다.
// 1. if문 이용
if(someDelegate != null)
{
someDelegate();
}
// 2. null 조건부 연산자 이용
someDelegate? .Invoke();
→ 위와 같은 방법을 이용해 대리자가 null인지 확인한 후 함수를 호출하여 오류를 방지할 수 있습니다.
1) if문을 이용해 대리자가 null이 아닌 경우에만 함수 호출 구문 실행
2) null 조건부 연산자를 통해 대리자가 null인지 확인 후 아닌 경우 Invoke 메소드 실행
1) 반환 타입이 없는 경우
SomeDelegate someDelegate = Foo;
someDelegate += Boo;
someDelegate.Invoke();
void Foo()
{
Console.WriteLine(nameof(Foo));
}
void Boo()
{
Console.WriteLine(nameof(Boo));
}
→ 여러개의 함수를 하나의 대리자에 대입한 후 대리자를 통해 함수를 실행하는 경우, 대입된 순서에 따라 함수가 차례로 실행됩니다.
2) 반환 타입이 있는 경우
// Func에 대해선 아래에 추가 정리 (반환값이 있는 대리자)
Func<int, int, int> func = Add;
func += Sub;
int a = func(10, 20);
Console.WriteLine(a);
int Add(int a, int b)
{
Console.WriteLine(nameof(Add));
return a + b;
}
int Sub(int a, int b)
{
Console.WriteLine(nameof(Sub));
return a - b;
}
→ 반환 타입이 같은 두개의 함수가 하나의 대리자에 저장되어 있는 경우 이전에 실행된 함수의 반환값은 버려지고, 마지막에 실행된 함수의 반환값만 반환됩니다.
→ Add가 출력되는 것을 통해 이전의 함수가 아예 실행되지 않는 것이 아님을 알 수 있습니다.
1) 반환값이 없는 대리자
2) 반환값이 없지만 매개변수가 있는 함수를 저장하는 경우 매개변수 리스트를 작성합니다.
// 두개의 int 타입 매개변수롤 받는 함수를 저장하는 경우
Action<int, int> someDelegate = 함수명;
3) Action은 최대 16개의 매개변수까지 받을 수 있도록 정의되어 있습니다.
Action<int, int> someDelegate = PrintAddedValue;
someDelegate += PrintSubtractedValue;
someDelegate(100, 30);
void PrintAddedValue(int a, int b) => Console.WriteLine(a + b);
void PrintSubtractedValue(int a, int b) => Console.WriteLine(a - b);
1) 반환값이 있는 대리자
2) Func 키워드에 이어지는 리스트의 마지막 값이 반환값의 타입을 의미합니다.
3) 최대 16개의 매개변수를 받을 수 있도록 정의되어 있습니다.
Func<int, int, int> func = Add;
int a = func(10, 20);
Console.WriteLine(a);
int Add(int a, int b)
{
Console.WriteLine(nameof(Add));
return a + b;
}
1) 이름이 없는 함수를 만들 수 있는 표현식
2) 람다표현식에서는 범위 밖의 변수를 캡쳐해서 사용할 수 있습니다.
3) 함수가 필요하지만, 재사용하지 않는 경우 사용할 수 있습니다.
1) 식 람다 (Expression Lambda)
(input-parameters) => expression
// action에 람다식을 대입하는 예시
Action action = () => Console.WriteLine("Expression Lambda");
2) 구문 람다 (Statement Lambda)
(input-parameters) => {sequence of statements};
// action에 람다식을 대입하는 예시
Action action = () =>
{
Console.WriteLine("Statement Lambda");
};