C# 이벤트(Event) 완벽 가이드: 구현부터 활용까지

이벤트의 기본 개념

이벤트란 무엇인가?

이벤트는 특정 상황이 발생했을 때 다른 객체에게その 정보를 전달하는 메커니즘입니다. 한 객체의 상태 변화가 다른 객체의 동작을Trigger할 필요가 있을 때 이 패턴을 사용합니다. 이벤트를 발생시키는 객체를 발행자(Publisher), 이벤트를接收하여 처리하는 객체를 구독자(Subscriber)라고 합니다. 이 관계를 기반으로 발행-구독 모델이 구성됩니다. 이벤트의 본질은 delegate 타입을 포장한 것입니다. 속성이 필드를封装하는 것처럼, 이벤트는 delegate 변수를 외부에서 임의로 호출하지 못하도록 보호하는 역할을 합니다. 이를 통해 클래스 외부에서 이벤트를 마음대로Trigger하는 것을 방지할 수 있습니다.

이벤트 구현 실습

기본 이벤트 구조

namespace EventDemo
{
    internal class Program
    {
        public delegate void ValueChangedHandler(int newValue);
        
        static void Main(string[] args)
        {
            var publisher = new CounterPublisher();
            var observer = new CounterObserver();

            publisher.OnValueChanged += observer.HandleValueChange;
            publisher.Increment();

            Console.ReadKey();
        }
    }

    class CounterPublisher
    {
        private int _counter = 0;
        
        public event ValueChangedHandler OnValueChanged;

        public void Increment()
        {
            _counter++;
            if (OnValueChanged != null)
            {
                OnValueChanged(_counter);
            }
        }
    }

    class CounterObserver
    {
        public void HandleValueChange(int value)
        {
            Console.WriteLine($"현재 카운터 값: {value}");
        }
    }
}

delegate와 event의 차이점 이해

event 키워드를 사용하지 않으면 외부에서 delegate를 직접 호출할 수 있어 문제가 발생할 수 있습니다. 다음 예제를 확인하세요:
namespace UnsafeEventDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var obj = new SampleObject();
            
            // event를 사용하지 않으면 외부에서 직접 호출 가능
            obj.PublicDelegate = () => Console.WriteLine("외부에서 직접 호출!");
            obj.PublicDelegate(); // 이렇게 마음대로Trigger 가능
            
            Console.ReadKey();
        }
    }

    class SampleObject
    {
        public Action PublicDelegate; // event 없이 선언
        
        public void Process()
        {
            PublicDelegate?.Invoke();
        }
    }
}
event 키워드를 추가하면 외부에서의 직접 호출을 막을 수 있습니다:
namespace SafeEventDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var obj = new SafeObject();
            
            // 구독은 가능
            obj.SafeEvent += () => Console.WriteLine("이벤트 구독 성공!");
            
            // 직접 호출은 불가능 - 컴파일 에러 발생
            // obj.SafeEvent(); 
            
            obj.Process();
            Console.ReadKey();
        }
    }

    class SafeObject
    {
        public event Action SafeEvent;
        
        public void Process()
        {
            SafeEvent?.Invoke();
        }
    }
}

Observer 패턴과 이벤트

이벤트는 GoF 디자인 패턴 중 Observer 패턴의 구현입니다. 실제 상황에 비유하면: - 신호등이 빨간불로 바뀌면,停車해야 하는 차들 - 뉴스 구독자가 새 기사를 전달받는 상황 이 패턴의 핵심은 발행자가 구독자의 존재를 몰라도 된다는 점입니다. 발행자는 이벤트를Trigger하기만 하면 되고, 구독자는 원할 때 가입하거나 탈퇴할 수 있습니다.

실제 활용: 신호등 대기 시뮬레이션

namespace TrafficSimulation
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var signal = new TrafficSignal();
            var car = new Vehicle();
            var pedestrian = new Pedestrian();

            signal.SignalChanged += car.OnSignalChanged;
            signal.SignalChanged += pedestrian.OnSignalChanged;

            signal.StartCountdown();
            Console.ReadKey();
        }
    }

    public delegate void SignalEventHandler(string signalColor);

    class TrafficSignal
    {
        public event SignalEventHandler SignalChanged;

        public void StartCountdown()
        {
            for (int i = 5; i >= 0; i--)
            {
                string color = i == 0 ? "GREEN" : "RED";
                Console.WriteLine($"신호등: {color}, 남은 시간: {i}");
                SignalChanged?.Invoke(color);
                Thread.Sleep(800);
            }
        }
    }

    class Vehicle
    {
        public void OnSignalChanged(string color)
        {
            if (color == "GREEN")
                Console.WriteLine("차량이 출발합니다.");
            else
                Console.WriteLine("차량이 정지합니다.");
        }
    }

    class Pedestrian
    {
        public void OnSignalChanged(string color)
        {
            if (color == "GREEN")
                Console.WriteLine("보행자가 건너갑니다.");
        }
    }
}

이벤트 인자 클래스 활용

복잡한 데이터를 전달해야 할 때는 EventArgs 파생 클래스를 사용합니다:
namespace OrderEventDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var customer = new RestaurantCustomer();
            var waiter = new RestaurantWaiter();

            customer.OrderPlaced += waiter.ServeOrder;
            customer.PlaceOrder();

            Console.ReadKey();
        }
    }

    public class OrderEventArgs : EventArgs
    {
        public string MenuName { get; set; }
        public int Quantity { get; set; }
    }

    public class RestaurantCustomer
    {
        public event EventHandler<OrderEventArgs> OrderPlaced;

        public void PlaceOrder()
        {
            Console.WriteLine("주문하는 중...");
            Thread.Sleep(1000);

            var args = new OrderEventArgs 
            { 
                MenuName = "Jjajangmyeon", 
                Quantity = 2 
            };
            
            OrderPlaced?.Invoke(this, args);
        }
    }

    class RestaurantWaiter
    {
        public void ServeOrder(object sender, OrderEventArgs e)
        {
            Console.WriteLine($"주문 받음: {e.MenuName} x {e.Quantity}");
            Console.WriteLine("서빙 완료!");
        }
    }
}

실제 활용 시나리오

타이머 이벤트 활용

namespace TimerDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var worker = new BackgroundWorker();
            var logger = new LogPrinter();

            worker.TaskCompleted += logger.PrintResult;
            worker.StartTask();

            Console.ReadKey();
        }
    }

    class BackgroundWorker
    {
        private System.Timers.Timer _timer;

        public event EventHandler<string> TaskCompleted;

        public void StartTask()
        {
            _timer = new System.Timers.Timer(1500);
            _timer.Elapsed += (s, e) =>
            {
                _timer.Stop();
                TaskCompleted?.Invoke(this, "작업 완료됨!");
            };
            _timer.Start();
        }
    }

    class LogPrinter
    {
        public void PrintResult(object sender, string message)
        {
            Console.WriteLine($"로그: {message}");
        }
    }
}

폼 이벤트 처리

namespace FormEventDemo
{
    public class CustomForm
    {
        public event EventHandler FormClicked;
        
        public void SimulateClick()
        {
            Console.WriteLine("폼 클릭됨");
            FormClicked?.Invoke(this, EventArgs.Empty);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var form = new CustomForm();
            var handler = new FormHandler();

            form.FormClicked += handler.HandleClick;
            form.SimulateClick();

            Console.ReadKey();
        }
    }

    class FormHandler
    {
        public void HandleClick(object sender, EventArgs e)
        {
            Console.WriteLine("클릭 이벤트 처리됨");
        }
    }
}

이벤트 vs Delegate: 핵심 정리

很多 개발자들이 이벤트를 "special한 delegate 필드"로 오해합니다. 하지만 실제로는:
  • 외부 호출 방지: event가 없으면 delegate를 클래스 외부에서 마음대로 호출 가능
  • 구독 제한: += 과 -= 로만事件을 관리할 수 있음
  • 캡슐화: event는 delegate를 포장하여 불필요한 접근을 차단
event 키워드는delegate에 대한 보호막 역할을 합니다. 이 보호막은 delegate 자체가 아니라 접근을 제한하는包装자입니다.

핵심 정리

이벤트는 C#에서 중요한 역할을 합니다. 특히 GUI 프로그래밍에서 마우스 클릭, 키보드 입력, 타이머到期などの 이벤트는日常 inúmerivable입니다. 이벤트 없이는 현대적인 애플리케이션 개발이 불가능하다고 해도 과언이 아닙니다. 이벤트의 기반은 delegate이며, delegate의 기반은 클래스입니다. 결국 모든 것은 객체지향 프로그래밍에서 비롯됩니다. 이러한根基을 이해하면 더 나은 코드를 작성할 수 있습니다.

태그: C# Events Delegates Observer Pattern WinForms

7월 11일 23:39에 게시됨