Value Types (Structures)


Value types aren’t as versatile as reference types, but they can provide better performance in many circumstances. The core value types (which include the majority of primitive types) are Boolean, Byte, Char, DateTime, Decimal, Double, Guid, Int16, Int32, Int64, SByte, Single, and TimeSpan. These are not the only value types, but rather the subset with which most Visual Basic developers consistently work. As you’ve seen, value types by definition store data on the stack.

Value types can also be referred to by their proper name: structures. The underlying principles and syntax of creating custom structures mirrors that of creating classes, covered in the next chapter. This section focuses on some of the built-in types provided by the .NET Framework - in particular, those built-in types known as primitives.

Boolean

The .NET Boolean type has been implemented in such a way that when converted to an integer, false is 0; true, when converted to an integer, has one of two possible values. In contrast to most languages (in which Boolean True equates to 1 'one'), Visual Basic converts a value of True to -1 'negative one'. This is one of the few (but not the only) legacy carryovers from older versions of Visual Basic. It was done to save developers who were migrating code that didn’t follow best practices, and actually relied on the underlying numeric value of the Boolean instead of checking for true or false. This allowed the VB migration wizard and the human developer to not examine every Boolean expression to ensure valid return values.

Visual Basic works as part of a multi-language environment, with metadata-defining interfaces, so the external value of True is as important as its internal value. Fortunately, Microsoft implemented Visual Basic in such a way that, while -1 is supported within Visual Basic, the .NET standard of 1 is exposed from Visual Basic methods to other languages.

Of course, this compromise involves making some decisions that add complexity to True or False evaluations. While a True value in a Boolean expression equates to –1, when converted to any other format, it equates to 1. This is best illustrated by some sample Visual Basic code. Keep in mind, though, that this code follows poor programming practice because it references Boolean values as integers (and does so with implicit conversions):

  Dim blnTrue As Boolean = True Dim blnOne As Boolean = 1 Dim blnNegOne As Boolean = -1 Dim blnFalse As Boolean = False 

The following condition, which is based on the implicit conversion of the Boolean, works even though the blnOne variable was originally assigned a value of 1:

  If blnOne = -1 Then    Console.WriteLine(blnTrue)    Console.WriteLine(blnOne.ToString)    Console.WriteLine(Convert.ToString(Convert.ToInt32(blnNegOne))) End If 

Implicit conversions such as the one in the preceding example work differently from explicit conversions. If you add sample code to explicitly convert the value of a Boolean type to an Integer type, and then test the result, the integer will be a positive 1. The implicit and explicit conversion of Boolean values is not consistent in Visual Basic. Converting blnNegOne to an integer results in a positive value, regardless of what was originally assigned:

  If Convert.ToInt16(blnNegOne) = 1 Then    Console.WriteLine(blnFalse)    Console.WriteLine(Convert.ToString(Convert.ToInt32(blnFalse))) End If 

The preceding code won’t compile if you are using Option Strict (more on this later), but it is a good illustration of what you should expect when casting implicitly, rather than explicitly. The output from this code is shown in Figure 2-1.

image from book
Figure 2-1

This demonstrates the risk involved in relying on implicitly converted values. If at some point the default value associated with True were to change, this code would execute differently. The difference between an explicit and implicit conversion is subtle, and you should ensure the following in order to avoid difficulty:

Tip 

Always use the True and False constants when working with Boolean variables.

The final area where this can be an issue is across languages. Here you need to consider the behavior of a referenced component within your Visual Basic code. You can look at a hypothetical class called MyCSharpClass that might have a single method TestTrue(). The method doesn’t need any parameters; it simply returns a Boolean, which is always True.

From the Visual Basic example, you can create an instance of MyCSharpClass and make calls to the TestTrue() method:

  Dim objMyClass as New MyCSharpClass.MyCSharpClass() If objMyClass.TestTrue() = 1 Then    Console.WriteLine("CSharp uses a 1 for true but does it" & _                   " implicitly convert to a 1 in VB?") Else    Console.WriteLine("Even classes implemented in other .NET languages" & _                   " are evaluated implicitly as -1 in Visual Basic") End If If objMyClass.TestTrue() = True Then    Console.WriteLine("CSharp True always converts to Visual Basic True.") End If 

It’s probably unclear whether the first conditional in this code will ever work; after all, C# uses a value of 1 to represent True. However, this code is running in Visual Basic, so the rules of Visual Basic apply. Even when you return a Boolean from a .NET language that uses 1, not -1, to represent True, the Visual Basic compiler ensures that the value of True is implicitly interpreted as –1. Figure 2-2 illustrates that the behavior of the second conditional is both clear and safe from future modifications of the VB language. If Visual Basic is modified at some future date to no longer use -1 to equate to True, then statements that instead compare to the Boolean True remain unaffected.

image from book
Figure 2-2

To create reusable code, it is always better to avoid implicit conversions. In the case of Booleans, if the code needs to check for an integer value, then you should explicitly evaluate the Boolean and create an appropriate integer. The code will be far more maintainable and prone to fewer unexpected results.

The Integer Types

Now that Booleans have been covered in depth, the next step is to examine the Integer types that are part of Visual Basic. Visual Basic 6.0 included two types of integer values: The Integer type was limited to a maximum value of 32767, and the Long type supported a maximum value of 2147483647. The .NET Framework adds a new integer type, the Short. The Short is the equivalent of the Integer value from Visual Basic 6.0; the Integer has been promoted to support the range previously supported by the Long type, and the Long type is bigger than ever. In addition, each of these types also has two alternative types. In all, Visual Basic supports nine Integer types:

Open table as spreadsheet

Type

Allocated Memory

Minimum Value

Maximum Value

Short

2 bytes

32768

32767

Int16

2 bytes

32768

32767

UInt16

2 bytes

0

65535

Integer

4 bytes

2147483648

2147483647

Int32

4 bytes

2147483648

2147483647

UInt32

4 bytes

0

4294967295

Long

8 bytes

9223372036854775808

9223372036854775807

Int64

8 bytes

9223372036854775808

9223372036854775807

UInt64

8 bytes

0

184467440737095551615

Short

A Short value is limited to the maximum value that can be stored in two bytes. This means there are 16 bits and the value can range between 32768 and 32767. This limitation may or may not be based on the amount of memory physically associated with the value; it is a definition of what must occur in the .NET Framework. This is important, because there is no guarantee that the implementation will actually use less memory than when using an Integer value. It is possible that in order to optimize memory or processing, the operating system will allocate the same amount of physical memory used for an Integer type and then just limit the possible values.

The Short (or Int16) value type can be used to map SQL smallint values.

Integer

An Integer is defined as a value that can be safely stored and transported in four bytes (not as a four-byte implementation). This gives the Integer and Int32 value types a range from 2147483648 to 2147483647. This range is more than adequate to handle most tasks.

The main reason to use an Int32 in place of an integer value is to ensure future portability with interfaces. For example, the Integer value in Visual Basic 6.0 was limited to a two-byte value, but is now a four-byte value. In future 64-bit platforms, the Integer value might be an eight-byte value. Problems could occur if an interface used a 64-bit Integer with an interface that expected a 32-bit Integer value, or, conversely, if code using the Integer type is suddenly passed to a variable explicitly declared as Int32. The solution is to be consistent. Use Int32, which would remain a 32-bit value, even on a 64-bit platform, if that is what you need. In addition, as a best practice, use Integer so your code can be unconcerned with the underlying implementation.

The new Integer value type matches the size of an integer value in SQL Server, which means that you can easily align the column type of a table with the variable type in your programs.

Long

The Long type is aligned with the Int64 value. Longs have an eight-byte range, which means that their value can range from 9223372036854775808 to 9223372036854775807. This is a big range, but if you need to add or multiply Integer values, then you need a large value to contain the result. It’s common while doing math operations on one type of integer to use a larger type to capture the result if there’s a chance that the result could exceed the limit of the types being manipulated.

The Long value type matches the bigint type in SQL.

Unsigned Types

Another way to gain additional range on the positive side of an Integer type is to use one of the unsigned types. The unsigned types provide a useful buffer for holding a result that might exceed an operation by a small amount, but this isn’t the main reason they exist. The UInt16 type happens to have the same characteristics as the Character type, while the UInt32 type has the same characteristics as a system memory pointer on a 32-byte system. However, never write code that attempts to leverage this relationship. Such code isn’t portable, as on a 64-bit system the system memory pointer changes and uses the UInt64 type. However, when larger integers are needed and all values are known to be positive, these values are of use. As for the low-level uses of these types, certain low-level drivers use this type of knowledge to interface with software that expects these values and are the underlying implementation for other value types. This is why, when you move from a 32-bit to a 64-bit system, you need new drivers for your devices, and why applications shouldn’t leverage this same type of logic.

The Decimal Types

Just as there are several types to store integer values, there are three implementations of value types to store real number values. The Single and Double types work the same way in Visual Basic as they did in Visual Basic 6.0. The difference is the Visual Basic 6.0 Currency type (which was a specialized version of a Double type), which is now obsolete; a new Decimal value type takes its place for very large real numbers.

Open table as spreadsheet

Type

Allocated Memory

Negative Range

Positive Range

Single

4 bytes

3.402823E38 to

1.401298E-45 to

1.401298E-45

3.402823E38

Double

8 bytes

1.79769313486231E308 to

4.94065645841247E-324 to

4.94065645841247E-324

1.79769313486232E308

Currency

Obsolete

-

-

Decimal

16 bytes

7922816251426433759

0.00000000000000

3543950335 to

00000000000001 to

0.0000000000000

7922816251426

000000000000001

4337593543950335

Single

The Single type contains four bytes of data, and its precision can range anywhere from 1.401298E-45 to 3.402823E38 for positive values and from 3.402823E38 to 1.401298E-45 for negative values.

It can seem strange that a value stored using four bytes (the same as the Integer type) can store a number that is larger than even the Long type. This is possible because of the way in which numbers are stored; a real number can be stored with different levels of precision. Note that there are six digits after the decimal point in the definition of the Single type. When a real number gets very large or very small, the stored value is limited by its significant places.

Because real values contain fewer significant places than their maximum value, when working near the extremes it is possible to lose precision. For example, while it is possible to represent a Long with the value of 9223372036854775805, the Single type rounds this value to 9.223372E18. This seems like a reasonable action to take, but it isn’t a reversible action. The following code demonstrates how this loss of precision and data can result in errors:

  Dim l As Long Dim s As Single l = Long.MaxValue Console.WriteLine(l) s = Convert.ToSingle(l) s -= 1000000000000 l = Convert.ToInt64(s) Console.WriteLine(l) 

This code creates a Long that has the maximum value possible, and outputs this value. Then it stores the value in a Single, subtracts 1000000000000, stores the value of the Single in the Long, and outputs the results, as shown in Figure 2-3. Notice that the results aren’t consistent with what you might expect.

image from book
Figure 2-3

As you can see, the result of what is stored in the single where the math operation actually occurs is not accurate in relation to what is computed using the long value. Therefore, both the Single and Double types have limitations, which are eventually addressed with the decimal type.

Double

The behavior of the previous example changes if you replace the value type of Single with Double. ADouble uses eight bytes to store values, and as a result has greater precision and range. The range for a Double is from 4.94065645841247E-324 to 1.79769313486232E308 for positive values and from 1.79769313486231E308 to 4.94065645841247E-324 for negative values. The precision has increased so that a number can contain 15 digits before the rounding begins. This greater level of precision makes the Double value type a much more reliable variable for use in math operations. It’s possible to represent most operations with complete accuracy with this value.

Decimal

The Decimal type (new in Visual Basic) is a hybrid that consists of a 12-byte integer value combined with two additional 16-bit values that control the location of the decimal point and the sign of the overall value. A Decimal value consumes 16 bytes in total and can store a maximum value of 79228162514264337593543950335. This value can then be manipulated by adjusting where the decimal place is located. For example, the maximum value while accounting for four decimal places is 7922816251426433759354395.0335. This is because a Decimal isn’t stored as a traditional number, but as a 12-byte integer value, with the location of the decimal in relation to the available 28 digits. This means that a Decimal does not inherently round numbers the way a Double does.

As a result of the way values are stored, the closest precision to zero that a Decimal supports is 0.0000000000000000000000000001. The location of the decimal point is stored separately, and the decimal type also stores a value that indicates whether its value is positive or negative separately from the actual value. This means that the positive and negative ranges are exactly the same, regardless of the number of decimal places.

Thus, the system makes a trade-off whereby the need to store a larger number of decimal places reduces the maximum value that can be kept at that level of precision. This trade-off makes a lot of sense. After all, it’s not often that you need to store a number with 15 digits on both sides of the decimal point, and for those cases you can create a custom class that manages the logic and leverages one or more decimal values as its properties.

Char and Byte

The default character set under Visual Basic is Unicode. Therefore, when a variable is declared as type Char, Visual Basic creates a two-byte value, since, by default, all characters in the Unicode character set require two bytes. Visual Basic supports the declaration of a character value in three ways. Placing a $c$ following a literal string informs the compiler that the value should be treated as a character, or the Chr and ChrW methods can be used. The following code snippet shows that all three of these options work similarly, with the difference between the Chr and ChrW methods being the range of valid input values that is available. The ChrW method allows for a broader range of values based on wide character input.

  Dim chrLtr_a As Char = "a"c Dim chrAsc_a As Char = Chr(97) Dim chrAsc_b as Char = ChrW(98) 

To convert characters into a string suitable for an ASCII interface, the runtime library needs to validate each character’s value to ensure that it is within a valid range. This could have a performance impact for certain serial arrays. Fortunately, Visual Basic supports the Byte value type. This type contains a value between 0 and 255 that exactly matches the range of the ASCII character set. When interfacing with a system that uses ASCII, it is best to use a Byte array. The runtime knows there is no need to perform a Unicode-to-ASCII conversion for a Byte array, so the interface between the systems operates significantly faster.

In Visual Basic, the Byte value type expects a numeric value. Thus, to assign the letter “a” to a Byte, you must use the appropriate character code. One option to get the numeric value of a letter is to use the Asc method, as shown here:

 Dim bytLtrA as Byte = Asc("a")

DateTime

The Visual Basic Date keyword has always supported a structure of both date and time. You can, in fact, declare date values using both the DateTime and Date types. Note that internally Visual Basic no longer stores a date value as a Double; however, it provides key methods for converting the current internal date representation to the legacy Double type. The ToOADate and FromOADate methods support backward compatibility during migration from previous versions of Visual Basic.

Visual Basic also provides a set of shared methods that provides some common dates. The concept of shared methods is described in more detail in the next chapter, which covers object syntax, but, in short, shared methods are available even when you don’t create an instance of a class. For the DateTime structure, the Now() method returns a Date value with the local date and time. This method has not been changed from Visual Basic 6.0, but the Today() and UtcNow() methods have also been added. These methods can be used to initialize a Date object with the current local date, or the date and time based on Universal Coordinated Time (also known as Greenwich Mean Time), respectively. You can use these shared methods to initialize your classes, as shown in the following code sample:

  Dim dteNow as Date = Now() Dim dteToday as Date = Today() Dim dteGMT as DateTime = DateTime.UtcNow() 




Professional VB 2005 with. NET 3. 0
Professional VB 2005 with .NET 3.0 (Programmer to Programmer)
ISBN: 0470124709
EAN: 2147483647
Year: 2004
Pages: 267

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net