[C#] 표준 이벤트 패턴

muchan
muchan
∙∙조회 10

EventArgs 클래스

.NET에서는 이벤트 데이터를 전달할 때 EventArgs를 상속한 클래스를 사용하는 것이 표준 패턴이다. 이벤트는 EventHandler  형태로 선언하며, 여기서 TEventArgs가 함께 전달할 이벤트 데이터다.

csharp
// 이벤트 데이터 클래스
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 예제

csharp
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를 받으면 에러 상황인 것이다

csharp
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)를 그대로 따른다.