📕델리게이트(Delegate, 대리자)
MS 기술 문서에서는 델리게이트를 이렇게 정의하고 있다.
A delegate is a type that represents references to methods with a particular parameter list and return type
직역하자면 메서드(특정 매개 변수 목록과 반환 타입을 가진)에 대한 참조를 나타내는 형식이다. 즉, 델리게이트를 통해서 메서드를 참조할 수 있다는 뜻이다. 그렇다면 델리게이트의 핵심 기능은 무엇일까? 마찬가지로 MS 기술 문서의 설명을 가져왔다.
Delegates are used to pass methods as arguments to other methods.
델리게이트는 메서드를 인자로 전달하는 데에 쓰인다.
델리게이트는 C++의 함수 포인터와 비슷하지만 인스턴스를 생성해 사용한다는 점이 다르다. 또한 함수 포인터는 하나의 함수 주소만 담을 수 있지만 델리게이트는 여러 메서드를 담을 수 있다는 차이도 있다.
📖델리게이트의 선언과 사용
델리게이트를 사용하려면 먼저 델리게이트를 선언해 주어야 한다.
- [접근제한자] [delegate] [반환형] [델리게이트 이름] [(매개 변수 목록)]
public delegate int PerformCalculation(int x, int y);
사용할 때는 델리게이트 인스턴스를 통해 직접 호출하듯이 사용한다.
public class DelegateTest
{
public delegate void Del(string message); //델리게이트 선언
public static void DelegateMethod(string message) //델리게이트에 담을 메서드
{
Console.WriteLine(message);
}
Del handler = DelegateMethod; // 인스턴스 생성 및 메서드 캡슐화
}
메서드 DelegateMethod가 델리게이트 인스턴스인 handler에 담겼다. 이제 handler를 호출하는 것으로 DelegateMethod를 호출할 수 있다.
handler("Hello World");
📌멀티캐스트 호출
델리게이트를 사용하면 한 번에 여러 메서드를 래핑하고 호출하는 것도 가능하다.
public class MethodClass
{
public void Method1(string message) { }
public void Method2(string message) { }
}
MethodClass를 만들고 그 안에 Method1과 Method2를 만들었다. 그리고 Del 인스턴스를 이용해 Method1과 Method2, 그리고 DelegateMethod를 각각 래핑해주었다.
var obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;
//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;
델리게이트에 메서드를 할당할 때는 더하기 또는 더하기 대입 연산자('+' 또는 '+=')를 사용하며, 위 코드에서는 두 가지 모두 사용했다. 이때 주의할 점은 델리게이트에 메서드를 할당할 때 이전에 입력된 메서드는 모두 사라진다는 것이다. 따라서 만약 위 코드에서 'allMethodsDelegate += d3;' 대신 'allMethodsDelegate = d3;'를 썼다면 최종적으로 allMethodsDelegate에는 d3만 할당되었을 것이다.
📌무명 메서드
델리게이트에 할당할 메서드가 델리게이트를 통해서만 호출된다면 굳이 메서드를 생성하지 않고도 실행문을 코드 블록 형태로 할당할 수 있다. 이렇게 하면 메서드를 생성하지 않아도 되기에 불필요한 오버헤드가 발생하지 않는다.
무명 메서드의 형식은 다음과 같다.
- [delegate] [( 매개 변수 목록 )] [{ 실행문 }]
Del d4 = delegate (int a, int b) { return a + b; };
Console.WriteLine(d4(3, 4)); // output: 7
📖Action 대리자, Func 대리자
델리게이트를 사용하려면 목적에 맞는 형태로 델리게이트를 선언해줘야 한다. 이는 무명 메서드를 사용할 때도 마찬가지이다. 그러나 무명 메서드는 델리게이트를 쓸 때만 쓰이므로 무명 메서드 때문에 굳이 새로 델리게이트를 선언하는 건 비효율적이다. 다행히 .NET 프레임워크에는 이를 해결할 Action 델리게이트와 Func 델리게이트가 미리 선언되어 있다. 참조하는 메서드가 반환 형식이 없다면 Action을, 있다면 Func을 쓴다.
다음은 각각 Action과 Func의 선언부이다.
- Action의 선언
public delegate void Action();
public delegate void Action<in T>(T arg);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
// Other variations removed for brevity.
- Func의 선언
public delegate TResult Func<out TResult>();
public delegate TResult Func<in T1, out TResult>(T1 arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
// Other variations removed for brevity
💡 Action과 Func 역시 델리게이트이기 때문에 무명 메서드로 사용할 수도 있다.(참고)
📕이벤트(Event)
이벤트란 객체가 작업 실행을 알리기 위해 보내는 메세지로, 사용자 조작 혹은 프로그램의 논리에 따라 발생한다. 이때 이벤트를 발생시키는 객체를 이벤트 전송자라고 하며 이벤트를 처리하는 메서드를 이벤트 핸들러(EventHandler)라고 한다.
이벤트 전송자는 이벤트를 발생시키만 할 뿐 어떤 객체에서 이벤트를 처리하는지는 모른다. 따라서 이벤트 핸들러 쪽에서 해당 이벤트를 구독해야 한다. 이벤트를 구독한다는 건 이벤트 전송자가 보내는 이벤트를 수신하겠다는 의미이며, 이때 한 이벤트를 여러 이벤트 핸들러가 동시에 구독하는 것도 가능하다.
🔎구체적인 예가 궁금하다면 '더보기' 클릭
게임에서 사용자가 마우스를 클릭했을 때 일어나는 상황을 떠올리면 쉽다. 방향키를 누르면 캐릭터가 이동하는 RPG 게임이 있다고 하자. 이때 '방향키 입력'은 이벤트 발생이고 '캐릭터 이동'은 이벤트 핸들러가 이벤트를 처리한 결과이다. 만약 방향키를 눌렀을 때 캐릭터의 스태미나도 감소하게 하고 싶다면, '스태미나 감소'를 처리하는 이벤트 핸들러를 만들어 '방향키 입력' 이벤트를 추가로 구독하면 된다.
📖이벤트 선언과 사용
이벤트를 선언하려면 델리게이트를 선언해야 한다. 이벤트가 델리게이트를 기반으로 만들어진 기능이기 때문이다. 이벤트 선언 방법은 다음 코드에서 확인할 수 있다.
📌코드: 이벤트 전송자
public delegate void Notify(); //delegate
public class ProcessBusinessLogic
{
public event Notify ProcessCompleted; //event
}
먼저 Notify라는 이름으로 델리게이트를 선언하고 이벤트 전송자인 ProcessBusinessLogic 클래스를 만들었다. 해당 클래스 내에는 'event' 키워드를 사용해 ProcessCompleted라는 이름으로 Notify 인스턴스를 생성해 주었다. 이때 Notify가 public으로 선언되었으므로 ProcessCompleted도 public이어야 한다.
public delegate void Notify(); // delegate
public class ProcessBusinessLogic
{
public event Notify ProcessCompleted; // event
public void StartProcess()
{
Console.WriteLine("Process Started!");
// some code here..
OnProcessCompleted();
}
protected virtual void OnProcessCompleted() //protected virtual method
{
//if ProcessCompleted is not null then call delegate
ProcessCompleted?.Invoke();
}
}
위 코드를 보면 StartProcess() 메서드의 마지막 줄에서 OnProcessCompleted() 메서드를 호출하는 걸 볼 수 있다. OnProcessCompleted()는 위에서 선언한 ProcessCompleted를 호출하는 메서드이다. 일반적으로 이벤트 호출 메서드는 접근 제한자 protected와 virtual 키워드를 사용해 가상 메서드로 정의하는 게 좋다. 파생 클래스에서 이벤트 발생 로직을 덮어 쓸 수 있어야 하기 때문이다. 단, 이때 파생 클래스는 반드시 부모 클래스의 이벤트 호출 메서드(위 코드에서는 'OnProcessCompleted()')를 항상 호출해야 한다.
📌코드: 이벤트 구독자
class Program
{
public static void Main()
{
ProcessBusinessLogic bl = new ProcessBusinessLogic();
bl.ProcessCompleted += bl_ProcessCompleted; // register with an event
bl.StartProcess();
}
// event handler
public static void bl_ProcessCompleted()
{
Console.WriteLine("Process Completed!");
}
}
ProcessCompleted 이벤트에 bl_ProcessCompleted()라는 이벤트 핸들러를 추가했다. 이때 연산자는 +=를 쓴다. 이후 이벤트 전송자 클래스의 StartProcess()가 실행되면 StartProcess() 내의 OnProcessCompleted()가 실행되고, 등록된 이벤트가 있다면 이벤트를 실행한다.
지금까지 살펴본 코드를 그림으로 표현하면 다음과 같다.
📌미리 정의된 이벤트 핸들러
NET 프레임워크는 델리게이트를 선언하지 않고도 이벤트를 사용할 수 있도록 EventHandler 델리게이트와 EventHandler<TEventArgs> 델리게이트를 제공한다. EventHandler 델리게이트는 이벤트 정보가 필요 없는 상황에서 사용하며 EventHandler<TEventArgs> 델리게이트는 이벤트 정보가 필요한 상황에서 사용한다.
두 델리게이트를 사용한 코드는 '더보기'에서 확인
🔎EventHandler
class Program
{
public static void Main()
{
ProcessBusinessLogic bl = new ProcessBusinessLogic();
bl.ProcessCompleted += bl_ProcessCompleted; // register with an event
bl.StartProcess();
}
// event handler
public static void bl_ProcessCompleted(object sender, EventArgs e)
{
Console.WriteLine("Process Completed!");
}
}
public class ProcessBusinessLogic
{
// declaring an event using built-in EventHandler
public event EventHandler ProcessCompleted;
public void StartProcess()
{
Console.WriteLine("Process Started!");
// some code here..
OnProcessCompleted(EventArgs.Empty); //No event data
}
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}
}
🔎EventHandler<TEventArgs>
class Program
{
public static void Main()
{
ProcessBusinessLogic bl = new ProcessBusinessLogic();
bl.ProcessCompleted += bl_ProcessCompleted; // register with an event
bl.StartProcess();
}
// event handler
public static void bl_ProcessCompleted(object sender, bool IsSuccessful)
{
Console.WriteLine("Process " + (IsSuccessful? "Completed Successfully": "failed"));
}
}
public class ProcessBusinessLogic
{
// declaring an event using built-in EventHandler
public event EventHandler<bool> ProcessCompleted;
public void StartProcess()
{
try
{
Console.WriteLine("Process Started!");
// some code here..
OnProcessCompleted(true);
}
catch(Exception ex)
{
OnProcessCompleted(false);
}
}
protected virtual void OnProcessCompleted(bool IsSuccessful)
{
ProcessCompleted?.Invoke(this, IsSuccessful);
}
}
🔖인용한 사이트
Events in C# (tutorialsteacher.com)
Events in C#
C# - Events An event is a notification sent by an object to signal the occurrence of an action. Events in .NET follow the observer design pattern. The class who raises events is called Publisher, and the class who receives the notification is called Subscr
www.tutorialsteacher.com
이벤트 처리 및 발생
대리자 모델을 기반으로 한 .NET 이벤트를 처리하고 발생시키는 방법을 알아봅니다. 이 모델은 구독자가 공급자를 등록하거나 공급자의 알림을 수신할 수 있도록 합니다.
docs.microsoft.com
'C#' 카테고리의 다른 글
구조체(Struct)와 클래스(Class) (0) | 2022.06.17 |
---|---|
생성자 (0) | 2022.06.17 |
프로퍼티(Property, 속성) (0) | 2022.06.14 |
람다 식(Lambda Expression) (0) | 2022.06.12 |
Class (0) | 2022.03.18 |