我在.Net Entity Framework中有一个实现INotifyPropertyChanged的类。我发现了一个有趣的问题,我的属性更改了setter触发了一个通知事件,但是在setter退出之前,这个更改的值是不可见的。
我实现了以下内容来测试属性值是否已更改,设置新值,然后通知相关方更改:
Public Event PropertyChanged(sender As Object,
e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
'' This method is called by the Set accessor of each property.
Private Sub NotifyPropertyChanged(PropertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
End Sub
''' <summary>
''' Sets the specified property to a value and raises an event if the property has changed.
''' </summary>
''' <typeparam name="T">Type of the destination field</typeparam>
''' <param name="field">Destination Field to check/update</param>
''' <param name="value">Field is to be set to this value, if it has not changed.</param>
''' <param name="PropertyName">Optional property name - filled in by compiler if left blank.</param>
''' <remarks></remarks>
Protected Function SetProperty(Of T)(ByRef field As T, value As T, _
PropertyName As String, _
Optional SupressEvent As Boolean = False) As Boolean
If Not EqualityComparer(Of T).Default.Equals(field, value) Then
field = value
If Not SupressEvent Then NotifyPropertyChanged(PropertyName)
Return True
End If
Return False
End Function
属性本身调用SetProperty,如下所示:
Public Property AccruedShares As Decimal
Get
Return Me.AccruedSharesValue
End Get
Set(value As Decimal)
SetProperty(Of Decimal)(Me.AccruedSharesValue, value, "AccruedShares", Loading)
End Set
End Property
问题是设置值为10并调用SetProperty会导致通知其他许多方法,但是在SetProperty退出并返回到集合之前,属性的值不会改变。例如在AccruedSharesValue上设置监视会显示值0.当事件触发时,所有其他方法都会看到值0,并且在代码退出SetProperty方法之前它不会更改为10。这与我通过ref工作传递值的方式完全相反。 ByRef应该立即更改传递的变量的值,而不是在方法退出后。
有没有人知道为什么会发生这种情况?
答案 0 :(得分:1)
我拿了一小段代码:
Public Property Population As Integer
Get
Return Me.PopulationValue
End Get
Set(value As Integer)
SetProperty(Of Integer)(Me.PopulationValue, value, "Population")
End Set
End Property
然后检查反汇编列表:
.method public specialname instance void
set_Population(int32 'value') cil managed
{
// Code size 31 (0x1f)
.maxstack 5
.locals init ([0] int32 VB$t_i4$S0)
IL_0000: ldarg.0
IL_0001: ldarg.0
IL_0002: callvirt instance int32 Armada.DataModels.City::get_PopulationValue()
IL_0007: stloc.0
IL_0008: ldloca.s VB$t_i4$S0
IL_000a: ldarg.1
IL_000b: ldstr "Population"
IL_0010: ldc.i4.0
IL_0011: callvirt instance bool Armada.DataModels.City::SetProperty<int32>(!!0&,
!!0,
string,
bool)
IL_0016: pop
IL_0017: ldarg.0
IL_0018: ldloc.0
IL_0019: callvirt instance void Armada.DataModels.City::set_PopulationValue(int32)
IL_001e: ret
} // end of method City::set_Population
显然,从上面的代码中,编译器从堆栈中弹出一个返回值,然后在调用从IL_0019行的SetProperty返回后将其赋值给my值。再次,这似乎只发生在通用方法上。我没有用普通方法看到这种行为。
我真的认为这应该被标记为微软的错误吗?
答案 1 :(得分:1)
我真的,真的, 真的 感谢您从IL中解决这个问题。我能够停止拔出我的头发,认为这是我的代码,并写了一些重点测试。
将此minimal, complete, and verifiable示例放在新的WPF项目(或其他)中。在每个End Sub
上放置断点并检查locals
窗口。
Class ObjectWithName
Public Property Name As String
End Class
Class MainWindow
Private myClassLevelString = "Intitial"
Private myClassLevelObject As New ObjectWithName() With {.Name = "Initial"}
Public Sub New()
InitializeComponent()
'Group 1
Generic(myClassLevelString, "Generic")
NonGenericStrings(myClassLevelString, "NonGenericStrings")
NonGenericObjects(myClassLevelString, "NonGenericObjects")
'Group 2
Generic(myClassLevelObject.Name, "Generic")
NonGenericStrings(myClassLevelObject.Name, "NonGenericStrings")
NonGenericObjects(myClassLevelObject.Name, "NonGenericObjects")
End Sub
Private Sub Generic(Of T)(ByRef p1 As T, p2 As T)
p1 = p2
Dim Group1 = myClassLevelString
Dim Group2 = myClassLevelObject.Name
' At this point:
' p1 = "Generic"
' Group1 = "Generic"
' Group2 = "Initial"
End Sub
Private Sub NonGenericStrings(ByRef p1 As String, p2 As String)
p1 = p2
Dim Group1 = myClassLevelString
Dim Group2 = myClassLevelObject.Name
' At this point:
' p1 = "NonGenericStrings"
' Group1 = "Generic" <-- This is NOT a typo!
' Group2 = "Generic"
End Sub
Private Sub NonGenericObjects(ByRef p1 As Object, p2 As Object)
p1 = p2
Dim Group1 = myClassLevelString
Dim Group2 = myClassLevelObject.Name
' At this point:
' p1 = "NonGenericObjects"
' Group1 = "NonGenericObjects" <-- This is not a typo either.
' Group2 = "NonGenericStrings"
End Sub
End Class
myClassLevelObject.Name
)与Me
的属性(即myClassLevelString
)中传递其他对象的属性可能是一个问题*。Me
'属性的行为不一致。
ByRef T
和ByRef Object
时,类级变量会立即更改。ByRef String
时,值“滞后”直到从函数返回。*我说“可能有问题”,因为我确定它不是一个错误。我们可能只是对这个问题不够了解...... 子>
答案 2 :(得分:1)
.NET 4.6再次确认,此行为仍然存在。
我正在将一些旧的VB6源代码转换为.Net。原始代码到处都使用ByRef(不是要更改此变量,老程序员只是喜欢使用ByRef)。
例如,在VB6中,以下操作有效,
Function GetNewString(ByRef old As String) As String
return old & "NEW"
End Function
Dim rs As ADO.RecordSet
Call GetNewString(rs.Fields("column1").Value)
尽管它是愚蠢的代码,但是可以工作。
但是,如果将所有内容都转换为VB.Net,并且仍然使用上面的代码,则除了记录集(和数据库)已更新之外,它仍然有效!就像
' To repalce ADO.RecordSet (internally uses DataTable / SqlConnection / OdbcConnection)
Dim rs As MyRecordSet
GetNewString(rs.Fields("column1").Value) ' Database will be updated once here!
Function GetNewString(ByRef old As String) As String
' This is what .Net added automatically.
' It means rs.Fields("column1").Value = old so the database will be updated!
old = old
return old & "NEW"
End Function
我必须手动将所有此类ByRef更改为ByVal。