Jak mogę wyczyścić subskrypcje zdarzeń w C #?

141

Weź następującą klasę C #:

c1 {
 event EventHandler someEvent;
}

Jeśli istnieje wiele prenumeraty c1„s someEventimprezy i chcę je wszystkie usunąć, co jest najlepszym sposobem osiągnięcia tego celu? Weź również pod uwagę, że subskrypcje tego zdarzenia mogą być / są lambdami / anonimowymi delegatami.

Obecnie moim rozwiązaniem jest dodanie ResetSubscriptions()metody do c1tych zestawów someEventna null. Nie wiem, czy ma to jakieś niewidoczne konsekwencje.

programista
źródło

Odpowiedzi:

181

Z poziomu klasy możesz ustawić (ukrytą) zmienną na null. Odwołanie zerowe to kanoniczny sposób efektywnego reprezentowania pustej listy wywołań.

Poza zajęciami nie możesz tego zrobić - wydarzenia w zasadzie ujawniają „subskrybuj” i „anuluj subskrypcję” i to wszystko.

Warto być świadomym tego, co tak naprawdę robią wydarzenia polowe - tworzą zmienną i zdarzenie w tym samym czasie. W ramach klasy odwołujesz się do zmiennej. Z zewnątrz nawiązujesz do wydarzenia.

Zobacz mój artykuł na temat wydarzeń i delegatów, aby uzyskać więcej informacji.

Jon Skeet
źródło
3
Jeśli jesteś uparty, możesz to wymusić poprzez refleksję. Zobacz stackoverflow.com/questions/91778/… .
Brian
1
@Brian: To zależy od implementacji. Jeśli jest to tylko wydarzenie lub wydarzenie podobne do pola EventHandlerList, możesz to zrobić. Musiałbyś jednak rozpoznać te dwa przypadki - i może być wiele innych implementacji.
Jon Skeet
@Joshua: Nie, ustawi zmienną tak, aby miała wartość null. Zgadzam się, że zmienna nie zostanie wywołana hidden.
Jon Skeet
@JonSkeet Tak właśnie (pomyślałem) powiedziałem. Sposób, w jaki został napisany, zdezorientował mnie przez 5 minut.
@JoshuaLamusga: Powiedziałeś, że wyczyści listę wywołań, która brzmi jak modyfikacja istniejącego obiektu.
Jon Skeet
34

Dodaj metodę do c1, która ustawi „someEvent” na null.

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}
programista
źródło
To jest zachowanie, które widzę. Jak powiedziałem w swoim pytaniu, nie wiem, czy coś przeoczyłem.
programista
8
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

Lepiej jest używać delegate { }niż nullunikać zerowego wyjątku ref.

Feng
źródło
2
Czemu? Czy mógłbyś rozwinąć tę odpowiedź?
S. Buda
1
@ S.Buda Ponieważ jeśli jest null, otrzymasz zerowy ref. To tak jak przy użyciu List.Clear()vs myList = null.
AustinWBryan
6

Ustawienie zdarzenia na null wewnątrz klasy działa. Po usunięciu klasy należy zawsze ustawić zdarzenie na null, GC ma problemy ze zdarzeniami i może nie wyczyścić usuniętej klasy, jeśli ma wiszące zdarzenia.

Jonathan C. Dickinson
źródło
6

Najlepszą praktyką w celu wyczyszczenia wszystkich subskrybentów jest ustawienie someEvent na wartość null przez dodanie innej metody publicznej, jeśli chcesz udostępnić tę funkcję na zewnątrz. Nie ma to niewidocznych konsekwencji. Warunkiem wstępnym jest pamiętanie o zadeklarowaniu SomeEvent ze słowem kluczowym „event”.

Proszę zobaczyć książkę - C # 4.0 w pigułce, strona 125.

Ktoś tutaj zaproponował użycie Delegate.RemoveAllmetody. Jeśli go używasz, przykładowy kod może być zgodny z poniższym formularzem. Ale to jest naprawdę głupie. Dlaczego nie tylko SomeEvent=nullwewnątrz ClearSubscribers()funkcji?

public void ClearSubscribers ()
{
   SomeEvent = (EventHandler) Delegate.RemoveAll(SomeEvent, SomeEvent);
   // Then you will find SomeEvent is set to null.
}
Cary
źródło
5

Można to osiągnąć za pomocą metod Delegate.Remove lub Delegate.RemoveAll.

Micheasza
źródło
6
Nie wierzę, że to zadziała z wyrażeniami lambda lub anonimowymi delegatami.
programista
3

Koncepcyjny rozszerzony nudny komentarz.

Raczej używam słowa „obsługa zdarzeń” zamiast „zdarzenie” lub „delegat”. I użył słowa „wydarzenie” dla innych rzeczy. W niektórych językach programowania (VB.NET, Object Pascal, Objective-C) „zdarzenie” jest nazywane „komunikatem” lub „sygnałem”, a nawet ma słowo kluczowe „wiadomość” i określoną składnię cukru.

const
  WM_Paint = 998;  // <-- "question" can be done by several talkers
  WM_Clear = 546;

type
  MyWindowClass = class(Window)
    procedure NotEventHandlerMethod_1;
    procedure NotEventHandlerMethod_17;

    procedure DoPaintEventHandler; message WM_Paint; // <-- "answer" by this listener
    procedure DoClearEventHandler; message WM_Clear;
  end;

Aby odpowiedzieć na ten „komunikat”, odpowiada „program obsługi zdarzeń”, niezależnie od tego, czy jest to pojedynczy delegat, czy wielu delegatów.

Podsumowanie: „zdarzenie” to „pytanie”, „obsługa zdarzeń” to odpowiedź (odpowiedzi).

umlcat
źródło
1

To jest moje rozwiązanie:

public class Foo : IDisposable
{
    private event EventHandler _statusChanged;
    public event EventHandler StatusChanged
    {
        add
        {
            _statusChanged += value;
        }
        remove
        {
            _statusChanged -= value;
        }
    }

    public void Dispose()
    {
        _statusChanged = null;
    }
}

Musisz zadzwonić Dispose()lub użyć using(new Foo()){/*...*/}wzorca, aby wypisać się ze wszystkich członków listy wywołań.

Jalal
źródło
0

Usuń wszystkie zdarzenia, załóżmy, że zdarzenie jest typu „Działanie”:

Delegate[] dary = TermCheckScore.GetInvocationList();

if ( dary != null )
{
    foreach ( Delegate del in dary )
    {
        TermCheckScore -= ( Action ) del;
    }
}
Googol
źródło
1
Jeśli jesteś wewnątrz typu, który zadeklarował zdarzenie, nie musisz tego robić, możesz po prostu ustawić go na null, jeśli jesteś poza typem, nie możesz uzyskać listy wywołań delegata. Ponadto kod zgłasza wyjątek, jeśli zdarzenie jest null, podczas wywoływania GetInvocationList.
Servy
-1

Zamiast ręcznie dodawać i usuwać wywołania zwrotne i mieć wszędzie zadeklarowaną grupę typów delegatów:

// The hard way
public delegate void ObjectCallback(ObjectType broadcaster);

public class Object
{
    public event ObjectCallback m_ObjectCallback;
    
    void SetupListener()
    {
        ObjectCallback callback = null;
        callback = (ObjectType broadcaster) =>
        {
            // one time logic here
            broadcaster.m_ObjectCallback -= callback;
        };
        m_ObjectCallback += callback;

    }
    
    void BroadcastEvent()
    {
        m_ObjectCallback?.Invoke(this);
    }
}

Możesz wypróbować to ogólne podejście:

public class Object
{
    public Broadcast<Object> m_EventToBroadcast = new Broadcast<Object>();

    void SetupListener()
    {
        m_EventToBroadcast.SubscribeOnce((ObjectType broadcaster) => {
            // one time logic here
        });
    }

    ~Object()
    {
        m_EventToBroadcast.Dispose();
        m_EventToBroadcast = null;
    }

    void BroadcastEvent()
    {
        m_EventToBroadcast.Broadcast(this);
    }
}


public delegate void ObjectDelegate<T>(T broadcaster);
public class Broadcast<T> : IDisposable
{
    private event ObjectDelegate<T> m_Event;
    private List<ObjectDelegate<T>> m_SingleSubscribers = new List<ObjectDelegate<T>>();

    ~Broadcast()
    {
        Dispose();
    }

    public void Dispose()
    {
        Clear();
        System.GC.SuppressFinalize(this);
    }

    public void Clear()
    {
        m_SingleSubscribers.Clear();
        m_Event = delegate { };
    }

    // add a one shot to this delegate that is removed after first broadcast
    public void SubscribeOnce(ObjectDelegate<T> del)
    {
        m_Event += del;
        m_SingleSubscribers.Add(del);
    }

    // add a recurring delegate that gets called each time
    public void Subscribe(ObjectDelegate<T> del)
    {
        m_Event += del;
    }

    public void Unsubscribe(ObjectDelegate<T> del)
    {
        m_Event -= del;
    }

    public void Broadcast(T broadcaster)
    {
        m_Event?.Invoke(broadcaster);
        for (int i = 0; i < m_SingleSubscribers.Count; ++i)
        {
            Unsubscribe(m_SingleSubscribers[i]);
        }
        m_SingleSubscribers.Clear();
    }
}
barthdamon
źródło
Czy możesz sformatować swoje pytanie i usunąć całą białą przestrzeń po lewej stronie? Może się to zdarzyć podczas kopiowania i wklejania z IDE
AustinWBryan
Właśnie pozbyłem się tej białej przestrzeni, mój błąd
barthdamon