In another post I referred to this topic as a phenomena. That’s pretty over-dramatic. But there are some important rules and guidelines for types in .NET and until you know why things happen as they do, it seems like voodoo. So here’s an all-in-one example to clear it all up. But, be warned that it’s going to be very confusing until it “clicks”.
A brief prelude: .NET has two variable types, value types and reference types. A value type is stored on the stack, is generally small, and contains its actual data. A reference type is stored on the heap, has an unknown or variable size, and contains pointers to the actual data. .NET also has two definitions that utilize these types: Structures and Classes. Structures are value types and Classes are reference types.
Method calls support two parameter keywords: ByVal and ByRef. ByVal passes the parameter data to the method by value, in other words, it sends the values to the method. ByRef sends the parameter data to the method by reference, it sends a reference to the parameter data. The thing that is most misunderstood is that if the parameter type is a reference type (a class) and it is sent ByVal, the value is a copy of the reference. When the parameter type is a value type (a structure) and is sent ByVal, the value is a copy of the value – a real copy. Here’s a quick real-world summary of method/parameter behavior:
- A class passed ByVal – the method works on the original (kind of)
- A structure passed ByVal – the method works on a copy
- A class passed ByRef – the method works on the original
- A structure passed ByRef – the method works on the original
If you’re still following, you might notice some vagueness in the most common method call type: classes passed ByVal. This will be explained after a demo of these different combinations. So for demonstration, we create a Class and a Structure with a public field:
Public Class NameClass Public Name As String Public Sub ChangeName() Name &= " (changed)" End Sub End Class Public Structure NameStructure Public Name As String Public Sub ChangeName() Name &= " (changed)" End Sub End Structure
A method is in each to modify the internal state. This will prove whether we are working on a copy or the original. Next, we make a class that uses these objects and modifies them in private methods with parameters passed in various combinations:
Public Class PersonClass Public Name1 As New NameClass Public Name2 As New NameStructure Public Sub UpdateNames() Name1.Name = "Class Name" Name2.Name = "Structure Name" Debug.WriteLine("Name1 before ByVal: " & Name1.Name) UpdateClassByVal(Name1) Debug.WriteLine("Name1 after ByVal: " & Name1.Name) Debug.WriteLine("Name2 before ByVal: " & Name2.Name) UpdateStructureByVal(Name2) Debug.WriteLine("Name2 after ByVal: " & Name2.Name) Debug.WriteLine("Name1 before ByRef: " & Name1.Name) UpdateClassByRef(Name1) Debug.WriteLine("Name1 after ByRef: " & Name1.Name) Debug.WriteLine("Name2 before ByRef: " & Name2.Name) UpdateStructureByRef(Name2) Debug.WriteLine("Name2 after ByRef: " & Name2.Name) End Sub Private Sub UpdateClassByVal(ByVal item As NameClass) item.ChangeName() End Sub Private Sub UpdateStructureByVal(ByVal item As NameStructure) item.ChangeName() End Sub Private Sub UpdateClassByRef(ByRef item As NameClass) item.ChangeName() End Sub Private Sub UpdateStructureByRef(ByRef item As NameStructure) item.ChangeName() End Sub End Class
So, after instantiating this class and calling UpdateNames, we get the following results:
Name1 before ByVal: Class Name
Name1 after ByVal: Class Name (changed)
Name2 before ByVal: Structure Name
Name2 after ByVal: Structure Name
Name1 before ByRef: Class Name (changed)
Name1 after ByRef: Class Name (changed) (changed)
Name2 before ByRef: Structure Name
Name2 after ByRef: Structure Name (changed)
In the same order as the bullet list above, we can see that Name2 (the structure) passed ByVal did not change, showing that the method was working on a copy. Everything else remained changed after leaving the method call, showing they were working on the original.
Now to add confusion and clarity to the ambiguity of passing classes ByVal…
When you pass a class to a method ByVal, you are sending a copy of the reference. Everything that is inside that class is a reference as well, so when you change a property, it’s still changing the same property in the original – they share the same reference. This essentially is like working on the original. However, you cannot change the class itself. What?
Here’s another bit of code to add to PersonClass to illustrate:
Public Sub UpdateObjects() Name1.Name = "Class Name" Debug.WriteLine("Name1 before ByVal: " & Name1.Name) UpdateClassObjectByVal(Name1) Debug.WriteLine("Name1 after ByVal: " & Name1.Name) Debug.WriteLine("Name1 before ByRef: " & Name1.Name) UpdateClassObjectByRef(Name1) Debug.WriteLine("Name1 after ByRef: " & Name1.Name) End Sub Private Sub UpdateClassObjectByVal(ByVal item As NameClass) item = New NameClass item.Name = "Replaced Class" End Sub Private Sub UpdateClassObjectByRef(ByRef item As NameClass) item = New NameClass item.Name = "Replaced Class" End Sub
What this code does is try to reassign the value of Name1 to a new instance. When you call UpdateObjects, you will see you can’t change the instance of Name1 when the parameter is passed ByVal, but you can when it is passed ByRef.
Name1 before ByVal: Class Name
Name1 after ByVal: Class Name
Name1 before ByRef: Class Name
Name1 after ByRef: Replaced Class
Again, because ByVal passes a copy of the reference where ByRef passes the actual reference. If you reassign the value when it is passed ByVal, you’re only reassigning to a copy, which has no effect on the original.
In real-world usage, using ByVal with classes is going to work for you 99% of the time, but you need to understand why and how things work to handle that odd 1% of cases and avoid crazy workarounds.