Używanie IDisposable do rezygnacji z subskrypcji wydarzeń

Mam klasę, która obsługuje zdarzenia z kontrolki WinForms. Bazując na tym, co robi użytkownik, odkładam jedną instancję klasy i tworzę nową, aby obsłużyć to samo zdarzenie. Najpierw muszę zrezygnować ze starej instancji z wydarzenia-dość łatwo. Chciałbym to zrobić w sposób niezastrzeżony, jeśli to możliwe, i wydaje się, że jest to praca dla IDisposable. Jednak większość dokumentacji zaleca IDisposable tylko w przypadku korzystania z niezarządzanych zasobów, co nie ma zastosowania proszę.

Jeśli zaimplementuję IDisposable i wypisuję się z Wydarzenia w Dispose (), czy wypaczam jego intencję? Czy powinienem zamiast tego podać funkcję Unsubscribe () i wywołać ją?


Edit: oto jakiś fałszywy kod, który pokazuje, co robię (używając IDisposable). Moja rzeczywista implementacja jest związana z jakimś zastrzeżonym powiązaniem danych (długa historia).

class EventListener : IDisposable
{
    private TextBox m_textBox;

    public EventListener(TextBox textBox)
    {
        m_textBox = textBox;
        textBox.TextChanged += new EventHandler(textBox_TextChanged);
    }

    void textBox_TextChanged(object sender, EventArgs e)
    {
        // do something
    }

    public void Dispose()
    {
        m_textBox.TextChanged -= new EventHandler(textBox_TextChanged);
    }
}

class MyClass
{
    EventListener m_eventListener = null;
    TextBox m_textBox = new TextBox();

    void SetEventListener()
    {
        if (m_eventListener != null) m_eventListener.Dispose();
        m_eventListener = new EventListener(m_textBox);
    }
}

W rzeczywistym kodzie, Klasa "EventListener" jest bardziej zaangażowana, a każda instancja jest unikalnie znaczące. Używam ich w kolekcji i tworzę/niszczę je, gdy użytkownik kliknie.


Wniosek

Akceptuję odpowiedź gbjbaanba, przynajmniej na razie. Mam wrażenie, że korzyści płynące z używania znanego interfejsu przewyższają wszelkie możliwe minusy używania go tam, gdzie nie ma kodu niezarządzanego (skąd użytkownik tego obiektu w ogóle o tym wie?).

Jeśli ktoś się nie zgadza-proszę pisać / komentować / edytować. Jeśli można postawić lepszy argument przeciwko IDisposable, then I ' ll change the accepted answer.

Author: Community, 2009-01-17

9 answers

Tak, śmiało. Chociaż niektórzy uważają, że IDisposable jest zaimplementowany tylko dla niezarządzanych zasobów , to nie jest to przypadek-niezarządzane zasoby po prostu zdarza się być największą wygraną i najbardziej oczywistym powodem do wdrożenia go. Myślę, że to nabrał tego pomysłu, ponieważ ludzie nie mogli wymyślić żadnego innego powodu, aby go użyć. To nie jest jak finalizer, który jest problemem z wydajnością i nie jest łatwy w obsłudze przez GC.

Umieść dowolny kod porządkowy w metodzie usuwania. Będzie jaśniej, czystiej i znacznie bardziej prawdopodobne, aby zapobiec wyciekom pamięci i cholernym wzroku łatwiejsze w użyciu poprawnie niż stara się pamiętać, aby usunąć swoje odniesienia.

Intencją IDisposable jest sprawienie, aby Twój kod działał lepiej bez konieczności wykonywania wielu ręcznych prac. Wykorzystaj jego moc na swoją korzyść i zapomnij o sztucznym" zamyśle projektowym".

Pamiętam, że było wystarczająco trudno przekonać Microsoftu o przydatności deterministycznej finalizacji, kiedy po raz pierwszy pojawił się. NET - wygraliśmy bitwę i przekonaliśmy ich, aby dodali ją (nawet jeśli był to wtedy tylko wzór projektowy), wykorzystajcie ją!

 38
Author: gbjbaanb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-16 22:43:51

Moim osobistym głosem byłoby posiadanie metody rezygnacji z subskrypcji w celu usunięcia klasy z wydarzeń. IDisposable to wzorzec przeznaczony do deterministycznego uwalniania niezarządzanych zasobów. W takim przypadku nie zarządzasz żadnymi niezarządzanymi zasobami i dlatego nie powinieneś wdrażać IDisposable.

IDisposable może być używany do zarządzania subskrypcjami zdarzeń, ale prawdopodobnie nie powinien. Dla przykładu wskazuję na WPF. Jest to biblioteka pełna wydarzeń i programów obsługi zdarzeń. Jednak praktycznie nie klasa w WPF implementuje IDisposable. Uznałbym to za wskazówkę, że należy zarządzać wydarzeniami w inny sposób.

 12
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-16 22:34:12

Jedną z rzeczy, która przeszkadza mi w używaniu wzorca IDisposable do wypisywania się z wydarzeń, jest problem z finalizacją.

Dispose() funkcja w IDisposable powinna być wywołana przez programistę, jednak jeśli nie zostanie wywołana przez programistę, jest zrozumiałe, że GC wywoła tę funkcję (przynajmniej standardowym wzorem IDisposable). W Twoim przypadku jednak, jeśli nie wywołasz Dispose nikt inny tego nie zrobi - Zdarzenie pozostaje i silne odniesienie wstrzymuje GC od wywołania finalizera.

The mere fakt, że Dispose () nie będzie wywoływane automatycznie przez GC wydaje mi się wystarczający, aby nie używać IDisposable w tym przypadku. Być może wymaga to nowego interfejsu specyficznego dla aplikacji, który mówi, że tego typu obiekt musi posiadać funkcję Cleanup wywołaną do usunięcia przez GC.

 8
Author: VitalyB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-06 13:13:08

Myślę, że jednorazowe jest do wszystkiego, co GC nie może zająć się automatycznie, A odniesienia do zdarzeń liczą się w mojej książce. Oto Klasa pomocników, którą wymyśliłem.

public class DisposableEvent<T> : IDisposable
    {

        EventHandler<EventArgs<T>> Target { get; set; }
        public T Args { get; set; }
        bool fired = false;

        public DisposableEvent(EventHandler<EventArgs<T>> target)
        {
            Target = target;
            Target += new EventHandler<EventArgs<T>>(subscriber);
        }

        public bool Wait(int howLongSeconds)
        {
            DateTime start = DateTime.Now;
            while (!fired && (DateTime.Now - start).TotalSeconds < howLongSeconds)
            {
                Thread.Sleep(100);
            }
            return fired;
        }

        void subscriber(object sender, EventArgs<T> e)
        {
            Args = e.Value;
            fired = true;
        }

        public void Dispose()
        {
            Target -= subscriber;
            Target = null;
        }

    }

Co pozwala na napisanie tego kodu:

Class1 class1 = new Class1();
            using (var x = new DisposableEvent<object>(class1.Test))
            {
                if (x.Wait(30))
                {
                    var result = x.Args;
                }
            }

Jeden efekt uboczny, nie możesz używać słowa kluczowego event na swoich zdarzeniach, ponieważ uniemożliwia to przekazanie ich jako parametru do konstruktora pomocniczego, jednak wydaje się, że nie ma to żadnych złych efektów.

 5
Author: Jason Coyne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-05 15:16:04

Z tego wszystkiego, co czytam o urządzeniach jednorazowych, wnioskuję, że zostały one wymyślone głównie w celu rozwiązania jednego problemu: uwolnienia niezarządzanych zasobów systemowych w odpowiednim czasie. Ale nadal wszystkie przykłady które znalazłem nie tylko koncentrują się na temacie niezarządzanych zasobów, ale mają również inną wspólną właściwość: Dispose jest wywoływany tylko po to, aby przyspieszyć proces, który w przeciwnym razie miałby miejsce później automatycznie (GC -> finalizer -> dispose)

Wywołanie dispose metoda, która anuluje subskrypcję zdarzenia, nigdy jednak nie nastąpi automatycznie, nawet jeśli dodasz finalizator, który zadzwoni do twojej dyspozycji. (przynajmniej nie tak długo, jak obiekt będący właścicielem zdarzenia istnieje - i jeśli zostanie wywołany, nie skorzystasz z wypisania się, ponieważ obiekt będący właścicielem zdarzenia również i tak zniknie)

Więc główną różnicą jest to, że zdarzenia w jakiś sposób budują Wykres obiektu, którego nie można zebrać, ponieważ obiekt obsługi zdarzeń nagle staje się odwołany do usługa, którą po prostu chciałeś odwołać / użyć. Nagle jesteś zmuszony do wywołania Dispose - No automatyczne usuwanie jest możliwe. Dispose otrzymałby tym samym subtelne inne znaczenie niż to, które można znaleźć we wszystkich przykładach, gdzie wywołanie Dispose - w brudnej teorii ;) - nie jest konieczne, ponieważ zostałoby wywołane automatycznie (w pewnym momencie)...

W każdym razie. Ponieważ jednorazowy wzór jest czymś, co już jest dość skomplikowane (dotyczy finalizatorów, które są trudne do prawidłowego i wiele wytycznych / kontraktów), a co ważniejsze w większości punktów nie ma nic wspólnego z tematem odwołującym się do zdarzenia, powiedziałbym, że łatwiej byłoby uzyskać to oddzielić w naszych głowach, po prostu nie używając tej metafory dla czegoś, co można nazwać "unroot from object graph" / "stop" / "turn off".

To, co chcemy osiągnąć, to wyłączyć / zatrzymać pewne zachowanie(poprzez wypisanie się ze zdarzenia). Fajnie by było mieć standardowy interfejs jak IStoppable z metodą Stop (), że przez kontrakt jest tylko skupiony na

  • odłączenie obiektu (+wszystkich jego własnych stoppables) od zdarzeń obiektów, których nie stworzył przez własne
  • Tak, że nie będzie już wywoływany w sposób implicit event style (dlatego może być postrzegany jako zatrzymany)]} Można je zbierać, gdy tylko znikną tradycyjne odniesienia do tego obiektu.]}

Wywołajmy jedyną metodę interfejsu, która wypisuje " Stop ()". Ty byś wiedział że zatrzymany obiekt jest w stanie akceptowalnym, ale tylko zostaje zatrzymany. Może zwykła nieruchomość "zatrzymana"również byłaby przyjemna.

Byłoby nawet sensowne posiadanie interfejsu" IRestartable", który dziedziczy po IStoppable i dodatkowo ma metodę" Restart ()", jeśli chcesz po prostu wstrzymać pewne zachowanie, które z pewnością będzie potrzebne ponownie w przyszłości, lub zapisać usunięty obiekt modelu w historii do późniejszego odzyskiwania.

Po tym wszystkim co piszę muszę przyznać się do właśnie widziałem przykład IDisposable gdzieś tutaj: http://msdn.microsoft.com/en-us/library/dd783449%28v=VS.100%29.aspx Ale w każdym razie, dopóki nie poznam każdego szczegółu i oryginalnej motywacji IObservable, powiedziałbym, że nie jest to najlepszy przykład przypadku użycia

  • ponieważ znowu jest to dość skomplikowany system wokół niego i mamy tylko mały problem tutaj
  • I może być tak, że jedną z motywacji tego całego nowego systemu jest pozbycie się zdarzeń w pierwszej kolejności, co spowodowałoby pewnego rodzaju przepełnienie stosu dotyczące oryginalnego pytania

Ale wydaje się, że są na dobrej drodze. W każdym razie: powinni użyć mojego interfejsu "IStoppable";) ponieważ mocno wierzę, że jest różnica w

  • Dispose: "you should call that method or something might leak if the GC happens to late"....

I

  • Stop: "you have to call this metoda zatrzymania pewnego zachowania "
 4
Author: Sebastian Gregor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-05 01:36:40

IDisposable jest mocno o zasoby, i źródło wystarczająco dużo problemów, aby nie błotniste wody dalej myślę.

Głosuję za metodą wypisania się z twojego własnego interfejsu.

 3
Author: annakata,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-16 22:39:03

Jedną z opcji może być nie wypisanie się z subskrypcji - po prostu zmiana znaczenia subskrypcji. Jeśli Obsługa zdarzenia może być wystarczająco inteligentna, aby wiedzieć, co ma zrobić na podstawie kontekstu, nie musisz w ogóle wypisywać się z subskrypcji.

To może lub nie być dobry pomysł w twoim konkretnym przypadku - nie sądzę, że mamy wystarczająco dużo informacji - ale warto to rozważyć.

 3
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-16 23:20:32

Inną opcją byłoby użycie słabych delegatów lub czegoś w rodzaju słabych zdarzeń WPFs, zamiast wyraźnego wypisywania się z subskrypcji.

P. S. [OT] uważam, że decyzja o dostarczeniu silnych delegatów jest jedynym najdroższym błędem projektowym platformy. NET.

 3
Author: Andreas Huber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-17 08:49:33

Nie, Nie powstrzymujesz intencji IDisposable. IDisposable jest przeznaczony jako uniwersalny sposób, aby zapewnić, że po zakończeniu używania obiektu można proaktywnie wyczyścić wszystko, co jest z nim związane. Nie musi to być tylko niezarządzane zasoby, może również zawierać zarządzane zasoby. A subskrypcja wydarzenia to tylko kolejny zarządzany zasób!

Podobny scenariusz, który często pojawia się w praktyce, polega na tym, że zaimplementujesz IDisposable na swoim typie, wyłącznie w kolejności aby zapewnić możliwość wywołania metody Dispose () na innym zarządzanym obiekcie. To nie jest perwersja, to po prostu uporządkowane zarządzanie zasobami!

 1
Author: Tim Lovell-Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-10 10:45:29