2017-01-24 9 views
1

Я заметил, что когда в поле поддержки объекта есть модификатор WithEvents, присвоение значений может «отставать» из-за отсутствия лучших слов. Я воспроизвел поведение в простой демонстрации, поэтому цель WithEvents не будет видна здесь (и, таким образом, она не будет конструктивной сказать «просто избавиться от него»)Отложенное присвоение поля поддержки WithEvents

Public Class ItemViewModel 
    Public Property Id As Integer 
End Class 

Public Class ViewModel 
    Inherits ViewModelBase 

    Private WithEvents _item As ItemViewModel = New ItemViewModel() With {.Id = 0} 
    Public Property Item As ItemViewModel 
     Get 
      Return _item 
     End Get 
     Set(value As ItemViewModel) 
      SetProperty(_item, value) 
     End Set 
    End Property 
... 

SetProperty определения :

Protected Function SetProperty(Of T)(ByRef field As T, value As T, <CallerMemberName> Optional name As String = Nothing) As Boolean 
    If (EqualityComparer(Of T).Default.Equals(field, value)) Then 
     Return False 
    End If 
    field = value 
    NotifyPropertyChanged(name) 
    Return True 
End Function 

Когда я обновляю Item свойство быть новый элемент с порядковым идентификатором Поглотитель свойство удар, как только пожары событий, как и ожидалось. Однако значение поля поддержки по-прежнему остается старым значением! Если я добавлю еще одно событие PropertyChanged сразу после вызова SetProperty, поле поддержки будет иметь правильное значение в этой точке. Конечно, если я выберу WithEvents, он работает как ожидается только с одним событием.

Это единственный раз, когда я видел, как SetProperty терпят неудачу таким образом. В чем проблема, вызываемая WithEvents?

ОБНОВЛЕНИЕ: Когда ViewModel реализует INotifyPropertyChanged непосредственно, вместо того, чтобы наследовать от основания и поднимает PropertyChanged после установки значения, он работает.

ответ

4

Что здесь происходит, так это то, что 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.

Надеюсь, что имеет смысл! :-)

+1

Спасибо, Джонатан! –

+0

Последующие действия: Есть давние проблемы, затрудняющие использование IDisposable. Поскольку мы не можем реализовать IDisposable и полагаться на вызов Dispose(), чтобы очистить оставшиеся дескрипторы, мысль заключалась в том, что использование WithEvents будет работать вокруг этого и обрабатывать очистку для нас. ** 1) ** Does WithEvents фактически освобождают ручки, когда объект больше не используется, поэтому он получает надлежащий сбор мусора? ** 2) ** Существует ли альтернатива IDisposable, которая позволила бы нам использовать AddHandler и RemoveHandler без утечки дескрипторов, когда объект больше не нужен? –

+1

Хм, ну, нет, 'WithEvents' вообще ничего не делает в отношении объектов размещения, потому что он не имеет никакого отношения к жизни объекта. На самом деле существует только один способ убедиться, что объекты, которые должны быть удалены, фактически удаляются, и это нужно вызвать метод Dispose. Если вы используете объект во время определенной области кода, вы можете организовать это автоматически с помощью инструкции 'Use'. –

Смежные вопросы