As long as I can remember, there has always been confusion about ByVal and ByRef/ref parameters. Here's the best way I can explain it:
You only need to pass an object by reference if and only if you plan on replacing that reference with a different reference. If you want to change the contents of the object being passed, you only need to pass it by value. Example:
Public Class Person
    Public Property FirstName As String
    Public Property LastName As String
End Class
Public Shared Sub ModifyPerson(ByVal someone As Person)
    ' Passed by value          ^^^^^
    someone.LastName = "Doe"
End Sub
Public Shared Sub Main(ByVal args() As String)
    Dim me As New Person
    me.FirstName = "Adam"
    me.LastName = "Maras"
    ModifyPerson(me)
    Console.WriteLine(me.LastName) ' Writes "Doe"
End Sub
Yes, the instance of Person called me is passed into ModifyPerson by value; that just means the reference to the instance is passed by value. A function can still modify the members of that reference. Now, try this:
Public Shared Sub Main(ByVal args() As String)
    Dim me As New Person
    me.FirstName = "Adam"
    me.LastName = "Maras"
    AssignByValue(me)
    Console.WriteLine(me.LastName) ' Writes "Maras"
    AssignByReference(me)
    Console.WriteLine(me.LastName) ' Writes "Doe"
End Sub
Public Shared Sub AssignByValue(ByVal someone As Person)
    Dim new As New Person
    new.FirstName = "John"
    new.LastName = "Doe"
    someone = new
End Sub
Public Shared Sub AssignByReference(ByRef someone As Person)
    Dim new As New Person
    new.FirstName = "John"
    new.LastName = "Doe"
    someone = new
End Sub
These functions differ because they try to modify the actual reference being passed in. AssignByValue has no effect on the Person named me because the parameter is passed by value. However, AssignByReference can change the value of that parameter in the method that called it, hence why the second call to Console.WriteLine(me.LastName) reflects the updated reference.