// System에 있는 이벤트 핸들러의 정의
public delegate void EventHandler<TEventArgs>
(object source, TEventArgs e) where TEventArgs : EventArgs;
이벤트 핸들러: .NET에서 이벤트를 일관된 패턴으로 사용하도록 구현해놓은 표준 객체
.Net에서는 표준 이벤트를 구현하는 예약어이지만 원래는 이벤트(마우스 클릭 등) 발생시 함께 발생시킬 메서드들도 모두 핸들러라고 부르기도 한다. 특정 이벤트를 관찰하고 다루기 때문이다.
- System.EventArgs 미리 정의된 클래스를 이용하거나 (아무 멤버도 없음. 정적 Empty 속성만 존재.)
- 발생하는 이벤트에 대한 정보 전달이 필요할 경우 EventArgs를 상속받는 파생 클래스를 작성하여 사용함.
EventArgs를 상속받는 파생 클래스를 작성하는 방법
- System.EventArgs를 상속 받는 파생클래스를 만듦
- 이벤트 발생시 전달할 정보를 멤버 변수로 만들고 각 변수에 대해 생성자를 둠.
- 각 변수는 일반적으로 readonly 접근한정자나 속성의 get만을 허용한다.
표준 이벤트를 직접 수동으로 작성하는 방법
- void를 리턴 타입으로 한다.
- 필요하다면 System.EventArgs를 상속 받는 파생클래스를 제네릭으로 받는다.
- EventHandler로 이름이 끝나야 한다.
- 이벤트를 발생시키는 함수는 protected virtual 함수이고 이름은 On으로 시작해야한다.
이전 포스트의 코드를 이벤트 핸들러를 이용한 표준 이벤트 패턴으로 바꾸고 테스트를 추가하면 아래와 같다.
using System;
// 표준 이벤트 NumberChanged의 매개변수 클래스
public class NumberChangedEventArgs : EventArgs
{
// EventArgs 클래스를 상속 받아 전달할 정보를 멤버 변수로 만든다.
// Generic을 이용할 수도 있다.
// 일반적으로 속성이나 readonly로 만든다.
public readonly int OldNum;
public int NewNum { get; }
// tip: 생성자 단축키 ctor → 탭
public NumberChangedEventArgs(int oldNum, int newNum)
{
OldNum = oldNum;
NewNum = newNum;
}
}
public class ReactiveNum
{
private int _value;
public ReactiveNum(int value)
{
_value = value
}
// System에 미리 정의된 표준 이벤트 형식
public event EventHandler NumberChanged;
protected virtual void OnNumberChanged(EventArgs e)
{
//if (NumberChanged != null) NumberChanged(this, e); 와 같은 구문이다.
NumberChanged?.Invoke(this, e);
/* 다중 스레드에서는 임시변수에 배정 후 점검, 호출해야 스레드 안정성 오류를 피할 수 있음.
var temp = NumberChanged;
temp?.Invoke (this, e);
*/
}
public int Value
{
get => _value;
set
{
if (_value == value) return;
//EventArgs 파생클래스를 생성해서 정보 전달.
OnNumberChanged(new NumberChangedEventArgs(_value, value))
_value = value;
}
}
}
class Test
{
static void Main()
{
ReactiveNum num = new ReactiveNum(1);
// 표준 이벤트 NumberChanged에 구독. 숫자가 변할 때마다 구독한 함수가 실행됨.
num.NumberChanged += NumIncreased2;
num.Value = 3;
//'2만큼 증가했습니다.'가 출력됨.
}
static void NumIncreased2(object sender, CustomEventArgs e)
{
if (e.NewNum - e.OldNum == 2) Console.WriteLine("2만큼 증가했습니다.");
}
}
추가 정보를 전달하지 않는 경우 비제네릭 EventHandler를 사용하면 된다.
위에서도 정보전달이 필요없는 함수라면 다음처럼 간결하게 짤 수 있다.
using System;
public class ReactiveNum
{
int _value;
public ReactiveNum(int value)
{
_value = value
}
public event EventHandler NumberChanged;
protected virtual void OnNumberChanged(EventArgs e)
{
NumberChanged?.Invoke(this, e);
}
public int Value
{
get => _value;
set
{
if (_value == value) return;
_value = value;
//쓸데없는 EventArgs 인스턴스를 생성하지 않으려고 Empty 속성을 사용함.
OnNumberChanged(EventArgs.Empty);
}
}
}
class Test
{
static void Main()
{
ReactiveNum num = new ReactiveNum(1);
num.NumberChanged += ConsoleWriteNumChanged;
//1과 다른 숫자를 입력하면 '숫자가 변했습니다.' 가 출력된다.
num.Value = int.Parse(Console.ReadLine());
}
static void ConsoleWriteNumChanged(object sender, EventArgs e)
{
Console.WriteLine("숫자가 변했습니다.");
}
}
참고문헌: C# 8.0 in a Nutshell, Joseph Albahari. Eric Johannsen. pp160-163.
'C#' 카테고리의 다른 글
C# 제네릭(Generics) 1 (0) | 2020.11.30 |
---|---|
C# 이벤트(Events) 접근자와 수정자 (0) | 2020.11.27 |
C# 이벤트(Events) (0) | 2020.11.26 |
C# 람다식(Lambda Expressions) (0) | 2020.11.16 |
C# 확장 메서드(Extension Methods) (0) | 2020.11.15 |