Возьмем следующий класс C#:
c1 {
event EventHandler someEvent;
}
Если есть много подписок на событие c1someEvent, и я хочу очистить их все, как лучше всего этого добиться? Также учтите, что подписки на это событие могут быть лямбда-выражениями / анонимными делегатами.
В настоящее время я решил добавить к ResetSubscriptions() метод c1, который устанавливает для someEvent значение null. Я не знаю, будет ли это иметь какие-то невидимые последствия.





Добавьте в c1 метод, который установит для someEvent значение null.
public class c1
{
event EventHandler someEvent;
public ResetSubscriptions() => someEvent = null;
}
Этого можно добиться с помощью методов Delegate.Remove или Delegate.RemoveAll.
Я не верю, что это сработает с лямбда-выражениями или анонимными делегатами.
Изнутри класса вы можете установить для (скрытой) переменной значение null. Пустая ссылка - это канонический способ эффективного представления пустого списка вызовов.
Вы не можете этого сделать извне класса - события в основном открывают "подписаться" и "отписаться" и все.
Стоит знать, что на самом деле делают полевые события - они одновременно создают переменную и как событие. Внутри класса вы в конечном итоге ссылаетесь на переменную. Со стороны вы ссылаетесь на событие.
См. Мой статья о мероприятиях и делегатах для получения дополнительной информации.
Если вы упрямы, вы можете заставить его прояснить ситуацию с помощью отражения. См. stackoverflow.com/questions/91778/….
@ Брайан: Это зависит от реализации. Если это только - событие типа поля или EventHandlerList, возможно, вы сможете это сделать. Однако вам придется распознать эти два случая - и может быть любое количество других реализаций.
@ Джошуа: Нет, он установит значение переменной null. Я согласен, что переменная не будет называться hidden.
@JonSkeet Это то, что я (подумал) я сказал. То, как это было написано, смутило меня на 5 минут.
@JoshuaLamusga: Ну, вы сказали, что это очистит список вызовов, что похоже на изменение существующего объекта.
Установка события в 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;?
Delegate[] dary = TermCheckScore.GetInvocationList();
if ( dary != null )
{
foreach ( Delegate del in dary )
{
TermCheckScore -= ( Action ) del;
}
}
Если вы находитесь внутри типа, который объявил событие, вам не нужно этого делать, вы можете просто установить для него значение null, если вы находитесь за пределами типа, вы не можете получить список вызовов делегата. Кроме того, ваш код выдает исключение, если событие имеет значение null при вызове GetInvocationList.
class c1
{
event EventHandler someEvent;
ResetSubscriptions() => someEvent = delegate { };
}
Лучше использовать delegate { }, чем null, чтобы избежать исключения null ref.
Почему? Не могли бы вы расширить этот ответ?
@ S.Buda Потому что, если он равен нулю, вы получите нулевое исх. Это похоже на использование List.Clear() против myList = null.
Это мое решение:
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, это может произойти
Просто избавился от этого белого пространства, моя плохая
Я наблюдаю такое поведение. Как я уже сказал в своем вопросе, я не знаю, упускаю ли я что-то из виду.