VB传递参数值ByRef在Method退出之前不会改变值?

时间:2015-02-13 01:59:45

标签: vb.net visual-studio-2010 parameter-passing inotifypropertychanged

我在.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应该立即更改传递的变量的值,而不是在方法退出后。

有没有人知道为什么会发生这种情况?

3 个答案:

答案 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

观察

  1. 这不是通用与非通用问题。
  2. 在(myClassLevelObject.Name)与Me的属性(即myClassLevelString)中传递其他对象的属性可能是一个问题*。
  3. 其他对象的属性通过“滞后”类级变量的更新直到函数返回来表现一致,尽管令人沮丧。
  4. Me'属性的行为不一致
    • 设置ByRef TByRef Object时,类级变量会立即更改。
    • 设置ByRef String时,值“滞后”直到从函数返回。
  5. *我说“可能有问题”,因为我确定它不是一个错误。我们可能只是对这个问题不够了解......

答案 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。