VB 传递参数值 ByRef 在方法退出之前不会更改值?
VB passing Parameter Value ByRef does not change value until Method exits?
我在 .Net Entity Framework 中有一个 Class 实现了 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
问题是设置一个值 say 10 并调用 SetProperty 会导致通知许多其他方法,但 属性 的值不会改变,直到 SetProperty 退出并且 returns回集合。例如在 AccruedSharesValue 上设置监视显示值 0。当事件触发时,所有其他方法看到值 0,并且直到代码步出 SetProperty 方法后才更改为 10。这与我通过 ref 工作传递值的看法完全相反。 ByRef 应该立即更改传递的变量的值,而不是在方法退出之后。
有人知道为什么会这样吗?
我拿了一小段代码:
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
从上面的代码可以看出,编译器从堆栈中弹出一个 return 值,然后将其分配给 my 值,在第 [=20 行的 SetProperty 调用 returns 之后=].同样,这似乎只发生在通用方法中。我没有用普通方法看到这种行为。
我真的认为这应该被标记为 Microsoft 错误?
我真的,真的,真的感谢你从 IL 中弄清楚这一点。我能够停止拔头发以为这是我的代码并编写一些重点测试。
要重现此内容:
将此 minimal, complete, and verifiable 示例放入新的 WPF 项目(或其他项目)中。在每个 End Sub
上放置断点并检查你的 locals
window.
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
)可能是一个问题*。
- 通过 "lagging" class 级变量的更新,直到函数 returns.
,其他对象的属性表现一致,尽管令人沮丧。
Me
' 属性的行为 不一致 。
- 当设置
ByRef T
s和ByRef Object
s时,class级变量立即改变。
- 当设置
ByRef String
s时,函数的值"lags"直到return。
* 我说 "might be a problem" 因为我 确定 这不是错误。我们可能只是不太了解......无论这属于什么领域!
.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。
我在 .Net Entity Framework 中有一个 Class 实现了 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
问题是设置一个值 say 10 并调用 SetProperty 会导致通知许多其他方法,但 属性 的值不会改变,直到 SetProperty 退出并且 returns回集合。例如在 AccruedSharesValue 上设置监视显示值 0。当事件触发时,所有其他方法看到值 0,并且直到代码步出 SetProperty 方法后才更改为 10。这与我通过 ref 工作传递值的看法完全相反。 ByRef 应该立即更改传递的变量的值,而不是在方法退出之后。
有人知道为什么会这样吗?
我拿了一小段代码:
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
从上面的代码可以看出,编译器从堆栈中弹出一个 return 值,然后将其分配给 my 值,在第 [=20 行的 SetProperty 调用 returns 之后=].同样,这似乎只发生在通用方法中。我没有用普通方法看到这种行为。
我真的认为这应该被标记为 Microsoft 错误?
我真的,真的,真的感谢你从 IL 中弄清楚这一点。我能够停止拔头发以为这是我的代码并编写一些重点测试。
要重现此内容:
将此 minimal, complete, and verifiable 示例放入新的 WPF 项目(或其他项目)中。在每个 End Sub
上放置断点并检查你的 locals
window.
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
)可能是一个问题*。 - 通过 "lagging" class 级变量的更新,直到函数 returns. ,其他对象的属性表现一致,尽管令人沮丧。
Me
' 属性的行为 不一致 。- 当设置
ByRef T
s和ByRef Object
s时,class级变量立即改变。 - 当设置
ByRef String
s时,函数的值"lags"直到return。
- 当设置
* 我说 "might be a problem" 因为我 确定 这不是错误。我们可能只是不太了解......无论这属于什么领域!
.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。