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

Using Classes and Structures in Visual Basic .NET

Visual Studio .NET 2003
 

Gordon Brown
Visual Studio Team
Microsoft Corporation

February 2002

Summary: This article is intended to help developers choose between a class and a structure when writing an application. You can review the basic points of comparison between the two types in the "Structures and Classes" topic of the Visual Studio .NET product documentation. To find this topic, start Visual Studio .NET, select Index from the Help menu, and type "structures, Visual Basic" in the "Look for" field.

The "Structures and Classes" topic presents the similarities and differences, and this article discusses criteria that you can use to select one type or the other. (7 printed pages)

Contents

Introduction
Storage Consumption
Independent Instances
Performance
Event Handling
Structure Example
Class Example
Conclusions

Introduction

Classes and structures are both container types, meaning they hold other types as members. A container type is useful for gathering a set of data items, such as customer information, that is closely related but cannot be held in a single elementary type. A class or structure can contain data members to hold these items and code members to manipulate them. The members can then be referred to individually, or the container can be treated as a single entity.

The common language runtime allows value types to have most of the same functionality as reference types. Visual Basic .NET exposes this capability by unifying the syntax for classes and structures. Superficially, the two types appear so similar that an application developer might not know which one is better for a given situation. Underneath the surface, however, significant differences can make the choice important.

Storage Consumption

Unless your application creates a large number of container types, the storage differences between class and structure instances are not likely to be significant. But it is still useful to understand how these types use memory.

Data Members

Because classes are reference types, an object variable uses only four bytes directly. These bytes point to the class instance data, which occupies memory elsewhere. Therefore, the overall storage consumption of an object variable is slightly greater than that of a structure variable with the same members.

Because structures are value types, a structure variable consumes at least the sum of the memory requirements of its data members. (Packing and alignment by the common language runtime can slightly increase the storage size.) Note that a reference type structure member, such as a String or Object, uses only four bytes within the structure, although the data to which it points takes up memory outside the structure.

Code Members

Code members, or procedures, are stored outside a class or structure instance and do not contribute to the memory size of that instance. Regardless of the number of instances of a particular class or structure type, each code member is stored in memory only once. Therefore the existence or size of code members is not a consideration in the choice between a class and a structure.

Copies of Variables

A variable is copied when it is assigned to another variable, when it is passed to a procedure ByVal, and when it is returned from a Function procedure. Visual Basic performs shallow copies, which means that it copies only the contents of a variable, and not any data to which those contents might refer. Therefore, a copy of a reference type variable duplicates only the four-byte pointer, and both the original and the copy point to the same data in memory. A copy of a value type variable replicates its entire contents in each new instance.

Independent Instances

Two Object variables can point to the same class instance, but two structure variables are always isolated from each other. If you need the ability to change the data through one variable and access the changed values through another variable, you must use class instances.

Performance

Reference types are managed on the heap, value types on the stack. There is a performance overhead for heap allocation, object access, and garbage collection, so class instances are less efficient than structure instances in this respect. The speed difference, which depends on many factors such as platform, loading, and data size, can range from unnoticeable to enormous.

Array Elements

Because an array is a reference type, its elements are all allocated on the heap, whether they are value or reference types. However, an array of value types is allocated contiguously, which improves locality of reference and reduces the risk of processor cache misses. Consequently, there is a preference for structures over classes as array elements.

Boxed Value Types

If you define a value type but subsequently treat it as an object, it must be boxed, or converted to type Object. The common language runtime boxes a value type instance by making a copy of it, embedding it in a newly allocated object, and storing pointers to its type information in the metadata.

You treat a structure as an object if you assign it to an Object variable or pass it to a procedure that takes an Object argument. If you do this, the common language runtime must box it and often unbox it, as well as manage it on the heap. This kind of treatment can make a structure considerably less efficient than if you had created it as a class in the first place.

A Visual Basic .NET Collection object stores all its elements as type Object. Therefore, if you use structures as the elements of such a collection, you incur overhead for boxing and unboxing.

An array of structures is already a reference type, so it does not undergo boxing when assigned or passed to an Object.

Data Types and COM Interop

A COM interop application makes calls between managed code and unmanaged code (COM). If your application does this, the data types of the arguments you pass can affect performance. Fewer and smaller arguments often result in greater speed, but the interop compatibility of their data types can also make a difference.

A data type is blittable if it has the same representation in both managed and unmanaged memory. This allows it to be copied across the managed/unmanaged boundary without conversion. The blittable types in Visual Basic .NET are Byte, Short, Integer, Long, Single, and Double. The common language runtime types SByte, UInt16, UInt32, and UInt64 are also blittable. If you can limit your data members to these types, you can improve performance for both classes and structures. However, the improvement is more pronounced with structures because a structure of all blittable members is itself blittable.

Data Size

If your application makes a large number of copies of a variable, the memory required for that variable can be a factor that determines whether it should be a value type or a reference type. There is a trade-off between copying all the bytes of a value type as opposed to allocating a new reference type on the heap. The more copies of a variable your application makes, the more important this distinction becomes.

A theoretical observation might serve as an initial guideline. Suppose you write a test application that does the following:

  1. Defines a structure and a class with identical data members, thus giving them a common data size.
  2. Declares two structure variables and two object variables representing instances of the structure and the class respectively.
  3. Executes a tight loop copying one structure variable to the other a large number of times.
  4. Executes another tight loop copying one object variable to the other the same number of times.

Depending on the execution platform and the loading from other tasks, you are likely to observe the following:

  • If the common data size is less than 16 bytes, the structure instance copy loop might be slightly faster than the class instance copy loop.
  • If the data size is 16 bytes, the loops might be approximately equal in timing.
  • If the data size is greater than 16 bytes, the class loop is likely to be faster.

As the data size increases, there is eventually a point at which the class loop is significantly faster than the structure loop. Even allowing for variations due to platform and loading, large data sizes are likely to be more efficient when defined in classes.

Note   The preceding observation is only a guideline and is not definitive in all situations. If a significant portion of your application involves copying such variables, the guideline might be a useful predictor.

Experimentation

There are so many factors affecting the relative performance of classes and structures that it is almost impossible to make a general recommendation. The ultimate decision depends on the unique characteristics of your application. This suggests that it might be worthwhile to experiment during development.

You can sometimes define a container type as either a Class or a Structure. When this is the case, you might be able to develop your application using one type, measure the overall performance, and then change to the other type and measure performance again. This approach is not possible if you use inheritance or generalized constructors, and it is not practical if you use multiple variables to refer to the same instance.

Event Handling

You can declare and raise an event in either a class instance or a structure, and you can declare a delegate in either type. However, while classes support full event handling, structures are more limited.

The WithEvents and Handles keywords set the connection between an event and its handlers at compile time. They are simple to design and write, but you cannot change the connection at run time. You can specify these keywords inside a class but not in a structure.

The AddHandler and RemoveHandler statements operate at run time and are more flexible than WithEvents and Handles. A structure can use AddHandler to make one of its procedures an event handler, provided it is a Shared Sub procedure. By contrast, a class can use AddHandler with either an instance procedure or a Shared Sub procedure.

Structure Example

The following example defines a structure to hold a complex number and perform some basic operations on it. The data members consume only 16 bytes, there is no requirement for polymorphism, and there are no events. Therefore this complex number type is a good candidate for a structure instead of a class.

' Structure to hold and manage a complex number
Public Structure Complex
   Public Real As Double
   Public Imag As Double   ' "Imaginary" part (coefficient of "i")
   ' Add another complex number to this one:
   Public Function Plus(ByVal Operand As Complex) As Complex
      Plus.Real = Operand.Real + Real
      Plus.Imag = Operand.Imag + Imag
   End Function
   ' Multiply this complex number by another one:
   Public Function Times(ByVal Operand As Complex) As Complex
      Times.Real = (Operand.Real * Real) - (Operand.Imag * Imag)
      Times.Imag = (Operand.Imag * Real) + (Operand.Real * Imag)
   End Function
   ' Invert this complex number:
   Public Function Reciprocal() As Complex
      Dim Denominator As Double = (Real * Real) + (Imag * Imag)
      If Denominator = 0 Then Throw New System.DivideByZeroException()
      Reciprocal.Real = Real / Denominator
      Reciprocal.Imag = -Imag / Denominator
   End Function
End Structure   ' Complex

Class Example

The following example defines a set of classes to hold basic information for different types of employees. Even though they do not define any procedures, these container types make use of inheritance and polymorphism. The only way to define such a data architecture is to use classes.

' Classes to hold data for various types of employees
Public Class Employee
   Public GivenName As String
   Public FamilyName As String
   Public Supervisor As Manager
   Public PhoneNumber As String   ' Allows "-", ".", "x" in number
End Class   ' Employee
' Manager of one or more other employees:
Public Class Manager
   Inherits Employee
   Public DirectReports() As Employee
End Class   ' Manager
' Temporary employee engaged for a limited time period:
Public Class Temporary
   Inherits Employee
   Public LastWorkDate As Date
End Class   ' Temporary
' Temporary employee contracted from an employment agency:
Public Class Contractor
   Inherits Temporary
   Public AgencyName As String
   Public AgencyPhoneNumber As String
End Class   ' Contractor

Conclusions

When you are defining a container type, consider the following criteria.

A structure can be preferable when:

  • You have a small amount of data and simply want the equivalent of the UDT (user-defined type) of previous versions of Visual Basic
  • You perform a large number of operations on each instance and would incur performance degradation with heap management
  • You have no need to inherit the structure or to specialize functionality among its instances
  • You do not box and unbox the structure
  • You are passing blittable data across a managed/unmanaged boundary

A class is preferable when:

  • You need to use inheritance and polymorphism
  • You need to initialize one or more members at creation time
  • You need to supply an unparameterized constructor
  • You need unlimited event handling support

If your container type does not fit clearly into either of these categories, define a Class. Classes are more flexible than structures, and the storage and performance differences are often negligible. Structures are intended primarily for types that behave like built-in types, not necessarily for general-purpose use.

Additional Resources

The following resources, which are part of the Visual Studio .NET documentation, provide additional information about structures and classes.

Structures and Classes
Explains the differences between structures and classes.
Events and Event Handlers
Explains the concept of events and how events control program flow.
Value Types
Describes the concept of value types in the .NET Framework.
Value Type Usage Guidelines
Provides the .NET Framework's general guidelines for proper usage of value types.
Blittable and Non-Blittable Types
Illustrates the distinction between blittable and non-blittable types in the .NET Framework.
Show:
© 2014 Microsoft