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

Видеоклип в Excel

На сайте excel.acdc.com представлен клип группы AC/DC в очень необычном формате. По ссылке можно скачать Excel-файл, который может проигрывать «видео».

Интересно узнать, как реализован такой фокус. Если посмотреть на техническую сторону этой занимательной безделушки, то можно увидеть следующий код макроса (код даже не закрыт паролем) на VBA, проигрывающий псевдо-видео:

Sub PlayVideo()

Dim i As Long
Dim Start, Delay

i = 100
Do While Sheet1.Cells(i, 17).Value <> ""

   Start = Timer                'Set start to internal timer
   Delay = Start + 0.083        'Set delay so frames change 12 per sec.

   'Display
   Do While Timer < Delay
      DoEvents
   Loop
   Sheet1.Range("B2").Value = Sheet1.Cells(i, 17).Value
   DoEvents

   If StopPlaying = True Then
      Exit Do
   End If

   Start = Timer                'and reset the timer
   Delay = Start + 0.083        'and the delay
   i = i + 1
Loop

'Stop audio
Call PlayBackStop
'Clear video
Sheet1.Range("B2").Value = ""
'Show logo
Sheet1.Range("B2").Value = Sheet1.Range("Q99").Value
'Move cursor
'Sheet1.Range("A1").Select

End Sub
&#91;/sourcecode&#93;
</p>
<p>Каждые 0.083 секунды, то есть примерно с частотой 12 кадров в секунду происходит заполнение ячеек новыми данными.</p>
<p> Проигрывание звука осуществляется через импортированную функцию библиотеке winmm.dll :</p> 
Private Declare Function sndPlaySound Lib "winmm.dll" Alias "sndPlaySoundA" _
        (ByVal lpszSoundName As String, ByVal uFlags As Long) As Long

Предварительно звук извлекается из xls-файла в файл ACDC.wav c помощью простого поиска заголовка внутри бинарного xls-файла. Файл ACDC.wav был предварительно внедрен в Excel-документ.

Оффлайн распознавание речи в Android 4.1

В очередном обновлении Android 4.1 Jelly Bean появилась функциональность оффлайн распознавания речи. Теперь использовать голосовой ввод можно даже тогда, когда отсутствует подключение к интернету. На конференции Google IO был продемонстрирован Galaxy Nexus, с помощью которого можно было произнести целую фразу и получить распознанный текст.

На данном этапе доступен только английский язык, но Google обещает добавить поддержку остальных языков в ближайшем будущем. О том, как интегрировать технологию Voice Typing в свое приложение можно прочитать здесь.