00. 사전 지식

더보기

대리자는 함수를 데이터화(자료형화) 해서 

 

기본 자료형 > 복합 자료형 > 사용자 정의 자료형 > 클래스 자료형 > 대리자 자료형

 

대리자를 사용하면, 함수를 변수처럼, 함수를 객체로, 함수를 클래스 자료형처럼 변환하여 사용할 수 있다.


 

01. 목표

더보기


“Button” 의 Click( ) 함수가 동작하면, Label 의 Show( ) 함수 실행을 구현하고 싶습니다.

이를 어떻게 구현할까요?

 

Qt 에서는 Connect 를 통해 손쉽게 함수와 함수를 연결했습니다.

C#에서는 함수를 변수처럼 사용하는 "대리자"와 대리자의 변형인 "이벤트"를 이용합니다.


 

02. 좀 더 간단한 목표

더보기

A. 좀 더 간단한 목표

 

“Button” 의 Click( ) 함수가 동작하면, Show( ) 함수가 실행되도록 구현한다고 가정합니다.

어떻게 구현할까요?

 

 

B. 간단한 정답

 

“Button” 클래스 내부에 Click( ) 함수 다음에, Show( ) 함수가 실행되도록 구현하면 됩니다.

이와 동일한 구조를 <Click( ) 함수 내부에, Show( ) 함수를 구현하는 방법 외>, Event 를 사용해 구현해 봅시다.

 

 

C. 함수를 변수처럼 사용하는 대리자와 이벤트

 

“Button” 클래스 내부에 Click( ) 함수내부에 EventHandler(대리자, 함수를 변수처럼 사용)에 Show( )함수를 등록합니다.

마치, Click( ) 함수 다음에, Show( ) 함수가 순차적으로 동작하는 것처럼 실행됩니다.

 

 

D. 컴파일 시 내부 형태

private EventHandler _ButtonClickEvent;

public event EventHandler ButtonClickEvent
{
    add    { _ButtonClicked += value; }  // += 연산자
    remove { _ButtonClicked -= value; }  // -= 연산자
}
private EventHandler<ButtonClickedEventArgs> _ButtonClickEvent;

public event EventHandler<ButtonClickedEventArgs> ButtonClickEvent
{
    add    { _ButtonClicked += value; }  // += 연산자
    remove { _ButtonClicked -= value; }  // -= 연산자
}

 

03. 구현 테스트

더보기
EventTester01.zip
0.07MB
public class Button                                      // Button 클래스 정의
{
    public event EventHandler ButtonClickEvent;          // 이벤트 선언 (표준 EventHandler 사용)

    public void Click()                                  // 버튼 클릭 시뮬레이션
    {
        Console.WriteLine("버튼이 클릭되었습니다!");
        ButtonClickEvent?.Invoke(this, EventArgs.Empty); // 이벤트 실행 (구독자가 있을 때만)
    }
}
public static void Show() // 이벤트 핸들러에서 호출될 Show() 메서드
{
    Console.WriteLine("레이블 텍스트 변경");
}
Button button = new Button();                    // [1] Button 객체 생성
button.ButtonClickEvent += (sender, e) => Show();// [2] 이벤트 구독 (+=)
button.Click();                                  // [3] 버튼 클릭 시뮬레이션

 


실행구조

 

 “Button” 의  Click( ) 함수가 실행되면, 

 내부에 EventHandler(대리자, 함수를 변수처럼 사용)에  등록된  

❺ Show( )함수가 동작합니다.

*마치, Click( ) 함수 다음에, Show( ) 함수가 순차적으로 동작하는 것처럼 실행됩니다.

 

이벤트 실행 관점을 Publisher, 등록된 함수를 Subscriber 라고 합니다.


 

04. 이벤트 실행시 매개변수 넘기기

더보기
using System;

namespace EventTester01
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Button button = new Button();         // [1] Button 객체 생성 (이름 전달)
            button.ButtonClickEvent += OnButtonClicked;       // [2] 이벤트 구독
            button.Click();                                   // [3] 클릭 시뮬레이션
        }

        // [4] 이벤트 핸들러 (매개변수 포함)
        private static void OnButtonClicked(object sender, ButtonClickEventArgs e)
        {
            Console.WriteLine($"레이블 텍스트 변경: {e.LabelText} 클릭됨. {e.ClickTime}");
        }
    }

    // [5] 사용자 정의 EventArgs 클래스 (매개변수 전달용)
    public class ButtonClickEventArgs : EventArgs
    {
        public string LabelText { get; }   // 버튼 이름
        public DateTime ClickTime { get; }  // 클릭 시각

        public ButtonClickEventArgs(string labelText)
        {
            LabelText = labelText;
            ClickTime = DateTime.Now;
        }
    }

    // [6] Button 클래스 정의
    public class Button
    {
        // 이벤트 선언 (제네릭 EventHandler<TEventArgs> 사용)
        public event EventHandler<ButtonClickEventArgs> ButtonClickEvent;

        public string labelText { get; set; } = "<@전달@>";

        // 클릭 시뮬레이션
        public void Click()
        {
            // 이벤트 발생 (구독자가 있을 때만)
            ButtonClickEvent?.Invoke(this, new ButtonClickEventArgs(labelText));
        }
    }
}

 

05. 이벤트를 안전하게 발생시키기

더보기
using System;

namespace EventTester01
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Button button = new Button("확인 버튼");          // [1] Button 객체 생성
            button.ButtonClicked += OnButtonClicked;           // [2] 이벤트 구독
            button.Click();                                    // [3] 클릭 시뮬레이션
        }

        private static void OnButtonClicked(object sender, ButtonClickedEventArgs e)
        {
            Console.WriteLine($"[이벤트 수신] {e.ButtonName} 이(가) 클릭됨!");
        }
    }

    // [4] 사용자 정의 EventArgs (버튼 이름 등 데이터 전달)
    public class ButtonClickedEventArgs : EventArgs
    {
        public string ButtonName { get; }

        public ButtonClickedEventArgs(string buttonName)
        {
            ButtonName = buttonName;
        }
    }

    // [5] Button 클래스
    public class Button
    {
        public string ButtonName { get; }

        // [1] 이벤트 선언 (EventHandler<TEventArgs>)
        public event EventHandler<ButtonClickedEventArgs> ButtonClicked;

        public Button(string name)
        {
            ButtonName = name;
        }

        // [2] 버튼 클릭 시뮬레이션
        public void Click()
        {
            Console.WriteLine($"[{ButtonName}] 버튼이 클릭되었습니다!");

            // 이벤트 발생 — “안전한 발생 메서드”를 통해 실행
            OnButtonClicked(new ButtonClickedEventArgs(ButtonName));
        }

        // [3] 이벤트를 안전하게 발생시키는 메서드 (핵심 포인트)
        protected virtual void OnButtonClicked(ButtonClickedEventArgs e)
        {
            // null 체크 후 이벤트 호출
            ButtonClicked?.Invoke(this, e);
        }
    }
}

 

  • ?.Invoke() 로 구독자가 없을 때 예외 방지
  • 이벤트 발생 로직을 한 곳에 집중
  • 하위 클래스에서 OnButtonClicked를 override 하여 로그나 조건 추가 가능
  • “이 메서드는 이벤트를 발생시키는 역할이다”를 명시적으로 표현
  • 예시: 상속으로 확장 가능
public class LoggingButton : Button
{
    public LoggingButton(string name) : base(name) { }

    protected override void OnButtonClicked(ButtonClickedEventArgs e)
    {
        Console.WriteLine($"[LOG] {e.ButtonName} 클릭 이벤트 발생");
        base.OnButtonClicked(e); // 실제 이벤트 발생
    }
}