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

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

이벤트는 대리자를 event 한정자로 수식하여 만듭니다.

이벤트를 선언하고 사용하는 절차

1. 대리자를 선언합니다. 

 – 대리자의 위치는 클래스 밖 또는 안에 선언해도 됩니다.

2. 선언한 대리자의 인스턴스를 event 한정자로 수식하여 선언합니다.

3. 이벤트 핸들러를 작성합니다.

– 이벤트 핸들러는  1. 에서 선언한 대리자와 일치하는 메소드면 됩니다.

4. 클래스의 인스턴스를 생성하고 이 객체의 이벤트에  3. 에서 작성한 이벤트 핸들러를 등록합니다.

5. 이벤트가 발생하면 이벤트 핸들러가 호출됩니다.


1. 대리자를 선언합니다. 

 – 대리자의 위치는 클래스 밖 또는 안에 선언해도 됩니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public delegate void EventHandler(string message);
// EventHandler는 대리자의 이름 입니다.
public delegate void EventHandler(string message); // EventHandler는 대리자의 이름 입니다.
public delegate void EventHandler(string message);
// EventHandler는 대리자의 이름 입니다.

2. 선언한 대리자의 인스턴스를 event 한정자로 수식하여 선언합니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// 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))
}
}
}
// 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)) } } }
// 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. 에서 선언한 대리자와 형식이 일치하는 메소드면 됩니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class MainApp
{
static public void MyHandler(string message)
{
Console.WriteLine(message);
}
}
//...
}
class MainApp { static public void MyHandler(string message) { Console.WriteLine(message); } } //... }
   class MainApp
    {

        static public void MyHandler(string message)
        {
        
            Console.WriteLine(message);
            
        }

    }

    //...


}

4. 클래스의 인스턴스를 생성하고 이 객체의 이벤트에  3. 에서 작성한 이벤트 핸들러를 등록합니다.

5. 이벤트가 발생하면 이벤트 핸들러가 호출됩니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
}
}
}
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); } } }
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);

        }

    }

}
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
}
}
}
}
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); } } } }
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로 수식되어 있으면 클래스 외부에서라도 얼마든지 호출이 가능합니다.

    – 콜백 용도로 많이 사용

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
}
}
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); } }
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);
        
    }
    
}

다른 예제

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
}
}
}
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); } } }
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 여부를 확인해야한다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
}
}
}
}
}
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); } } } } }
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 대리자를 다른 클래스가 호출하지 못하게 제한하는 것이 바람직하다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
*/
}
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); */ }
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을 확인하지 않고 이벤트 발생시킬 수 있다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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 class Thermostat { public event EventHandler<TemperatureArgs> OnTemperatureChange = delegate { }; public class TemperatureArgs : EventArgs { public TemperatureArgs(float newTemperature) { NewTemperature = newTemperature; } public float NewTemperature { get; set; } } }
    public class Thermostat
    {

        public event EventHandler<TemperatureArgs> OnTemperatureChange = delegate { };

        public class TemperatureArgs : EventArgs
        {

            public TemperatureArgs(float newTemperature)
            {
            
                NewTemperature = newTemperature;
                
            }

            public float NewTemperature { get; set; }
            
        }

    }

이벤트 구현의 보편적인 방식

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
 public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

결과적으로 public Action<float> OnTemperatureChange 대리자 형식의 매개변수 1개가 새로운 매개변수 2개로 대체됬으며

각각 이벤트 게시자와 이벤트 데이터다.

첫번째 매개변수인 sender는 대리자를 호출한 클래스의 인스턴스를 가리키고 있어야 한다.

두번째 매개변수인 TEventArgs e 는 Thermostat.TemperatureArgs 형식이다.

 Thermostat.TemperatureArgs 는 추가로 NewTemperature하는 속성을 정의해 구독자에게 보낼 온도를 저장하는 수단으로 이용한다.

댓글 달기

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