이벤트 – 이것이 C# 이다.
이벤트는 대리자를 event 한정자로 수식하여 만듭니다.
이벤트를 선언하고 사용하는 절차
1. 대리자를 선언합니다.
– 대리자의 위치는 클래스 밖 또는 안에 선언해도 됩니다.
2. 선언한 대리자의 인스턴스를 event 한정자로 수식하여 선언합니다.
3. 이벤트 핸들러를 작성합니다.
– 이벤트 핸들러는 1. 에서 선언한 대리자와 일치하는 메소드면 됩니다.
4. 클래스의 인스턴스를 생성하고 이 객체의 이벤트에 3. 에서 작성한 이벤트 핸들러를 등록합니다.
5. 이벤트가 발생하면 이벤트 핸들러가 호출됩니다.
1. 대리자를 선언합니다.
– 대리자의 위치는 클래스 밖 또는 안에 선언해도 됩니다.
public delegate void EventHandler(string message); // EventHandler는 대리자의 이름 입니다.
2. 선언한 대리자의 인스턴스를 event 한정자로 수식하여 선언합니다.
// 1. 대리자 선언
public delegate void EventHandler(string message);
// EventHandler는 대리자의 이름 입니다.
class MyNotifier
{
    // 2. 선언한 대리자의 인스턴스를 event 한정자로 수식하여 선언
    public event EventHandler SomethingHappend;
    public void DoSomething(int number)
    {
        int temp = number % 10;
        if (temp != 0 && temp % 3 == 0)
        {
        
             // 2. number가 3, 6, 9 로 끝나는 값이 될때마다 이벤트가 발생
            SomethingHappend(String.Format(" {0} : 짝 ", number))
        }
    }
}
3. 이벤트 핸들러를 작성합니다.
– 이벤트 핸들러는 1. 에서 선언한 대리자와 형식이 일치하는 메소드면 됩니다.
   class MainApp
    {
        static public void MyHandler(string message)
        {
        
            Console.WriteLine(message);
            
        }
    }
    //...
}
4. 클래스의 인스턴스를 생성하고 이 객체의 이벤트에 3. 에서 작성한 이벤트 핸들러를 등록합니다.
5. 이벤트가 발생하면 이벤트 핸들러가 호출됩니다.
class MainApp
{
    static public void MyHandler(string message)
    {
        Console.WriteLine(message);
    }
    static void Main(string[] args)
    {
        // 인스턴스 생성
        MyNotifier notifier = new MyNotifier();
        // 이벤트 등록
        notifier.SomethingHappend += new EventHandler(MyHandler);
          
        for (int i = 0; i < 30; i++)
        {
             // 이벤트가 발생하면 이벤트 핸들러가 호출됩니다.
            notifier.DoSomething(i);
        }
    }
}
using System;
namespace EventTest
{
    delegate void EventHandler(string message);
    class MyNotifier 
    {
    
        public event EventHandler SomethingHappened;
        
        public void DoSomething(int number)
        {
        
            int temp = number % 10;
            if ( temp != 0 && temp % 3 == 0)
            {
            
                SomethingHappened(String.Format("{0} : 짝", number));
                
            }
            
        }
        
    }
    
    class MainApp
    {
    
        static public void MyHandler(string message)
        {
        
            Console.WriteLine(message);
            
        }
        static void Main(string[] args)
        {
        
            MyNotifier notifier = new MyNotifier();
            
            notifier.SomethingHappened += new EventHandler(MyHandler);
            
            for (int i = 1; i < 30; i++)
            {
            
                notifier.DoSomething(i);
                
            }
            
        }
        
    }
    
}

그렇다면 대리자와 이벤트의 차이점은 무엇일까?
이벤트는 public 한정자로 선언되어도 자신이 선언된 클래스 외부에서는 호출이 불가능합니다.
해당 이벤트를 포함하고 있는 클래스 안에서만 이벤트를 발생시킬 수 있다는 것.
– 대리자의 문제점인 불충분한 캡슐화를 보완하고 객체의 상태변화나 사건의 발생을 알리는 용도로 사용합니다.
반면 대리자는 public 이나 internal로 수식되어 있으면 클래스 외부에서라도 얼마든지 호출이 가능합니다.
– 콜백 용도로 많이 사용
static void Main(string[] args)
{
    MyNotifier notifier = new MyNotifier();
    
    // 불가능한 호출
    notifier.SomethingHappened("테스트");
    notifier.SomethingHappened += new EventHandler(MyHandler);
    for (int i = 1; i < 30; i++)
    {
    
        notifier.DoSomething(i);
        
    }
    
}
다른 예제
using System;
namespace Cooler
{
    public class Thermostat
    {
    
        private float currentTemperature;
        // OnTemperatureChange 속성은 Action<float> 대리자 형식으로 구독자 목록을 저장한다.
        public Action<float> OnTemperatureChange { get; set; }
        // CurrentTemperature 속성은 Thermostat 클래스에서 제공하는
        // 현재 온도값을 설정하거나 가져온다.
        public float CurrentTemperature
        {
        
            get 
            { 
            
            return currentTemperature; 
            
            }
            
            set
            {
            
                if (value != currentTemperature)
                {
                
                    currentTemperature = value;
                    
                    this.OnTemperatureChange(currentTemperature);
                    // 구독자 호출
                    
                }
                
            }
            
        }
    }
    // 온도 조절장치는 가열 및 냉각 단위 즉,
    // 온도 변화를 여러 개의 수신기(구독자)로 전달(게시)한다.
    // Cooler와 Heater의 개체 정의
    // 각 클래스느 장치를 켜야하는 기준 온도 값(Temperature)을 가지고 있다.
    class Cooler
    {
        public float Temperature { get; set; }
        // 생성자    기준 온도 값 생성
        public Cooler(float temperature)
        {
            Temperature = temperature;
            
        }
        // Temperature 과 newTemperature을 비교하여 장치의 ON OFF 를 결정
        // 구독자 메서드 역할을 하게되며 Thermostat 클래스에서 정의하는 대리자와
        // 일치하는 매개변수 및 반환 형식을 가져야 한다.
        public void OnTemperatureChanged(float newTemperature)
        {
        
            if (newTemperature > Temperature)
            {
            
                Console.WriteLine("Cooler : On");
                
            }
            
            else
            {
            
                Console.WriteLine("Cooler : Off");
                
            }
        }
        class Heater
        {
        
            public float Temperature;
            public Heater(float temperature)
            {
            
                Temperature = temperature;
            }
            public void OnTemperatureChanged(float newTemperature)
            {
            
                if (newTemperature < Temperature)
                {
                
                    Console.WriteLine("Heatrer : On");
                    
                }
                
                else
                {
                
                    Console.WriteLine("Heater : Off");
                    
                }
            }
        }
        static void Main(string[] args)
        {
        
            Thermostat thermostat = new Thermostat();
            
            Heater heater = new Heater(60);
            
            Cooler cooler = new Cooler(80);
            
            string temperature;
            // 게시자와 구독자 연결
            thermostat.OnTemperatureChange += heater.OnTemperatureChanged;
            
            thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;
            Console.Write("Enter temperaure : ");
            
            temperature = Console.ReadLine();
            
            thermostat.CurrentTemperature = int.Parse(temperature);
        }
    }
}

주의 알림을 받을 구독자가 하나도 없다면 OnTemperatureChange는 Null이며 NullReferenceException이 발생한다.
이와 같은 상황을 피하기 위해서 이벤트를 발생시키기 전에 null 여부를 확인해야한다.
public class Thermostat
    {
        private float currentTemperature;
        public Action<float> OnTemperatureChange { get; set; }
        public float CurrentTemperature
        {
            get
            {
                return currentTemperature;
            }
            set
            {
                if (value != currentTemperature)
                {
                    currentTemperature = value;
                    if (null != OnTemperatureChange)
                    {
                    
                        this.OnTemperatureChange(currentTemperature);
                        
                    }
                    
                }
            }
        }
    }
또한 대리자의 문제점은 아래와 같은 불충분한 캡슐화이다.
thermostat 클래스의 OnTemperatureChange 대리자를 다른 클래스가 호출하지 못하게 제한하는 것이 바람직하다.
static void Main(string[] args)
        {
            Thermostat thermostat = new Thermostat();
            Heater heater = new Heater(60);
            Cooler cooler = new Cooler(80);
            string temperature;
            // 게시자와 구독자 연결
            thermostat.OnTemperatureChange += heater.OnTemperatureChanged;
            thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;
            thermostat.OnTemperatureChange(50);  
            // 온도의 변화가 없음에도 구독자에게 알림
            
            /*  온도 변화
            Console.Write("Enter temperaure : ");
            temperature = Console.ReadLine();
            thermostat.CurrentTemperature = int.Parse(temperature);
            */
            
        }
이벤트의 캡슐화
원래의 클래스에서 변경사항
1. 기존의 OnTemperatureChange 속성 대신에 공용 속성 OnTemperatureChange를 새로 선언.
2. event 키워드를 추가로 적용하면 공용 대리자 필드에 할당 연산자를 외부에서 사용할 수 없다.
– event 키워드가 제공하는 캡슐화로 클래스의 외부에서 이벤트를 발생하거나 실수로 기존 구독자를 제거할 수 없다.
또한 대리자를 포함하고 있는 클래스만 대리자를 호출해 구독자들에게 이벤트를 발행할 수 있다.
3. //… OnTemperatureChange = delegate { }; 이벤트를 선언할 때 빈 대리자를 할당하여 null을 확인하지 않고 이벤트 발생시킬 수 있다.
    public class Thermostat
    {
        public event EventHandler<TemperatureArgs> OnTemperatureChange = delegate { };
        public class TemperatureArgs : EventArgs
        {
            public TemperatureArgs(float newTemperature)
            {
            
                NewTemperature = newTemperature;
                
            }
            public float NewTemperature { get; set; }
            
        }
    }
이벤트 구현의 보편적인 방식
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
결과적으로 public Action<float> OnTemperatureChange 대리자 형식의 매개변수 1개가 새로운 매개변수 2개로 대체됬으며
각각 이벤트 게시자와 이벤트 데이터다.
첫번째 매개변수인 sender는 대리자를 호출한 클래스의 인스턴스를 가리키고 있어야 한다.
두번째 매개변수인 TEventArgs e 는 Thermostat.TemperatureArgs 형식이다.
Thermostat.TemperatureArgs 는 추가로 NewTemperature하는 속성을 정의해 구독자에게 보낼 온도를 저장하는 수단으로 이용한다.



