본문 바로가기
Language/C#

C# delegate, Event

by aloHyomora 2024. 4. 19.

C#에서 delegate와 event는 이벤트 기반 프로그래밍에서 중요한 역할을 합니다.

이번 포스팅에선 delegate와 event에 대해 알아봅시다

Delegate

delegate는 메서드에 대한 참조를 보관하는 타입입니다.
다시 말해, delegate는 하나 이상의 메서드를 가리킬 수 있는 변수와 같습니다.

 

delegate를 사용하면 메서드를 다른 메서드의 매개변수로 전달하거나, 메서드를 변수에 할당하는 등의 작업을 할 수 있습니다.

이를 통해 코드의 유연성을 높이고, 이벤트 핸들러나 콜백 메서드 같은 기능을 구현할 때 유용하게 사용됩니다.

 

아래는 기본적인 delegate 코드 예시이다.

public delegate void MyDelegate(string message);

public class Example
{
    public void MyMethod(string message)
    {
        Console.WriteLine(message);
    }

    public void Execute()
    {
        MyDelegate del = new MyDelegate(MyMethod);
        del("Hello, Delegate!");
    }
}

 

Delegate 변수 초기화

Delegate 변수를 초기화하는 가장 기본적인 방법은 다음과 같다.

여기서 'MyDelegate'는 delegate 타입이며, 'MyMethod'는 이 delegate 타입에 맞는 메서드이다.

MyDelegate del = new MyDelegate(MyMethod);

// C# 2.0 이상에서는 delegate 인스턴스를 생성할 때 'new' 키워드를 생략할 수 있으며,
// 다음과 같이 더 간결하게 작성할 수 있다.
MyDelegate del = MyMethod;

 

Delegate에 메서드 추가, 삭제하기

Delegate에 변수에 메서드를 추가하고 싶을 때는 '+=' 연산자를 사용할 수 있습니다. 이렇게 하면 여러 메서드를 delegate의 invocation list에 추가할 수 있으며, delegate가 호출될 때 list에 있는 모든 메서드가 순서대로 실행됩니다.

제거하고 싶을 때는 '-=' 연산자를 사용하면 해당 메서드가 invocation list에서 제거됩니다.

del += AnothorMethod; // 추가하기
del -= AnothorMethod; // 제거하기

 

위 내용을 담은 예시 코드입니다.

public delegate void MyDelegate(string message);

public void MyMethod(string message)
{
	Debug.log($"MyMethod : {message}");
}


public void AnotherMethod(string message)
{
	Debug.log($"AnotherMethod : {message}");
}

// Delegate 초기화 및 사용
MyDelegate del = MyMethod;

// Delegate에 또 다른 메서드 추가
del += AnotherMethod;

// Delegate 호출
del("Hello, World!");

// 출력 결과
// MyMethod : Hello, World!
// AnotherMethod : Hello, World!

 

ChatGPT 

Q :

delegate 변수의 매개 변수가 string message인데, delegate 변수에 등록할 메서드 역시 같은 매개변수 형식을 가져야 하는 거야?

A : 

네, 맞습니다. delegate에 등록할 메서드는 delegate가 정의한 매개변수의 형식반환 타입일치해야 합니다. 이는 delegate의 계약이라고 볼 수 있는데, 이를 통해 delegate를 사용하는 코드가 일관된 방식으로 메서드를 호출할 수 있게 해 줍니다.

 

예를 들어, delegate가 다음과 같이 정의되었다면:

public delegate void MyDelegate(string message);

 

이 delegate 타입의 변수에 등록할 수 있는 메서드는 매개변수로 string 타입의 message를 받고, 반환 타입이 void인 메서드여야 합니다.

즉, delegate에 등록하려는 메서드의 시그니처(signature, 즉 메서드의 반환 타입과 매개변수의 타입 및 순서)가 delegate 정의와 일치해야 합니다.

 

올바른 메서드 예시:

public void CorrectMethod(string message)
{
    Console.WriteLine(message);
}

 

올바르지 않은 메서드 예시(메서드의 반환 타입과 매개변수 타입이 일치하지 않음):

public int IncorrectMethod(int number) // 이 메서드는 MyDelegate에 맞지 않음
{
    Console.WriteLine(number);
    return 13;
}

 

delegate에 메서드를 등록할 때는 이러한 규칙을 따라야 하며, 이를 통해 delegate를 호출하는 코드는 어떤 메서드가 실행될지 명확하게 알 수 있고, 타입 안전성을 보장받을 수 있습니다.

 

Event

우선 시작하기에 앞서, Unity 개발을 하다보니 헷갈리는 개념이 있었다.

Event Listener, Event Handler, Event, AddListener, Invoke, 등등...

 
공부해봅시다
 
 
C#에서 event는 특별한 형태의 delegate를 기반으로 합니다. 
event는 객체의 특정 사건이 발생했을때 다른 객체나 메서드에 신호를 보내는 메커니즘을 제공합니다. 
 
이를 통해 객체 간의 결합도를 낮추면서도 서로 상호작용할 수 있게 해줍니다.
 
event는 주로 UI 동작, 사용자 입력, 또는 다른 중요한 상태 변화를 처리할 때 사용됩니다.
 
 
string 타입의 매개변수를 전달받아 출력하는 이벤트의 예시를 코드로 살펴봅시다

코드의 이해를 돕기 위해 변수 이름을 변경했습니다. 

using System;

// 1. Delegate 정의 : 이벤트 핸들러가 가질 메서드의 시그니처를 정의합니다.
public delegate void MyEventHandler(string message);

public class Publisher
{
	// 2. Event  선언 : delegate를 기반으로 하는 이벤트를 선언합니다.
    public event MyEventHandler MyEvent;
    
    // 이벤트 발생시키는 메서드
    public void RaiseEvent()
    {
    	// 5. Invoke : 이벤트에 등록된 메서드(이벤트 핸들러)를 호출합니다.
        // 이벤트가 null 이 아닐 경우에만 호출하여 NullReferenceException 방지
        MyEvent?.Invoke("Hello from Publisher");
    }
}

public class Subscriber
{
	// 이벤트 핸들러 : 이벤트가 발생했을 때 실행할 메서드를 정의합니다.
    public void OnMyEvent(string message)
    {
    	Console.WriteLine($"Event received with message : {message}");
    }
}

class Program
{
	static void Main(string[] args)
    {
    	Publisher publisher = new Publisher();
        Subscriber subscriber = new subscriber();
        
        // 3. Event 구독 : 이벤트에 이벤트 핸들러(리스너 메서드)를 등록합니다.
        publisher.MyEvent += subscriber.OnMyEvent;
        
        // 이벤트 발생
        publisher.RaiseEvent();
        
        // 4. Event 구독 해제 : 이벤트에서 이벤트 핸들러를 제거합니다.
        publisher.MyEvent -= subscriber.OnMyEvent;
    }
}
 

조금 더 변수와 이벤트 핸들러(메서드)의 이름을 명확하게 구분해봅시다.

  • Delegate 타입 이름: Delegate 타입의 이름은 이 delegate의 용도나 사용될 컨텍스트를 반영할 수 있도록 명명하는 것이 좋습니다. 예를 들어, 문자열 메시지를 처리하는 이벤트의 경우, delegate 타입 이름을 MessageEventHandler로 할 수 있습니다.
public delegate void MessageEventHandler(string message);
  • 이벤트 이름: 이벤트의 이름은 이벤트가 나타내는 동작이나 발생 상황을 기반으로 명명합니다. 예를 들어, 메시지를 처리하는 이벤트라면 MessageReceived라고 할 수 있습니다.
public event MessageEventHandler MessageReceived;
  • 이벤트 핸들러 메서드 이름: 이벤트 핸들러 메서드의 이름은 해당 메서드가 어떤 이벤트에 반응하여 어떤 작업을 수행하는지를 반영할 수 있도록 명명합니다. 예를 들어, 메시지 수신 이벤트에 대한 핸들러라면 HandleMessageReceived라고 할 수 있습니다.
public void HandleMessageReceived(string message)
{
    Console.WriteLine($"Received message: {message}");
}

 

ChatGPT

Q :

등록된 메서드는 이벤트 핸들러이고 이때의 메서드를 이벤트에 구독하는 과정에서 subscriber 객체 자체가 이벤트 리스너로 동작하는 거야

A :

맞습니다. 정리하자면, 이벤트 시스템에서의 "구독(subscribe)" 과정에서 메서드는 이벤트 핸들러 역할을 하고, 해당 메서드를 이벤트에 등록하는 객체(여기서는 Subscriber 객체)는 이벤트 리스너 역할을 합니다.

 

이벤트 핸들러는 실제 이벤트가 발생했을 때 수행될 로직을 포함하는 메서드입니다.

이벤트 리스너는 이벤트의 발생을 "듣고" 있으며, 이벤트 발생 시 등록된 이벤트 핸들러를 호출합니다.

 

이 과정에서 Subscriber 객체는 자신의 메서드(OnMyEvent 메서드)를 특정 이벤트(MyEvent 이벤트)에 등록하여, 이벤트가 발생할 때 해당 메서드가 호출되도록 합니다.

 

따라서, Subscriber 객체는 이벤트 리스너의 역할을 수행하며, OnMyEvent 메서드는 등록된 이벤트 핸들러의 역할을 수행합니다.