В механизме событий .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 measureTime = (action, actionCaption) =>
{
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]
Полный код решения с тестами можно скачать по ссылке.