[C#] Effective C# 7장. 델리게이트를 이용하여 콜백을 표현하라.
- C#/Effective C# 책 정리
- 2023. 2. 23. 21:00
참조
1. 콜백(Call-Back)이란?
아빠 : "찬우야, 아빠가 책 읽는 동안 마당의 잔디를 깎아주렴."
아들 : "아빠, 우선 마당 청소를 했어요."
아들 : "아빠, 잔디깎기에 기름을 넣었어요."
아들 : "아빠, 잔디깎기가 동작하지 않아요."
아빠 : "그래 내가 동작시켜볼게."
아들 : "아빠, 다 했어요."
- 위의 간단한 대화는 콜백(Call-Back) 을 설명하기 위한 대화입니다.
- 아빠는 아들에게 일을 시켰고, 아들은 수차례에 걸쳐 아빠에게 상태 보고를 합니다.
- 아빠는 상태를 확인하기 위해 잠깐씩 책을 읽는 것을 멈추긴 했지만, 책 읽기를 완전히 중단한 채로 아들이 일을 마치기를 기다리지는 않습니다.
- 아들은 중요하다고 생각하는 상태를 아빠에게 알리면서 도움을 요청하기도 했습니다.
- 콜백(Call-Back) 은 서버가 클라이언트에게 비동기적으로 피드백을 주기 위해서 주로 사용하는 방법입니다.
- 이를 위해 멀티스레딩 기술도 사용되고, 동기적으로 상태를 갱신하는 기법도 활용됩니다.
- C# 에서 콜백은 델리게이트(delegate) 를 이용하여 표현됩니다.
2. 델리게이트의 다양한 활용 기법
- 델리게이트를 이용하면 타입 안정적인 콜백을 정의할 수 있습니다.
- 대부분의 경우 델리게이트는 event와 함께 사용되지만 반드시 그래야 하는 것은 또 아닙니다.
- 여러 클래스가 상호 통신을 수행해야 할 때, 클래스간의 결합도를 낮추기 위해서 인터페이스 말고 델리게이트를 사용할 수도 있습니다.
- 델리게이트는 런타임에 통지 대상을 설정할 수 있습니다.
- 하나의 델리게이트가 여러 메서드에 대한 참조가 가능하기 때문에, 다수의 클라이언트에게 통지를 보낼 수 있습니다.
- 참조 메서드는 Static 메서드일 수도 있고, 인스턴스 메서드일 수도 있습니다.
3. .NET Framework 라이브러리의 델리게이트 형태
- .NET Framework는 Predicate<T>, Action<>, Func<> 와 같은 형태로 자주 사용하는 델리게이트를 정의해 두었습니다.
- Predicate<T> : 조건을 검사하여 bool 값을 반환하는 델리게이트
- Func<> : 여러 개의 매개변수를 받아 특정 타입의 단일 결과값을 반환하는 델리게이트
- Action<> : 여러 개의 매개변수를 받지만 결과값의 타입이 void인 델리게이트
- LINQ는 이러한 개념을 기반으로 만들어 졌습니다.
List<int> numbers = Enumerable.Range(1,200).ToList();
var oddNumbers = numbers.Find(n => n % 2 == 1);
var test = numbers.TrueForAll(n => n < 50);
numbers.RemoveAll(n => n % 2 == 0);
numbers.ForEach(item => Console.WriteLine(item));
- Find() 메서드는 Predicate<int> 형식의 델리게이트를 사용해서 리스트 내에 포함된 요소에 대하여 테스트를 수행합니다.
- TrueForAll() 메서드 또한 비슷한 방법으로 동작하는데, 각 요소를 개별적으로 테스트하되 모든 항목이 테스트를 통과할 경우에만 True를 반환합니다.
- RemoveAll() 메서드는 델리게이트에서 정의한 테스트를 통과한 항목들을 리스트에서 제거합니다.
- ForEach() 메서드는 리스트 내의 각 요소에 대하여 델리게이트로 지정한 동작을 수행합니다.
4. 멀티캐스트란?
- 하나의 델리게이트가 하나의 메서드 호출을 래핑하지 않고 두 개 이상의 메서드를 래핑하여 호출하는 것을 말합니다.
- 예를 들어, Sum 메서드를 참조하는 델리게이트가 있는데, Minus 메서드를 호출하기 위해 다른 델리게이트를 또 만들지 않고, Sum 메서드를 참조하는 델리게이트에 Minus 메서드를 추가하는 것을 의미합니다.
using System;
namespace ConsoleApp6
{
delegate void Del(int a, int b);
class Program
{
static void Sum(int a, int b) => Console.WriteLine($"Result : {a + b}");
static void Minus(int a, int b) => Console.WriteLine($"Result : {a - b}");
static void Main(string[] args)
{
int a = 10;
int b = 5;
Del dele = new Del(Sum);
dele += new Del(Minus);
dele(a, b);
}
}
}
5. 델리게이트 멀티캐스트
- 위에서 멀티캐스트의 개념을 알아 보았습니다.
- 모든 델리게이트는 기본적으로 멀티캐스트가 가능합니다.
- 일반적으로 동일한 타입의 매개변수를 취하더라도 반환 타입이 다른 경우 서로 다른 델리게이트 타입으로 간주하며 , 컴파일러는 이 둘 사이의 형변환을 허용하지 않습니다.
- 멀티개스트 델리게이트는 한 번만 호출하면, 델리게이트 객체에 추가된 모든 대상 함수가 호출됩니다.
- 하지만 이러한 구조에는 두 가지 주의해야 할 부분이 있습니다.
5.1. 주의 1 : 예외 안전성이 좋지 않다.
- 멀티캐스트 델리게이트의 내부 동작 방식은 대상 함수들을 연속적으로 호출하는 형태로 구현됩니다.
- [-델리게이트는 어떤 예외도 잡지 않으며, 따라서 예외가 발생하면 함수 호출 과정이 중단됩니다.-]
5.2. 주의 2 : 마지막 호출 대상 함수의 반환값만이 델리게이트의 반환값이다.
List<ComplicatedClass> container = new List<ComplicatedClass>();
public void LengthOperation(Func<bool> pred)
{
foreach(ComplicatedClass cl in container)
{
cl.DoLengthyOperation();
//사용자가 임의로 중단을 요청했는지 확인
if(false == pred())
return;
}
}
- 위의 LengthOperation 메서드를 멀티캐스트 델리게이트로 사용하면 문제가 발생합니다.
Func<bool> cp = () => CheckWithUser();
cp += () => CheckWithSystem();
c.LengthOperation(cp);
- 델리게이트의 반환값은 체인 마지막으로 호출된 함수의 반환값이 되며, 다른 반환값은 모두 무시됩니다.
- 따라서 위의 예제의 경우 CheckWithUser() 의 반환값은 무시됩니다.
- 이러한 문제를 해결하려면, 델리게이트에 포함된 호출 대상 콜백 함수를 직접 다뤄야 합니다.
public void LengthOperation2(Func<bool> pred)
{
bool bContinue = true;
foreach(ComplicatedClass cl in bContinue)
{
cl.DoLengthyOperation();
foreach (Func<bool> pr in pred.GetInvocationList())
bContinue &= pr();
if (!bContinue)
return;
}
}
- 위처럼 코드를 작성하면 델리게이트에 추가된 개별 메서드가 true를 반환하는 경우에만 다음 메서드에 대한 호출을 이어갈 수 있습니다.
6. 정리
- 델리게이트는 런타임에 콜백을 구성하는 최고의 방법입니다.
- 델리게이트를 사용하면 콜백을 사용해야 하는 클라이언트를 더욱 단순하게 구성할 수 있을 뿐 아니라 런타임에 콜백함수를 구성할 수 있습니다.
- 게다가 하나의 델리게이트에 여러 개의 콜백 함수를 추가할 수도 있습니다.
- .NET 환경에서 콜백이 필요한 경우에는 반드시 델리게이트를 사용합시다.
728x90
'C# > Effective C# 책 정리' 카테고리의 다른 글
[C#] Effective C# 3장. 캐스트보다는 is,as가 좋다. (0) | 2023.02.24 |
---|---|
[C#] Effective C# 5장. 문화권별로 다른 문자열을 생성하려면 FormattableString을 사용하라. (0) | 2023.02.24 |
[C#] Effective C# 9장. 박싱과 언박싱을 최소화하라. (0) | 2023.02.23 |
[Effective C#] 45. 메서드가 실패했음을 알리기 위해서 예외를 이용하라 (0) | 2021.11.30 |
Effective C# 개정판 3판 | 33장 필요한 시점에 필요한 요소를 생성하라. (0) | 2021.10.22 |
이 글을 공유하기