<--! 수학 기호 --> [C#] 객체에 일어난 사건 알리기! : 이벤트(Event)

새소식

반응형
Language/C#

[C#] 객체에 일어난 사건 알리기! : 이벤트(Event)

  • -

*이 글은 <이것이 C#이다> 책을 바탕으로 공부한 글입니다.

 

 

 

 

어떤 일이 발생하는 것을 알 수 있는 방법은 크게 두 가지가 있다.

 

  1. 일이 발생하는지 계속 감시하기
  2. 일이 발생하면 알려달라고 하고 다른 일 하고 있기

 

두 번째는 뭔가 익숙하지 않나? 저번 글에서 다뤘던 콜백(Callback) 내용이다. 이것에 관해, C#에서는 대리자(Delegate)라는 녀석이 존재하지만, 이벤트(Event)라는 녀석도 존재한다. 하나만 있으면 될 것 같은데, 왜 C#에서 이 둘을 제공하고 있고 어떤 점이 다른지에 대해 차근차근 알아보도록 하자.

 

 

이벤트(Event)

 

제일 흔하게 볼 수 있는 이벤트가 아마 알람 시계가 아닐까 싶다. 우리는 알람 시계의 시간을 설정 해놓고, 다른 일을 하고 있다가 시간이 되면 알람 소리를 들어서 시간이 다 된 것을 감지한다. 어떤 일이 발생하면 알려달라고 해 놓고, 나는 그동안 다른 일을 하고 있는 비동기적인 방식인 셈이다.

 

프로그래밍에서도 이러한 이벤트는 효과적으로 사용될 수 있다. 알람 시계처럼 특정 시간이 됐을 때 알려주거나, UI 버튼을 클릭했을 때 이를 알려주는 객체 등이 대표적인 예다. 일이 발생하는지 계속 감시하느라 다른 일을 못하고 있는 방식이 아니기 때문에 컴퓨터 리소스 측면에서도 효율적이다.

 

그렇다면, 이런 이벤트를 발생시키는 객체는 어떻게 만들 수 있을까?

 

 

이벤트는 대리자(Delegate)와 동작 원리가 비슷하다.

 

이 쯤 되면 대리자만 있으면 되지, 왜 이벤트가 있는지 의문이 들 수도 있다. 일단 그건 나중에 설명하기로 하고, 이벤트 선언하는 방식을 먼저 알아보자. 

 

 

1. 대리자를 선언한다. 대리자는 클래스 안과 밖 어디서 선언하든 상관없다.

delegate void EventHandler(string message);

 

이벤트를 선언하기 위해서는 먼저 대리자가 필요하다. 이벤트가 대리자를 한정자로 수식해서 만들기 때문이다.

 

2. 클래스 내에서 1번에서 선언한 대리자의 인스턴스를 event 한정자로 수식해서 선언한다.

class Notifier
{
    // 1번 절차에서 선언했던 대리자를 한정자로 수식해서 만든다.
    public event EventHandler SomethingHappend;
}

 

Notifier 클래스를 만들어서, 클래스 내부에 이벤트를 선언해줬다. 이 때, event 키워드와 이전에 만들었던 대리자를 수식해서 선언한다. 그리고 Notifier 클래스 내부에 다음과 같은 메소드를 하나 만들었다.

public void DoSomething(int number)
{
    int temp = number % 10;
    
    if(temp != 0 && temp % 3 == 0)
    {
        SomethingHappend(String.Format("{0} : 짝", number));
    }
}

 

 

3. 이벤트 핸들러를 작성한다. 이벤트 핸들러는 1번에서 선언한 대리자와 일치하는 메소드여야 한다.

class MainProgram
{
    // SomethingHappend 이벤트에서 사용할 이벤트 핸들러(MyHanlder)는 EventHandler 대리자의
    // 형식과 동일한 메소드여야 한다.
    
    static public void MyHandler(string message)
    {
        Console.WriteLine(message);
    }
}

 

이벤트 핸들러에 사용할 메소드를 선언해줬다. DoSomething() 메소드에서 SomethingHappend 이벤트를 호출하고 있는데, 아직은 SomethingHappend 이벤트에 등록된 메소드가 없다. 그래서, SomethingHappend 이벤트에 등록해줄 메소드를 선언해준 것인데, 이때 선언했던 대리자 형식과 일치해야 한다.

 

 

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

class MainProgram
{
    static public void MyHandler(string message)
    {
        Console.WriteLine(message);
    }
    
    
    static void Main(string[] args)
    {
        Notifier notifier = new Notifier();
        notifier.SomethingHappend += new EventHandler(MyHandler);  // 이벤트 핸들러 등록
        
        for(int i = 1; i < 30; i++)
        {
            notifier.DoSomething(i);
        }
    }
}

 

클래스 객체를 만들고, 객체 내부에 있는 이벤트에 아까 등록하려고 만들었던 메소드를 등록해줬다.

등록제거는 대리자에서 그랬던 것처럼 +=-= 연산자로 할 수 있다.

 

그리고 이제 for 문을 돌면서 객체의 메소드를 호출하고 있는데, 해당 메소드 내부에서 조건에 충족하면 이벤트를 발생시키는 코드를 짜놨었다. 이벤트에 MyHandler()라는 메소드를 등록해놨으니, 이벤트가 발생할 때마다 MyHandler() 메소드가 호출될 것이다.

 

 

주의) 이벤트는 이벤트 처리기를 등록하지 않아도 컴파일 에러가 발생하지 않는다.

 

그렇기 때문에 초기화 하는 걸 까먹어서 이벤트를 놓치는 경우가 있다. 책에서 제시한 방법 중 하나는 선언과 동시에 우선 익명 메소드로 미리 초기화는 해둔다고 한다. 최악의 경우에도 프로그램이 다운되는 것을 막을 수 있으니

 

 

 

"그래서 이벤트는 왜 사용하는건데?"

 

이 때까지 C# 이벤트를 선언하고 사용하는 방법을 알아보았다. 근데 대리자만 있어도 충분할 것 같은데, 왜 이벤트까지 제공하는 것일까?

 

이벤트와 대리자의 가장 큰 차이점은 이벤트는 외부에서 직접 사용할 수 없다는 점에 있다.

이벤트는 public 한정자로 선언돼 있어도 자신이 선언된 클래스 외부에서는 호출이 불가능하다. (외부에서 이벤트 등록은 가능) 위에서 했던 예제도 보면, 이벤트는 클래스 내부 메소드에서 호출했다.

 

반면, 대리자는 public이나 internal로 수식되어 있으면 클래스 외부에서라도 얼마든지 호출이 가능하다.

객체 외부에서 이벤트를 임의로 호출하여 조종할 수 있게 된다면, 시스템 마비를 일으킬 수 있는 큰 위협이 된다.

대리자로는 이러한 위협을 막을 수 없기 때문에 이벤트라는 것을 도입했다.

 

따라서 대리자는 콜백 용도로 사용하고, 이벤트는 객체의 상태 변화나 사건의 발생을 알리는 용도로 구분해서 사용해야 한다.

 

728x90
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.