대리자란 무엇인가?
C#에서 대리자(delegate)는 객체 지향 프로그래밍의 핵심 개념 중 하나로, 메서드를 참조하기 위한 타입입니다. 대리자를 사용하면 메서드를 변수처럼 저장하고, 매개변수로 전달하거나, 다른 메서드로부터 반환받을 수 있습니다. 이는 프로그램의 유연성을 높여주며, 이벤트 처리, 콜백 함수 구현, 비동기 프로그래밍과 같은 고급 기능을 가능하게 합니다.
대리자를 사용하는 방법
- 대리자 선언 : 대리자 타입을 선언합니다. 이는 대리자가 참조할 메서드의 시그니처(반환 타입 및 매개변수)를 정의합니다.
- 대리자 인스턴스화 : 선언된 대리자 타입을 사용하여 대리자 인스턴스를 생성합니다. 이때, 대리자가 참조할 메서드를 지정합니다.
- 대리자 호출 : 대리자 인스턴스를 통해 메서드를 호출합니다. 대리자를 호출하면 대리자가 참조하는 메서드가 실행됩니다.
using System;
namespace DelegateExample
{
// 1. 대리자 선언
public delegate void MyDelegate(string message);
class Program
{
static void Main(string[] args)
{
// 2. 대리자 인스턴스화
MyDelegate del = new MyDelegate(DisplayMessage);
// 3. 대리자 호출
del("Hello, World!");
}
// 대리자가 참조할 메서드
public static void DisplayMessage(string message)
{
Console.WriteLine(message);
}
}
}
대리자를 사용하는 이유
이 글을 읽으면서 대리자의 사용 필요성이 바로 명확해지지 않을 수 있습니다. 메서드를 직접 호출하는 것이 표면적으로 더 단순하고 직관적으로 보일 수 있기 때문입니다. 그러나 대리자는 .NET 고급 프로그래밍의 핵심을 이루며, 콜백 메커니즘의 구현, 이벤트 처리, 비동기 프로그래밍 등 다양한 영역에서 광범위하게 활용됩니다.
이 글에서는 대리자를 사용하는 것과 사용하지 않는 것의 차이를 콜백 메커니즘 구현과 이벤트 처리의 두 가지 주요 사례를 통해 비교해 볼 것입니다. 이러한 비교를 통해, 대리자를 사용하는 이유와 그것이 프로그래밍에 어떤 가치를 더하는지에 대한 이해를 깊게 할 수 있을 것입니다.
콜백 메커니즘 구현 : 인터페이스 사용
콜백 메커니즘은 프로그램에서 어떤 작업이 완료된 후 특정 동작을 실행할 수 있게 하는 편리한 방법입니다. 대리자를 사용하지 않고 이를 구현하는 방법 중 하나는 인터페이스를 정의하고 사용하는 것입니다.
// 콜백 인터페이스 정의
public interface ICallback
{
void OnComplete();
}
// 콜백 인터페이스를 구현하는 클래스
public class CallbackHandler : ICallback
{
public void OnComplete()
{
Console.WriteLine("Task completed. Now running the callback.");
}
}
public class WithoutDelegate
{
public void ProcessTask(ICallback callback)
{
// 작업 처리
Console.WriteLine("Task is being processed.");
// 콜백 호출
callback.OnComplete();
}
}
class Program
{
static void Main(string[] args)
{
WithoutDelegate wd = new WithoutDelegate();
ICallback callback = new CallbackHandler();
// 작업 실행과 콜백 전달
wd.ProcessTask(callback);
}
}
이 예시에서는 ICallback 인터페이스를 통해 콜백 메커니즘을 구현하고 있습니다. ICallback 인터페이스는 작업 완료 시 호출될 OnComplete 메서드를 정의합니다. CallbackHandler 클래스는 이 인터페이스를 구현하여 실제로 작업 완료 시 수행될 로직을 포함합니다. WithoutDelegate 클래스의 ProcessTask 메서드는 이 인터페이스를 매개변수로 받아, 작업 처리 후 OnComplete 메서드를 호출하여 콜백을 실행합니다.
콜백 메커니즘 구현 : 대리자 사용
대리자를 사용하는 콜백 메커니즘 구현은 프로그래밍에서 매우 강력한 패턴 중 하나입니다. 대리자를 활용함으로써, 메서드를 다른 메서드의 매개변수로 전달할 수 있게 되며, 이는 작업 완료 후 실행될 콜백을 유연하게 지정할 수 있게 해줍니다.
public delegate void Callback();
public class WithDelegate
{
public void ProcessTask(Callback callback)
{
// 작업 처리
Console.WriteLine("Task is being processed.");
// 콜백 호출
callback?.Invoke();
}
}
class Program
{
static void Main(string[] args)
{
WithDelegate wd = new WithDelegate();
// 콜백 메서드 정의
Callback callback = () => Console.WriteLine("Task completed. Now running the callback.");
// 작업 실행과 콜백 전달
wd.ProcessTask(callback);
}
}
위의 예시에서 WithDelegate 클래스는 대리자 Callback을 매개변수로 받는 ProcessTask 메서드를 통해 콜백 메커니즘을 구현합니다. 이 대리자는 작업 완료 후 실행할 메서드를 참조합니다. 프로그램 실행 시, 사용자는 람다 표현식을 사용하여 콜백 메서드를 정의하고, 이를 ProcessTask 메서드에 전달합니다. 작업 처리가 완료되면, 전달된 콜백이 호출되어 추가적인 작업을 수행할 수 있습니다.
이벤트 처리 : 인터페이스 사용
이벤트 처리는 프로그래밍에서 다양한 상황에 대응하기 위해 필수적인 기능입니다. 대리자를 활용하지 않고 이벤트를 처리하는 방법 중 하나는 인터페이스 기반의 콜백 메커니즘을 사용하는 것입니다. 이 접근 방식에서, 이벤트를 구독할 클래스는 특정 인터페이스를 구현해야 하며, 이 인터페이스는 이벤트 발생 시 호출될 메서드를 정의합니다.
// 이벤트 핸들러의 역할을 하는 인터페이스 정의
public interface IEventHandler
{
void HandleEvent();
}
// 인터페이스를 구현하는 클래스 정의 1
public class EventHandler1 : IEventHandler
{
public void HandleEvent()
{
Console.WriteLine("EventHandler1 called.");
}
}
// 인터페이스를 구현하는 클래스 정의 2
public class EventHandler2 : IEventHandler
{
public void HandleEvent()
{
Console.WriteLine("EventHandler2 called.");
}
}
// 이벤트를 발생시키는 클래스 구현 (대리자를 사용하지 않음)
public class WithoutDelegate
{
private List<IEventHandler> subscribers = new List<IEventHandler>();
public void Subscribe(IEventHandler handler)
{
subscribers.Add(handler);
}
public void Unsubscribe(IEventHandler handler)
{
subscribers.Remove(handler);
}
public void TriggerEvent()
{
foreach (var handler in subscribers)
{
handler.HandleEvent();
}
}
}
// 구독자를 등록하고 이벤트를 발생시키는 메인 프로그램
class Program
{
static void Main(string[] args)
{
var publisher = new WithoutDelegate();
var handler1 = new EventHandler1();
var handler2 = new EventHandler2();
publisher.Subscribe(handler1);
publisher.Subscribe(handler2);
// 이벤트 발생
publisher.TriggerEvent();
// 구독 해제
publisher.Unsubscribe(handler1);
// 다시 이벤트 발생
publisher.TriggerEvent();
}
}
이 인터페이스 기반 접근법은 이벤트 처리를 위해 대리자 대신 사용될 수 있으며, 이벤트 발생 시 등록된 모든 구독자 객체의 메서드를 호출하여 이벤트에 반응합니다.
이벤트 처리 : 대리자 사용
반면, 대리자를 사용하는 이벤트 처리 방식은 이벤트를 훨씬 간단하게 정의하고 관리할 수 있게 해줍니다. C#의 event 키워드와 대리자를 사용하면, 이벤트 핸들러의 추가와 제거가 매우 간편해지고, 코드의 가독성이 높아집니다.
public class WithDelegate
{
// 이벤트 정의
public event Action MyEvent;
public void TriggerEvent()
{
// 이벤트 핸들러 호출
MyEvent?.Invoke();
}
}
class Program
{
static void Main(string[] args)
{
WithDelegate wd = new WithDelegate();
// 이벤트 핸들러
Action handler1 = () => Console.WriteLine("Event1 triggered!");
Action handler2 = () => Console.WriteLine("Event2 triggered!");
// 이벤트 구독
wd.MyEvent += handler1;
wd.MyEvent += handler2;
// 이벤트 발생
wd.TriggerEvent();
// 이벤트 구독 해제
wd.MyEvent -= handler2;
// 이벤트 발생
wd.TriggerEvent();
}
}
대리자를 사용하지 않는 경우, 모든 구독자는 동일한 메서드 시그니처를 구현해야 하고, 이벤트 관리를 위해 추가적인 코드가 필요합니다. 대리자를 사용하면, 이벤트 구독과 발행 과정이 더 유연하고 간결해지며, 다양한 시그니처의 메서드를 쉽게 처리할 수 있습니다.
'C#' 카테고리의 다른 글
C# Early Return 패턴이란? (0) | 2024.02.08 |
---|---|
C# 스토어드 프로시저의 매개변수 이름 추출하기 (0) | 2024.01.31 |
C# WPF에 WebView2 연결하기 (0) | 2024.01.19 |
WPF MVVM 패턴, 왜 사용할까? (0) | 2024.01.17 |
오프셋(Offset)이란 무엇인가? (39) | 2024.01.17 |