8. 이벤트 (요약)

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. 구현 테스트


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); // 실제 이벤트 발생
}
}