Using Classes and Structures in Visual Basic .NET
Visual Studio Team
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)
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.
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.
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, 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.
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.
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.
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.
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:
- Defines a structure and a class with identical data members, thus giving them a common data size.
- Declares two structure variables and two object variables representing instances of the structure and the class respectively.
- Executes a tight loop copying one structure variable to the other a large number of times.
- 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.
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.
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.
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
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
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.
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.