1. 참고 자료

더보기

1. 참고 링크

MSDN

 

 

2. 학습목표

 

-

 

 

3. 학습순서

  1. Callback 개념과 Delegate 용어
  2. 대리자를 사용하는 이유
  3. Delegate를 선언하고 사용하는 방법
  4. 일반화 대리자를 사용하는 방법
  5. Multicast Delegete (대리차 체인)

 


4. 대리자 사용 목적

  1. 메서드를 변수처럼 사용하기 위해
  2. 이벤트 처리
  3. 메서드 호출을 런타임에 동적으로 변경할 때
  4. 코드의 결합도 낮춤 (느슨한 결합, Loosely Coupled)
  5. 여러 개의 메서드를 하나의 대리자에 연결, 한 번에 실행 가능
  6. LINQ, 람다 표현식에서의 활용
  7. 비동기 호출 (BeginInvoke/EndInvoke)

 

 

 

 

 

2. 대리자 사용법

더보기

1. 기본 문법

// 반환형과 매개변수 목록을 포함한 선언
// 함수 헤더에 delegate 로 수식하면 된다.
delegate 반환형 대리자변수명(매개변수목록);
//[.NET Frame 1.0]
delegate 반환형 대리자변수명 = new delegate(함수명);
//[.NET Frame 2.0]
delegate 반환형 대리자변수명 = 함수명;

 

 

2. 예시

// 기본 사용법
delegate int Calculate(int a, int b); // 두 int를 받아 int 반환
int Add(int x, int y) => x + y;
Calculate calc = Add;
int result = calc(3, 5); // 결과: 8
// 멀티케스트 예시
delegate void Notify();
void Hello() => Console.WriteLine("Hello");
void Bye() => Console.WriteLine("Bye");
Notify notify = Hello;
notify += Bye;
notify();
// 출력:
// Hello
// Bye

 

 

3. 함수의 매개변수로 사용 예시

 

매개변수 사용법 1. 매개변수 자리에 변수를 사용해 값 전달

int Add(int a, int b){
int result = 0;
return a + b; // 매개변수로 전달받은 두 값 계산
}

 

매개변수 사용법  2. 매개변수 자리에 객체를 사용해 값 전달

class object_ {
public int a;
public int b;
}
int Add_obj(object_ obj) {
int result = 0;
return (int)obj.a + (int)obj.a; // 객체로 전달받은 두 값 계산
}

 

매개변수 사용법  3. 매개변수에 대리자를 사용해 로직 전달

delegate int A(int a, int b);
int Add_dele(A func){
int result = 0;
return func(1, 2); // 매개변수로 전달받은 대리자를 사용한 계산
}

 

 

 

 

 

3. 느슨한 결합 구조

더보기

1. 느슨한 결합 (Loosely Coupled) 구조란?

 

호출하는 쪽은 '무엇을 호출하는지'를 모르고도 기능을 실행할 수 있습니다.

 

 

2. 시나리오

  • Notifier는 메시지를 알리는 역할
  • Logger와 EmailSender는 그 메시지를 처리
  • Notifier는 직접 Logger나 EmailSender를 알지 못함결합도 낮음

 

 

3. 소스코드

using System;
namespace LooselyCoupledExample
{
// 1. 대리자 정의
delegate void NotifyHandler(string message);
// 2. 알림을 보내는 클래스 (발신자)
class Notifier
{
public NotifyHandler? OnNotify;
public void DoSomething()
{
Console.WriteLine("Something happened!");
OnNotify?.Invoke("Event triggered in Notifier.");
}
}
// 3. 로그를 기록하는 클래스 (수신자 1)
class Logger { public void Log(string msg) => Console.WriteLine($"[Logger] {msg}"); }
// 4. 이메일을 보내는 클래스 (수신자 2)
class EmailSender { public void SendEmail(string msg) => Console.WriteLine($"[Email] {msg}"); }
class Program
{
static void Main()
{
var notifier = new Notifier();
var logger = new Logger();
var email = new EmailSender();
// 어떤 메서드가 호출될지는 Notifier는 모름
notifier.OnNotify += logger.Log;
notifier.OnNotify += email.SendEmail;
notifier.DoSomething();
}
}
}

 

 

4. 참고

  • 대리자를 통해 호출자는 피호출자(구현체)에 의존하지 않음
  • 코드 수정 없이 기능을 교체하거나 확장할 수 있음
  • SOLID 원칙을 기반으로 이해할 것
  • 이런 구조는 전략 패턴(Strategy Pattern), 옵저버 패턴(Observer Pattern)의 기초가 됨

 

 

5. 이후 이벤트와 SOLID 학습 후 다시 참고할 것

using System;
namespace LooselyCoupledExample
{
// 1. 대리자 정의 (이벤트 기반 사용 시 필요 없음, Action<string> 사용 가능)
// delegate void NotifyHandler(string message);
// 2. 알림을 보내는 클래스 (발신자)
class Notifier
{
public event Action<string>? OnNotify;
public void DoSomething()
{
Console.WriteLine("Something happened!");
OnNotify?.Invoke("Event triggered in Notifier.");
}
}
// 3. 인터페이스 기반 수신자 정의
interface IReceiver
{
void Handle(string msg);
}
// 4. 로그를 기록하는 클래스 (수신자 1)
class Logger : IReceiver
{
public void Handle(string msg) => Console.WriteLine($"[Logger] {msg}");
}
// 5. 이메일을 보내는 클래스 (수신자 2)
class EmailSender : IReceiver
{
public void Handle(string msg) => Console.WriteLine($"[Email] {msg}");
}
class Program
{
static void Main()
{
var notifier = new Notifier();
// 어떤 메서드가 호출될지는 Notifier는 모름 (느슨한 결합)
IReceiver logger = new Logger();
IReceiver email = new EmailSender();
// 이벤트에 메서드 연결
notifier.OnNotify += logger.Handle;
notifier.OnNotify += email.Handle;
notifier.DoSomething();
}
}
}

 

 

소스코드를 대리자에서 이벤트 기반 구조로 수정했습니다.

인터페이스(IReceiver)를 통해 느슨한 결합을 유지하는 형태로 리팩토링했습니다.

 

Notifier는 Logger나 EmailSender에 직접 의존하지 않고,

IReceiver 인터페이스를 통해 동작을 호출합니다.

 

 

4. Action, Func, Predicate

더보기

1. Action<T> 예제

  • 반환값이 없는 메서드 호출에 사용 (void)
using System;
class ActionExample
{
static void PrintMessage(string msg) => Console.WriteLine($"[Action] {msg}");
static void Main()
{
Action<string> show = PrintMessage;
show("Hello from Action!");
}
}

 

 

2. Func<T, TResult> 예제

  • 반환값이 있는 메서드 호출에 사용
using System;
class FuncExample
{
static int Add(int a, int b) => a + b;
static void Main()
{
Func<int, int, int> sum = Add;
int result = sum(3, 4);
Console.WriteLine($"[Func] Result = {result}");
}
}

 

 

3. Predicate<T> 예제

  • bool을 반환하는 조건식 메서드에 사용
using System;
using System.Collections.Generic;
class PredicateExample
{
static bool IsEven(int num) => num % 2 == 0;
static void Main()
{
Predicate<int> checkEven = IsEven;
List<int> nums = new List<int> { 1, 2, 3, 4, 5 };
List<int> evens = nums.FindAll(checkEven);
Console.WriteLine("[Predicate] Even numbers: " + string.Join(", ", evens));
}
}

 

 

 

 

 

5. 대리자에서 이벤트로

더보기

1. 소스코드 /w 주석

// 'Callback'이라는 이름의 대리자를 선언합니다.
// 이 대리자는 string 형식의 매개변수를 받아 반환값이 없는 메서드를 참조할 수 있습니다.
delegate void Callback(string message);
// Button 클래스: Click_Signal이라는 대리자 타입의 이벤트 역할 필드를 가집니다.
class Button
{
// 외부에서 메서드를 연결할 수 있는 대리자 필드
public Callback? Click_Signal; // null 허용으로 초기에는 연결된 메서드가 없을 수 있음
}
// Label 클래스: 화면에 메시지를 출력하는 역할
class Label
{
string name; // 레이블의 이름
// 생성자: Label 객체 생성 시 이름을 초기화
public Label(string name) => this.name = name;
// 슬롯 함수: 버튼의 대리자를 통해 호출될 메서드
public void Slot_Function(string message) =>
Console.WriteLine($"{name}.SomethingHappened : {message}"); // 메시지 출력
}
// 버튼을 클릭하면, 레이블에 출력되는 동작을 가정합니다.
class MainApp
{
static void Main(string[] args)
{
// Click_Signal 대리자를 가진 Button 객체를 생성합니다.
// conn.Click_Signal은 나중에 특정 메서드(콜백 함수)를 참조합니다.
var conn = new Button();
// 레이블을 만들고 이름을 "Label"로 설정합니다.
var lbl = new Label("Label");
// Click_Signal이 발생하면, 동작할 레이블의 slot 함수와 연결합니다.
conn.Click_Signal += lbl.Slot_Function;
// conn 객체의 Click_Signal 대리자가 실행되면(버튼이 눌리거나, 값이 입력되면)
// label의 Slot_Function 함수가 "You've got mail."을 전달받아 실행됩니다.
conn.Click_Signal("You've got mail.");
}
}

 

 

 2. 소스코드

using System;
namespace DelegateTest10
{
delegate void Callback(string message);
class Button
{
public Callback? Click_Signal;
}
class Label
{
string name;
public Label(string name) => this.name = name;
public void Slot_Function(string message) =>
Console.WriteLine($"{name}.SomethingHappened : {message}");
}
class MainApp
{
static void Main(string[] args)
{
var conn = new Button();
var lbl = new Label("Label");
conn.Click_Signal += lbl.Slot_Function;
conn.Click_Signal("You've got mail.");
}
}
}