Remember the C++ pointer stuff that you learned back in school? Messy stuff! Thank goodness for .NET where we don't have to worry about pointers and references, right?
It's still important to understand what kind of object you're working with. In some cases, your variable is really a pointer. In other cases, the variable is the data.
Can you Handle It? I dare you!
First, let's start with a little test... Take a look at the following code snippets and see if you can figure out the answers.
So you did the test right? Kinda tricky huh!
So what's going on here? Well it goes back to those pointer concepts from C++. The .NET CLR has two basic categories of objects. Reference Types and Value Types.
Reference Types involve, well, references. Taking the samples from the test, let's say I do the following:
Dim oNumber as New MyNumberClass()
The New keyword tells the CLR to create a new instance of MyNumberClass. Since MyNumberClass is a reference type, it is created in a section of memory called the Heap. The heap is basically a big bucket of objects. Each object has a numeric address so that the CLR can find it.
After creating the object, the CLR assigns the address of the object to the variable oNumber. Now the oNumber variable points to the object. It is a reference to the object in the heap. Hence the term reference types.
Now, let's look at value types. Again, taking a line from the samples, let's say I do the following:
Dim oNumber as New MyNumberStructure()
Dim oNumber as MyNumberStructure
When the CLR sees a structure, it creates an instance on the Stack. The stack is a portion of memory dedicated for the current function calls that run the program.
The above two statements are basically equivalent because the CLR allocates the memory for a value type at the time it is declared. The variable doesn't point to the instance, the variable is the instance.
- Assigning the variable to another variable, or passing it to a function will make a copy of the data.
- For a function to change the value that the caller owns, the method must be declared ByRef.
- Assigning the variable to another variable makes both variable point to the same instance of the object. It does not "clone" the class. See below for instructions on cloning objects.
- Passing a variable to a function as a ByVal parameter passes a copy of the address to the object. The method can access and change the same object as the caller.
- Passing a variable to a function as a ByRef parameter passes the address of the object. The function can do the same things as the ByVal parameter scenario, plus the function can make the caller's variable point to a different instance of an object.
So what is a value type? What is a reference type?
The following are value types:
- Nullable(Of )
- Anything you delcare with the Structure keyword
The following are reference types:
- Most of the other stuff in the framework
- Anything you declare with the Class keyword
Notes about Structures
You may not have created your own structures, but you probably use them in your code all the time. The most common one I use is the System.Guid structure. This is not a class. It's a value type.
The thing you may not have realized is that Structures can have constructors, methods, and properties. This is the case with the Guid class.
But before you start writing all your stuff as structures, keep in mind that they are really only meant as data storage containers for primitive data types. The typical examples are things like a Point structure (which has x and y coordinates). Or an imaginary number structure (real part, imaginary part).
In most cases, classes are a much better alternative because the behave more in the way that we'd expect when passing them around and when calling functions.
.NET has some special cases. The following is not an exhaustive list, but it covers some of it.
1) The CLR will treat primitive types like a reference type in certain situations. The most common case is when you store a primitive type (like an integer) in a variable that is declared as an Object. In that case, the CLR will "Box" the type and store it on the heap. That way the value will behave the way an Object would behave. Objects are by default reference types. Boxing takes CPU cycles and extra memory, so your app will slow down if you use a lot of boxing. That's part of the reason that Generics in .NET 2.0 is so helpful. They let you do certain scenarios without boxing.
2) Strings are reference types, but they are immutable. They are stored on the heap, but you cannot change a string. Rather, each time you add or remove parts of a string, the CLR creates a copy of the string and points your variable to that new string.
3) When using remoting, you must inherit from MarshallByRefObject if you want the remoting infrastructure to leave your instance of the object on the hosting server. Otherwise, remoting will make a copy of your class on the remote machine.
How to Clone an Object
Let's say that you do want to have two distinct copies of an object. If the object is a class you need to "clone" the class.
There are two types of cloning operations: Shallow and Deep clones. To understand this, remember that a class can have member variables that point to other classes. In some cases, you have a "tree" of objects.
To clone a class, you create a new instance of the same type of class. Then you copy the values from the original instance to the new instance.
A Shallow Clone will only copy the top-most object's properties. A Deep Clone will copy the values for all instances in the object tree. This means that as you are copying properties, you need to know whether the property points to another class instance. If so, you need to clone that class too. You need to walk the tree all the way down to the bottom nodes and do a clone an all properties that are reference types.
Some objects in the framework have methods to clone themselves. If so, you can use this method to get a cloned copy of the class. Otherwise, you'll need to write your own.
A shortcut to doing a per-property copy is to use binary serialization. This works in some cases where all the classes in the tree are serializable. If so, you serialize the first instance into a byte array. Then you de-serialize it into a new instance. The result is a cloned copy of the original object.
MSDN: Using Classes and Structures
This article goes further into when it is better to use a class or structure.