我注意到当属性的支持字段具有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
,它可以正常工作。
答案 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;通过AddHandler
和RemoveHandler
运算符的方法。在C#中,使用+=
和-=
运算符表示完全相同的基础操作,其中左侧参数是事件成员引用。
WithEvents
为您提供的是隐藏AddHandler
和RemoveHandler
来电的语法糖。重要的是要认识到,电话仍然存在,它们只是隐含的。
所以,当你编写这样的代码时:
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
,因此它可以写一个新值。但是,由于ByRef
,WithEvents
成员实际上是一个属性。那么,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
为止。
希望有道理! : - )