Пользовательское событие - рекомендации по реализации списка вызовов - PullRequest
1 голос
/ 09 апреля 2010

Я ищу несколько указателей по реализации пользовательских событий в VB.NET (Visual Studio 2008, .NET 3.5).

Я знаю, что "обычные" (нестандартные) события на самом деле являются делегатами, поэтому я думал об использовании делегатов при реализации пользовательского события. С другой стороны, Эндрю Троелсен "Pro VB 2008 и платформа .NET 3.5" использует типы коллекций во всех своих примерах пользовательских событий и примеры кодов от Microsoft соответствует этой линии мысли.

Итак, мой вопрос: какие соображения следует учитывать при выборе одного дизайна над другим? Каковы плюсы и минусы для каждого дизайна? Что из этого напоминает внутреннюю реализацию "обычных" событий?

Ниже приведен пример кода, демонстрирующий два дизайна.

Public Class SomeClass
    Private _SomeEventListeners As EventHandler
    Public Custom Event SomeEvent As EventHandler
        AddHandler(ByVal value As EventHandler)
            _SomeEventListeners = [Delegate].Combine(_SomeEventListeners, value)
        End AddHandler

        RemoveHandler(ByVal value As EventHandler)
            _SomeEventListeners = [Delegate].Remove(_SomeEventListeners, value)
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
            _SomeEventListeners.Invoke(sender, e)
        End RaiseEvent
    End Event

    Private _OtherEventListeners As New List(Of EventHandler)
    Public Custom Event OtherEvent As EventHandler
        AddHandler(ByVal value As EventHandler)
            _OtherEventListeners.Add(value)
        End AddHandler

        RemoveHandler(ByVal value As EventHandler)
            _OtherEventListeners.Remove(value)
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
            For Each handler In _OtherEventListeners
                handler(sender, e)
            Next
        End RaiseEvent
    End Event
End Class

Ответы [ 2 ]

3 голосов
/ 10 апреля 2010

Я бы сказал, используйте простой делегат; просто - это уже (внутренне) список, поэтому вы дублируете усилия, заключая его в List<T>. У вас также есть накладные расходы на дополнительные объекты списка и массивы, которые могут быть нулевыми, что больше влияет на сборку мусора и т. Д.

Кроме того, если вы делаете лотов из этого, рассмотрите EventHandlerList, который существует для обеспечения эффективного доступа к разреженным событиям - то есть, где вы хотите выставить множество событий, но многие из них могут быть не назначены.

Первый пример гораздо ближе к «стандартным» событиям (хотя вы можете захотеть следить за неназначенными / нулевыми обработчиками при вызове Invoke, поскольку это может быть ноль). Кроме того, обратите внимание, что некоторые языки (я, честно говоря, не знаю, что здесь делает VB) применяют синхронизацию к событиям, но в действительности очень немногие события на самом деле должны быть поточно-ориентированными, так что это может быть избыточным.

edit также бывает, что существует функциональная разница между ними:

  • подход делегата обрабатывает разные экземпляры с одинаковым целевым методом / экземпляром как равные (я не думаю, что List<T> будет)
  • дубликаты делегатов: делегат удаляет последний первым; список удаляет самое раннее первое
  • composites: добавьте A, добавьте B и удалите (A + B) - с делегатом это должно привести к нулю / пустому, но подход списка сохранит A и B (при удалении (A + B) не удается найти что-нибудь)
0 голосов
/ 24 марта 2011

Использование MulticastDelegate для хранения списка подписанных событий, безусловно, является подходящим подходом, но я не особенно заинтересован в нем. Чтобы добавить или удалить событие из MulticastDelegate, нужно сделать одну из двух вещей:

  1. Получите блокировку, создайте новый делегат из старого, но с добавленным или удаленным событием, установите указатель делегата на этот делегат, а затем снимите блокировку
  2. Скопируйте ссылку на старый делегат, создайте из него новый делегат, но с добавленным или удаленным событием используйте Interlocked.CompareExchange, чтобы сохранить ссылку на новый, если старый не изменился, и начните все сначала, если Сбой CompareExchange.

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

Ни один из подходов не кажется особенно чистым. Если кто-то планирует вызывать все события простым вызовом делегата, производительность вызова события может компенсировать производительность добавления / удаления. С другой стороны, если кто-то планирует использовать GetInvocationList, чтобы обернуть вызовы событий в блоки try / catch, может быть лучше использовать просто (соответствующим образом заблокированный) список или другую такую ​​структуру данных.

...