14 콜백함수

김민영·2023년 1월 17일
0

C# 기초 프로그래밍

목록 보기
8/18

🏹 콜백함수

  1. 콜백함수란?
    : 함수 혹은 프레임워크에 의해 호출되는 함수를 지칭합니다.
    → 일반적으로 프로그래머가 프레임워크 혹은 라이브러리의 함수를 호출하는데, 콜백함수는 프레임워크 혹은 라이브러리가 프로그래머의 함수를 호출합니다.

🏹 대리자 (Delegate)

1. 대리자란

1) 함수를 저장하기위한 타입

void OnCollision(Action action)
{
	action();  // 인수로 받은 함수를 호출
}

→ 프레임워크 혹은 라이브러리가 프로그래머의 함수를 호출(콜백)하기 위해서는 위 예시와 같이 호출할 함수를 인수로 넘길 수 있어야하며, 이는 함수를 저장할 수 있는 알맞은 타입이 존재함을 의미합니다. 함수를 저장하기위한 타입이 바로 대리자(Delegate)입니다.

2) 직접 대리자를 정의할 수도 있지만, Action과 Func 같은 미리 정의된 대리자들을 사용하는 것을 권장합니다.

3) 대리자는 참조타입입니다.

2. 대리자(타입) 정의 방법

delegate void SomeDelegate();
delegate int SomeDelegate2();

delegate(키워드) 저장할 함수의 반환 타입 대리자의 이름(저장할 함수의 매개변수 리스트);

3. 객체 선언 및 함수 대입

▶ 선언 및 대입하기

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) -= 연산자를 통해 대리자에 저장된 함수를 제거할 수 있습니다.

  • 추가하지 않은 함수를 제거하려고 시도하는 경우, 컴파일 오류 및 별도의 동작이 실행되지 않습니다.
  • 추후 배우게 될 observer 패턴에 관련 개념이 이용됩니다.
// 대리자에 추가하지 않은 함수를 제거하려는 예시
SomeDelegate someDelegate = Foo;
someDelegate -= Boo;
someDelegate.Invoke();

void Foo()
{
    Console.WriteLine(nameof(Foo));
}

void Boo()
{
    Console.WriteLine(nameof(Boo));
}

4. 대리자를 이용한 함수 호출

▶ 호출연산자와 .Invoke()

// 1. 호출연산자 사용
someDelegate();  

// 2. Invoke 메소드 사용
someDelegate.Invoke();

→ 대리자를 통해 함수를 간접적으로 호출할 수 있습니다.

▶ NullReferenceException 방지

SomeDelegate someDelegate;
someDelegate();

→ 위와 같이 대리자를 초기화하지 않고 호출연산자를 사용하는 경우, NullReferenceException이 발생합니다.

// 1. if문 이용
if(someDelegate != null)
{
	someDelegate();
}

// 2. null 조건부 연산자 이용
someDelegate? .Invoke();

→ 위와 같은 방법을 이용해 대리자가 null인지 확인한 후 함수를 호출하여 오류를 방지할 수 있습니다.

1) if문을 이용해 대리자가 null이 아닌 경우에만 함수 호출 구문 실행
2) null 조건부 연산자를 통해 대리자가 null인지 확인 후 아닌 경우 Invoke 메소드 실행

  • null 조건부 연산자 (? .)
    : ? 앞의 객체가 null인지 확인한 후, null인 경우 null을 반환 / null이 아닌 경우 ? 다음의 속성이나 메소드를 실행합니다.

▶ 여러개의 함수가 대입되어 있는 경우

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가 출력되는 것을 통해 이전의 함수가 아예 실행되지 않는 것이 아님을 알 수 있습니다.

🏹 Action

1. Action이란

1) 반환값이 없는 대리자

  • 즉, delegate void ~로 정의한 대리자 타입과 같은 의미입니다.

2) 반환값이 없지만 매개변수가 있는 함수를 저장하는 경우 매개변수 리스트를 작성합니다.

// 두개의 int 타입 매개변수롤 받는 함수를 저장하는 경우
Action<int, int> someDelegate = 함수명;

3) Action은 최대 16개의 매개변수까지 받을 수 있도록 정의되어 있습니다.

2. Action 활용 예시

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);

🏹 Func

1. Func란

1) 반환값이 있는 대리자

2) Func 키워드에 이어지는 리스트의 마지막 값이 반환값의 타입을 의미합니다.

3) 최대 16개의 매개변수를 받을 수 있도록 정의되어 있습니다.

2. Func 활용 예시

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;
}

🏹 람다표현식 (Lambda Expression)

1. 람다표현식이란?

1) 이름이 없는 함수를 만들 수 있는 표현식

  • 이름이 없기 때문에 가독성이 다소 떨어집니다.

2) 람다표현식에서는 범위 밖의 변수를 캡쳐해서 사용할 수 있습니다.

  • 범위에 해당하지 않는 변수도(매개변수로 특정 변수를 전달해주지 않아도) 필요한 변수에 접근하여 사용할 수 있습니다.
  • 주소값(reference)을 사용하는 방식이기에 변수의 값을 변경하는 경우 실제 값이 변경됩니다.

3) 함수가 필요하지만, 재사용하지 않는 경우 사용할 수 있습니다.

  • 남용에 유의해야 합니다.

2. 두가지 형태

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");
};

0개의 댓글