ByRef vs ByVal Clarification

南笙酒味 提交于 2019-11-26 18:58:38
JaredPar

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!