이벤트 – 이것이 C# 이다.

이벤트 – 이것이 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하는 속성을 정의해 구독자에게 보낼 온도를 저장하는 수단으로 이용한다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Scroll to Top