延迟分配给WithEvents支持字段

时间:2017-01-24 18:30:00

标签: wpf vb.net events inotifypropertychanged

我注意到当属性的支持字段具有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属性更新为具有递增ID的新项时,一旦事件触发,属性getter就会按预期被命中。但是,支持字段的值仍然是旧值!如果我在PropertyChanged调用之后立即添加另一个SetProperty事件,则支持字段将在该点具有正确的值。当然,如果我取出WithEvents,它只能按预期工作,只有一个事件。

这是我唯一一次看到SetProperty以这种方式失败的时候。 WithEvents导致的问题是什么?

更新:当ViewModel直接实施INotifyPropertyChanged时,而不是从基础继承,并在设置值后引发PropertyChanged,它可以正常工作。

1 个答案:

答案 0 :(得分:4)

这里发生的是WithEvents是.NET Framework本身不支持的功能。 VB.NET在.NET之上实现它。这个功能是因为它也是由VB6提供的。但是,在VB6中实现该功能的方式非常不同,因为COM和.NET之间的事件模型存在根本差异。

我不知道VB6如何实现这个功能;这不是真的相关。重要的是事件如何与.NET协同工作。基本上,使用.NET时,事件必须明确地挂钩和取消挂钩。定义事件时,与属性的定义方式有很多相似之处。特别是,有一种方法可以为事件添加一个处理程序,一种方法可以删除一个处理程序,类似于&#34; set&#34;之间的对称性。并且&#34;得到&#34;一个属性的方法。

事件使用这样的方法的原因是隐藏来自外部调用者的附加处理程序列表。如果类外部的代码可以访问附加处理程序的完整列表,则它可能会干扰它,这将是一个非常糟糕的编程实践,可能导致非常混乱的行为。

VB.NET公开对这些事件的直接调用&#34;添加&#34;并且&#34;删除&#34;通过AddHandlerRemoveHandler运算符的方法。在C#中,使用+=-=运算符表示完全相同的基础操作,其中左侧参数是事件成员引用。

WithEvents为您提供的是隐藏AddHandlerRemoveHandler来电的语法糖。重要的是要认识到,电话仍然存在,它们只是隐含的。

所以,当你编写这样的代码时:

Private WithEvents _obj As ClassWithEvents

Private Sub _obj_GronkulatedEvent() Handles _obj.GronkulatedEvent
  ...
End Sub

..您要求VB.NET确保将任何对象分配给_obj (请记住您可以随时更改该对象引用),事件{ {1}}应该由GronkulatedEvent处理。如果您更改了引用,则应立即分离旧对象Sub,并附加新对象GronkulatedEvent

VB.NET通过将您的字段转换为属性来实现此功能。添加GronkulatedEvent表示字段WithEvents(或者,在您的情况下,_obj实际上不是字段。创建一个秘密后备字段,然后_item成为一个属性,其实现如下所示:

_item

那么,为什么会导致&#34;滞后&#34;你看?好吧,你不能通过财产&#34; ByRef&#34;。要传递一些东西&#34; ByRef&#34;,你需要知道它的内存地址,但是一个属性隐藏了后面的内存地址&#34; get&#34;和&#34;设置&#34;方法。在像C#这样的语言中,您只会得到编译时错误:属性不是L值,因此您无法传递对它的引用。但是,VB.NET更宽容,并在幕后编写额外的代码,以使事情适合你。

在您的代码中,您将{em>看起来像的字段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 成员传递到_item,其中包含参数SetProperty,因此它可以写一个新值。但是,由于ByRefWithEvents成员实际上是一个属性。那么,VB.NET做了什么?它为_item的调用创建一个临时局部变量,然后在调用后将其分配回属性:

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 将您的字段转换为属性,因此VB.NET必须将实际的赋值推迟到属性,直到调用WithEvents为止。

希望有道理! : - )