C #에서 이벤트 처리기를 명시 적으로 제거해야합니까?


120

몇 가지 이벤트를 제공하는 수업이 있습니다. 해당 클래스는 전역으로 선언되지만 해당 전역 선언에 인스턴스화되지는 않습니다. 필요한 메서드에서 필요에 따라 인스턴스화됩니다.

해당 클래스가 메서드에 필요할 때마다 인스턴스화되고 이벤트 핸들러가 등록됩니다. 메서드가 범위를 벗어나기 전에 이벤트 처리기를 명시 적으로 제거해야합니까?

메서드가 범위를 벗어나면 클래스의 인스턴스도 마찬가지입니다. 범위를 벗어난 인스턴스에 등록 된 이벤트 핸들러를 남겨두면 메모리 풋 프린트에 영향을 미칩니 까? (이벤트 핸들러가 GC가 더 이상 참조되지 않는 클래스 인스턴스를 보지 못하도록하는지 궁금합니다.)

답변:


184

귀하의 경우에는 모든 것이 좋습니다. 이벤트 핸들러 의 대상 을 라이브로 유지하는 이벤트 를 게시 하는 객체입니다 . 그래서 내가 가지고 있다면 :

publisher.SomeEvent += target.DoSomething;

그런 다음 publisher참조가 target있지만 반대는 아닙니다.

귀하의 경우 게시자는 가비지 수집 대상이 될 것이므로 (다른 참조가 없다고 가정) 이벤트 처리기 대상에 대한 참조가 있다는 사실은 관련이 없습니다.

까다로운 경우는 게시자가 수명이 길지만 구독자가 원하지 않는 경우 입니다. 경우 처리기를 구독 취소해야합니다. 예를 들어 대역폭 변경에 대한 비동기 알림을 구독 할 수있는 일부 데이터 전송 서비스가 있고 전송 서비스 개체가 오래 지속된다고 가정합니다. 이렇게하면 :

BandwidthUI ui = new BandwidthUI();
transferService.BandwidthChanged += ui.HandleBandwidthChange;
// Suppose this blocks until the transfer is complete
transferService.Transfer(source, destination);
// We now have to unsusbcribe from the event
transferService.BandwidthChanged -= ui.HandleBandwidthChange;

(실제로 이벤트 핸들러를 유출하지 않도록 finally 블록을 사용하고 싶을 것입니다.) 구독을 취소하지 않았다면는 BandwidthUI최소한 전송 서비스만큼 오래 지속됩니다.

개인적으로 나는 거의 이것을 접하지 않습니다. 일반적으로 이벤트를 구독하는 경우 해당 이벤트의 대상은 최소한 게시자만큼 유지됩니다. 예를 들어 양식은 그 위에있는 버튼만큼 지속됩니다. 이 잠재적 인 문제에 대해 아는 것은 가치가 있지만 일부 사람들은 참조가 어느 방향으로 가는지 알지 못하기 때문에 필요하지 않을 때 걱정한다고 생각합니다.

편집 : 이것은 Jonathan Dickinson의 의견에 대답하는 것입니다. 첫째, 동등성 동작을 명확하게 제공하는 Delegate.Equals (object) 문서를 살펴보십시오 .

둘째, 구독 취소가 작동하는 것을 보여주는 짧지 만 완전한 프로그램이 있습니다.

using System;

public class Publisher
{
    public event EventHandler Foo;

    public void RaiseFoo()
    {
        Console.WriteLine("Raising Foo");
        EventHandler handler = Foo;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
        else
        {
            Console.WriteLine("No handlers");
        }
    }
}

public class Subscriber
{
    public void FooHandler(object sender, EventArgs e)
    {
        Console.WriteLine("Subscriber.FooHandler()");
    }
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;
         publisher.RaiseFoo();
         publisher.Foo -= subscriber.FooHandler;
         publisher.RaiseFoo();
    }
}

결과 :

Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers

(Mono 및 .NET 3.5SP1에서 테스트되었습니다.)

추가 편집 :

이는 구독자에 대한 참조가있는 동안 이벤트 게시자를 수집 할 수 있음을 증명하기위한 것입니다.

using System;

public class Publisher
{
    ~Publisher()
    {
        Console.WriteLine("~Publisher");
        Console.WriteLine("Foo==null ? {0}", Foo == null);
    }

    public event EventHandler Foo;
}

public class Subscriber
{
    ~Subscriber()
    {
        Console.WriteLine("~Subscriber");
    }

    public void FooHandler(object sender, EventArgs e) {}
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;

         Console.WriteLine("No more refs to publisher, "
             + "but subscriber is alive");
         GC.Collect();
         GC.WaitForPendingFinalizers();         

         Console.WriteLine("End of Main method. Subscriber is about to "
             + "become eligible for collection");
         GC.KeepAlive(subscriber);
    }
}

결과 (.NET 3.5SP1에서, Mono는 여기에서 약간 이상하게 작동하는 것 같습니다. 나중에 살펴 보겠습니다) :

No more refs to publisher, but subscriber is alive
~Publisher
Foo==null ? False
End of Main method. Subscriber is about to become eligible for collection
~Subscriber

2
나는 이것에 동의하지만 가능하다면 "그러나 가입자는 원하지 않는다"는 의미의 예에 대해 간략하게 설명하거나 가급적 참조 할 수 있습니까?
Peter McG

@Jon : 많이 감사합니다. 흔하지는 않지만 사람들이 불필요하게 이것에 대해 걱정하는 것을 보았습니다.
Peter McG

-= 작동하지 않습니다. -= 새 델리게이트가 생성되고 델리게이트는 target 메서드를 사용하여 동등성을 확인하지 않고 델리게이트에 대해 object.ReferenceEquals ()를 수행합니다. 새 델리게이트는 목록에 존재하지 않습니다. 효과가 없으며 이상하게도 오류가 발생하지 않습니다.
Jonathan C Dickinson

2
@Jonathan : 아니요, 대리자는 대상 메서드를 사용하여 동등성을 확인합니다. 편집에서 증명할 것입니다.
Jon Skeet

나는 인정한다. 익명의 대표자들과 혼동했습니다.
Jonathan C Dickinson

8

귀하의 경우에는 괜찮습니다. 나는 원래 귀하의 질문을 거꾸로 읽었습니다. 구독자게시자가 아니라 범위를 벗어났다는 것 입니다. 이벤트 게시자가 범위를 벗어나면 참조 면 구독자에 (물론 구독자 자체가 아님)도 함께 이동하므로 명시 적으로 제거 할 필요가 없습니다.

내 원래 대답은 이벤트 구독자 를 만들면 어떻게되는지에 대한 것입니다. 구독을 취소하지 않고 범위를 벗어나게 입니다. 귀하의 질문에는 적용되지 않지만 역사를 위해 그대로 두겠습니다.

클래스가 여전히 이벤트 핸들러를 통해 등록되어 있으면 여전히 도달 할 수 있습니다. 여전히 살아있는 물체입니다. 이벤트 그래프를 따라가는 GC는 연결되었음을 알 수 있습니다. 예, 이벤트 핸들러를 명시 적으로 제거 할 수 있습니다.

객체가 원래 할당 범위를 벗어났다고해서 GC 후보가되는 것은 아닙니다. 라이브 참조가 남아있는 한 라이브입니다.


1
여기에서는 구독 취소가 필요하지 않다고 생각합니다. GC는 이벤트 게시자가 아닌 이벤트 게시자의 참조 를 확인 하고 여기서 우려하는 게시자입니다.
Jon Skeet

@Jon Skeet : 당신이 맞아요. 나는 질문을 거꾸로 읽었습니다. 현실을 반영하기 위해 내 대답을 수정했습니다.
Eddie
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.