다음을 통해 공유


약한 이벤트 패턴

애플리케이션에서는 이벤트 원본에 연결된 처리기는 처리기를 원본에 연결한 수신기 개체와 조정하여 제거되지 않을 수 있습니다. 이 경우 메모리 누수로 이어질 수 있습니다. WPF(Windows Presentation Foundation)는 특정 이벤트에 대한 전용 관리자 클래스를 제공하고 해당 이벤트에 대한 수신기에 인터페이스를 구현하여 이 문제를 해결하는 데 사용할 수 있는 디자인 패턴을 도입합니다. 이 디자인 패턴을 약한 이벤트 패턴이라고 합니다.

약한 이벤트 패턴을 구현하는 이유는 무엇인가요?

이벤트를 수신 대기하면 메모리 누수가 발생할 수 있습니다. 이벤트를 수신 대기하는 일반적인 방법은 처리기를 원본의 이벤트에 연결하는 언어별 구문을 사용하는 것입니다. 예를 들어 C#에서 해당 구문은 source.SomeEvent += new SomeEventHandler(MyEventHandler)입니다.

이 기술은 이벤트 원본에서 이벤트 수신기에 대한 강력한 참조를 만듭니다. 일반적으로 수신기에 대한 이벤트 처리기를 연결하면 수신기가 원본의 개체 수명에 영향을 받는 개체 수명을 갖게 됩니다(이벤트 처리기가 명시적으로 제거되지 않는 한). 그러나 특정 상황에서는 수신기의 개체 수명이 원본의 수명이 아니라 현재 애플리케이션의 시각적 트리에 속하는지 여부와 같은 다른 요인에 의해 제어되도록 할 수 있습니다. 원본 개체 수명이 수신기의 개체 수명을 초과할 때마다 일반 이벤트 패턴은 메모리 누수로 이어집니다. 즉, 수신기는 의도한 것보다 더 오래 활성 상태로 유지됩니다.

약한 이벤트 패턴은 이 메모리 누수 문제를 해결하도록 설계되었습니다. 수신기가 이벤트에 등록해야 할 때마다 약한 이벤트 패턴을 사용할 수 있지만 수신기는 등록 취소 시점을 명시적으로 알지 못합니다. 소스의 개체 수명이 수신기의 유용한 개체 수명을 초과할 때마다 약한 이벤트 패턴을 사용할 수도 있습니다. (이 경우 유용성은 여러분에 의해 결정됩니다.) 약한 이벤트 패턴을 사용하면 수신기가 어떤 방식으로든 수신기의 개체 수명 특성에 영향을 주지 않고 이벤트를 등록하고 받을 수 있습니다. 실제로 원본의 암시적 참조는 수신기가 가비지 수집에 적합한지 여부를 결정하지 않습니다. 참조는 약한 참조이므로 약한 이벤트 패턴 및 관련 API의 이름을 지정합니다. 수신기는 가비지 수집되거나 제거될 수 있으며, 원본은 이제 소멸된 개체에 대한 수집 불가능한 처리기 참조를 유지하지 않고도 계속할 수 있습니다.

약한 이벤트 패턴은 누가 구현해야 하나요?

약한 이벤트 패턴을 구현하는 것은 주로 컨트롤 작성자에게 흥미로울 것입니다. 컨트롤 작성자는 컨트롤의 동작과 억제, 컨트롤이 삽입되는 애플리케이션에 미치는 영향에 대해 주로 책임을 집니다. 여기에는 컨트롤 개체 수명 동작, 특히 설명된 메모리 누수 문제의 처리가 포함됩니다.

특정 시나리오는 기본적으로 약한 이벤트 패턴의 적용에 적합합니다. 이러한 시나리오 중 하나는 데이터 바인딩입니다. 데이터 바인딩에서 원본 개체는 바인딩의 대상인 수신기 개체와 완전히 독립적이어야 합니다. WPF 데이터 바인딩의 많은 측면에는 이미 이벤트가 구현되는 방식에 약한 이벤트 패턴이 적용되어 있습니다.

약한 이벤트 패턴을 구현하는 방법

약한 이벤트 패턴을 구현하는 방법에는 세 가지가 있습니다. 다음 표에서는 세 가지 방식을 나열하고 각각을 사용해야 하는 경우에 대한 몇 가지 지침을 제공합니다.

접근 방식 구현 시기
기존 약한 이벤트 관리자 클래스 사용 구독하려는 이벤트에 해당 WeakEventManager가 있는 경우 기존의 약한 이벤트 관리자를 사용합니다. WPF에 포함된 약한 이벤트 관리자 목록은 WeakEventManager 클래스의 상속 계층 구조를 참조하세요. 포함된 약한 이벤트 관리자가 제한되어 있으므로 다른 방법 중 하나를 선택해야 할 수 있습니다.
제네릭 약한 이벤트 관리자 클래스 사용 기존 WeakEventManager를 사용할 수 없고,, 구현하기 쉬운 방법을 원하며, 효율성에 대해 걱정하지 않는 경우 제네릭 WeakEventManager<TEventSource,TEventArgs>를 사용합니다. 제네릭 WeakEventManager<TEventSource,TEventArgs>는 기존 또는 사용자 지정 약한 이벤트 관리자보다 덜 효율적입니다. 예를 들어 제네릭 클래스는 이벤트 이름이 지정된 이벤트를 검색하기 위해 더 많은 리플렉션을 수행합니다. 또한 제네릭 WeakEventManager<TEventSource,TEventArgs>를 사용하여 이벤트를 등록하는 코드는 기존 또는 사용자 지정 WeakEventManager를 사용하는 것보다 더 자세한 정보입니다.
사용자 지정 약한 이벤트 관리자 클래스 만들기 기존 WeakEventManager를 사용할 수 없고 최상의 효율성을 원하는 경우 사용자 지정 WeakEventManager를 만듭니다. 사용자 지정 WeakEventManager를 사용하여 이벤트를 구독하는 것이 더 효율적이지만 처음에 더 많은 코드를 작성하는 비용이 발생합니다.
타사 약한 이벤트 관리자 사용 NuGet은 여러 약한 이벤트 관리자가 있으며 많은 WPF 프레임워크도 패턴을 지원합니다.

다음 섹션에서는 약한 이벤트 패턴을 구현하는 방법을 설명합니다. 이 토론을 위해 구독할 이벤트에는 다음과 같은 특징이 있습니다.

  • 이벤트 이름은 SomeEvent입니다.

  • 이벤트는 EventSource 클래스에서 발생합니다.

  • 이벤트 처리기에는 SomeEventEventHandler 또는 EventHandler<SomeEventEventArgs> 형식이 있습니다.

  • 이벤트는 SomeEventEventArgs 형식의 매개 변수를 이벤트 처리기에 전달합니다.

기존 Weak Event Manager 클래스 사용

  1. 기존 약한 이벤트 관리자를 찾습니다.

    WPF에 포함된 약한 이벤트 관리자 목록은 WeakEventManager 클래스의 상속 계층 구조를 참조하세요.

  2. 일반 이벤트 연결 대신 새 약한 이벤트 관리자를 사용합니다.

    예를 들어 코드에서 다음 패턴을 사용하여 이벤트를 구독하는 경우:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    다음 패턴으로 변경합니다.

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    마찬가지로 코드에서 다음 패턴을 사용하여 이벤트에서 구독을 취소하는 경우:

    source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
    

    다음 패턴으로 변경합니다.

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

제네릭 Weak Event Manager 클래스 사용

  1. 일반 이벤트 연결 대신 제네릭 WeakEventManager<TEventSource,TEventArgs> 클래스를 사용합니다.

    WeakEventManager<TEventSource,TEventArgs>를 사용하여 이벤트 수신기를 등록하는 경우 다음 코드와 같이 이벤트 원본 및 EventArgs 형식을 클래스에 형식 매개 변수로 제공하고 AddHandler를 호출합니다.

    WeakEventManager<EventSource, SomeEventEventArgs>.AddHandler(source, "SomeEvent", source_SomeEvent);
    

사용자 지정 Weak Event Manager 클래스 만들기

  1. 다음 클래스 템플릿을 프로젝트에 복사합니다.

    이 클래스는 WeakEventManager 클래스에서 상속됩니다.

    class SomeEventWeakEventManager : WeakEventManager
    {
    
        private SomeEventWeakEventManager()
        {
        }
    
        /// <summary>
        /// Add a handler for the given source's event.
        /// </summary>
        public static void AddHandler(EventSource source,
                                      EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedAddHandler(source, handler);
        }
    
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(EventSource source,
                                         EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedRemoveHandler(source, handler);
        }
    
        /// <summary>
        /// Get the event manager for the current thread.
        /// </summary>
        private static SomeEventWeakEventManager CurrentManager
        {
            get
            {
                Type managerType = typeof(SomeEventWeakEventManager);
                SomeEventWeakEventManager manager =
                    (SomeEventWeakEventManager)GetCurrentManager(managerType);
    
                // at first use, create and register a new manager
                if (manager == null)
                {
                    manager = new SomeEventWeakEventManager();
                    SetCurrentManager(managerType, manager);
                }
    
                return manager;
            }
        }
    
        /// <summary>
        /// Return a new list to hold listeners to the event.
        /// </summary>
        protected override ListenerList NewListenerList()
        {
            return new ListenerList<SomeEventEventArgs>();
        }
    
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent += new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent -= new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Event handler for the SomeEvent event.
        /// </summary>
        void OnSomeEvent(object sender, SomeEventEventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }
    
  2. SomeEventWeakEventManager 이름을 사용자 고유의 이름으로 바꿉니다.

  3. 이전에 설명한 세 개의 이름을 이벤트에 해당하는 이름으로 바꿉니다. (SomeEvent, EventSourceSomeEventEventArgs)

  4. 약한 이벤트 관리자 클래스의 표시 유형(public/internal/private)을 관리하는 이벤트와 동일한 표시 유형으로 설정합니다.

  5. 일반 이벤트 연결 대신 새 약한 이벤트 관리자를 사용합니다.

    예를 들어 코드에서 다음 패턴을 사용하여 이벤트를 구독하는 경우:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    다음 패턴으로 변경합니다.

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    마찬가지로 코드에서 다음 패턴을 사용하여 이벤트 구독을 취소하는 경우:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    다음 패턴으로 변경합니다.

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

참고 항목