C#에서 델리게이트(delegate)는 함수를 가리키는 참조 타입으로, C++의 함수 포인터와 유사하지만 타입 안전성과 객체 지향적 특성을 갖춘 강력한 도구입니다. 이 가이드에서는 델리게이트를 활용해 함수 포인터처럼 사용하는 방법을 단계별로 설명하며, 선언부터 고급 활용까지 다룹니다.
1. 델리게이트란?
델리게이트는 특정 시그니처(반환 타입과 매개변수)를 가진 메서드를 참조할 수 있는 타입입니다. 이를 통해 메서드를 변수처럼 전달하거나 호출할 수 있으며, 이벤트 처리, 콜백 함수, 비동기 프로그래밍 등에 유용하게 사용됩니다.
2. 델리게이트 선언하기
델리게이트를 사용하려면 먼저 타입을 정의해야 합니다. delegate
키워드를 사용하며, 반환 타입과 매개변수를 지정합니다.
// 매개변수가 없는 void 반환 델리게이트
public delegate void MyDelegate();
// 두 개의 int 매개변수를 받고 int를 반환하는 델리게이트
public delegate int MathOperation(int a, int b);
3. 델리게이트 인스턴스 생성하기
델리게이트 타입을 선언한 후, 인스턴스를 만들어 특정 메서드를 참조하게 합니다.
public class Example
{
// 참조할 메서드 정의
public void SayHello()
{
Console.WriteLine("안녕하세요!");
}
public static void Main()
{
Example ex = new Example();
// 델리게이트 인스턴스 생성
MyDelegate del = ex.SayHello; // new MyDelegate() 생략 가능
}
}
4. 델리게이트 호출하기
델리게이트 인스턴스를 생성하면 이를 호출해 참조된 메서드를 실행할 수 있습니다.
del(); // "안녕하세요!" 출력
5. 멀티캐스트 델리게이트
델리게이트는 여러 메서드를 동시에 참조할 수 있는 멀티캐스트 기능을 지원합니다. +=
로 메서드를 추가하고, -=
로 제거할 수 있습니다.
public class Example
{
public void SayHello() => Console.WriteLine("안녕하세요!");
public void SayGoodbye() => Console.WriteLine("안녕히 가세요!");
public static void Main()
{
Example ex = new Example();
MyDelegate del = ex.SayHello;
del += ex.SayGoodbye;
del(); // "안녕하세요!"와 "안녕히 가세요!" 순서대로 출력
}
}
6. 익명 메서드와 람다 표현식 활용
별도의 메서드 정의 없이 익명 메서드나 람다 표현식으로 델리게이트를 간결하게 사용할 수 있습니다.
// 익명 메서드
MyDelegate del1 = delegate { Console.WriteLine("익명 메서드"); };
// 람다 표현식
MyDelegate del2 = () => Console.WriteLine("람다 표현식");
del1(); // "익명 메서드" 출력
del2(); // "람다 표현식" 출력
7. 제네릭 델리게이트 사용하기
C#에서는 Action
, Func
, Predicate
같은 제네릭 델리게이트를 제공해 별도의 선언 없이도 유연하게 사용할 수 있습니다.
Action<T>
: 반환 값이 없는 메서드Func<T, TResult>
: 반환 값이 있는 메서드Predicate<T>
: bool 반환 메서드
// Action 예제
Action<string> print = s => Console.WriteLine(s);
print("안녕, Action!"); // "안녕, Action!" 출력
// Func 예제
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 5)); // 8 출력
8. 델리게이트와 이벤트 연결하기
델리게이트는 이벤트 핸들러로 자주 사용됩니다. 이벤트는 외부에서 직접 호출을 막고, 핸들러 추가/제거만 허용합니다.
public class Button
{
public delegate void ClickHandler(object sender, EventArgs e);
public event ClickHandler Click;
public void OnClick()
{
Click?.Invoke(this, EventArgs.Empty); // null 체크 후 호출
}
}
Button btn = new Button();
btn.Click += (sender, e) => Console.WriteLine("버튼 클릭됨!");
btn.OnClick(); // "버튼 클릭됨!" 출력
9. 비동기 프로그래밍과 델리게이트
과거에는 BeginInvoke
와 EndInvoke
로 비동기 호출을 했지만, .NET Core에서는 지원되지 않습니다. 현대적인 방식은 async
와 await
를 사용하는 것입니다.
Func<Task<int>> asyncOp = async () =>
{
await Task.Delay(1000);
return 42;
};
int result = await asyncOp();
Console.WriteLine(result); // 42 출력
10. 델리게이트 사용 시 주의사항
- null 체크: 호출 전
?.Invoke()
로 null 여부를 확인하세요. - 멀티캐스트 반환 값: void가 아닌 경우 마지막 메서드의 반환 값만 반환됩니다.
- 스레드 안전성: 멀티스레드 환경에서는 동기화가 필요할 수 있습니다.
요약
델리게이트는 C#에서 함수 포인터처럼 메서드를 참조하고 호출하는 강력한 도구입니다. 선언, 인스턴스 생성, 호출을 기본으로, 멀티캐스트, 람다 표현식, 제네릭 델리게이트 등을 활용하면 코드를 유연하고 간결하게 작성할 수 있습니다. 이벤트 처리와 비동기 작업에서도 핵심 역할을 하며, 타입 안전성과 객체 지향적 특성 덕분에 유지보수성과 재사용성을 높일 수 있습니다.
이벤트와 델리게이트는 C#에서 객체 간 통신을 느슨하게 결합된 방식으로 처리할 수 있게 해주는 강력한 도구입니다. 아래 예제들은 다양한 상황에서 이를 활용하는 방법을 보여줍니다.
예제 8: 타이머 이벤트와 델리게이트
타이머가 주기적으로 이벤트를 발생시키고, 이를 구독한 핸들러가 실행되는 예제입니다.
using System;
public class Timer
{
// 델리게이트 정의
public delegate void TickHandler(object sender, EventArgs e);
// 이벤트 선언
public event TickHandler Tick;
// 타이머 시작 메서드
public void Start(int seconds)
{
for (int i = 0; i < seconds; i++)
{
System.Threading.Thread.Sleep(1000); // 1초 대기
Tick?.Invoke(this, EventArgs.Empty); // 이벤트 발생
}
}
}
public class Program
{
public static void Main()
{
Timer timer = new Timer();
// 이벤트 핸들러 등록
timer.Tick += (sender, e) => Console.WriteLine("틱! 1초가 지났습니다.");
// 타이머 시작 (3초 동안 실행)
timer.Start(3);
// 출력:
// 틱! 1초가 지났습니다.
// 틱! 1초가 지났습니다.
// 틱! 1초가 지났습니다.
}
}
설명: Timer
클래스에서 Tick
이벤트를 정의하고, Start
메서드가 주기적으로 이벤트를 호출합니다. 구독자는 매초 메시지를 출력합니다.
예제 9: 이벤트에 조건 추가
특정 조건에서만 이벤트가 호출되도록 설정한 예제입니다.
using System;
public class Stock
{
public delegate void PriceChangedHandler(string stockName, double newPrice);
public event PriceChangedHandler PriceChanged;
private double _price;
public double Price
{
get => _price;
set
{
if (_price != value && value > 100) // 가격이 100 이상일 때만 이벤트 발생
{
_price = value;
PriceChanged?.Invoke("ABC 주식", _price);
}
}
}
}
public class Program
{
public static void Main()
{
Stock stock = new Stock();
// 이벤트 핸들러 등록
stock.PriceChanged += (name, price) =>
Console.WriteLine($"{name}의 가격이 {price}로 변경되었습니다!");
stock.Price = 50; // 이벤트 발생 안 함 (100 미만)
stock.Price = 150; // "ABC 주식의 가격이 150로 변경되었습니다!" 출력
}
}
설명: Stock
클래스에서 가격이 100을 초과할 때만 PriceChanged
이벤트를 발생시키며, 주식 이름과 새로운 가격을 핸들러에 전달합니다.
예제 10: 이벤트 발생자 식별
이벤트를 발생시킨 객체를 구체적으로 식별할 수 있도록 sender를 활용한 예제입니다.
using System;
public class Device
{
public string Name { get; }
public delegate void StatusChangedHandler(object sender, string status);
public event StatusChangedHandler StatusChanged;
public Device(string name)
{
Name = name;
}
public void ChangeStatus(string newStatus)
{
StatusChanged?.Invoke(this, newStatus);
}
}
public class Program
{
public static void Main()
{
Device device1 = new Device("장치1");
Device device2 = new Device("장치2");
// 이벤트 핸들러 등록
device1.StatusChanged += (sender, status) =>
Console.WriteLine($"{((Device)sender).Name} 상태: {status}");
device2.StatusChanged += (sender, status) =>
Console.WriteLine($"{((Device)sender).Name} 상태: {status}");
device1.ChangeStatus("켜짐"); // "장치1 상태: 켜짐" 출력
device2.ChangeStatus("꺼짐"); // "장치2 상태: 꺼짐" 출력
}
}
설명: sender
를 통해 이벤트를 발생시킨 Device
객체를 식별하고, 해당 객체의 Name
속성을 출력합니다.
예제 11: 이벤트와 상태 패턴 결합
상태 변화를 이벤트로 알리는 예제입니다.
using System;
public enum TrafficLightState { Red, Yellow, Green }
public class TrafficLight
{
public delegate void StateChangedHandler(TrafficLightState newState);
public event StateChangedHandler StateChanged;
private TrafficLightState _state;
public TrafficLightState State
{
get => _state;
set
{
if (_state != value)
{
_state = value;
StateChanged?.Invoke(_state);
}
}
}
}
public class Program
{
public static void Main()
{
TrafficLight light = new TrafficLight();
// 이벤트 핸들러 등록
light.StateChanged += state =>
Console.WriteLine($"신호등 상태: {state}");
light.State = TrafficLightState.Red; // "신호등 상태: Red" 출력
light.State = TrafficLightState.Green; // "신호등 상태: Green" 출력
light.State = TrafficLightState.Yellow; // "신호등 상태: Yellow" 출력
}
}
설명: TrafficLight
클래스에서 상태가 변경될 때마다 StateChanged
이벤트를 발생시켜 새로운 상태를 알립니다.
요약
위 예제들은 이벤트와 델리게이트를 활용한 다양한 시나리오를 다룹니다:
- 타이머 이벤트: 주기적인 이벤트 발생.
- 조건 기반 이벤트: 특정 조건에서만 이벤트 호출.
- 발생자 식별:
sender
를 활용해 이벤트를 발생시킨 객체 식별. - 상태 패턴과의 결합: 상태 변화를 이벤트로 통지.
이처럼 이벤트와 델리게이트는 유연성과 확장성을 제공하며, 객체 간의 느슨한 결합을 유지하면서도 강력한 통신 메커니즘을 구현할 수 있게 해줍니다.
답글 남기기