Как очистить подписки на события в C#?

Возьмем следующий класс C#:

c1 {
 event EventHandler someEvent;
}

Если есть много подписок на событие c1someEvent, и я хочу очистить их все, как лучше всего этого добиться? Также учтите, что подписки на это событие могут быть лямбда-выражениями / анонимными делегатами.

В настоящее время я решил добавить к ResetSubscriptions() метод c1, который устанавливает для someEvent значение null. Я не знаю, будет ли это иметь какие-то невидимые последствия.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
144
0
100 489
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Добавьте в c1 метод, который установит для someEvent значение null.

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}

Я наблюдаю такое поведение. Как я уже сказал в своем вопросе, я не знаю, упускаю ли я что-то из виду.

programmer 30.09.2008 19:42

Этого можно добиться с помощью методов Delegate.Remove или Delegate.RemoveAll.

Я не верю, что это сработает с лямбда-выражениями или анонимными делегатами.

programmer 30.09.2008 19:45
Ответ принят как подходящий

Изнутри класса вы можете установить для (скрытой) переменной значение null. Пустая ссылка - это канонический способ эффективного представления пустого списка вызовов.

Вы не можете этого сделать извне класса - события в основном открывают "подписаться" и "отписаться" и все.

Стоит знать, что на самом деле делают полевые события - они одновременно создают переменную и как событие. Внутри класса вы в конечном итоге ссылаетесь на переменную. Со стороны вы ссылаетесь на событие.

См. Мой статья о мероприятиях и делегатах для получения дополнительной информации.

Если вы упрямы, вы можете заставить его прояснить ситуацию с помощью отражения. См. stackoverflow.com/questions/91778/….

Brian 30.10.2010 01:36

@ Брайан: Это зависит от реализации. Если это только - событие типа поля или EventHandlerList, возможно, вы сможете это сделать. Однако вам придется распознать эти два случая - и может быть любое количество других реализаций.

Jon Skeet 30.10.2010 10:49

@ Джошуа: Нет, он установит значение переменной null. Я согласен, что переменная не будет называться hidden.

Jon Skeet 11.05.2016 20:37

@JonSkeet Это то, что я (подумал) я сказал. То, как это было написано, смутило меня на 5 минут.

user1881400 11.05.2016 21:01

@JoshuaLamusga: Ну, вы сказали, что это очистит список вызовов, что похоже на изменение существующего объекта.

Jon Skeet 11.05.2016 21:01

Установка события в null внутри класса работает. Когда вы удаляете класс, вы всегда должны устанавливать для события значение null, GC имеет проблемы с событиями и может не очистить удаленный класс, если у него есть висячие события.

Концептуальный расширенный скучный комментарий.

Я предпочитаю использовать слово «обработчик событий» вместо «событие» или «делегат». И использовал слово «событие» для других вещей. В некоторых языках программирования (VB.NET, Object Pascal, Objective-C) «событие» называется «сообщением» или «сигналом» и даже имеет ключевое слово «сообщение» и особый синтаксис сахара.

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;

И, чтобы ответить на это «сообщение», отвечает «обработчик событий», будь то один делегат или несколько делегатов.

Резюме: «Событие» - это «вопрос», «обработчик (и) события» - это ответ (ы).

Лучшая практика для очистки всех подписчиков - установить для someEvent значение null, добавив еще один общедоступный метод, если вы хотите предоставить эту функцию извне. Это не имеет невидимых последствий. Предварительное условие - не забудьте объявить SomeEvent с ключевым словом event.

См. Книгу - Краткое описание C# 4.0, стр. 125.

Кто-то здесь предложил использовать метод Delegate.RemoveAll. Если вы его используете, образец кода может соответствовать приведенной ниже форме. Но это действительно глупо. Почему не просто SomeEvent=null внутри функции ClearSubscribers()?

public void ClearSubscribers ()
{
   SomeEvent = (EventHandler) Delegate.RemoveAll(SomeEvent, SomeEvent);
   // Then you will find SomeEvent is set to null.
}

Delegate.RemoveAll допустимо для MulticastDelegate: public delegate string TableNameMapperDelegate(Type type);public static TableNameMapperDelegate TableNameMapper;?

Kiquenet 05.08.2020 12:29

Удалите все события, предположим, что это событие относится к типу «Действие»:

Delegate[] dary = TermCheckScore.GetInvocationList();

if ( dary != null )
{
    foreach ( Delegate del in dary )
    {
        TermCheckScore -= ( Action ) del;
    }
}

Если вы находитесь внутри типа, который объявил событие, вам не нужно этого делать, вы можете просто установить для него значение null, если вы находитесь за пределами типа, вы не можете получить список вызовов делегата. Кроме того, ваш код выдает исключение, если событие имеет значение null при вызове GetInvocationList.

Servy 21.12.2013 00:03

class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

Лучше использовать delegate { }, чем null, чтобы избежать исключения null ref.

Почему? Не могли бы вы расширить этот ответ?

S. Buda 01.11.2018 01:06

@ S.Buda Потому что, если он равен нулю, вы получите нулевое исх. Это похоже на использование List.Clear() против myList = null.

AustinWBryan 29.06.2020 08:51

Это мое решение:

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

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

Вам нужно вызвать Dispose() или использовать шаблон using(new Foo()){/*...*/}, чтобы отписаться от всех членов списка вызовов.

Вместо того, чтобы добавлять и удалять обратные вызовы вручную и везде объявлять кучу типов делегатов:

// 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);
    }
}

Вы можете попробовать этот общий подход:

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();
    }
}

Не могли бы вы отформатировать свой вопрос и удалить все пробелы слева? Когда вы копируете и вставляете из IDE, это может произойти

AustinWBryan 29.06.2020 08:32

Просто избавился от этого белого пространства, моя плохая

barthdamon 29.06.2020 15:11

Другие вопросы по теме