일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 17070번
- 코테
- dfs
- 백준 17070번
- 백준
- 백준 c++ 2468번
- 2870번
- Lv.3
- 플레이어 이동
- 수학숙제
- c++
- 오브젝트 풀링
- 백준 2870번
- 2870번 수학숙제 c++
- 백준 c++ 2870번
- 백준 1103번 c++
- 백준 1103번
- 백준 17070번 c++
- 2468 c++
- Lv2
- 백준 1103번 게임
- Unity
- 2870번 c++
- 프로그래머스
- C#
- Algorithm
- 유니티
- Beakjoon
- 코딩테스트
- 2870번 수학숙제
- Today
- Total
주녘공부일지
[C#] 이벤트 (Event) + 표준 이벤트 패턴, 이벤트 접근자&수정자 본문
0. 이벤트 (Event)
방송자 : 대리자가 있는 필드 형식으로, 대리자를 호출해 정보를 방송한다는 의미
구독자 : 대리자가 호출할 대상 메서드를 등록하는 형식으로, '+=', '-=' 연산자를 호출해 해당 방송의 청취를 시작 or 중단함
// 대리자 정의
public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);
public class Stock
{
string symbol;
decimal price;
public Stock(string symbol) { this.symbol = symbol; }
// 이벤트 대리자
public event PriceChangedHandler PriceChanged;
public decimal Price
{
get { return price; }
set
{
if (price == value) return; // 변한 값이 없다면 그냥 반환
decimal oldPrice = price;
price = value;
// 호출 목록이 비어있지 않으면 이벤트 발동
PriceChanged?.Invoke(oldPrice, price);
}
}
}
- 위 예제에서 event 키워드가 없어도 결과는 동일하지만, 그렇게 되면 구독자들이 서로 간섭할 수 있게 됨
-> 간섭이란? PriceChanged를 직접 조작하여 구독자 배정, 해제하는 등
-> 이벤트의 주된 목적은 구독자들이 서로 간섭하지 못하게 하는 것
1. 표준 이벤트 패턴
using System;
// 1번 Point ( 코드 하단 설명 참조 )
public class PriceChangedEventArgs : EventArgs
{
public readonly decimal LastPrice;
public readonly decimal NewPrice;
public PriceChangedEventArgs(decimal lastPrice, decimal newPrice)
{
LastPrice = lastPrice;
NewPrice = newPrice;
}
}
public class Stock
{
string symbol;
decimal price;
public Stock(string symbol) { this.symbol = symbol; }
// 3번 Point ( 코드 하단 설명 참조 )
public event EventHandler<PriceChangedEventArgs> PriceChanged;
// 2번 Point ( 코드 하단 설명 참조 )
protected virtual void OnPriceChanged (PriceChangedEventArgs e)
{
PriceChanged?.Invoke (this, e);
}
public decimal Price
{
get { return price; }
set
{
if (price == value)
return;
decimal oldPrice = price;
price = value;
OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
}
}
}
class Test
{
static void Main()
{
Stock stock = new Stock("THPW");
stock.Price = 27.10M;
// PriceChanged 이벤트에 등록
stock.PriceChanged += Stock_PriceChanged;
stock.Price = 31.59M;
}
static void Stock_PriceChanged(object sender, PriceChangedEventArgs e)
{
if ((e.NewPrice - e.LastPrice) / e.LastPrice > 0.1M)
Console.WriteLine("주의, 주가 10% 상승!");
}
}
1번) public class PriceChangedEventArgs : EventArgs
닷넷 프레임워크에 미리 정의되어 있는 System.EventArgs는 정적 Empty 속성 외에는 아무런 멤버도 없음
- EventArgs 파생 클래스는 재사용성을 위해, 담고 있는 정보를 반영하는 이름을 붙임
+ 일반적으로 자신의 자료를 읽기 전용 필드로 노출함
2번) protected virtual void OnPriceChanged (PriceChangedEventArgs e)
- 표준 이벤트 패턴에 따르려면 이벤트를 알리는 보호 가상 메서드가 반드시 필요한데 이 메서드명은 반드시 "On"으로 시작해야 하며. EventArgs 형식의 인수 하나를 받아야 함
3번) public event EventHandler<PriceChangedEventArgs> PriceChanged;
이벤트를 위한 대리자로, 이는 지켜야하는 규칙이 3가지 있음
1) 반환 형식은 반드시 void 이여야 함
2) 인수 두 개를 받아야 함
-> object ( 이벤트 방송자 지정 ), EventArgs 파생 클래스 ( 전달할 추가 정보를 담음 )
3) 이름은 반드시 "EventHandler"로 끝나야 함
+ 닷넷 프레임워크에는 위 조건들을 만족하는 System.EventHandler가 정의되어 있음 (제네릭)
public delegate void EventHandler<TEventArgs>
(object source, TEventArgs e) where TEventArgs : EventArgs;
2. 이벤트 접근자
이벤트 접근자(accessor)는 이벤트에 대한 '+=', '-=' 연산을 그 이벤트에 맞는 방식으로 구현하기 위한 것으로, 기본적으로 컴파일러가 암묵적으로 이벤트 접근자들을 구현해줌
public class Broadcaster
{
public event PriceChangedHandler PriceChanged;
}
위와 같은 예제의 선언된 이벤트에 대해 컴파일러는 내부적으로 아래와 같은 형태로 바꾸어 컴파일
// 전용 대리자 필드
private EventHandler priceChanged;
// 공용 이벤트 접근자 함수인 add, remove
public event EventHandler PriceChanged
{
add { priceChanged += value; }
remove { priceChanged -= value; }
}
+ 컴파일러는 add, remove 블록들을 add_PriceChanged, remove_PriceChanged 메서드로 바꾸어서 컴파일
3. 명시적 이벤트 접근자
컴파일러의 기본 구현 대신 명시적 이벤트 접근자를 이용하면 바탕 대리자의 저장과 접근에 좀 더 복잡한 전략을 사용할 수 있음
- ex 1) 이벤트 접근자들이 다른 클래스에 이벤트 방송을 위임하기만 할 경우
- ex 2) 클래스가 너무 많은 수의 이벤트를 노출해 대리자 인스턴스를 딕셔너리에 저장하는 게 더 유리할 경우
( 널 대리자 필드 참조들을 수십 개씩 저장하는 것보다 딕셔너리의 저장소가 더 유리할 수 있음 )
ex 3) 이벤트를 선언하는 인터페이스를 명시적으로 구현
public interface IFoo { event EventHandler Ev; }
class Foo : IFoo
{
private EventHandler ev;
// 이벤트 접근자 명시적 구현 ( ex 3 )
event EventHandler IFoo.Ev
{
add { ev += value; }
remove { ev -= value; }
}
}
4. 이벤트 수정자
메서드처럼 이벤트에도 여러 수정자를 적용해 가상, 재정의, 추상, 봉인 이벤트로 만들 수 있음 ( 정적 이벤트도 가능 )
public class Foo
{
public static event EventHandler<EventArgs> StaticEvent;
public virtual event EventHandler<EventArgs> VirtualEvent;
}
참고도서) C# 6.0 완벽가이드
'Programming > Definition, Etc' 카테고리의 다른 글
[C# Reference] 우선순위 큐 ( Priority Queue ) + Heap (0) | 2024.02.02 |
---|---|
[C#] 대리자(delegate) vs 인터페이스(interface) (0) | 2023.11.10 |
[C#] 대리자 (Delegate - Action, Func) (0) | 2023.11.10 |
[C#] 제네릭 (Generic) (0) | 2023.10.27 |
[C#] 열거형 (enum type) (1) | 2023.10.15 |