В механизме событий .NET отсутствует стандартная возможность избежать повторной подписки на сообщения. То есть, при повторной подписке на один и тот же обработчик будет вызываться столько раз, сколько раз была произведена подписка на событие. Но можно реализовать такую функциональность в программе самостоятельно одним из двух способов. Первый способ — удалять обработчик через оператор -= при добавлении нового подписчика и тут же его добавлять:
public class NonDuplicateEventArgs : EventArgs { public NonDuplicateEventArgs(string message) { Message = message; } public string Message { get; set; } } protected event EventHandler<nonDuplicateEventArgs> _nonDuplicateEvent; /// <summary> /// Событие с проверкой на добавление обработчиков-дубликатов путем удаления и добавления обработчика заново /// </summary> public event EventHandler<nonDuplicateEventArgs> NonDuplicateEvent { add { // пытаемся удалить обработчик, затем добавляем снова _nonDuplicateEvent -= value; _nonDuplicateEvent += value; } remove { _nonDuplicateEvent -= value; } }
Второй способ заключается в том, чтобы в явном виде проверять, присутствует ли обработчик в списке вызовов:
using System.Linq; private EventHandler<nonDuplicateEventArgs> _nonDuplicateEventInvocList; /// <summary> /// Событие с проверкой на добавление обработчиков-дубликатов через GetInvocationList /// </summary> public event EventHandler<nonDuplicateEventArgs> NonDuplicateEventInvocList { add { if (_nonDuplicateEventInvocList == null || !_nonDuplicateEventInvocList.GetInvocationList().Contains(value)) { _nonDuplicateEventInvocList += value; } } remove { _nonDuplicateEventInvocList -= value; } }
Для того, чтобы решить, что же выбрать в своем приложении, я сделал небольшой тест. Как и ожидалось, метод с GetInvocationList для проверки одного и того же обработчика работает существенно медленнее, чем удаление и добавление — более чем в 5 раз медленнее (11.7 секунд против 2.2)
Action
{
var time = DateTime.Now;
action();
TimeSpan span = DateTime.Now — time;
Console.WriteLine(«{0} time: {1:0.000}», actionCaption,span.TotalSeconds);
};
int testCount = 10000000;
//замеряем скорость добавления одного и того же подписчика на событие
SmartEvents smartEvents = new SmartEvents();
measureTime(() =>
{
for (int i = 0; i < testCount; i++)
{
smartEvents.NonDuplicateEvent += OnNonDuplicateEvent;
}
}, "NonDuplicate");
measureTime(() =>
{
for (int i = 0; i < testCount; i++) { smartEvents.NonDuplicateEventInvocList += OnNonDuplicateEvent; } }, "NonDuplicateEvent with InvocationList"); [/sourcecode]
Однако если увеличивать количество обработчиков, то разница между двумя способами становится все менее заметной. Для 6 обработчиков разница будет несущественна, но первый метод все-таки быстрее, поэтому стоит использовать именно его.
//замеряем скорость добавления нескольких подписчиков в случайном порядке
var methods = new EventHandler
{
OnNonDuplicateEvent, OnNonDuplicateEvent2,
OnNonDuplicateEvent3, OnNonDuplicateEvent4,
OnNonDuplicateEvent5, OnNonDuplicateEvent6
};
testCount /= methods.Length;//чтобы долго не ждать
Random r = new Random(0);
var list = Enumerable.Range(0, testCount).Select(z => methods[r.Next(methods.Length)]).ToArray();
measureTime(() =>
{
for (int i = 0; i < testCount; i++)
{
smartEvents.NonDuplicateEvent += list[i];
}
}, "NonDuplicate for" + methods.Length + "subscribers");
measureTime(() =>
{
for (int i = 0; i < testCount; i++) { smartEvents.NonDuplicateEventInvocList += list[i]; } }, "NonDuplicateEvent with InvocationList" + methods.Length + "subscribers"); } [/sourcecode] Полный код решения с тестами можно скачать по ссылке.
Комментарии: