Export (0) Print
Expand All
Around the World with Visual Basic
Asynchronous Method Execution Using Delegates
Building a Progress Bar that Doesn't Progress
Calling All Operators
Create a Graphical Editor Using RichTextBox and GDI+
Creating A Breadcrumb Control
Creating a Five-Star Rating Control
Creating and Managing Secondary Threads
Data Binding Radio Buttons to a List
Deploying Assemblies
Designing With Custom Attributes
Digital Grandma
Doing Async the Easy Way
Extracting Data from .NET Assemblies
Implementing Callbacks with a Multicast Delegate
Naming and Building Assemblies in Visual Basic .NET
Programming Events of the Framework Class Libraries
Programming I/O with Streams in Visual Basic .NET
Reflection in Visual Basic .NET
Remembering User Information in Visual Basic .NET
Advanced Basics: Revisiting Operator Overloading
Scaling Up: The Very Busy Background Compiler
Synchronizing Multiple Windows Forms
Thread Synchronization
Updating the UI from a Secondary Thread
Using Inheritance in the .NET World
Using the ReaderWriterLock Class
Visual Basic: Simplify Common Tasks by Customizing the My Namespace
What's My IP Address?
Windows Forms Controls: Z-order and Copying Collections
Expand Minimize

From the June 2002 issue of MSDN Magazine.

MSDN Magazine
Objects and Values, Part I
I
n this month's column I am going to begin a two-part discussion on a very important aspect of the .NET Framework: the difference between objects and values. The .NET Framework treats objects and values very differently. It's important to understand these differences if you intend to design software and write code efficiently using Visual Basic® .NET.
      As you probably already know, the .NET Framework and the Visual Basic .NET programming language are built on top of a standard execution engine known as the Common Language Runtime (CLR). The CLR is based on a type system and a programming model that are collectively known as the Common Type System (CTS).
      CTS supports two fundamentally different kinds of types: value and reference. Examples of value types are built-in primitives such as Boolean, integer, double, and decimal. User-defined structures and enumerations are also value types. Reference types are system-defined classes as well as the classes you create in your projects.
      The biggest difference between a value type and a reference type is in how the CLR manages their memory. When you declare a variable based on a value type, the CLR allocates memory for the value type instance wherever the variable is declared. For example, the memory for a local variable declared using a value type is allocated on the stack. The memory for a field declared as a value type within a class definition is allocated inside the host object. The memory allocated for a value type variable holds the actual instance of the type.
      The memory allocated for a reference type variable never contains an actual instance of the type itself. It contains the value of a logical pointer that either points to a reference type instance or has a value of nothing.
      Reference type instances are usually created using the New operator followed by a class name. When you create one like this, the CLR allocates memory for the instance on a system-provided heap. Keep in mind that the CLR is also responsible for managing the lifetime of instances that have been created on this heap. The CLR manages the lifetime of reference type instances on this heap via garbage collection.

Figure 1 Type Differences
Figure 1 Type Differences

      Figure 1 shows the most fundamental difference between a value type and a reference type: the memory layout that would occur given the following lines of code:
Sub Main
  '*** create a value type instance
  Dim var1 As Integer = 10
  '*** create a reference type instance
  Dim var2 As New Class1()
End Sub
      In .NET terminology, value type instances are known as "values" while reference type instances are known as "objects." You can think of values as formatted chunks of memory. Objects are more heavyweight than values. They require a more sophisticated scheme for allocating and managing their memory. Objects require more overhead than values and consequently they provide a few more valuable capabilities.

Value Types

      What criteria does the CLR use to determine whether a type is a value type or a reference type? The CTS uses a simple set of rules. Every type that inherits either directly or indirectly from System.ValueType is a value type. If it does not inherit from System.ValueType, it's a reference type.
      The diagram in Figure 2 shows how value types fit into the inheritance hierarchy of the CTS. As you can see, the most commonly used are system-provided primitive types. User-defined enumerations and structures are also value types because they inherit from System.ValueType.

Figure 2 CTS Inheritance Hierarchy
Figure 2 CTS Inheritance Hierarchy

      Value types are all sealed. This means that it's impossible to create a type that inherits from a value type. Reference types do not suffer from the same limitation; many reference types have been designed to support inheritance and can act as a base class to the classes you write.
      When you create a new user-defined enumeration or structure, you do not have to explicitly inherit from System.ValueType. Instead, the Visual Basic .NET compiler does the translation for you. What happens during compilation of an enumeration definition that looks like this?
Enum AccountTypeEnumeration
  Savings
  Checking
End Enum
The Visual Basic .NET compiler generates a type definition that looks as if it had been created using the following code:
NotInheritable Class AccountTypeEnumeration
               Inherits System.Enum
  '*** shared const fields added for values
  Public Const Savings As Integer = 0
  Public Const Checking As Integer = 1
End Class
The compiler treats structure definitions similarly. Look at the following definition for a user-defined structure:
Structure BankAccount
  Public BankAccount As Integer
  Public Type As AccountTypeEnumeration
  Public Balance As Decimal
End Structure
The Visual Basic .NET compiler generates a type definition that looks as if it had been created using this code:
NotInheritable Class BankAccount
               Inherits System.ValueType
  Public ID As Integer
  Public Type As AccountTypeEnumeration
  Public Balance As Decimal
End Class
      As in the case of enumerations, the Visual Basic .NET compiler adds the code to make sure your type definition meets the requirements for a value type. It is interesting to note that the Visual Basic .NET compiler prohibits programmers from creating a type that explicitly inherits from System.ValueType. The only way to create a user-defined value type is to use either the Enum construct or the Structure construct.
      You probably recall that earlier versions of Visual Basic supported user-defined types (UDTs). UDTs and structures in Visual Basic .NET have some similar characteristics. However, structures in the new Visual Basic are far more capable than UDTs. For example, you can create a structure that exposes public methods and properties. You can create a structure that contains a parameterized constructor for initialization. A structure can even implement an interface.
      The capabilities of a structure are similar to those of a class with a few noteworthy exceptions. As I've already mentioned, a structure cannot be used as a base type. A structure definition cannot contain a custom implementation for a default constructor. Instead, the implementation for a structure's default constructor is always handled by the CLR itself. You can always expect that the default constructor provided by the CLR will initialize all the fields inside a new instance to their default values. For example, all fields based on numeric values types are initialized to a value of zero. All fields based on reference types are initialized to a value of Nothing.
      As I have mentioned, you are not allowed to add a default constructor to a structure definition. You are allowed to add one or more public parameterized constructors. Here's a structure that provides a parameterized constructor to make initialization more convenient to client-side code:
Public Structure BankAccount
  Private AccountNumber As Integer
  Private Balance As Decimal
  '*** parameterized constructor
  Sub New(ByVal AccountNumber As Integer, _
          ByVal Balance As Decimal)
      Me.AccountNumber = AccountNumber
      Me.Balance = Balance
  End Sub
'*** other members omitted for clarity
End Structure
If you want to access this parameterized constructor, you do so using the New operator. Here's some client-side code that creates instances of the structure using the parameterized constructor you've just seen:
'*** create structure instances on stack using New operator
Dim act1 As New BankAccount(1024, 342.32D)
Dim act2 As BankAccount = New BankAccount(3292, 988.72D)
      One peculiarity in this example is that structures support instantiation through use of the New operator. This catches some programmers off guard because they think the New operator should only be used to create heap-based objects. Visual Basic .NET had to support using the New operator on structures in order to access parameterized constructors.
      The most important that to keep in mind about this example is that the New operator has no effect on how the CLR allocates memory for an object. An instance of a structure is always created where the variable is declared. The use of the New operator does not force the CLR to allocate memory for structures in the same manner as it does for classes. Structures are always treated as value types while classes are always treated as reference types.

Deciding Between Classes and Structures

      When you are designing and writing code with Visual Basic .NET, you are often faced with the decision to create a class or a structure. As you now know, classes have reference type semantics while structures have value type semantics. Figure out what's best for you depending on how you want the CLR to manage memory for instances of this type. The choice between a class and a structure affects how instances are created and duplicated as well as how parameter values are passed during method invocation.
      Here's an example to put things in perspective. Imagine you have created the following two type definitions:
Public Structure BankAccountStructure
  Public AccountNumber As Integer
  Public Balance As Decimal
End Structure

Public Class BankAccountClass
  Public AccountNumber As Integer
  Public Balance As Decimal
End Class
      Now, look at what happens when you create instances from these two types and assign their values to other variables. First, what happens when you assign one value type variable to another value type variable?
Dim var1 As BankAccountStructure
var1.AccountNumber = 1001
var1.Balance = 839.54D
Dim var2 As BankAccountStructure = var1
You get a duplicate instance. In other words, the value types support copy-on-assignment. Things are quite different when you assign one reference type variable to another:
Dim var1 As New BankAccountClass
var1.AccountNumber = 2054
var1.Balance = 412.45D
Dim var2 As BankAccountClass = var1
In this example, you should be able to see that there is really just one instance of BankAccountClass. Reference types are not like value types in that they do not support copy-on-assignment. The CLR handles the assignment of one reference type variable to another by copying the reference to the object, as opposed to copying the object itself.
      Now, let's discuss how parameter values are passed back and forth during method invocation with structures and classes. Imagine you are designing a method that passes a value type using the ByVal keyword. In other words, you are passing a value type instance with by-value semantics:
Sub Method1(ByVal param1 As BankAccountStructure)
  '*** implementation cannot modify caller's variable
End Sub
When a caller invokes this method, the CLR copies an instance of the value type on the stack. This has the effect of giving the method implementation its own private copy of the value type instance. As you would expect, the method implementation cannot modify the caller's variable. How do things change when you declare a ByVal parameter using a reference type instead of a value type?
Sub Method2(ByVal param1 As BankAccountClass)
  '*** implementation can modify object
End Sub
How does changing the parameter to a reference type affect what happens at runtime? Your intuition might tell you that you are passing a copy of the object by value because you have used the ByVal keyword. Not so. The CLR passes a copy of the reference, not the object. Both the caller and the method implementation have their own private copies of references that point to the same object. This means that the method implementation sees the same object as the caller. Therefore, the method implementation can make modifications to the object that can be seen by the caller.
      You've just seen what happens when you pass value types and reference types using the ByVal keyword. Now I'll show you what happens when you pass parameters with by-reference semantics using the ByRef keyword. Once again, you must differentiate between the case of passing a value type and passing a reference type. Look at the following method definition, which contains a value type parameter defined using the ByRef keyword:
Sub Method3(ByRef param1 As BankAccountStructure)
  '*** implementation can modify caller's variable
End Sub
      When a value type parameter is passed with by-reference semantics, the CLR does not copy the parameter value. Instead, the method implementation is given access to the value type instance in the caller's stack frame. Therefore, the method implementation can modify the caller's variable.
      The fourth and final case for parameter passing is when a reference type parameter is passed with by-reference semantics. Here is a method that contains a reference type parameter defined using the ByRef keyword:
Sub Method4(ByRef param1 As BankAccountClass)
  '*** implementation can modify object 
  '*** implementation can modify caller's variable
End Sub
In this case, the CLR does not make a copy of the caller's reference variable value. Instead, the method implementation is given access to the reference variable value in the caller's stack frame. As in the case of passing a reference type with the ByVal keyword, the method implementation can modify the object. However, declaring a reference type parameter using the ByRef keyword has one distinct advantage over the ByVal keyword: the method implementation can assign the caller's reference variable value to another object or to a value of Nothing.

Cloning Objects

      As you have seen, value types support copy-on-assignment while reference types do not. This means that there is no built-in support that allows you to create a copy of an object. Therefore, whenever you design a class, ask yourself if you want the class to support cloning. An object that supports cloning allows a client to call a special method that creates a copy of the object on demand.
      How do you define a class that supports cloning? You must provide a custom method for copying one instance of the class to another. By convention, this method should be named Clone. You should also explicitly implement the ICloneable interface to advertise the fact that your class supports cloning. Here's a class that supports cloning:
Public Class Dog : Implements ICloneable
  '*** fields holding object state
  Public Name As String
  Public Age As Integer
  '*** cloning support
  Public Function Clone() As Object _
         Implements ICloneable.Clone
    '*** create another instance
    Dim DogClone As New Dog()
    '*** copy field values to new instance
    DogClone.Name = Me.Name
    DogClone.Age = Me.Age
    '*** return clone reference to caller
    Return DogClone
  End Function
End Class
      The Clone method has a return value defined using System.Object. The Clone method must provide a standard return value for all reference types that support cloning. This means that when strict type checking has been turned on, a client must explicitly convert the return value when calling the Clone method. Here's the client-side code required to call this method and clone a second instance of the Dog class:
Dim dog1 As New Dog()
dog1.Name = "Spot"
dog1.Age = 10
'*** copy instance through calling Clone
Dim dog2 As Dog = CType(dog1.Clone(), Dog)
      Take a closer look at the implementation of the Clone method in the Dog class. You can see that this method implementation creates a second instance of the Dog class using the New operator. After this, the implementation of Clone copies all the field values from the original instance to the cloned instance.
      This implementation of the Clone method is fairly simple. It's what is known as a shallow copy, an exact in-memory copy of the original instance. For this reason, a shallow copy is also commonly called a "bitwise copy" or a "memberwise copy."
      The CLR provides an even easier technique to implement a shallow copy. The System.Object class provides a protected method named MemberwiseClone that can do all the work for you. Since every class inherits from the System.Object class, you can always call the MemberwiseClone method inside the implementation of your Clone method. The implementation of the Clone method you saw earlier can be rewritten in a single line of code:
Public Function Clone() As Object Implements ICloneable.Clone
  '*** clone instance and return reference
  Return Me.MemberwiseClone
End Function
This technique has the same effect as the technique that involved the New operator and manually copying field values from one instance to another. Both techniques involve creating a cloned object that is a shallow copy. Most programmers prefer the technique using MemberwiseClone because it's easier and it eliminates the need to modify your implementation of Clone when you add or remove fields from your class definition.
      The techniques you've just seen for shallow-copy cloning are acceptable for some classes but unacceptable for others. Shallow-copy cloning is usually only appropriate for a class whose fields are only based on value types and/or the String type. In such a case, a shallow copy doesn't create any problems. The values of value type fields are copied to the cloned object. With a field based on the String type, a reference is copied. This doesn't pose any problems because string objects are immutable in the CLR. It is impossible to change the value for a string field in the cloned object and have that change visible to the original object. Therefore, the original object and the cloned object act as truly independent entities.
      A Clone method that performs a shallow copy is usually unacceptable when a class contains one or more fields based on reference types other than the String type. Shallow-copy cloning is insufficient because it does not copy the objects that are referenced by the fields of the object being cloned. Therefore, the fields of the original object and the fields of the cloned object end up with references to the same objects. As you can see, the problem with shallow-copy cloning is that it only copies the target object. A shallow copy does not copy the entire object graph behind the target object.
      When shallow-copy cloning is insufficient, you must resort to a cloning technique that copies the target object as well as all the other objects referenced by the target object. This is what is known as a deep copy. This class provides a Clone method that performs a deep copy on an object graph containing three objects:
Public Class TwoDogOwner : Implements ICloneable
  Public Dog1 As Dog
  Public Dog2 As Dog
  Public Function Clone() As Object Implements ICloneable.Clone
    Dim NewClone As TwoDogOwner 
    NewClone = CType(Me.MemberwiseClone, TwoDogOwner)
    NewClone.Dog1 = CType(Dog1.Clone, Dog)
    NewClone.Dog2 = CType(Dog2.Clone, Dog)
    Return NewClone
  End Function
End Class
The class TwoDogOwner contains two fields based on the Dog class. A shallow copy would produce a second TwoDogOwner object that referenced the same two Dog objects as the original TwoDogOwner object. However, the TwoDogOwner implementation of Clone has been written to clone the two Dog objects as well. Therefore, the cloning operation copies the entire object graph, instead of merely the object at the top of the object graph.
      One last thing about deep-copy cloning: it is much easier to implement a deep copy in the Clone method when every reference type used as a field supports the Clone method. As shown in the previous example, a TwoDogOwner object simply calls Clone on each Dog object. If a class contains a field that does not support the Clone method, it will be harder and sometimes impossible to implement a deep-copy clone correctly.

Summary

      You have seen the basic differences between objects and values. Objects are heap-based entities that are accessed through the use of reference variables while values are simply formatted chunks of memory. You have also seen several design issues that will help you to decide whether to use objects or values in software design.
      In my next column, I am going to continue this story by explaining how the CLR manages the lifetime of objects on the heap. This, in turn, will lead to a discussion of how to properly write the clean up for objects designed for a .NET application. I will also describe a phenomenon that occurs with value type instances known as boxing. Stay tuned.

Send questions and comments for Ted to instinct@microsoft.com.
Ted Pattison is an instructor and researcher at DevelopMentor (http://www.develop.com), where he co-manages the Visual Basic curriculum. The second edition of Ted's book, Programming Distributed Applications with COM and Microsoft Visual Basic 6.0, was published by Microsoft Press in June 2000.

Show:
© 2014 Microsoft