[C#] 표준 이벤트 패턴
EventArgs 클래스
.NET에서는 이벤트 데이터를 전달할 때 EventArgs를 상속한 클래스를 사용하는 것이 표준 패턴이다. 이벤트는 EventHandler 형태로 선언하며, 여기서 TEventArgs가 함께 전달할 이벤트 데이터다.
// 이벤트 데이터 클래스
class PriceChangedEventArgs : EventArgs
{
public decimal OldPrice { get; }
public decimal NewPrice { get; }
public decimal ChangePercent { get; }
public PriceChangedEventArgs(decimal oldPrice, decimal newPrice)
{
OldPrice = oldPrice;
NewPrice = newPrice;
if (oldPrice != 0)
{
ChangePercent = (newPrice - oldPrice) / oldPrice * 100;
}
}
}
class Stock
{
private string _symbol;
private decimal _price;
// 표준 이벤트 패턴: EventHandler<TEventArgs>
public event EventHandler<PriceChangedEventArgs> PriceChanged;
public Stock(string symbol, decimal initialPrice)
{
_symbol = symbol;
_price = initialPrice;
}
public decimal Price
{
get => _price;
set
{
if (_price == value)
{
return;
}
decimal oldPrice = _price;
_price = value;
// 이벤트 발생
OnPriceChanged(new PriceChangedEventArgs(oldPrice, _price));
}
}
// protected virtual로 선언하여 상속 시 재정의 가능
protected virtual void OnPriceChanged(PriceChangedEventArgs e)
{
PriceChanged?.Invoke(this, e);
}
public override string ToString()
{
return $"{_symbol}: {_price:C}";
}
}
class Program
{
static void Main()
{
Stock msStock = new Stock("MSFT", 100.00m);
// 표준 패턴: sender와 EventArgs를 받음
msStock.PriceChanged += (sender, e) =>
{
Stock stock = (Stock)sender;
Console.WriteLine($"[{stock}]");
Console.WriteLine($" 이전 가격: {e.OldPrice:C}");
Console.WriteLine($" 현재 가격: {e.NewPrice:C}");
Console.WriteLine($" 변동률: {e.ChangePercent:F2}%");
Console.WriteLine();
};
msStock.Price = 110.00m;
msStock.Price = 105.50m;
msStock.Price = 120.00m;
}
}
이벤트(PriceChanged)를 직접 Invoke하지 않고, OnPriceChanged라는 virtual 메서드를 따로 만들어 그 안에서 호출한다. 이렇게 한 단계 감싸두면 상속받은 자식 클래스가 이 메서드를 재정의해서 이벤트 발생 시점의 동작을 바꿀 수 있다.
표준 패턴 핵심 규칙
- 이벤트 핸들러 시그니처: void Handler(object sender, EventArgs e)
- 이벤트 발생 메서드: OnEventName (protected virtual)
- 이벤트 데이터: EventArgs를 상속한 클래스로 전달
- 이벤트를 발행한 객체는 object sender로 형변환해서 넘긴다 (Invoke(this, e))
- 데이터는 EventArgs를 상속한 클래스의 필드에 담아, 부모 타입으로 넘긴다
Firebase.Database 예제
public sealed class ValueChangedEventArgs : EventArgs
{
public DataSnapshot Snapshot { get; private set; }
public DatabaseError DatabaseError { get; private set; }
internal ValueChangedEventArgs(DataSnapshot snapshot)
{
Snapshot = snapshot;
}
internal ValueChangedEventArgs(DatabaseError error)
{
DatabaseError = error;
}
}
EventArgs를 상속받았으므로 표준 패턴으로 만들어진 이벤트 클래스라는 것을 확인할 수 있다
생성자로 snapshot을 받으면 정상 상황이고 DatabaseError를 받으면 에러 상황인 것이다
private void OnValueChanged(object sender, ValueChangedEventArgs args)
{
if (args.DatabaseError != null)
{
Debug.LogError($"[Leaderboard] 리스너 오류: {args.DatabaseError}");
return;
}
List<LeaderboardEntry> leaderboard = ParseEntries(args.Snapshot);
DispatchUpdateAsync(leaderboard).Forget();
}
여기서 이벤트 OnValueChanged는 표준 시그니처 void Handler(object sender, ValueChangedEventArgs args)를 그대로 따른다.

