ByRef vs ByVal Clarification - vb.net

I'm just starting on a class to handle client connections to a TCP server. Here is the code I've written thus far:
Imports System.Net.Sockets
Imports System.Net
Public Class Client
Private _Socket As Socket
Public Property Socket As Socket
Get
Return _Socket
End Get
Set(ByVal value As Socket)
_Socket = value
End Set
End Property
Public Enum State
RequestHeader ''#Waiting for, or in the process of receiving, the request header
ResponseHeader ''#Sending the response header
Stream ''#Setup is complete, sending regular stream
End Enum
Public Sub New()
End Sub
Public Sub New(ByRef Socket As Socket)
Me._Socket = Socket
End Sub
End Class
So, on my overloaded constructor, I am accepting a reference to an instance of a System.Net.Sockets.Socket, yes?
Now, on my Socket property, when setting the value, it is required to be ByVal. It is my understanding that the instance in memory is copied, and this new instance is passed to value, and my code sets _Socket to reference this instance in memory. Yes?
If this is true, then I can't see why I would want to use properties for anything but native types. I'd imagine there can be quite a performance hit if copying class instances with lots of members. Also, for this code in particular, I'd imagine a copied socket instance wouldn't really work, but I haven't tested it yet.
Anyway, if you could either confirm my understanding, or explain the flaws in my foggy logic, I would greatly appreciate it.

I think you're confusing the concept of references vs. value types and ByVal vs. ByRef. Even though their names are a bit misleading, they are orthogonal issues.
ByVal in VB.NET means that a copy of the provided value will be sent to the function. For value types (Integer, Single, etc.) this will provide a shallow copy of the value. With larger types this can be inefficient. For reference types though (String, class instances) a copy of the reference is passed. Because a copy is passed in mutations to the parameter via = it won't be visible to the calling function.
ByRef in VB.NET means that a reference to the original value will be sent to the function (1). It's almost like the original value is being directly used within the function. Operations like = will affect the original value and be immediately visible in the calling function.
Socket is a reference type (read class) and hence passing it with ByVal is cheap. Even though it does perform a copy it's a copy of the reference, not a copy of the instance.
(1) This is not 100% true though because VB.NET actually supports several kinds of ByRef at the callsite. For more details, see the blog entry The many cases of ByRef

Remember that ByVal still passes references. The difference is that you get a copy of the reference.
So, on my overloaded constructor, I am accepting a reference to an instance of a System.Net.Sockets.Socket, yes?
Yes, but the same would be true if you asked for it ByVal instead. The difference is that with ByVal you get a copy of the reference — you have new variable. With ByRef, it's the same variable.
It is my understanding that the instance in memory is copied
Nope. Only the reference is copied. Therefore, you're still working with the same instance.
Here's a code example that explains it more clearly:
Public Class Foo
Public Property Bar As String
Public Sub New(ByVal Bar As String)
Me.Bar = Bar
End Sub
End Class
Public Sub RefTest(ByRef Baz As Foo)
Baz.Bar = "Foo"
Baz = new Foo("replaced")
End Sub
Public Sub ValTest(ByVal Baz As Foo)
Baz.Bar = "Foo"
Baz = new Foo("replaced")
End Sub
Dim MyFoo As New Foo("-")
RefTest(MyFoo)
Console.WriteLine(MyFoo.Bar) ''# outputs replaced
ValTest(MyFoo)
Console.WriteLine(MyFoo.Bar) ''# outputs Foo

My understanding has always been that the ByVal/ByRef decision really matters most for value types (on the stack). ByVal/ByRef makes very little difference at all for reference types (on the heap) UNLESS that reference type is immutable like System.String. For mutable objects, it doesn't matter if you pass an object ByRef or ByVal, if you modify it in the method the calling function will see the modifications.
Socket is mutable, so you can pass any which way you want, but if you don't want to keep modifications to the object you need to make a deep copy yourself.
Module Module1
Sub Main()
Dim i As Integer = 10
Console.WriteLine("initial value of int {0}:", i)
ByValInt(i)
Console.WriteLine("after byval value of int {0}:", i)
ByRefInt(i)
Console.WriteLine("after byref value of int {0}:", i)
Dim s As String = "hello"
Console.WriteLine("initial value of str {0}:", s)
ByValString(s)
Console.WriteLine("after byval value of str {0}:", s)
ByRefString(s)
Console.WriteLine("after byref value of str {0}:", s)
Dim sb As New System.Text.StringBuilder("hi")
Console.WriteLine("initial value of string builder {0}:", sb)
ByValStringBuilder(sb)
Console.WriteLine("after byval value of string builder {0}:", sb)
ByRefStringBuilder(sb)
Console.WriteLine("after byref value of string builder {0}:", sb)
Console.WriteLine("Done...")
Console.ReadKey(True)
End Sub
Sub ByValInt(ByVal value As Integer)
value += 1
End Sub
Sub ByRefInt(ByRef value As Integer)
value += 1
End Sub
Sub ByValString(ByVal value As String)
value += " world!"
End Sub
Sub ByRefString(ByRef value As String)
value += " world!"
End Sub
Sub ByValStringBuilder(ByVal value As System.Text.StringBuilder)
value.Append(" world!")
End Sub
Sub ByRefStringBuilder(ByRef value As System.Text.StringBuilder)
value.Append(" world!")
End Sub
End Module

Think of C, and the difference between a scalar, like int, and an int pointer, and a pointer to an int pointer.
int a;
int* a1 = &a;
int** a2 = &a1;
Passing a is by value.
Passing a1 is a reference to a; it is the address of a.
Passing a2 is a reference to a reference; what is passed is the address of a1.
Passing a List variable using ByRef is analogous to the a2 scenario. It is already a reference. You are passing a reference to a reference. Doing that means that not only can you change the contents of the List, you can can change the parameter to point to an entirely different List. It also means you can not pass a literal null instead of an instance of List

Related

Why does the Default Property access the setter when removing an item

In the code below, when trying to remove an item from the Cases list the code breaks in the Setter with an index out of bounds. When running the debugger in VisualStudio 2017 it successfully goes through the Remove() function and deletes the last item but after returning to Main() it will break on the Setter and the call stack says it is coming from the Remove call. Example code below:
Sub Main()
Dim Cases As Collection = New Collection()
Dim caseIndex As Integer = 2
Cases.Remove(Cases(caseIndex))
End Sub
Public Class Collection
Public WithEvents Cases As List(Of CaseClass)
Public Sub New()
Cases = New List(Of CaseClass)()
Cases.Add(New CaseClass)
Cases.Add(New CaseClass)
Cases.Add(New CaseClass)
End Sub
Default Public Property BeltCase(ByVal Index As Integer) As CaseClass
Get
Return Cases(Index)
End Get
Set(ByVal Value As CaseClass)
Cases(Index) = Value
End Set
End Property
Public Sub Remove(ByRef BeltCase As CaseClass)
Cases.Remove(BeltCase)
End Sub
End Class
Public Class CaseClass
Public test As Int16
End Class
Call Stack:
TestingVBBug.exe!TestingVBBug.Module1.Collection.set_BeltCase(Integer Index,TestingVBBug.Module1.CaseClass Value) Line 25 Basic
TestingVBBug.exe!TestingVBBug.Module1.Main() Line 6 Basic
So why would we be going through the Setter at all. And why does that happen after we exit the remove function?
The problem is caused by your Remove() method, that is, you have a ByRef parameter (for some reason). When you use ByRef, any changes made to the parameter inside the method must be reflected to the variable that was passed to the method. That happens by reassigning the value to the original variable.
In your case, it works like this:
The Remove() method is called and a variable (Cases(caseIndex)) is passed to it.
Some work is done inside the Remove() method which might, or might not include changing the value of the parameter BeltCase.
The value of the parameter BeltCase gets reassigned to the variable that was originally passed to the method (which is Cases(caseIndex)).
As a result of the above step, the setter of the BeltCase property gets called with Index = 2 which raises the out of range exception because Cases(2) doesn't exist (was removed).
To confirm, you can see this problem go away when you replace this line:
Cases.Remove(Cases(caseIndex))
..with:
Dim myCase As CaseClass = Cases(caseIndex)
Cases.Remove(myCase)
That way, you create a new variable which refers to the same CaseClass object and most importantly avoid calling the setter of your Collection.BeltClase property.
However, a better solution would be to not use ByRef in the first place since you don't seem to need it in this situation. So, simply use Public Sub Remove(ByVal BeltCase As CaseClass) instead.
Check this question for more about ByVal and ByRef with objects.
One last thing, please don't call your class Collection because it can be very confusing to anyone looking at your project.

How to assign a value to a variable of type Double, that has been passed as Object?

I am trying to assign a value to global variable, which has a Property of type Double. This Property is passed as Object and the assignment fails.
In the example code below, the value is never assigned to the actual object, but only locally:
Public Class Form1
Friend Home As New Building
Private Sub AssignValues() Handles Me.Load
'Objects of different types are added to a list
Dim listObjects As New List(Of Object)
listObjects.Add(Home.Surface)
'All the Objects in listObjects are assigned a value that
'is stored as String
For Each o As Object In listObjects
SetProperty(o, "45.6")
Debug.Print("Surface = " & Home.Surface.ToString)
Next
End Sub
Private Sub SetProperty(ByRef Variable As Object, ByVal Value As String)
Select Case Variable.GetType
Case GetType(Double)
Variable = CDbl(Value)
Case Else
'...
End Select
End Sub
End Class
Public Class Building
Dim _surface As Double = 0
Public Property Surface As Double
Get
Return _surface
End Get
Set(ByVal value As Double)
_surface = value
End Set
End Property
End Class
The program invariably outputs Surface = 0 instead of 45.6. What am I doing wrong?
I tried to pass the Variable as reference, as suggested here, but without success. I also read about using Reflection, but there ought to be something simpler than that...
When your adding home.surface to the list, your adding a copy of the double to the list and then adjusting that copy. Stick a watch on "o" and see how it changes whilst home.surface remains the same.
If you want to use reflection, try something along these lines.
Dim prop As Reflection.PropertyInfo = o.GetType().GetProperty("Surface")
prop.SetValue(o, 45.6)
With Variable.GetType you will get always Object, because this is the type of Variable. What you can do with an Object is converting/casting it into a different type (like Double).
The best way to determine the "original type" from where the Object comes would be including an additional variable telling it. Another option might be converting the given Object into the target Type and see if it is not nothing/does not trigger an error. But this second option is not too accurate, mainly when dealing with "equivalent types" like Doubles/Integers.

ByRef in VB.NET

I have written the following code in VB.NET:
Dim obj As Object
obj = "00"
Test(obj)
MsgBox(obj)
Private Sub Test(ByRef num As Integer)
End Sub
Private Sub Test(ByVal num As Integer)
End Sub
When the value "00" is passed "ByRef" in the method "Test" it converts to 0. But if the value "00" is passed "ByVal" it keeps the same value as "00". How the passed value is being converted only depending of the signature?
In VB6 although the default passing type is "ByRef", still the same code keeps the same value("00")
Could anybody explain the reason behind this contradictory behaviour in VB6 and VB.NET?
The way you are doing it, the ByRef changes the type of the object from string to integer. By default, integer do not have trailling "0" when covnerted to strings.
This example below might help you understand what is hapenning.
Sub Main()
Dim o1 As Object = "00"
Dim o2 As Object = "00"
Console.WriteLine(o1.GetType().ToString())
Test1(o1)
Console.WriteLine(o1.GetType().ToString())
Console.WriteLine(o2.GetType().ToString())
Test2(o2)
Console.WriteLine(o2.GetType().ToString())
Console.ReadLine()
End Sub
Sub Test1(ByVal num As Integer)
End Sub
Sub Test2(ByRef num As Integer)
End Sub
Output
System.String
System.String
System.String
System.Int32
I suggest you always turn Option Strict On, this will remove a lot of confusion.
The object is of type System.String. It cannot be passed ByRef to a method, it is of the wrong type. So the compiler has to work around it and rewrites the code:
Dim obj As Object
obj = "00"
Dim $temp As Integer
$temp = CInt(obj)
Test($temp)
obj = $temp '' <=== Here
MsgBox(obj)
The indicated statement is the one that changes the object from a string to an integer. Which, converted again to a string by the MsgBox() call, produces "0" instead of "00".
Notable is that C# does not permit this and generate a compile error. This rewriting trick is rather nasty, if the method itself changes the original object then you'll have a very hard time guessing what is going on since that doesn't change the passed argument value.
ByRef means that value passes by reference and in function will be used the same value what has been sent.
ByVal means that value passes by value (function creates a copy of passed value) and you use only copy of value.

why are overloaded private shared function not accessible when called from shared function in same class

Came across something I found interesting and would love an explanation.
Edit
This question is not meant to be answered with what should be done to fix it. I know the fixes. I want an explanation of why the compiler does what it does. Ex. Are the private functions not considered given this scenario?
Problem
I have a class that has a public shared(static) function called WhatIs. WhatIs takes a parameter that has a collection of objects. the code iterates over this collection and calls a WhatIs function that has a parameter matching type of what the object is.
When executed, an InvalidCastException exception is thrown because the execution is trying to call the WhatIs function that started this, not the one for the type provided.
That's weird, but what made it odd to me was when you change the private shared functions to public shared then it works fine.
Even odder, when you explicit cast the object then it works even if the function is private.
What?! someone please explain
Code
the guts:
Public Class House
Public Property Furniture As ICollection(Of Object)
Public Sub New()
Furniture = New List(Of Object)
End Sub
End Class
Public Class Chair
Public Property IsComfortable As Boolean
End Class
Public Class Table
Public Seats As Integer
End Class
Public Class HouseExaminer
Public Shared Function WhatIs(thing As House) As String
Dim isA As String = "a house that contains "
For Each item In thing.Furniture
isA &= WhatIs(item)
Next
Return isA
End Function
Private Shared Function WhatIs(thing As Chair) As String
Return "a " & If(thing.IsComfortable, "comfortable", "uncomfortable") & " chair "
End Function
Private Shared Function WhatIs(thing As Table) As String
Return "a table that seats " & thing.Seats & " iguanas"
End Function
End Class
to test
Imports System.Text
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports stuff
<TestClass()>
Public Class HouseExaminerTests
<TestMethod()>
Public Sub TestWhatIs()
Dim given As New House()
Dim expected As String
Dim actual As String
given.Furniture.Add(New Chair() With {.IsComfortable = True})
given.Furniture.Add(New Table() With {.Seats = 4})
expected = "a house that contains a comfortable chair a table that seats 4 iguanas"
actual = HouseExaminer.WhatIs(given)
Assert.Equals(expected, actual)
End Sub
End Class
result
debug the test and you get this:
InvalidCastException
Method invocation failed because 'Public Shared Function WhatIs(thing As stuff.House) As String' cannot be called with these arguments:
Argument matching parameter 'thing' cannot convert from 'Chair' to 'House'.
These changes make it work but why?!
make em public
change the private shared functions in HouseExaminer to public, rerun test. spoiler, it works
explicitly cast the objects
change them back to private then replace
isA &= WhatIs(item)
with
If TypeOf item Is Chair Then isA &= WhatIs(CType(item, Chair))
If TypeOf item Is Table Then isA &= WhatIs(CType(item, Table))
rerun test, and what do u know, it works
Firstly, it looks like you have implicit conversions turned on. That is the start of the issue. Secondly, you define Furniture as a List(of Object). Your first call to WhatIs is succeeding. The compiler is making a best guess as to which overload to use when passing what it sees as simply Object as it iterates through thing.Furniture, and it determines the public static version of the WhatIs method to be the most appropriate. It then attempts to implicitly convert Object to House, and inevitably fails.
Why does casting work? Because it takes the guess work out of determining which overload to use.
Moral of the story is: Don't make the compiler guess. Implicit conversion can lead to tricky bugs.
Edit: Why doesn't the compiler see the other overloaded functions?
The compiler has to determine the correct overload to use at compile time. It does not wait until runtime to determine which overload to use, and therefore doesn't have the luxury of inspecting the type of the object to determine the most appropriate overload.
Since the compiler only knows that furniture is a List(Of Object), technically (with implicit conversion turned on) all three of the overloads are deemed "appropriate," but the compiler must choose one. It ranks the possible overload candidates, and chooses the public version ahead of the private ones.
Use always
Option Strict On
You cannot make it more flexible by adding Methods equal in name, just with different parametertypes.
Update
Private Function ShowMe(data As Integer) As String
Return data.ToString
End Function
Private Function ShowMe(data As String) As String
Return data
End Function
Private Function ShowMe(data As Double) As String
Return data.ToString
End Function
Dim bla As New List(Of Object)
if you then call
bla.Add(12)
bla.Add("hi")
bla.Add(1.2)
Dim text As String
text = ShowMe(bla(0))
text = ShowMe(bla(1))
text = ShowMe(bla(2))
then the compiler will always complain that the correct method does not exist, because the correct method is not selected by checking the type, instead it is selected by the definition, for which type the container is defined for.
Private Function ShowMe(data As Object) As String
Return data.ToString
End Function
this would be called for all integer, doubles and strings. If it is not available, then some methods are used that can do some kind of automatic conversion. Thats why you can put an integer in a float, or put a number in a string.
One way would be to check for its type and do an explizit type conversion
For Each ele As Object In bla
If TypeOf ele Is Integer Then
text = ShowMe(CInt(ele))
ElseIf TypeOf ele Is Double Then
text = ShowMe(CDbl(ele))
Else
text = ShowMe(CStr(ele))
End If
Next
But this is still not so clean. If you want to access properties that all objects should support, then put them in a container and define the type as something that assures that those properties exist.

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
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.
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.