在方法退出之前,传递参数值ByRef的VB不会更改值



我在.Net实体框架中有一个实现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退出并返回到集合之前,该属性的值不会更改。例如,在AccreedSharesValue上设置手表显示值为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

显然,从上面的代码中,编译器从堆栈中弹出一个返回值,然后在调用从IL_0019行的SetProperty返回后将其分配给my值。同样,这似乎只发生在通用方法中。我没有用正常的方法看到这种行为。

我真的认为这应该被贴上微软漏洞的标签吗?

我真的,真的非常感谢你从IL中了解到这一点。我不再认为这是我的代码,而是写了一些重点测试。

要复制此内容:

把这个最小的、完整的、可验证的例子放在一个新的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

观察

  1. 这不是一般性与非一般性的问题
  2. 传递其他对象的属性(即myClassLevelObject.Name)与传递Me的属性(如myClassLevelString)可能会出现问题
  3. 其他对象的属性表现一致,尽管令人沮丧,因为它们"滞后"类级变量的更新,直到函数返回
  4. Me的属性的行为不一致。
    • 设置ByRef T s和ByRef Object s时,类级别变量会立即更改
    • 设置ByRef String s时,值"滞后",直到函数返回

*我说"可能是个问题",因为我确信这不是一个bug。我们可能只是对…了解不够。。。不管它属于哪个区域

通过.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。

相关内容

  • 没有找到相关文章

最新更新