Event (이벤트)

2022. 1. 18. 00:00CSharp/Advance

반응형

클래스나 개체에서 특정 상황이 발생할 때 이벤트를 통해 다른 클래스나 개체에 이를 알려 줄 수 있다. 

이벤트를 보내거나 발생시키는 클래스를 게시자 (publisher)

이벤트를 받거나 처리하는(handle) 클래스를 구독자 (subscribers)

 

이벤트는 선언된 클래스 내에서만 호출할 수 있는 특수한 종류의 멀티캐스트 대리자이다.

 

Dotnet 에서 표준적으로 이벤트를 게시하는 방법이 EventHandler 를 이용하는 방법이므로 이를 이용하자

 

아래와 같은 방식으로 선언 

public event EventHandler<CustomEventArgs> RaiseCustomEvent;

 

원래 delegate 로 표현하면 아래와 같다고 생각하면 된다. 

public delegate void CustomEventHandler(object sender, CustomEventArgs args);

 

예제)

public class StandardEvents
{
    public class CustomEventArgs : EventArgs
    {
        public CustomEventArgs(string message)
        {
            Message = message;
        }
        public string Message { get; set; }
    }

    // Class that publishes an event
    public class Publisher
    {
        // Declare the event using EventHandler<T>
        public event EventHandler<CustomEventArgs> RaiseStandardEvent;
        public void FiredStandardEvent()
        {
            // Write some code that does something useful here
            // then raise the event. You can also raise an event
            // before you execute a block of code.
            OnRaiseCustomEvent(new CustomEventArgs("표준 이벤트 발생"));
        }

        // Wrap event invocations inside a protected virtual method
        // to allow derived classes to override the event invocation behavior
        protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<CustomEventArgs> raiseEvent = RaiseStandardEvent;
            // Event will be null if there are no subscribers
            if (raiseEvent != null)
            {
                // Format the string to send inside the CustomEventArgs parameter
                e.Message += $" at {DateTime.Now}";
                // Call to raise the event.
                raiseEvent(this, e);
            }
        }
    }

    //Class that subscribes to an event
    public class Subscriber
    {
        private readonly string _id;
        public Subscriber(string id, Publisher pub)
        {
            _id = id;
            // Subscribe to the event
            pub.RaiseStandardEvent += OnRaiseStandardEvent;
        }
        // Define what actions to take when the event is raised.
        private void OnRaiseStandardEvent(object sender, CustomEventArgs e)
        {
            Console.WriteLine($"{_id} received this message: {e.Message}");
        }
    }

    public class Test : ITest
    {
        public void Run()
        {
            var pub = new Publisher();
            var sub1 = new Subscriber("sub1", pub);
            //var sub2 = new Subscriber("sub2", pub);

            // Call the method that raises the event.
            pub.FiredStandardEvent();
        }
    }
}

 

이벤트 등록

Add (+=)

pub.RaiseCustomEvent += HandleCustomEvent;

 

이벤트 삭제

Remove (-=)

pub.RaiseCustomEvent -= HandleCustomEvent;

 

*** delegate 와의 차이 *** 

1. event 는 정의한 class 내부에서만(상속한 class 에서도 불가) event 를 trigger 할 수 있다. 

delegate 는 정의한 class 뿐만 아니라 외부에서도 delegate 를 trigger 할 수 있다. 

2. event 는 interface 에 정의 할 수 있다. 

delegate type 으로 정의된 변수를 interface 에서 사용하려면 property 형태로 만들어야한다. 

 

event 는 단순한 수정자 이다. 

 

delegate 같은 경우 치명적인 문제점(?) 이 있는데  바로 delegate type 변수를 생성한 class 외부에서

delegate 자체를 trigger 할 수 있다는 것이다.  어떤 말이냐 하면 아래 코드를 보자

public class DelegatesVsEvents
{
    // 비표준 형태의 event type 선언
    public delegate void NonStandardEventType(string message);
    // Class that publishes an event
    public class Publisher
    {
        // NonStandardEventType 의 event 수정자를 통해 delegate 변수 생성
        public event NonStandardEventType RaiseEvent;

        // NonStandardEventType 의 delegate 변수 생성
        public NonStandardEventType RaiseDelegate;
        public void FiredRaiseEvent()
        {
            RaiseEvent("이벤트 발생");
        }
        public void FiredRaiseDelegate()
        {
            RaiseEvent("delegate 발생");
        }

    }
    //Class that subscribes to an event
    public class Subscriber
    {
        private readonly string _id;
        public Subscriber(string id, Publisher pub)
        {
            _id = id;
            // Subscribe to the event
            pub.RaiseEvent += OnRaiseEvent;
            // Subscribe to the delegate
            pub.RaiseDelegate += OnRaiseDelegate;

            // 기본적으로 위 두가지는 같다. 
            // 하지만

            //pub.RaiseEvent("내가 훔쳤다 ㅋㅋㅋㅋ"); // 컴파일 되지 않는다.
            pub.RaiseDelegate("내가 훔쳤다 ㅋㅋㅋㅋ"); // 가능하다. 
        }
        private void OnRaiseEvent(string message)
        {
            message += $" at {DateTime.Now}";
            Console.WriteLine($"OnRaiseEvent {_id} received this message: {message}");
        }

        private void OnRaiseDelegate(string message)
        {
            message += $" at {DateTime.Now}";
            Console.WriteLine($"OnRaiseDelegate {_id} received this message: {message}");
        }
    }

    public class Test : ITest
    {
        public void Run()
        {
            var pub = new Publisher();
            var sub1 = new Subscriber("sub1", pub);
            //var sub2 = new Subscriber("sub2", pub);


            // event 발생;
            pub.FiredRaiseEvent();
            // delegate 발생;
            pub.FiredRaiseEvent();
        }
    }
}

 

즉 아래 코드 두부분이 event 와 delegate 의 가장 큰 차이점이라고 할 수 있다. 

//pub.RaiseEvent("내가 훔쳤다 ㅋㅋㅋㅋ"); // 컴파일 되지 않는다.
pub.RaiseDelegate("내가 훔쳤다 ㅋㅋㅋㅋ"); // 가능하다.

event 는 event 자체를 외부에서 trigger 할 수 없다. 내부에서만 가능하다. 이게 event 수정자의 역할이다. 

 

event 는 interface 상에 정의 할 수 있다. 

delegate 는 단순 field 로 인식 하므로 interface 상에 정의 하려면 property 형태여야 한다. 

 

delegate type 선언 이란 의미는 아래와 같다. 

// Delegate type 선언
public delegate Shape CreateShapeDelegate();
public delegate void AddCircleDelegate(Circle e);
public delegate void AddRectangleDelegate(Rectangle e);
public delegate void AddTriangleDelegate(Triangle e);

선언된 delegate type 을 이용하여 delegate 변수를 정의 한다는 의미는 아래와 같다. 

// 선언된 deletegate type 을 이용하여 변수 정의 
public CreateShapeDelegate? MultiAction;
public AddCircleDelegate? AddCircleDelegateHandler;
public AddRectangleDelegate? AddRectangleDelegateHandler;
public AddTriangleDelegate? AddTriangleDelegateHandler;

 

그렇다면 event 에서 우리가 원하는 특정 delegate type 을 사용하여 event 를 fire 해보자

public class NonStandardEvents
{
    // 비표준 형태의 event type 선언
    public delegate void NonStandardEventType(string message);
    // Class that publishes an event
    public class Publisher
    {
        // NonStandardEventType 의 event 변수 생성
        public event NonStandardEventType RaiseNonStandardEvent;
        public void FiredNonStandardEvent()
        {
            RaiseNonStandardEvent("비표준 이벤트 발생");
        }
    }
    //Class that subscribes to an event
    public class Subscriber
    {
        private readonly string _id;
        public Subscriber(string id, Publisher pub)
        {
            _id = id;
            // Subscribe to the event
            pub.RaiseNonStandardEvent += OnRaiseNonStandardEvent;
        }
        private void OnRaiseNonStandardEvent(string message)
        {
            message += $" at {DateTime.Now}";
            Console.WriteLine($"{_id} received this message: {message}");
        }
    }

    public class Test : ITest
    {
        public void Run()
        {
            var pub = new Publisher();
            var sub1 = new Subscriber("sub1", pub);
            //var sub2 = new Subscriber("sub2", pub);

            // 비표준 event 실행;
            pub.FiredNonStandardEvent();
        }
    }
}

 

관련영상

https://youtu.be/fwXVT5U4oT8

 

 

 

 

 

 

반응형

'CSharp > Advance' 카테고리의 다른 글

제네릭 컬렉션 (Generic Collection)  (0) 2022.01.24
Record  (0) 2022.01.21
Lambda advanced  (0) 2022.01.20
Action, Func, Lambda  (0) 2022.01.19
Delegate (대리자)  (0) 2022.01.17