Что здесь происходит, так это то, что WithEvents
- это функция, которая сама по себе не поддерживает .NET Framework. VB.NET реализует его поверх .NET. Функция там, потому что она также была предоставлена VB6. Однако способ, которым была реализована функция в VB6, очень отличается из-за фундаментального различия в моделях событий между COM и .NET.
Я не буду вдаваться в то, как VB6 реализовал эту функцию; это не актуально. Важно то, как события работают с .NET. В основном, с .NET, события должны быть явно подключены и отцеплены. Когда события определены, существует много параллелей с тем, как определяются свойства. В частности, существует метод, который добавляет обработчик события и метод, который удаляет обработчик, аналогичный симметрии между методами «set» и «get», которые имеет свойство.
Причина, по которой используются такие методы, заключается в том, чтобы скрыть список подключенных обработчиков со сторонних абонентов. Если бы код вне класса имел доступ к полному списку подключенных обработчиков, было бы возможно, чтобы он вмешивался в него, что было бы очень плохой практикой программирования, что могло бы привести к очень запутанному поведению.
VB.NET предоставляет прямые вызовы этим методам «добавить» и «удалить» с помощью операторов AddHandler
и RemoveHandler
. В C# точно такая же основная операция выражается с помощью операторов +=
и -=
, где левым аргументом является ссылка на элемент события.
Что дает WithEvents
, это синтаксический сахар, который скрывает вызовы AddHandler
и RemoveHandler
. Важно отметить, что звонки все еще там, они просто неявные.
Итак, когда вы пишете код, как это:
Private WithEvents _obj As ClassWithEvents
Private Sub _obj_GronkulatedEvent() Handles _obj.GronkulatedEvent
...
End Sub
..you просите VB.NET, чтобы гарантировать, что любому объекту присвоен _obj
(имея в виду, что вы можете в любой момент изменить эту ссылку на объект), событие GronkulatedEvent
должно обрабатываться этим Sub
. Если вы измените ссылку, то старый объект GronkulatedEvent
должен быть немедленно отсоединен, а новый объект GronkulatedEvent
прилагается.
VB.NET реализует это, превращая ваше поле в собственность. Добавление WithEvents
означает, что поле _obj
(или, в вашем случае, _item
) является на самом деле не поле. Секрет поле подложки создается, а затем _item
становится свойство, реализация которого выглядит следующим образом:
Private __item As ItemViewModel ' Notice this, the actual field, has two underscores
Private Property _item As ItemViewModel
<CompilerGenerated>
Get
Return __item
End Get
<CompilerGenerated, MethodImpl(Synchronized)>
Set(value As ItemViewModel)
Dim previousValue As ItemViewModel = __item
If previousValue IsNot Nothing Then
RemoveHandler previousValue.GronkulatedEvent, AddressOf _item_GronkulatedEvent
End If
__item = value
If value IsNot Nothing Then
AddHandler value.GronkulatedEvent, AddressOf _item_GronkulatedEvent
End If
End Set
End Property
Итак, почему это вызывает «отставание» вы видите? Ну, вы не можете передать собственность «ByRef». Чтобы передать что-то «ByRef», вам нужно знать его адрес памяти, но свойство скрывает адрес памяти за методами «get» и «set». На языке, таком как C#, вы просто получите ошибку времени компиляции: свойство не является значением L, поэтому вы не можете передать ссылку на него. Тем не менее, VB.NET более прощает и пишет лишний код за кулисами, чтобы заставить вас работать.
В коде, вы передаете, что выглядит поле, в _item
элемент, в SetProperty
, который принимает параметр ByRef
поэтому он может записать новое значение. Но, из-за WithEvents
, член _item
действительно является собственностью. Итак, что делает VB.NET? Это создает временную локальную переменную для вызова SetProperty
, а затем присваивает его обратно в собственность после вызова:
Public Property Item As ItemViewModel
Get
Return _item ' This is actually a property returning another property -- two levels of properties wrapping the actual underlying field -- but VB.NET hides this from you
End Get
Set
' You wrote: SetProperty(_item, value)
' But the actual code emitted by the compiler is:
Dim temporaryLocal As ItemViewModel = _item ' Read from the property -- a call to its Get method
SetProperty(temporaryLocal, value) ' SetProperty gets the memory address of the local, so when it makes the assignment, it is actually writing to this local variable, not to the underlying property
_item = temporaryLocal ' Once SetProperty returns, this extra "glue" code passes the value back off to the property, calling its Set method
End Set
End Property
Итак, потому что WithEvents
конвертированы ваше поле в собственности, VB.NET пришлось отложить фактическое присваивание этому свойству до тех пор, пока не вернется вызов SetProperty
.
Надеюсь, что имеет смысл! :-)
Спасибо, Джонатан! –
Последующие действия: Есть давние проблемы, затрудняющие использование IDisposable. Поскольку мы не можем реализовать IDisposable и полагаться на вызов Dispose(), чтобы очистить оставшиеся дескрипторы, мысль заключалась в том, что использование WithEvents будет работать вокруг этого и обрабатывать очистку для нас. ** 1) ** Does WithEvents фактически освобождают ручки, когда объект больше не используется, поэтому он получает надлежащий сбор мусора? ** 2) ** Существует ли альтернатива IDisposable, которая позволила бы нам использовать AddHandler и RemoveHandler без утечки дескрипторов, когда объект больше не нужен? –
Хм, ну, нет, 'WithEvents' вообще ничего не делает в отношении объектов размещения, потому что он не имеет никакого отношения к жизни объекта. На самом деле существует только один способ убедиться, что объекты, которые должны быть удалены, фактически удаляются, и это нужно вызвать метод Dispose. Если вы используете объект во время определенной области кода, вы можете организовать это автоматически с помощью инструкции 'Use'. –