Chapter 11. Structures and Enumerations


In This Chapter

• Implementing Structures

Overloading Operators

Structures and Common Language Specification

Enumerations


So far, we have discussed a lot of important concepts about .NET development with Visual Basic. Just to mention some key topics, we have covered class fundamentals, objects’ lifetime, types’ organization, and exception handling. We applied all these concepts to reference types (classes). But in your developer life, you will often work with value types, both built-in and custom ones. In Chapter 4, “Data Types and Expressions,” you started with the most important built-in value types in the .NET Framework. To complete your skills, though, you now need to know how to implement your own value types. In this chapter you get this information. You first understand structures and how to create them. Then you learn how to extend structures with custom versions of operators. Finally, you learn about enumerations, another kind of value type in the .NET Framework. This is not just a simple illustration because you also gain information on memory allocation to get a complete overview of this development area.

Understanding Structures

Structures in .NET development are the way to create custom value types. You find a lot of similarities between classes and structures, although this section explains some important differences. You create structures using a Structure..End Structure block. The following code provides an example of a structure representing a fictitious order received by your company:

Public Structure Order

    Public Property OrderID As Integer
    Public Property OrderDate As Date
    Public Property ShippedDate As Date
    Public Property CustomerID As Integer
    Public Property EmployeeID As Integer
    End Sub
End Structure

Structure can expose several members, such as fields, properties, and methods, as it happens for classes. The first important difference has to do with constructors. First, to create an instance of a structure, you aren’t required to use the New keyword as you are for classes. The following code shows how you can instantiate a structure:

Dim o As Order
Dim o1 As New Order

Both syntaxes are legal. The only difference is that if you don’t use the New keyword, the variable will be marked as unused until you assign a value and the VB compiler will show a warning message about this. Then you can assign (or invoke) members of the structure by typing the name followed by a dot:

o.OrderDate = Date.Now
o.OrderID = 1
'Other assignments..

Notice that, when you declare a structure without assigning its members, the compiler assigns the structure members a default value (which is usually zero for value types and Nothing for reference types). You can also utilize the object initializers feature discussed in Chapter 7, “Class Fundamentals,” to initialize members of a structure. The following syntax is allowed but requires the specification of the New keyword:

Dim o As New Order With {.OrderID = 1, .OrderDate = Date.Now,
    .ShippedDate = Date.Now.AddDays(1), .CustomerID = 1,
    .EmployeeID = 1}

The second difference is that Visual Basic automatically provides an implicit constructor with no parameters, and you are not enabled to explicitly provide a constructor that receives no parameters; if you try, the Visual Basic compiler throws an error. You are instead enabled to provide a constructor that receives arguments, as follows:

Public Sub New(ByVal Id As Integer,
               ByVal OrderDate As Date,
               ByVal ShippedDate As Date,
               ByVal CustomerId As Integer,
               ByVal EmployeeId As Integer)

    Me.OrderID = Id
    Me.OrderDate = OrderDate
    Me.ShippedDate = ShippedDate
    Me.CustomerID = CustomerId
    Me.EmployeeID = EmployeeId
End Sub

As previously stated, structures can expose methods and fields but also shared members. Consider the following new implementation of the structure:

Public Structure Order

    Private Shared orderCount As Integer

    Public Property OrderID As Integer
    Public Property OrderDate As Date
    Public Property ShippedDate As Date
    Public Property CustomerID As Integer
    Public Property EmployeeID As Integer

    Public Sub New(ByVal Id As Integer,
                   ByVal OrderDate As Date,
                   ByVal ShippedDate As Date,
                   ByVal CustomerId As Integer,
                   ByVal EmployeeId As Integer)

        Me.OrderID = Id
        Me.OrderDate = OrderDate
        Me.ShippedDate = ShippedDate
        Me.CustomerID = CustomerId
        Me.EmployeeID = EmployeeId

        orderCount += 1
    End Sub

    Public Shared Function Count() As Integer
        Return orderCount
    End Function
End Structure

As you can see, now a private shared field exists that provides a counter for the instances of the type. Moreover, a shared method named Count returns the number of instances of the structure. The following code snippet demonstrates how the method works:

Dim firstOrder As New Order(1, Date.Now, Date.Now, 1, 1)
Dim secondOrder As New Order(2, Date.Now, Date.Now, 1, 1)
'Returns 2
Console.WriteLine(Order.Count)

The preceding code returns 2 because there are two active instances of the structure. Notice that the code would not work if you initialized the structure using object initializers because the orderCount field is incremented in the parameterized constructor.

Assigning Structures to Variables

Because structures are value types, assigning an instance of a structure to a variable declared as of that type creates a full copy of the data. The following brief code demonstrates this:

'Creates a real copy of firstOrder
Dim thirdOrder As Order
thirdOrder = firstOrder

In the preceding code, thirdOrder is a full copy of firstOrder. You can easily check this by using the data tips feature of the Visual Studio Debugger or adding the variable to a Watch window.

Passing Structures to Methods

Structures can be passed to methods as arguments. For example, consider the following method that simulates an order process taking an instance of the previously shown Order structure:

Private Sub ShowOrderInfo(ByVal orderInstance As Order)
    Console.WriteLine("Order info:")
    Console.WriteLine("ID: {0}, Date received: {1}",
                      orderInstance.OrderID,
                      orderInstance.OrderDate)
    Console.ReadLine()
End Sub

You can then invoke the method, passing the desired instance as follows:

Dim firstOrder As New Order(1, Date.Now, Date.Now, 1, 1)
ShowOrderInfo(firstOrder)

The preceding code produces an output that looks like this:

ID: 1, Date received: 05/30/2012 22:44:11

Members’ Visibility

Structures’ members require you to specify a scope qualifier. Structures accept only the Private, Public, and Friend qualifiers. If no qualifier is specified, Public is provided by default. Only fields can be declared using the Dim keyword, and they are equivalent to Public. The following line demonstrates this:

Public Structure Order
'Means Public:
    Dim orderCount As Integer

Inheritance Limitations and Interface Implementation

As for other built-in value types, structures implicitly inherit from the System.ValueType type that inherits from System.Object. This is the only inheritance level allowed for structures. This means that, different from reference types (classes), structures can neither inherit nor derive from other structures. Therefore, the Inherits keyword is not allowed within structures. Because structures derive from System.Object, they inherit only from classes such as the Equals, GetHashCode, and ToString methods that can also be overridden within structures. Chapter 12, “Inheritance,” provides detailed information on inheritance and overriding. Structures can instead implement interfaces; therefore, the Implements keyword is enabled. Chapter 13, “Interfaces,” discusses interfaces.

Memory Allocation

Structures are value types. This means they are allocated in the stack. Such behavior provides great efficiency to structures because when they are no longer necessary, the Common Language Runtime (CLR) removes them from the Stack and avoids the need of invoking the garbage collector as happens for reference types. But this is just a general rule. Structures’ members can expose any kind of .NET type and therefore reference types, too. The following revisited implementation of the Order structure provides an example, exposing an OrderDescription property of type String that is a reference type:

Public Structure Order

    Public Property OrderID As Integer
    Public Property OrderDate As Date
    Public Property ShippedDate As Date
    Public Property CustomerID As Integer
    Public Property EmployeeID As Integer

    Public Property OrderDescription As String
End Structure

In this scenario, the garbage collection process is invoked to free up memory space when the OrderDescription is released and causes an increase in performance overhead. With that said, value types are faster and more efficient than reference types only if they do not expose members that are reference types. Another important consideration is that when you pass or assign a structure to a method or variable, the actual value and data of the structure are passed (or copied in assignments) unless you pass the structure to a method by reference. If the data you want to represent is large, you should consider reference types.

Organizing Structures

You can optimize structures’ efficiency with a little bit of work. This work is related to the order in which you implement members within a structure. For example, consider the following code:

Public Structure VariousMembers
    Public Property anInteger As Integer
    Public Property aByte As Byte
    Public Property aShort As Short
End Structure

Notice in which order the members are exposed. Because of their memory allocation in bytes, it’s preferable to expose members in the order of bytes they require. This is a revisited, more efficient version of the structure:

Public Structure VariousMembers
    Public Property aByte As Byte
    Public Property aShort As Short
    Public Property anInteger As Integer
End Structure

If you are in doubt, don’t worry. The .NET Framework offers an interesting attribute named StructLayout, exposed by the System.Runtime.InteropServices namespaces, which tells the compiler to organize a structure in the most appropriate way and that you can use as follows:

'Requires
'Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Auto)>
Public Structure VariousMembers
    Public Property aByte As Byte
    Public Property aShort As Short
    Public Property anInteger As Integer
End Structure


Important Note

Remember that if you use the StructLayout attribute, you cannot pass your structure to unmanaged code such as Windows APIs. If you try, the compiler throws an exception. This is because the Windows APIs expect members to be presented in a predefined order and not reorganized at compile time.


Overloading Operators

In Chapter 4 you learned about operators offered by the Visual Basic grammar. Although Visual Basic does not enable creating new custom operators, it offers the possibility of overloading existing operators. In other words, you have the ability to extend existing operators with custom versions. You might wonder when and why this could be necessary. You get an answer to this question with the following code. Consider the simple structure that represents a three-dimensional coordinate:

Public Structure ThreePoint

    Public Property X As Integer
    Public Property Y As Integer
    Public Property Z As Integer

    Public Sub New(ByVal valueX As Integer, ByVal valueY As Integer,
                   ByVal valueZ As Integer)
        Me.X = valueX
        Me.Y = valueY
        Me.Z = valueZ
    End Sub
End Structure

Now imagine that, for any reason, you want to sum two instances of the structure using the + operator. If you try to write the following code

'Won't compile, throws an error
Dim result As ThreePoint = t1 + t2

the Visual Basic compiler throws an error saying that the + operator is not defined for the ThreePoint structure. You should begin understanding why operator overloading can be a good friend. The same situation is for other operators. In Visual Basic, you overload operators using a Public Shared Operator statement within your type definition. The following code overloads the + and - operators:

Public Shared Operator +(ByVal firstValue As ThreePoint,
                         ByVal secondValue As ThreePoint) As ThreePoint
    Return New ThreePoint With {.X = firstValue.X + secondValue.X,
                                .Y = firstValue.Y + secondValue.Y,
                                .Z = firstValue.Z + secondValue.Z}
End Operator

Public Shared Operator -(ByVal firstValue As ThreePoint,
                         ByVal secondValue As ThreePoint) As ThreePoint
    Return New ThreePoint With {.X = firstValue.X - secondValue.X,
                                .Y = firstValue.Y - secondValue.Y,
                                .Z = firstValue.Z - secondValue.Z}
End Operator

Of course, this is just an example, and you might want to perform different calculations. Both overloads return a ThreePoint structure whose members have been populated with the sum and the difference between the X, Y, and Z properties, respectively, from both initial instances. When overloading operators, you need to remember that some of them require you to also overload the negation counterpart. For example, the equality = operator cannot be overloaded alone but requires the overloading of the inequality <> operator. You will be informed by the Visual Basic background compiler when an operator can’t be overloaded alone. The following code shows an overloading example of equality and inequality operators for the ThreePoint structure:

Public Shared Operator =(ByVal firstValue As ThreePoint,
                         ByVal secondValue As ThreePoint) As Boolean
    Return (firstValue.X = secondValue.X) _
            AndAlso (firstValue.Y = secondValue.Y) _
            AndAlso (firstValue.Z = secondValue.Z)
End Operator

Public Shared Operator <>(ByVal firstValue As ThreePoint,
                          ByVal secondValue As ThreePoint) As Boolean
    Return (firstValue.X <> secondValue.X) _
            OrElse (firstValue.Y <> secondValue.Y) _
            OrElse (firstValue.Z <> secondValue.Z)
End Operator

IntelliSense can help you understand which operators can be overloaded. For your convenience, a list of operators that can be overloaded is provided in Table 11.1.

Table 11.1. Operators That Can Be Overloaded

Image

Operators Context

Overloading operators is discussed in this chapter because operators such as sum and subtraction make more sense with value types. However, this technique is also allowed with classes—for example, for comparison operators. You can certainly overload operators within reference types, too.


Overloading CType

The CType operator also can be overloaded to provide appropriate mechanisms for converting to and from a custom type. The interesting thing in overloading CType is that you have to consider both situations studied in Chapter 4, known as widening and narrowing conversions (see that topic for further details). Continuing the previous example of the ThreePoint structure, the following code snippet offers a special implementation of CType, enabling conversions to and from an array of integers:

'From ThreePoint to Array of Integer
Public Shared Narrowing Operator CType(ByVal instance As ThreePoint) _
                                       As Integer()
    Return New Integer() {instance.X,
                          instance.Y,
                          instance.Z}
End Operator

'From Integer() to ThreePoint
Public Shared Widening Operator CType(ByVal instance As Integer()) _
                                      As ThreePoint
    If instance.Count < 3 Then
        Throw New ArgumentException("Array is out of bounds",
                                    "instance")
    Else
        Return New ThreePoint With {.X = instance(0),
                                    .Y = instance(1),
                                    .Z = instance(2)}
    End If
End Operator

The code is quite simple. Notice how you must specify a keyword corresponding to the effective kind of conversion (Widening and Narrowing) and how, within the Widening definition, the code performs a basic validation ensuring that the array of integers contains at least three items.


CType Conventions

As a convention, your type should implement an overload of CType that converts from a String into the custom type. Such conversion should also be offered implementing two methods conventionally named as Parse and TryParse that you saw in action in Chapter 4 with several primitive types.


Structures and Common Language Specification

The Common Language Specification (CLS) has established specific rules for structures. If you want your structure to be CLS-compliant, you need to overload the equality and inequality operators and redefine the behavior of the Equals and GetHashCode methods inherited from Object. Listing 11.1 shows an example of a CLS-compliant structure.

Listing 11.1. Building a CLS-compliant Structure


<CLSCompliant(True)>
Public Structure CLSCompliantStructure

    Public Shared Operator =(ByVal obj1 As CLSCompliantStructure,
                             ByVal obj2 As CLSCompliantStructure) As Boolean
        Return obj1.Equals(obj2)
    End Operator

    Public Shared Operator <>(ByVal obj1 As CLSCompliantStructure,
                              ByVal obj2 As CLSCompliantStructure) As Boolean
        Return Not obj1.Equals(obj2)
    End Operator

    Public Overrides Function Equals(ByVal obj As Object) As Boolean
        Return Object.Equals(Me, obj)
    End Function

    Public Overrides Function GetHashCode() As Integer
        Return Me.GetHashCode
    End Function
End Structure


If you are not already familiar with overriding, you can read the next chapter and then take a look back at the preceding code.

Enumerations

Enumerations are another kind of value type available in the .NET Framework. They represent a group of constants enclosed within an Enum..End Enum code block. An enumeration derives from System.Enum, which derives from System.ValueType. The following is an example of enumeration:

'These are all Integers
Public Enum Sports
    Biking      '0
    Climbing    '1
    Swimming    '2
    Running     '3
    Skiing      '4
End Enum

By default, enumerations are sets of integer values. The preceding code defines a Sports enumeration of type Integer, which stores a set of integer constants. The Visual Basic compiler can also automatically assign an integer value to each member within an enumeration, starting from zero, as indicated in comments. You can eventually manually assign custom values, but you should avoid this when possible because the standard behavior ensures that other types can use your enumeration with no errors. The following code shows how you can change the result type of an enumeration instead:

Public Enum LongSports As Long
    Biking
    Climbing
    'and so on...
End Enum

IntelliSense can help you understand that enumerations support only numeric types, such as Byte, Short, Integer, Long, UShort, UInteger, ULong, and SByte. Notice that enumerations can be made of mixed numeric data types if assigned with values of different numeric types.


Writing Enumerations

Enumerations are easy to use and fast to implement. They are essentially read-only groups of read-only constants. Because of this, use them when you are sure that those values need no modifications; otherwise, consider implementing a structure instead.


Using Enumerations

You use enumerations as any other .NET type. For example, consider the following method that receives the Sports enumeration as an argument and returns a response depending on which value has been passed:

Private Sub AnalyzeSports(ByVal sportsList As Sports)
    Select Case sportsList
        Case Is = Sports.Biking
            Console.WriteLine("So, do you really like biking my friend?")
        Case Is = Sports.Climbing
            Console.WriteLine("I do not like climbing like you!")
        Case Else
            Console.WriteLine("Every sport is good!")
    End Select
End Sub

The following code snippet then declares a variable of type Sports, assigns a value, and then invokes the method by passing the variable:

Dim mySport As Sports = Sports.Climbing
AnalyzeSports(mySport)

Notice how IntelliSense comes in when you need to specify a value whose type is an enumeration. Figure 11.1 shows the IntelliSense pop-up window related to our custom enumeration.

Image

Figure 11.1. IntelliSense provides flexibility in choosing and assigning enumerations.

Useful Methods from System.Enum

As mentioned at the beginning of this section, all enumerations derive from the System.Enum class. Such a type exposes some shared methods that enable performing operations on enumerations. This subsection explains how you can take advantage of methods for working on enumerations.

GetValues and GetNames

The first two methods described are GetValues and GetNames. Both enable retrieving an array of items stored within an enumeration, but GetValues gets an array of integers corresponding to the numeric values of enumerations’ items, whereas GetNames retrieves an array of strings storing the names of the enumerations’ items. Continuing the example of the Sports enumeration, consider the following code:

For Each item As Integer In System.Enum.GetValues(GetType(Sports))
    Console.WriteLine(item)
Next

This code’s output is the following list:

0
1
2
3
4

GetNames works similarly except that it returns an array of strings:

For Each item As String In System.Enum.GetNames(GetType(Sports))
    Console.WriteLine(item)
Next

And this code produces the following output:

Biking
Climbing
Swimming
Running
Skiing

Notice how both methods require a System.Type argument instead of a System.Enum; therefore, you must invoke the GetType operator. Another interesting thing about the syntax is that the System.Enum class full name for invoking its methods is used here because the Enum class is exposed by the System namespace that is always imported at project level. Technically, you could just write invocations as follows: Enum.GetNames. But this is not allowed in Visual Basic because of conflicts with the Enum reserved keyword. To use the simplified syntax, you can enclose the Enum work within square brackets as follows:

[Enum].GetNames(GetType(Sports))

The Visual Basic compiler enables this syntax perfectly equivalent to the previous one. Notice that the IDE will add the square brackets for you when typing Enum. Now, let’s discover other useful methods.

GetName

GetName works similarly to GetNames, except that it returns just a single name for a constant. Consider the following code:

'Returns Climbing
Console.WriteLine(System.Enum.GetName(GetType(Sports), 1))

You need to pass the type instance and the value in the enumeration whose name you want to retrieve.

IsDefined

IsDefined checks whether the specified constant exists within an enumeration and returns a Boolean value. The following code looks first for an existing value and then for a nonexisting one:

'Returns True
Console.WriteLine(System.Enum.IsDefined(GetType(Sports), "Climbing"))
'Returns False
Console.WriteLine(System.Enum.IsDefined(GetType(Sports), "Soccer"))

ToString and Parse

System.Enum also provides two methods for converting to and from string. The ToString method is inherited from System.Object and is redefined so that it can provide a string representation of the specified value. Consider the following code snippet:

'Sports.Climbing
Dim mySport As Sports = CType(1, Sports)
Console.WriteLine(mySport.ToString)

Such code returns Climbing, which is the string representation of the specified constant value. Also notice how, if Option Strict is On, you must explicitly convert the value into a Sports enumeration using CType. Parse is the opposite of ToString and gets the corresponding numeric value within an enumeration depending on the specified string. The following code provides an example:

Console.WriteLine("Enter your favorite sport:")
Dim sport As String = Console.ReadLine
Dim result As Sports = CType(System.Enum.Parse(GetType(Sports),
                       sport, True), Sports)
'Returns 2
Console.WriteLine("The constant in the enumeration for {0} is {1}",
                  sport.ToString, CInt(result))
Console.ReadLine()

The previous code requires the input from the user, who has to enter a sport name. Using Parse, the code obtains the element in the enumeration corresponding to the entered string. For example, if you enter Swimming, the code produces the following output:

The constant in the enumeration for Swimming is 2

Notice how Parse can receive a third argument of type Boolean that enables specifying if the string comparison must ignore casing.


Assigning Enums to Integers

You can assign an enumeration variable to an Integer type without a conversion operator.


Using Enums As Return Values from Methods

A common usage of enumerations is representing different results from methods that return a numeric value, as often happens for methods that return a number for communicating the result of the code. Consider the following code, which defines an enumeration that a method uses to communicate the result of a simple elaboration on a file:

Public Enum Result
    Success = 0
    Failed = 1
    FileNotFound = 2
End Enum

Public Function ElaborateFile(ByVal fileName As String) As Result
    Try
        Dim text As String = My.Computer.FileSystem.ReadAllText(fileName)

        'Do some work here on your string

        Return Result.Success

    Catch ex As IO.FileNotFoundException
        Return Result.FileNotFound
    Catch ex As Exception
        Return Result.Failed
    End Try
End Function

Each Return statement returns an Integer value from 0 to 2 depending on the method result, but using an enumeration provides a more convenient way for understanding the result, as demonstrated in the following code:

Sub OpenFile()
    Dim res As Result = ElaborateFile("myfile.txt")
    'Success = 0
    If res = Result.Success Then
        Console.WriteLine("Success")
        'FileNotFound = 2
    ElseIf res = Result.FileNotFound Then
        Console.WriteLine("File not found")
        'Failed = 1
    ElseIf res = Result.Failed Then
        Console.WriteLine("The elaboration failed")
    End If
End Sub

Enum Values As Bit Flags

Enumerations can be designed for supporting bitwise operations by marking them with the Flags attribute. This allows combining enumeration values with bitwise operators such as OR. Consider the following implementation of the Sports enumeration that was described previously:

<Flags>
Public Enum Sports
    Biking
    Climbing
    Swimming
    Running
    Skiing
End Enum

By applying the Flags attribute, the values for each enumeration value now become bitflag patterns that have a binary representation such as the following:

00000000
00000001
00000010
00000100
00001000

Combining all values with the OR operator will result in a 11111111 binary value. For example, you could perform an evaluation like the following:

'sportsTest is 0000010
Dim sportsTest As Sports =
    Sports.Biking And Sports.Climbing Or Sports.Swimming

This kind of approach is useful when you want to be able to perform bitwise operations and comparisons.

Enumerations and Common Language Specification

When introducing enumerations, you learned that they support only numeric types. There is another limitation if you plan to implement CLS-compliant enumerations. Only CLS-compliant types can characterize CLS-compliant enumerations; therefore, the SByte, UShort, UInteger, and ULong types cannot be used within CLS-compliant enumerations. Another important consideration is that CLS-compliant enumerations must be decorated with the Flag attribute. The following is an example of a CLS-compliant enumeration:

<Flags()> Public Enum ClsCompliantEnum As Byte
    FirstValue = 0
    SecondValue = 1
    ThirdValue = 2
End Enum


Note

The Flag attribute indicates to the compiler that an enumeration has to be considered as a bit field.


Summary

In this chapter you saw another important part of .NET development with Visual Basic, which is related to creating custom value types. Structures are the .NET way of building custom value types and can expose methods, properties, and fields. There are several similarities with classes, but structures are value types allocated in the Stack and cannot inherit or derive from other structures but can implement interfaces. Because of their nature, structures are susceptible to operations. This requires, in certain situations, the need for specific operators. The .NET Framework enables overloading operators to provide custom implementations of unary, binary, and logical operators—a technique that is allowed for classes. Another kind of value types is enumeration, which represent a group of read-only constants and that are optimized for Integer values, offering several shared methods for performing operations on constants composing the enumeration. An overview of how Common Language Specification rules the implementation of structures and enumerations completed the chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.2.225