프로그래밍 언어 및 IT 정보/C#

c# 대리자(Delegate)와 이벤트(Event) 개념잡기

Himer_torr 2023. 6. 4. 00:30
반응형

오늘은 이벤트(Event)와 대리자(delegate)에 대해 글을 써보려 한다.

C#의 핵심 개념 중 하나로, 이를 이용하여 이벤트 기반 프로그래밍을 구현할 수 있다.

 

1. 대리자(Delegates)

대리자는 C#의 중요한 개념 중 하나로, 메서드를 참조하는 타입이다. 대리자를 사용하면 매서드를 변수처럼 

전달하고 호출할 수 있습니다. 대리자는 다음과 같은 상황에서 유용하게 사용된다.

 

1. 이벤트 처리 : 이벤트와 함께 대리자를 사용하여 이벤트 핸들러를 등록하고 호출할 수 있다.

    이벤트가 발생하면 등록된 모든 이벤트 핸들러가 호출되어 처리된다.

2. 비동기 프로그래밍 : 대리자를 사용하여 비동기 작업을 처리할 수 있다. BeginInvoke 및 EndInvoke 매서드를 사용하여

    대리자를 비동기적으로 호출하고 결과를 처리할 수 있다.

3. 콜백 매서드 : 대리자를 사용하여 다른 매서드의 콜백 함수를 전달할 수 있다. 비동기 작업이 완료되면 콜백 함수가 호출

    되어 추가 작업을 처리할 수 있다.

 

대리자 선언은 다음과 같이 할 수 있다.

delegate void MyDelegate(string message);

위의 선언을 보면 'MyDelegate'는 'void' 반환형을 가지고 'string'을 인수로 받는 대리자이다.

이제 이 대리자를 사용하여 매서드를 참조하고 호출할 수 있다.

MyDelegate delegate1 = Method1;
MyDelegate delegate2 = Method2;

MyDelegate multiDelegate = delegate1 + delegate2;
multiDelegate("Hello, delegates!");

대리자는 다중 대리자도 지원한다. 다중 대리자는 "+" 연산자를 사용하여 연결도 가능하다.

다중 대리자를 호출하면 연결된 모든 매서드가 순차적으로 호출된다.

using System;

delegate void MyDelegate();

class Program
{
    static void Main()
    {
        MyDelegate myDelegate = Method1;
        myDelegate += Method2;
        myDelegate += Method3;

        // 등록된 모든 메서드 호출
        myDelegate();

        Console.WriteLine("프로그램이 종료되었습니다.");
    }

    static void Method1()
    {
        Console.WriteLine("메서드 1 호출");
    }

    static void Method2()
    {
        Console.WriteLine("메서드 2 호출");
    }

    static void Method3()
    {
        Console.WriteLine("메서드 3 호출");
    }
}

 

2. 이벤트(Event)

이벤트는 c#에서 발생하는 특정한 동작을 나타내는 신호로 해당 동작에 대한 응답을 받을 수 있도록 프로그램에

알림을 제공한다. 이벤트는 객체 간의 상호작용에서 중요한 역할을 수행하며, 일종의 통신 메커니즘으로 볼 수 있다.

이벤트는 발생한 사건을 처리하기 위해 이벤트 핸들러라고 하는 메서드를 등록하고, 이벤트가 발생하면 등록된 모든 이벤트 핸들러가 순차적으로 호출되어 해당 동작에 대한 응답을 처리한다. 이벤트 핸들러는 이벤트에 대한 구독자로서, 이벤트가 발생하면 자동으로 호출되어 처리 작업을 수행한다. 이벤트는 주로 다음과 같은 상황에서 사용되는데,

 

1. 이벤트 핸들링: 이벤트는 객체 간의 상호작용에서 중요한 역할을 한다. 이벤트를 사용하여 다른 객체나 사용자의 동작에 대한 알림을 제공하고, 이에 대한 응답으로 특정 동작을 수행한다. 예를 들어, 버튼 클릭, 마우스 이동, 키 입력 등의 동작에 대한 이벤트를 처리하여 UI 상호작용을 구현할 수 있다.

2. 이벤트 기반 아키텍처: 이벤트는 이벤트 기반 아키텍처에서 핵심적인 역할을 수행한다. 이벤트를 사용하여 애플리케이션의 다양한 컴포넌트나 모듈 간의 통신을 단순하고 유연하게 구성할 수 있다. 이벤트를 통해 컴포넌트 간의 결합도를 낮추고 재사용성을 높일 수 있다.

3. 이벤트 드리븐 프로그래밍: 이벤트는 이벤트 드리븐 프로그래밍 패턴에서 핵심적인 요소이다. 이 패턴은 비동기적인 상황에서 이벤트에 의해 트리거 되는 동작을 효과적으로 처리하는 방법을 제공한다.

예를 들어, 네트워크 통신, 사용자 입력, 외부 리소스 접근 등의 비동기 작업을 이벤트에 의해 처리할 수 있다.

 

이벤트는 클래스 내부에서 선언되며, 다음과 같은 형식으로 선언된다.

public event EventHandler MyEvent;

위의 선언을 보면 'MyEvent'는 'EventHandler'라는 대리자 형식 이벤트이다.

'EventHandler' 대리자는 .NET 프레임워크에서 제공하는 기본 이벤트 처리를 위한 대리자로, 매개변수로 object와 EventArgs를 받는다.

이벤트는 클래스 외부에서 사용자가 직접 호출할 수 없으며, 해당 클래스의 매서드 내에서만 호출하여 발생시켜야 한다.

 

이벤트 핸들러는 등록과 이벤트를 발생하기 위해선 아래와 같은 소스를 넣어주어야 한다.

MyEvent += EventHandlerMethod;
MyEvent?.Invoke(this, EventArgs.Empty);

위의 작성한 것처럼 이벤트는 해당 클래스 내부에서만 호출이 가능하다.

아래 쉬운 예제를 보면서 이해해 보자.

using System;

class Button
{
    // 이벤트 선언
    public event EventHandler Click;

    public void OnClick()
    {
        // 이벤트 발생
        Click?.Invoke(this, EventArgs.Empty);
    }
}

class Program
{
    static void Main()
    {
        Button button = new Button();

        // 이벤트 핸들러 등록
        button.Click += Button_Click;

        // 버튼 클릭 시 이벤트 발생
        button.OnClick();
    }

    static void Button_Click(object sender, EventArgs e)
    {
        Console.WriteLine("버튼이 클릭되었습니다!");
    }
}

위 예제를 보면 'Button'  클래스는 'Click'라는 이벤트를 선언한다. 이벤트는 'EventHandler' 델리게이트를 기반으로 한다.

'Button' 클래스의 'OnClick' 메서드에서 이벤트를 발생시킨다.

 

'Program' 클래스에서 Button 인스턴스를 생성하고, Click 이벤트에 대한 이벤트 핸들러를 등록한다.

이후 button.OnClick() 메서드를 호출하여 버튼 클릭 시 이벤트가 발생하도록 한다.

이벤트 핸들러인 'Button_Click' 매서드는 이벤트가 발생할 때 호출되어 "버튼이 클릭되었습니다!"라는 메세지를 출력한다.

 

3. 이벤트와 대리자를 사용할 때의 주의사항과 권장 사례

 

메모리 누수: 이벤트 핸들러 등록 후에 해당 이벤트 핸들러가 제대로 해제되지 않으면 메모리 누수가 발생할 수 있다. 이벤트 등록 후에는 해당 이벤트를 구독해제하는 것이 중요하다.

필요 없는 이벤트 핸들러는 제거하여 메모리 관리를 해야 한다.

그러나 C# 6.0 이후부터는 약한 이벤트 패턴(Weak Event Pattern)이 도입되었다.

약한 이벤트 패턴을 사용하면 이벤트 핸들러가 자동으로 해제되어 메모리 누수를 방지할 수 있다.

이 패턴은 WeakEventManager 클래스를 통해 구현할 수 있으며, WeakEventManager를 사용하면 이벤트 핸들러를 약한 참조로 관리하므로 가비지 컬렉터가 필요 없는 이벤트 핸들러를 정리할 수 있다.
약한 이벤트 패턴을 사용하려면 몇 가지 추가 작업이 필요하다. 일반적으로 약한 이벤트 패턴은 WPF(Windows Presentation Foundation) 및 Xamarin.Forms과 같은 특정 UI 프레임워크에서 주로 사용된다. 하지만 일반적인 C# 응용 프로그램에서는 약한 이벤트 패턴을 직접 구현하거나 외부 라이브러리를 사용해야 할 수 있다.
따라서 일반적인 C# 프로그래밍에서는 이벤트의 구독과 구독해제를 수동으로 관리하는 것이 좋다. 명시적으로 구독을 해제하여 메모리 누수를 방지하고, 필요 없는 이벤트 핸들러를 제거하여 성능을 개선할 수 있다.

 

스레드 안전성: 멀티스레드 환경에서 이벤트가 발생하는 경우 스레드 안전성에 유의해야 한다. 이벤트 핸들러의 동작이 다중 스레드에서 안전하게 이루어지도록 동기화 메커니즘을 사용하거나 스레드 안전한 컬렉션을 활용해야 한다.

적절한 이벤트 네이밍: 이벤트의 이름은 명확하고 일관성 있게 지어야 한다. 이벤트가 발생하는 동작이나 상태를 잘 표현하고, 다른 개발자들이 이해하기 쉽도록 네이밍해야 한다.

이벤트 오버헤드: 이벤트 처리는 추가적인 오버헤드를 가질 수 있으므로, 과도한 이벤트 사용은 성능에 영향을 줄 수 있습니다. 필요한 경우에만 이벤트를 사용하고, 불필요한 이벤트 발생을 최소화하는 것이 좋다.

문서화와 가독성: 이벤트와 대리자를 사용한 코드는 다른 개발자가 이해하기 쉽도록 문서화하고 가독성을 고려해야 한다. 이벤트의 역할과 의도를 명확히 설명하고, 코드에 주석을 추가하여 이해를 돕는 것이 좋다.

 

 

 

반응형