Как избежать повторной подписки на события в C#

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


Комментарии:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *