Chapter 2 introduced the structure as the replacement for the Type construct. Types only supported the aggregation of data; the Structure construct is more closely related to the class construct than the Type construct.
Structures support fields, properties, events, and methods . This section will demonstrate how to define structures and how to implement each of the new capabilities of structures. The last subsection discusses features that are not supported in Structure constructs, which comprise the differences between structures and classes.
Use the Structure construct where you would have used a Type in VB6, keeping in mind all the additional capabilities afforded by the new construct.
Defining Fields and Properties
Fields are data members of aggregate types. Generally fields are Private members representing the underlying value of a property. Properties are generally used to represent the public means of referencing a field.
The distinction between field and property exists because it was decided years ago that the most reliable way to constrain data use was to access data through methods. Properties are really methods that look like data.
You will learn more about the Private and Public keywords in Chapter 7. For now, suffice to say that members of a structure (or class) can have an access specifier , including Private or Public, and these keywords constrain how consumers can use these members, or if consumers can even use members.
Private members can't be used by consumers of a structure, but public members can be. Listing 5.15 demonstrates defining a private field in a structure and allowing access to that field using a public property.
Listing 5.15 Private fields and public properties
1: Public Structure Description 2: 3: Private FEyeColor As EyeColor 4: 5: Public Property EyeColor() As EyeColor 6: Get 7: Return FEyeColor 8: End Get 9: 10: Set(ByVal Value As EyeColor) 11: FEyeColor = Value 12: End Set 13: End Property 14: 15: End Structure
Line 3 defines the field FEyeColor. The F-prefix identifies the name as a field, and more importantly, dropping the F yields a perfect name for the associated property, EyeColor. This pairing of fields and properties makes it very easy and convenient to choose field and property names and keep the associations simple. This convention allows us to program quickly and concisely.
Lines 5 through 13 demonstrate the basic syntax of a property statement. Notice that a single property statement, containing a getter and setter, is distinct from the two individual property statements employed in VB6. Read Chapter 7 for complete coverage of the property idiom.
Listing 5.15 demonstrates a basic use of fields and properties. Although EyeColor can be used just like data, the Get and Set blocks work just like proceduresyou can write any validation code (or other code) that helps you constrain the way the data is used.
Adding Structure Methods
The Structure construct supports methods too. Thus you can add functions and subroutines to your structures, associating behaviors with your aggregate, structure data types.
Suppose we add two more fields and their associated properties to our Description to support storing a first and last name with the description. We might want to implement a function to return a formatted full name rather than requiring the user to enter a full name. Also, we decide that we might want the full name displayed in first-name-first or first-name-last order. Listing 5.16 demonstrates one complete possible revision, adding the fields, properties, and new method.
Listing 5.16 Adding methods to structures
1: Public Structure Description 2: 3: Private FEyeColor As EyeColor 4: Private FFirstName, FLastName As String 5: 6: Public Property EyeColor() As EyeColor 7: Get 8: Return FEyeColor 9: End Get 10: 11: Set(ByVal Value As EyeColor) 12: FEyeColor = Value 13: End Set 14: End Property 15: 16: Public Property FirstName() As String 17: Get 18: Return FFirstName 19: End Get 20: Set(ByVal Value As String) 21: FFirstName = Value 22: End Set 23: End Property 24: 25: Public Property LastName() As String 26: Get 27: Return FLastName 28: End Get 29: Set(ByVal Value As String) 30: FLastName = Value 31: End Set 32: End Property 33: 34: Private Function FirstNameOrder() As String 35: Return FirstName & " " & LastName 36: End Function 37: 38: Private Function LastNameOrder() As String 39: Return LastName & ", " & FirstName 40: End Function 41: 42: Public Function FullName(Optional ByVal UseFirstNameOrder _ 43: As Boolean = True) As String 44: If (UseFirstNameOrder) Then 45: Return FirstNameOrder() 46: Else 47: Return LastNameOrder() 48: End If 49: End Function 50: 51: End Structure
The revisions include the fields FFirstName and FLastName on line 4, the FirstName and LastName properties on lines 16 to 32, and the three methods, FirstNameOrder, LastNameOrder, and FullName from lines 34 through 49. The public method is a function that returns the formatted full name based on the Optional parameter UseFirstNameOrder. FirstNameOrder and LastNameOrder are Private methods because they present partial implementation of the function FullName. Making FirstNameOrder and LastNameOrder private simply keeps consumers from having to worry about two methods. All a consumer needs to worry about is the FullName method and the order desired. This constitutes a subjective implementation choice; a reasonable person could have made FirstNameOrder and LastNameOrder public and dispensed with the FullName method altogether. Generally, I try to keep public methods to a minimum, but that is a preference rather than a rule.
As you can see from Listing 5.16, structures can become quite complex.
As you learned in Chapter 2, all structures are System.ValueType entities. All value types implicitly have a default, parameterless constructor.
Structure variables can be declared the same way as intrinsic types, like Integers, without the New keyword, or, you can declare Structures with the New keyword if you've defined a constructor that takes parameters and want to define an instance of the structure using the parameterized constructor. Whether you use the New keyword or not when declaring structure instances, structures are still value types as opposed to reference types. Keep in mind that the distinction between a value type and reference type is copy- by-value versus copy-by-reference when instances are assigned, as depicted earlier in Figure 5.3.
From Chapter 2, you know that a constructor is a special method responsible for initializing new instances. In Visual Basic .NET, constructors are defined as the subroutine New(). Constructors may or may not have parameters and can be overloaded. For example, in the following declaration, the variable varname represents any valid variable name and type represents any valid data type, including user-defined types:
Dim varname As New type ()
This example explicitly invokes the New() constructor, although it may look as if you are calling a procedure type() .
You can't inherit from structures. All structures get a single default constructorSub New()from their common ancestor ValueType, and you can define parameterized constructors. You can't overload the default constructor, as all Structures are implicitly declared with the NotInheritable keyword. Refer to Chapter 7 for more on classes and declaration attributes like NotInheritable, and refer to the section "Unsupported Structure Features" for more information on features that aren't supported when working with structures.
When you declare a structure variable, you are implicitly calling the empty constructor Sub New(); you can also explicitly construct a Structure by typing Dim MyStructure As New StructureType(), where StructureType is a user-defined structure. You must use the New keyword to invoke parameterized constructors.
Suppose we want to define a constructor for the Description structure that takes a first and last name. The facts that we want to initialize two fields and that they are both strings determine what the parameters for the constructor will be:
Public Sub New(ByVal AFirstName As String, ByVal ALastName As String) FFirstName = AFirstName FLastName = ALastName End Sub
By convention, I matched the parameter names to the fields they will initialize by using the same root name with an A-prefix for the parameters and simply assigning the param-eters to the associated field names.
To construct Description structures using the new constructor, you will have to use the Form of the Dim statement that uses the New keyword.
Constructor calls in Visual Basic .NET are a little confusing. The way New is used makes New look like a modifier on the type and the type actually look like the method call. For example, New Description("Noah", "Kimmel") looks like we are invoking a method Description with the modifier New. The C++ language treats New as an operator and constructors always have the same name as the class (or struct, in C++). Hence, if Description were defined as a type in C++, Description would actually be the constructor.
Just keep in mind that in Visual Basic .NET, New is actually the constructor but the parameters are placed in parentheses after the type. Only Microsoft knows why New is used this way.
Dim MyDescription As New Description("Paul", "Kimmel")
The preceding statement actually calls the New subroutine defined at the beginning of this subsection.
Defining Structure Events
You can define events for structures, but you can't associate an event with a procedure using the WithEvents statement and the Handles clause. To define events for structures and associate them with an event handler, you need to complete several specific steps; we'll use the Description structure as a frame of reference to demonstrate:
Listing 5.17 shows all the revisions to a data entry form and the Description structure necessary to implement and respond to Description.Changed events. Code not involved in the revision was hidden using code outlining to keep the listing from getting out of hand.
Listing 5.17 Defining and responding to Structure events
1: Public Class Form1 2: 3: [...] 4: 5: Private FDescription As Description 6: [...] 7: 8: Private Sub Assign()[...] 9: 10: Private Sub Button1_Click(ByVal sender As System.Object, _ [...] 11: 12: Private Sub InitializeEyeColors()[...] 13: 14: Private Sub Changed(ByVal ADescription As Description) 15: MsgBox(ADescription.FullName & " changed") 16: End Sub 17: 18: Private Sub Initialize() 19: AddHandler FDescription.Changed, AddressOf Changed 20: InitializeEyeColors() 21: End Sub 22: 23: Private Sub Form1_Load(ByVal sender As System.Object, _ 24: ByVal e As System.EventArgs) Handles MyBase.Load 25: 26: Dim D As New Description() 27: Initialize() 28: 29: End Sub 30: 31: Private Sub Button2_Click(ByVal sender As System.Object, _[...] 32: 33: End Class 34: 35: Public Enum EyeColor[...] 36: 37: Public Structure Description 38: 39: [...] 40: Public Event Changed(ByVal ADescription As Description) 41: 42: Public Property EyeColor() As EyeColor 43: [...] 44: 45: Set(ByVal Value As EyeColor) 46: FEyeColor = Value 47: RaiseEvent Changed(Me) 48: End Set 49: End Property 50: 51: Public Property FirstName() As String 52: [...] 53: 54: Set(ByVal Value As String) 55: FFirstName = Value 56: RaiseEvent Changed(Me) 57: End Set 58: End Property 59: 60: Public Property LastName() As String 61: [...] 62: 63: Set(ByVal Value As String) 64: FLastName = Value 65: RaiseEvent Changed(Me) 66: End Set 67: End Property 68: 69: [...] 70: 71: End Structure
You've seen a lot of this code in previous listings in this section, so those parts not related to the topic of this section were hidden with code outlining (introduced in Chapter 1). All of the lines specifically part of the declaration, raising, or handling of the event are in boldface for your convenience.
Beginning at the top of the listing, each aspect of incorporating the Structure event is defined. Line 5 declares a Description variable named FDescription. Lines 14 through 16 define the event handler in the Form1 class using the same name as the event in the Description structure on line 40. Line 19 adds the handler Form1.Changed to the Description.Changed invocation list. Line 27 calls the Initialize method to initialize various aspects of the form. (This strategy is employed by convention.) Line 40 defines the event for the Description structure; this syntax is identical to the syntax used for event definitions in any entity. Lines 47, 56, and 65 raise the event in the setters (property Set methods) for the EyeColor, FirstName, and LastName properties, respectively.
As you can determine from Listing 5.17, defining events and event handlers for structures is identical to defining them for classes and modules. The biggest difference is that only classes support the WithEvents statement and Handles clause. Events in structures and modules are associated with handlers using the AddHandler statement.
Declaring Structure Variables
Events are value types, so they are declared like intrinsic types with the access specifier or Dim, followed by a name for the structure, the keyword As, and the structure name. As demonstrated in the previous section, you can also create an instance of a structure using the New keyword. To initialize a structure using a parameterized New constructor, you must use the New keyword. By default, the non-parameterized constructor defined in the ValueType class is invoked when you declare a structure.
ValueType is the immediate ancestor of structures. Structures aren't inheritable and you can't override the constructor defined in ValueType. More information on structure-related features that are not supported is upcoming in the "Unsupported Structure Features" section.
Information hiding is an aspect of object-oriented programming. The premise is based on the idea that too much information is a bad thing when trying to solve problems. The affirmative principle is divide et impera, or divide and conquer.
The access specifiers Public and Private are supported for structures. Public members can be referenced by consumers; private members can only be referenced internally. (The Protected and Protected Friend access specifiers aren't supported because they relate to inheritance, which isn't supported for structures.)
Structure Arguments and Return Types
VB6 limits passing and returning structure types. You may only define structures as private members in VB6, and only pass and return structures between private members in VB6. These limitations severely inhibit the utility of UDTs in VB6.
Visual Basic .NET supports passing and returning structures in both public and private methods, and you can define nested structures (that is, types in classes) or standalone structures.
Using the Description structure introduced in Listing 5.15, the following subroutine and function are legal Visual Basic .NET code but would cause a compiler error in VB6 unless both the structure and the procedures were defined in a module or declared private in a form or class:
Public Structure Description[...] Private FDescription As Description Public Function GetDescription() As Description Return FDescription End Public Sub SetDescription(ByVal Value As Description) FDescription = Value End Sub
The first statement represents the definition of our structure from Listing 5.15, Description. The second statement defines a variable of type Description. Both the function and subroutine are public. GetDescription returns a public structure and SetDescription accepts a public structure. The equivalent code isn't supported in VB6. Public UDTs in Visual Basic .NET make the structure much more powerful than the Type capabilities from VB6.
Unsupported Structure Features
Structures are not classes. Although the Visual Basic .NET structure is more closely related to the C++ struct idiom and the Visual Basic .NET class, there are some aspects of classes not supported in structures.
You may use a structure in new code for several reasons. Probably the most common example is to support legacy code, like the Windows API. However, structures aren't classes, and aside from a minor convenience in their declaration, you should consider using a class instead of a structure for new code. In fact, there is even a refactoring technique named "Replace Record with Data Class" that discusses the motivation for replacing classes with structures. The biggest reason to use structures is if some conjoined aspect of your program requires them, but in most cases new aggregate types should be defined as classes.
The following features are not supported by structures:
Structures are value types. When you assign structures, the code actually performs a memberwise copy of the members of the structure. Object assignment performs a reference assignment. Consequently, passing and returning structure variables will incur more overhead than using instances of classes.