Fit Dev
Fit Dev

Reputation: 3689

VB.NET 2012 Property Set on Property Get

I am having a very weird situation in VS 2012 RC VB.NET project targeting .NET 2.0. For some reason the property's Set method is called in addition to its Get method:

This works as expected:

Dim _searchparray = New Byte() {37, 115, ...}
Dim rep() As Byte = _opt.ReplaceBytes
If Arrays.CompareTo(rep, _searchparray, 1, False) = -1 AndAlso _opt.SearchMatchPlaceholderInReplaceBytes Then ...

That is _opt.ReplaceBytes's Get method is called only once, and it's Set method is not called.

But this does not work:

Dim _searchparray = New Byte() {37, 115, ...}
If Arrays.CompareTo(_opt.ReplaceBytes, _searchparray, 1, False) = -1 AndAlso _opt.SearchMatchPlaceholderInReplaceBytes Then ...

Here, first _opt.ReplaceBytes's Get method is called, then Arrays.CompareTo returns and THEN _opt.ReplaceBytes's Set method is called! Why? The call stack indicates that the caller is the last line in the sample above! But where does it set the property? It cannot be in Arrays.CompareTo because the Set method is called after the function returned a value, and it cannot be set via _opt.SearchMatchPlaceholderInReplaceBytes's Get method either, because its Get method returns the value of the underlying field and does nothing else!

Does any one have an explanation for this weird behavior? Thanks.

Here's the entire sample project that demonstrates this:

Imports System.Runtime.CompilerServices

Module Module1

Sub Main()
    Dim _opt As New Opts
    Dim _searchparray = New Byte() {37, 115}
    If Arrays.CompareTo(_opt.ReplaceBytes, _searchparray, 1, False) = -1 AndAlso _opt.SearchMatchPlaceholderInReplaceBytes Then
        Console.WriteLine("0")
    End If
    Console.WriteLine("1")
End Sub

End Module

Module Arrays

<Extension()> _
Friend Function CompareTo(Of T As IEquatable(Of T))(ByRef SearchArray() As T, ByRef AnotherArray() As T, ByRef aWildCardElement As T, Optional aUseWildcards As Boolean = True) As Integer
    Dim min As Integer = If(SearchArray.Length < AnotherArray.Length, SearchArray.Length, AnotherArray.Length) - 1
    If aUseWildcards AndAlso aWildCardElement IsNot Nothing Then
        For i = 0 To min
            If SearchArray(i).Equals(aWildCardElement) Then Continue For 
            If Not SearchArray(i).Equals(AnotherArray(i)) Then Return i
        Next
    Else
        For i = 0 To min
            If Not SearchArray(i).Equals(AnotherArray(i)) Then Return i
        Next
    End If
    If SearchArray.Length = AnotherArray.Length Then
        Return -1
    Else
        Return min + 1
    End If
End Function

End Module



Public Class Opts

Private _ReplaceBytes() As Byte = New Byte() {}
<Xml.Serialization.XmlIgnore()> _
Public Property ReplaceBytes As Byte()
    Get
        Return _ReplaceBytes
    End Get
    Set(ByVal value As Byte())
        _ReplaceBytes = value
    End Set
End Property

Private _SearchMatchPlaceholderInReplaceBytes As Boolean = False
Public Property SearchMatchPlaceholderInReplaceBytes() As Boolean
    Get
        Return _SearchMatchPlaceholderInReplaceBytes 'Set breakpoint here 
    End Get
    Set(ByVal value As Boolean)
        'Set breakpoint here too
        _SearchMatchPlaceholderInReplaceBytes = value
    End Set
End Property

End Class

Namespace Global.System.Runtime.CompilerServices

<AttributeUsage((AttributeTargets.Method Or (AttributeTargets.Class Or AttributeTargets.Assembly))), System.Reflection.Obfuscation(ApplyToMembers:=True, Exclude:=True)> _
Public NotInheritable Class ExtensionAttribute
    Inherits Attribute
    Public Sub New()
    End Sub
End Class

End Namespace

Upvotes: 2

Views: 1260

Answers (2)

Meta-Knight
Meta-Knight

Reputation: 17875

It seems that in VB.NET, when you pass a property of Array type by reference, it is copied back at the end of the function. This makes sense because passing it ByRef means that the reference might have changed inside the function. Since it's a property that has been passed by reference, we affect the (possibly changed) reference to the property's setter.

The solution would be to pass it by value instead (ByVal). You have no need to pass it by reference in your code. In fact, in most cases, it is better to pass it by value than by reference. Only use ByRef if a parameter also acts as a return value.

Upvotes: 1

Hans Passant
Hans Passant

Reputation: 942000

This is an interaction between the ByRef declaration and passing a property as the argument. This is forbidden in C# but the VB.NET compiler works around the problem.

By declaring the argument ByRef, you tell the compiler that you might modify the passed object reference. Which is fine if you pass a local variable as the method argument, that local variable gets updated when your code assigns the argument. But this is a problem when you pass a property, such an assignment would have to call the property setter. Which in turn invalidates the passed argument. Which can cause a very difficult to diagnose bug.

The C# compiler just forbids this due to the bug possibilities. The VB.NET compiler however works around it by ensuring that the setter gets called after the method stops executing. Which is exactly what you saw with the debugger. Trouble is, it always calls the setter, even if you didn't modify the argument.

The workaround is obvious, using ByRef is just a bug. Your method does not actually assign the SearchArray argument. The argument needs to be ByVal.

Upvotes: 3

Related Questions