In This Chapter
• Understanding Reference Types
• Differences Between Value Types and Reference Types
• Converting Between Value Types and Reference Types
• Working with .NET Fundamental Types
• Iterations, Loops, and Conditional Code Blocks
Every programming task manipulates data. Data can be of different kinds; you will often work with strings, dates and time, numbers, files, and custom data. Each of them is represented by a data type. The .NET Framework 4.5 (but also the Windows Runtime for Windows 8) provides tons of built-in data types and enables developers to easily create their own custom data types. In this chapter you learn how the .NET Framework handles data types and how you can work with value types and reference types. When you gain knowledge of data types, you can learn how to use them with special Visual Basic language constraints such as loops, iterations, and special statements. This is a fundamental chapter, so you should pay particular attention to the concepts that you need to understand before we discuss the object-oriented programming with Visual Basic 2012.
The .NET Framework provides a special way for manipulating data types, which is named Common Type System. The Common Type System is important, so you need to know something about it before reading discussions on data types. In its name, the word Common has two particular meanings. First, the Common Type System provides a unified model for exposing data types so that all the .NET languages, such as Visual Basic, Visual C#, and Visual F#, can consume the same data types. For example, a 32-bit integer is represented by the System.Int32
data type, and all the .NET languages can invoke the System.Int32
object for declaring integers because this type is provided by the .NET Framework and is language-independent. Second, each data type is an object that inherits from the System.Object
class as we discuss next.
In the .NET development you might hear that everything is an object. This is because all the types in the .NET Framework, including built-in and custom types, inherit from the System.Object
class. Inheritance is a concept that is part of the object-oriented programming topic and that is discussed later in the book. We can define it as a way for reusing and extending data types so developers can create their hierarchy of types. System.Object
provides the primary infrastructure all .NET types must have. The .NET Framework ships with thousands of built-in data types that all derive from System.Object
. But why is this class so important in the Common Type System? The answer is simple: the Common Type System ensures that all .NET types inherit from System.Object
; particularly both value types and reference types inherit from System.Object
. At this point an overview of value types and reference types is required before delving into both categories.
Value types are those data types that store their data directly. Examples of value types are integers (System.Int32
), Boolean (System.Boolean
), and bytes (System.Byte
). Value types are stored in a memory area called Stack. They are represented by (and defined via) structures that are enclosed in Structure..End Structure
code blocks. The following is an example of a value type containing a value:
Dim anInteger As System.Int32 = 5
Reference types are instead data types that, as their name implies, are just a reference to the actual data. In other words, reference types store the address of their data in the Stack whereas the actual data is stored in the Managed Heap. Reference types are represented by classes. The following is an example of reference type:
Class Person
Property FirstName As String
Property LastName As String
End Class
Don’t Be Afraid of Memory Locations
This paragraph is just an overview of value types and reference types. If you never heard about the Stack and the Managed Heap, don’t worry. Details will be provided later in this chapter, when discussing value and reference types.
Reference types inherit directly from System.Object
or from other classes that derive from Object
. This is because System.Object
is a reference type. So the question that you will probably ask now is “If both value types and reference types have to inherit from System.Object
, how can value types inherit from System.Object
if it is a reference type?” The answer is that in the case of value types an intermediate type named System.ValueType
inherits from System.Object
and ensures that all deriving objects are treated as value types. This is possible because the Common Language Runtime can distinguish how types are defined and consequently can distinguish between value types and reference types.
Naming System.Object
Object is also a reserved word of the Visual Basic programming language and is the representation of System.Object
. Because of this, we refer indistinctly to Object
as System.Object
.
At this point, it is necessary to provide an overview of both System.Object
and System.ValueType
. Instead of showing a diagram of inheritance, it is a good idea to offer a Visual Studio–oriented view so you can better understand what happens within the development environment. This can be accomplished via the Object Browser tool window introduced in Chapter 2, “Getting Started with the Visual Studio 2012 IDE.” You can browse for both classes by just writing their names in the search box. Figure 4.1 shows how System.Object
is defined, which members it exposes, and a full description.
Several things are worth mentioning. First, consider the class description. As you can see, System.Object
is defined as the root of the type hierarchy, and all other classes within the .NET Framework derive from System.Object
. This means that custom classes automatically inherit from Object
. This class provides the infrastructure for all derived classes and exposes some methods. Because System.Object
is important and because you will often invoke methods inherited from Object
, it’s convenient to get a simple reference for each method. Table 4.1 describes methods exposed by System.Object
.
Methods listed in Table 4.1 are covered several times during the rest of the book, so don’t be afraid if something is not clear at the moment. If you now refer to Figure 4.1, you can see how the Object
class has no base types. This is because, as we said before, Object
is the root in the type hierarchy. Considering this last sentence, if you instead try to expand the Derived types node, you see a list of hundreds of .NET Framework built-in classes that derive from Object
. One of these classes is System.ValueType
. Figure 4.2 shows how this class is represented in the Object Browser.
You should focus on the description first. As you can see, System.ValueType
is the base class for all value types. It is declared as MustInherit
(a clause discussed in detail in Chapter 12, “Inheritance”) that means it must necessarily be inherited and that it can’t work as a standalone object, providing the base infrastructure for derived value types. If you expand the Base types node, you see that ValueType
is a derived class from Object
. If you then try to expand the Derived types node, you get a large list of types that inherit from ValueType
, such as Boolean
, Byte
, and other primitive data types. As mentioned before, the CLR can determine that a type deriving from System.ValueType
must be treated as a value type and not as a reference type.
Value types are data types that directly store data that they define. For example, a System.Int32
object represents a value type that can store an integer number as in the following line of code:
Dim anInteger As System.Int32 = 5
Among value types, the most common are numeric types that enable, for example, performing math operations or implementing counters or just storing a numeric value. Value types enable developers to choose the best data type according to the particular scenario. As we mentioned at the beginning of this chapter, value types are Structure objects. The .NET Framework provides several built-in value types that cover most needs in your development process, although you can create custom data types. In this section you learn about most common built-in value types and about building your own structures, including how value types are declared and used and how they are stored in memory.
The .NET Framework Base Class Library provides lots of built-in value types that you can use according to your needs. Each value type is a structure exposed by the Base Class Library.
Visual Basic 2012 provides reserved words that are counterparts of the most common value type names. For example, the System.Int32
value type has an alias in the Integer
reserved word. The following two lines of code are perfectly equivalent:
Dim anInteger As System.Int32 = 0
Dim anInteger As Integer = 0
You might use indistinctly both .NET names and the Visual Basic reserved words when referring to built-in value types.
Although you are allowed to invoke value types with both .NET names and the Visual Basic counterparts’ keywords, it’s a best practice to choose the .NET names when developing reusable class libraries according to the Microsoft Common Language Specification. We discuss this topic later in this chapter. In such a scenario it is a good practice because you should avoid language-dependent features and practices when developing assemblies bound to also work with other .NET languages; this will not change the results, but your code will be cleaner, and you will use a .NET-oriented approach instead of a language-oriented one.
Table 4.2 lists the most common value types in the .NET Framework, showing a description and the Visual Basic-related keywords.
If you are upgrading from Visual Basic 6, you have to keep in mind that VB 6’s Long
is an Integer
in .NET and that VB 6’s Integer
is Short
in .NET.
As you might notice in Table 4.2, most built-in value types are exposed by the System
namespace except for the BigInteger
type that is instead exposed by the System.Numerics
namespace.
Memory Requirements
You might wonder what should influence your choice when working with value types. The answer is that it depends. Of course, you should take care of memory allocation. If you know that you need to work with a small number, you will probably do best if choosing a Byte
instead of a Short
. Regarding this, consider that Byte
requires 8 bits, Short
requires 16 bits, Integer
and Single
require 32 bits, Long
and Double
require 64 bits, and Decimal
requires 128 bits. The Visual Basic compiler is optimized for 32-bit integers, so choosing Integer
is of course a better choice than Short,
but, in the case of very small numbers, choose Byte
(1 byte) instead of Integer
(4 bytes).
In this paragraph you learn to use value types. The following demonstration assumes you have created a new Visual Basic project for the Console (see Chapter 2 for details). Listing 4.1 shows how you can declare variables storing value types. You can write the code inside the Main
method for learning purposes.
Sub Main()
'Declares an Integer
Dim anInteger As Integer = 2
'Declares a double and stores the result of a calculation
Dim calculation As Double = 74.6 * 834.1
'Declares one byte storing a hexadecimal value
Dim oneByte As Byte = &H0
'Declares a single character
Dim oneCharacter As Char = "a"c
'Declares a decimal number
Dim sampleDecimal As Decimal = 8743341.353531135D
'Declares a Boolean variable
Dim isTrueOrFalse As Boolean = True
'Declares a BigInteger
Dim arbitraryInteger As New System.Numerics.BigInteger(800000)
Console.WriteLine(anInteger)
Console.WriteLine(calculation)
Console.WriteLine(oneByte)
Console.WriteLine(oneCharacter)
Console.WriteLine(isTrueOrFalse)
Console.WriteLine(arbitraryInteger)
Console.ReadLine()
End Sub
You can declare variables of the desired value types using the Dim
keyword followed by the identifier of the variable and by the As
clause that then requires the type specification.
Notes About Dim and As
Dim
is the most important keyword for declaring variables and is commonly used in local code blocks. It is also worth mentioning that in .NET Framework you can declare different kinds of objects both with Dim
and with other keywords according to the scope of the objects (such as fields, properties, and classes). This is discussed in Chapter 7, “Class Fundamentals.” Then in Chapter 20, “Advanced Language Features,” you learn about another important feature in .NET Framework known as the Local Type Inference that avoids the need to add the As
clause in particular scenarios such as data access with LINQ.
You can also declare more than one variable within the same Dim
statement. You can write something like this:
Dim anInteger As Integer = 2, calculation As Double = 3.14,
TrueOrFalse As Boolean = True
You can also declare more than one variable of the same type just by specifying such a type once, as in the following line of code:
'Three integers
Dim anInteger, secondInteger, thirdInteger As Integer
If you upgrade from Visual Basic 6, this is a great change because in a declaration like the preceding one VB 6 automatically assigns Variant
instead of the appropriate data type. Generally you do not need to specify the constructor (the New
keyword) when declaring value types. This is because in such situations the constructor addition is implicit and provided by the compiler behind the scenes. An exception to this general rule is the BigInteger
type that instead allows the constructor to be explicit, but it also allows inline initialization. Listing 4.1 also shows how you can get the value stored in value types. In our example values are written to the Console window, but you can use values the most appropriate way for you. Figure 4.3 shows the result of the code in Listing 4.1.
Pay attention when using Char
and String
data types. Because both types require their content to be enclosed within quotes, the value of a Char
must be followed by the C
letter that tells the compiler to treat that value as a single character (if you are using Option Strict On
). The Decimal
data type also has a similar behavior. When you declare a decimal value (see Listing 4.1), you must ensure that the value is followed by the upper D
character; otherwise, the compiler treats the number as a Double
and raises an error. Identifiers like C
and D
are also known as literal type characters and are available for a number of primitive types, as summarized in Table 4.3.
Literal type characters are not available for the following types:
• Boolean
• Byte
• Date
• Object
• SByte
• String
At the beginning of the section, we mentioned that value types directly store the data to which they refer. This can be easily verified with assignments. Consider the following code:
Sub DoAssignments()
Dim anInteger As Integer = 10
Dim anotherInteger As Integer = anInteger
Console.WriteLine(anInteger)
Console.WriteLine(anotherInteger)
Console.ReadLine()
End Sub
This code produces the following output:
10
10
This is because the value of anInteger
has been assigned to another variable of type Integer
, named anotherInteger
. anotherInteger
is a copy of the first variable and lives its own life, independent from anInteger
. If you now write the following line of code after anotherInteger
assignment
anotherInteger = 5
the code produces the following output:
10
5
So you have changed the value of anotherInteger
while you left unchanged the value of anInteger
because they are two different objects with separate lives. Although this can appear obvious, it is important because it is the base for later understanding the different behavior in reference types. You might also use assignments in situations in which you need to get a result without knowing values that produce the result itself, such as in calculations that require an input from the user. With regard to this, consider the following code:
Dim firstNumber As Double = 567.43
Dim secondNumber As Double = 321.52
Dim result As Double = firstNumber * secondNumber
Console.WriteLine(result)
Console.ReadLine()
In the preceding code, you get the result of a multiplication given two numbers. In real scenarios, the two numbers would be provided by the user and the result variable would store the result of the calculation. Such calculations are performed not on numbers but on the value of variables that store numbers. This means that you do not need to know in advance the numbers; you just work on variables and assignments.
Note
You could probably expect a code example in which the input should be provided by the user. Reference types, conversion operators, and parsing methods have not been discussed yet—those will be discussed later. So here the goal was to avoid confusion because user input is provided as String
objects. There will be appropriate code examples when needed, as in the next paragraph.
In most cases you will require users to enter an input that you will then need to elaborate, or you could simply read the content of a text file and then convert such content into the appropriate .NET value type. Typically, user input is provided as strings; you will also get strings when reading text files. In business applications you need to implement validation rules on the user input, but it is worth mentioning that value types offer common methods for analyzing the contents of a string and check whether such content matches a particular value type. Even if we have not discussed the String
object (which is a reference type), yet the following code samples are easy to understand. For example, consider the following code that declares some strings:
Dim firstValue As String = "1000"
Dim secondValue As String = "True"
Dim thirdValue As String = "123.456"
The content of each string is a representation of a particular value type: firstValue
content represents an integer, secondValue
represents a Boolean, and thirdValue
represents a Double
. We could parse the content of each string and transform it into the appropriate value type as follows:
Dim anInteger As Integer = Integer.Parse(firstValue)
Dim aBoolean As Boolean = Boolean.Parse(secondValue)
Dim aDouble As Double = Double.Parse(thirdValue)
As you can see, value types expose a method called Parse
that converts the string representation of a numeric or logical value into the correspondent value type. Integer.Parse
converts the “1000” string into a 1000 integer and so on. If the compiler cannot perform the conversion, a FormatException
error will be thrown and the application execution will be broken. To avoid possible errors, you could use another method called TryParse
that returns True
if the conversion succeeds or False
if it fails. For example, consider the following code:
Dim testInteger As Integer
Dim result = Integer.TryParse(secondValue, testInteger)
The code attempts to convert a string that contains the representation of a Boolean value into an Integer
. Because this is not possible, TryParse
returns False
. Notice that in this particular case you don’t perform an assignment to another variable (such as in the case of Parse
) because TryParse
requires the variable that will store the value as the second argument, passed by reference (a more detailed explanation on passing arguments by reference is provided in Chapter 7).
Value Types Methods
Because the purpose of this book is to examine the Visual Basic 2012 language features—and examining all the .NET Framework available types and members isn’t possible—only methods common to all value types are described. Methods and members specific to some value types are left to you for future studies. Always remember that when you do not know an object member, IntelliSense and the Object Browser together with the MSDN documentation can be your best friends, providing useful information.
Value types (including System.Char
and System.DateTime
) also expose two properties named MinValue
and MaxValue
that contain the minimum accepted value and the maximum accepted value for each type, respectively. For example, the following line of code
Console.WriteLine(Integer.MinValue)
produces -2147483648 as the result. The following line of code
Console.WriteLine(Char.MaxValue)
produces the ? character as the result. Finally, the following line of code
Console.WriteLine(Date.MaxValue)
produces 12/31/9999 11:59:59PM as the result.
MinValue
and MaxValue
can be useful for two purposes: The first purpose is for times when you don’t remember the minimum and maximum values accepted by a particular value type. The second purpose deals with comparisons—you might need to check whether a value or a number is in the range of accepted values by a particular data type. Now we have completed a general overview of value types. Next, we focus on optimizations and on using special value types such as System.DateTime.
When working with value types, you should always choose the best type for your needs. For example, if you need to work with a number composed in the Integer
range, you should not use a Long
type. Moreover, the Visual Basic compiler (and, behind the scenes, the CLR) provides optimizations for the System.Int32
and System.Double
types, so you should always use these types when possible. For example, use an Int32
instead of an Int16,
although the number you work with is composed in the range of the Int16
. Other considerations about unsigned value types are related to compliance with Microsoft Common Language Specification, which is a topic discussed later in this chapter.
Nullable Types
Sometimes you need to assign null values to value types—for example, when mapping SQL Server data types to .NET data types for fetching data. To accomplish this, the .NET Framework provides support for the nullable types. Because nullable types have the syntax of Generics objects, and because you first need to know how Generics work, they are discussed in Chapter 14, “Generics and Nullable Types.”
The System.Numerics.BigInteger
is a value type exposed by the System.Numerics
namespace and requires a reference to the System.Numerics.dll assembly. It represents a signed, arbitrarily large integer number. This means that it doesn’t have minimum and maximum values, opposite of other value types such as Integer
and Long
. Instantiating a BigInteger
is easy, as you can see from the following line of code:
Dim sampleBigInteger As New System.Numerics.BigInteger
You can assign any signed number to a BigInteger
because it has no minimum and maximum values, as demonstrated by the following code snippet:
'Neither minimum nor maximum values
sampleBigInteger = Byte.MinValue
sampleBigInteger = Long.MaxValue
Byte
and Long
are the smallest and the biggest acceptable signed integers, respectively. BigInteger
directly supports integer types such as SByte
, Byte
, UInteger
, Integer
, UShort
, Short
, ULong
, and Long
. You can also assign to a BigInteger
the values of type Double
, Single
, and Decimal
, but you do need to accomplish this passing the value as an argument to the constructor or performing an explicit conversion using CType
(assuming that Option Strict
is On
). The following code demonstrates both situations:
'The constructor can receive arguments, Double is accepted
Dim sampleBigInteger2 As New _
System.Numerics.BigInteger(123456.789)
'Single is accepted but with explicit conversion
Dim singleValue As Single = CSng(1234.56)
Dim sampleBigInteger3 As New System.Numerics.BigInteger
sampleBigInteger3 = CType(singleValue,
Numerics.BigInteger)
Notice that rounding will occur when converting floating types to BigInteger
. Such structure also offers shared methods for performing arithmetic operations. You can add, subtract, divide, and multiply BigIntegers
as in the following code:
'Assumes an Imports System.Numerics directive
'Sum
Dim sum As BigInteger =
BigInteger.Add(sampleBigInteger, sampleBigInteger2)
'Subtract
Dim subtraction As BigInteger =
BigInteger.Subtract(sampleBigInteger, sampleBigInteger2)
'Division
Dim division As BigInteger =
BigInteger.Divide(sampleBigInteger, sampleBigInteger3)
'Multiplication
Dim multiplication As BigInteger =
BigInteger.Multiply(sampleBigInteger2, sampleBigInteger3)
You can also perform complex operations, such as exponentiation and logarithm calculations as demonstrated here:
'Power
Dim powerBI As BigInteger = BigInteger.Pow(sampleBigInteger2, 2)
'10 base logarithm
Dim log10 As Double = BigInteger.Log10(sampleBigInteger3)
'natural base logarithm
Dim natLog As Double = BigInteger.Log(sampleBigInteger, 2)
As usual, IntelliSense can be your best friend when exploring methods from BigInteger
and can help you understand what other math calculations you can perform.
Building custom value types is accomplished by creating structures. Because creating structures can also be a complex task and is part of the object-oriented programming topic, it is thoroughly discussed in Chapter 11, “Structures and Enumerations.”
Reference types are represented by classes. Classes are probably the most important items in modern programming languages and are the basis of object-oriented programming. Reference types have one big difference versus value types. Variables that declare a reference type do not store the data of the type itself; they just store an address to the data. In other words, they are just pointers to the data. To better explain (and understand) this fundamental concept, let’s look at an example. Consider the following class Person
, which exposes two simple properties:
Class Person
Property FirstName As String
Property LastName As String
End Class
You need to create an instance of such a class so that you can store data (in this case, setting properties) and then manipulate the same data. This can be accomplished by the following lines of code:
Dim onePerson As New Person
onePerson.FirstName = "Alessandro"
onePerson.LastName = "Del Sole"
Strongly Typed Objects
In .NET development, you often encounter the words strongly typed. This definition can be explained with an example. The onePerson
object in the preceding code is strongly typed because it is of a certain type, Person
. This means that onePerson
can accept an assignment only from compliant objects, such as other Person
objects. Such restriction is important because it avoids errors and problems. Moreover, the compiler knows how to treat such a specialized object. A variable of type Object
is instead not strongly typed because it is just of the root type and is not specialized. Object
can accept anything, but without restrictions the usage of non-strongly typed objects could lead to significant problems. Chapter 14 discusses Generics, so you’ll get a more thorough understanding of strongly typed objects.
Now you have an instance of the Person
class, named onePerson
. Consider the following line of code:
Dim secondPerson As Person = onePerson
A new object of type Person
(secondPerson
) is declared and is assigned with the onePerson
object. Because of the equality operator, you would probably expect secondPerson
to be an exact copy of onePerson
. We could consider at this point some edits to the secondPerson
object; for example, we could modify the first name:
secondPerson.FirstName = "Alex"
We can try to check the result of the previous operations by simply writing the output to the Console window. Let’s begin by writing the result of secondPerson
:
Console.WriteLine(secondPerson.FirstName)
Console.WriteLine(secondPerson.LastName)
Console.ReadLine()
As you might correctly expect, the preceding code produces the following result:
Alex
Del Sole
Now let’s simply write the result for onePerson
to the Console window:
Console.WriteLine(onePerson.FirstName)
Console.WriteLine(onePerson.LastName)
Console.ReadLine()
This code produces the following result:
Alex
Del Sole
As you can see, editing the first name in secondPerson
also affected onePerson
. This means that secondPerson
is not a copy of onePerson
. It is instead a copy of the reference to the actual data. Now you should have a better understanding of reference types. We can say that, as their name implies, reference types have an address in memory where data is stored and variables declaring and instantiating reference types just hold a reference to that data. To get a real copy of data, you should write something like this:
Dim secondPerson As New Person
secondPerson.FirstName = onePerson.FirstName
secondPerson.LastName = onePerson.LastName
Then you could edit secondPerson
’s properties, ensuring that this will not affect the onePerson
object. This kind of technique for creating a clone for a reference type is good with objects exposing only a few properties, but fortunately there are more interesting techniques to clone more complex objects. These are discussed in the “Deep Copy and Shallow Copy” section. As a clarification, notice that, in the .NET Framework, String is a reference type but it’s actually treated as a value type, as I will explain in a few paragraphs.
The .NET Framework 4.5 ships with tons of reference types exposed by the Base Class Library (BCL) and that cover most needs. However, you will often use several reference types in the development process that many other reference types derive from. Table 4.4 lists the most common reference types.
You can use a number of reference types when developing real-life applications, and most of them are discussed in subsequent chapters; however, the ones listed in Table 4.4 provide the basis for working with reference types. Most of them are the base infrastructure for other important derived classes. We previously discussed System.Object
, so we will not do it again. It is instead worth mentioning how System.String
is a reference type, although it seems natural to think about it as a value type. System.String
, or simply String
, is used as a value type, so it will not be difficult to build strings. By the way, strings are immutable (that means “read-only”), so each time you edit a string, the runtime creates a new instance of the String class and passes in the edited string. Because of this, editing strings using System.String
can cause unnecessary usage of memory. To solve this problem, the .NET Framework provides more efficient ways, as you will see in the sections “Working with Strings,” “Working with Dates,” and “Working with Arrays.”
Value types and reference types differ in several ways. In the previous sections we saw how they differ in assignment and how a value type can directly store data, whereas a reference type stores only the address to the actual data. We now explain such implementation and provide information on the other differences between value and reference types.
Value types and reference types are differently allocated in memory. Value types are allocated in the Stack. The Stack is a memory area where methods are executed according to the last-in, first-out manner. The first method pushed to the Stack is the application entry point; that is, the Main
method. When Main
invokes other methods, the CLR creates a sort of restore point and pushes those methods to the Stack. When the method needs to be executed, data required by that method is also pushed to the Stack. When a method completes, the CLR removes (popping) it from the Stack together with its data, restoring the previous state. Because of this ordered behavior, the Stack is efficient, and the Common Language Runtime can easily handle it. Consider the following line of code, declaring a variable of type Integer
, therefore a value type:
Dim anInteger As Integer = 5
Figure 4.4 shows how the anInteger
variable is allocated in the Stack.
On the contrary, reference types are allocated in a memory area named Managed Heap. Different from how objects are treated in the Stack, objects in the Managed Heap are allocated and deallocated randomly. This provides fast allocation but requires more work for the CLR. To keep things ordered, the CLR needs two instruments: the Garbage Collector and Memory Manager. We provide details about this architecture in Chapter 8, “Managing an Object’s Lifetime.” At the moment you need to understand how reference types and their data are allocated. Consider the following lines of code, declaring a new version of the Person
class and an instance of this class:
Class Person
Property Name As String
Property Age As Integer
End Class
Dim onePerson As New Person
onePerson.Name = "Alessandro Del Sole"
onePerson.Age = 35
As you can see, there is now a property in this class (Age
) that is a value type. The instance of the class will be allocated in the Heap, and its reference (onePerson
) will be allocated in the Stack. Figure 4.5 provides a visual representation of this scenario.
Because the Person
class handles a value type in one of its properties, such value types stay in the Stack. Figure 4.6 completes this overview. In the second part of this book, you will have different opportunities to explore reference types and memory management when discussing the object’s lifetime.
There are a couple of differences with regard to principles related to object-oriented programming. Although these principles are discussed in detail in Part II, “Object-Oriented Programming with Visual Basic 2012,” it’s convenient to mention them here.
The first principle deals with inheritance, which is covered in Chapter 12. Classes (that is, reference types) support inheritance, whereas structures (value types) do not. Consider the following code:
Class Person
Property FirstName As String
Property LastName As String
End Class
Class Developer
Inherits Person
Property UsedProgrammingLanguage As String
Public Overrides Function ToString() As String
Return Me.LastName
End Function
End Class
In this example, the Person
class is the base class and provides the basic properties for representing a hypothetical person. The Developer
class inherits from Person (see the Inherits
keyword), and this means that the Developer
class will expose both the FirstName
and LastName
properties plus the new one named UsedProgrammingLanguage
. It also redefines the behavior of the default ToString
method so that it can return a more significant name for the object. In Visual Basic 2012, you can inherit from only one object at a time. Thus, Developer can inherit only from Person
and not from any other object. If you need multiple-level inheritance, you should architect your object’s framework so that a second class can inherit from the first one, the third one from the second one, and so on. Structures do not support inheritance at all, except for the fact that they inherit by nature from System.ValueType
.
Both classes and structures provide support for interfaces implementation. For example, you could implement the IComparable
interface in both cases:
Class Person
Implements IComparable
Property FirstName As String
Property LastName As String
Public Function CompareTo(ByVal obj As Object) As Integer Implements
System.IComparable.CompareTo
'Write your code here
End Function
End Class
Structure Dimension
Implements IComparable
Public Function CompareTo(ByVal obj As Object) As Integer Implements
System.IComparable.CompareTo
'Write your code here
End Function
Property X As Integer
Property Y As Integer
Property Z As Integer
End Structure
Inheritance and interfaces are discussed in Part II, so don’t worry if something is not clear.
When you declare a reference type, you need an instance before you can use it (with the exception of shared classes, which are discussed in Chapter 7). You create an instance by invoking the constructor via the New
keyword. When you declare a value type, the new variable is automatically initialized to a default value; this is usually zero for numbers and False
for Boolean
. Because of this, value types do not need to have a default constructor invoked. The Visual Basic compiler still accepts declaring a value type and invoking the constructor, which also initializes the type with the default value. However, in this case you cannot initialize the value. The following code snippet demonstrates this:
'Declares an Integer and sets the value to zero
Dim anInt As New Integer
'Initialization not allowed with New
Dim anotherInt As New Integer = 1
'Allowed
Dim aThirdInt As Integer = 1
Finalizers are related to the object’s lifetime, as discussed in Chapter 8. As for constructors, it’s convenient having a small reference. We previously said that when methods using value types complete their execution, they are automatically removed from the Stack together with the data. This is managed by the CLR, and because of this ordered behavior, value types do not need to be finalized. In contrast, reference types are allocated on the Heap and have a different behavior. Deallocation from memory is handled by the Garbage Collector that needs finalizers on the reference type’s side to complete its work.
A lot of times value types store data directly, whereas reference types store only the address of the data. Although you can create and consume types according to your needs, there are some concerns with performance, particularly regarding methods. Methods can accept parameters, also known as arguments. Arguments can be value types or reference types. If you pass a value type to a method, you pass to that method all the data contained in the value type, which could be time-consuming and cause performance overhead. Passing a reference type passes only the address to the data, so it could be faster and more efficient. There could be situations in which you need to pass one or more value types to methods. This depends on your needs. Generally, the performance difference in such a scenario is not relevant, but it will depend on the size of the value type. If your method receives a large value type as an argument but is invoked only once, performance should not be affected. But if your method is invoked many times, perhaps passing a reference type would be better. Just be aware of this when implementing your methods.
Answering this question is not simple. It depends. If you need to implement a custom type that will act similarly to a value type (for example, a type that works with numbers), you should choose a Structure
. If you need to implement an object for storing a large amount of data, it could be a good idea to choose a class
. Such considerations are not mandatory, and their purpose is simply to let you think a little bit more about what you are going to implement and what your needs are.
In your developer life, you often need to convert one data type into another in different types of situations. For example, you might need to convert a value type into another one or just convert an instance of type Object
into a strongly typed object. In this section, you learn how to convert between data types and about conversion operators, beginning with basic but important concepts, such as implicit conversions, boxing, and unboxing.
Previously we discussed the System.Object
class. As you might remember, such a class is the root in the class hierarchy. That said, you can assign both reference types and value types to an Object
instance because they both inherit from System.Object
. Consider the following lines of code:
Dim anInt As Object = 10
Dim onePerson As Object = New Person
The first line assigns a value type (Integer
) to an Object
, and the second one assigns an instance of the Person
class to an Object
. Visual Basic 2012 always enables such assignments because they are always safe. What is unsafe is trying to assign an Object
to a strongly typed instance, such as assigning Object
to an instance of the Person
class. This is quite obvious because Object
can represent whatever type, and the compiler cannot be sure if that type is a Person
, which can cause errors. Consider the following line of code:
Dim onePerson As Person = New Object
The code is trying to assign an instance of the Object
class to an instance of Person
. The Visual Basic compiler enables handling such situations in two different ways, depending on how Option Strict
is set. We discussed Option Strict
in Chapter 2, and now you can see the first usage. If Option Strict
is set to On
, the preceding line of code causes an error. The Visual Basic compiler does allow an implicit conversion from Object
to a strongly typed object, throwing an error message that you can see in the code editor. Figure 4.7 shows this error message.
This is useful because it prevents type conversion errors. If you want to perform an assignment of this kind, you need to explicitly convert Object
into the appropriate data type. For example, this can be accomplished using the CType
conversion operator, as in the following line of code:
Dim onePerson As Person = CType(New Object, Person)
A conversion operator offers another advantage: It communicates whether the conversion is possible; if it’s not, you can handle the situation, particularly at runtime. The code editor also simplifies the addition of a CType
conversion by offering a convenient correction pop-up you enable by clicking the small red line that appears under the bad object and that will automatically convert into the appropriate type (see Figure 4.8 for an example). It is worth mentioning that CType
will work without throwing an exception only if the target type for the conversion is the correct type.
By the way, the Visual Basic compiler provides a way to allow implicit conversions avoiding error messages. To accomplish this, you need to set Option Strict
to Off
. You can write the following line of code (preceding all the other code and Imports directives):
Option Strict Off
You could also adjust Option Strict
settings in the Compiler tab of the My Project window, as shown in Chapter 2. Assigning an Object
to a Person
, as we did before, is perfectly legal. But please be careful: If you do not need to perform such assignments, please avoid Option Strict Off
and instead use Option Strict On
. This can ensure less runtime and compile time errors and enable you to write more efficient code.
Option Strict Off: When?
You should almost never set Option Strict
to Off
. There is only one situation in which you should set Option Strict
to Off
—late binding. This topic is discussed in Chapter 46, “Reflection.” Outside this particular scenario, never set Option Strict
to Off
so that you can be sure that you are working with strongly typed objects and that the compiler, debugger, and CLR will enable you to quickly find errors. The other reason you should not set Option Strict
to Off
is because performance will suffer. You’ll learn about this, and other reasons, in Chapter 14. I suggest you set Option Strict
to On
as a default in the Visual Studio 2012 options.
The Common Type System enables implicit conversions. It also enables conversions between reference types and value types, and vice versa, because both inherit from System.Object
. You often need to work with two techniques, boxing and unboxing, when you have methods that receive arguments of type Object
. See the tip at the end of this section for details.
Boxing occurs when converting a value type to a reference type. In other words, boxing is when you assign a value type to an Object
. The following lines of code demonstrate boxing:
Dim calculation As Double = 14.4 + 32.12
Dim result As Object = calculation
The calculation variable, which stores a value deriving from the sum of two numbers, is a Double
value type. The result variable, which is of type Object
and therefore a reference type, is allocated in the Heap and boxes the original value of calculation so that you now have two copies of the value, one in the Stack and one in the Heap. Figure 4.9 shows how boxing causes memory allocation.
Boxing requires performance overhead. This will be clearer when reading the next section.
Unboxing occurs when you convert a reference type to a value type. You perform unboxing when converting an Object
into a value type. Continuing with the previous example, the following line of code demonstrates unboxing:
Dim convertedResult As Double = CType(result, Double)
Unboxing can cause another copy of the original value (the same stored in the calculation variable) to be created and allocated in the Stack. Figure 4.10 shows a representation of what happens when unboxing a value.
Boxing and unboxing also cause performance overhead, so they should always be avoided if not truly needed. This is because value types directly store the data they refer to and therefore create three copies of the data, which can consume more resources than necessary. If the value types you box and unbox are small, performance might not be influenced (or, better, you might not see the difference). But if the value types store a large amount of data, the loss of performance could be significant.
Avoiding Boxing and Unboxing
Boxing and unboxing can be necessary if you have methods that receive arguments of type Object
. There are a couple of best practices that you can take when implementing methods, such as using Generics or implementing overloads of methods that can accept multiple strongly typed arguments. Generics and overloads are discussed later in the book.
Early Binding and Late Binding
When talking about reference types, another important topic is early binding and late binding. Although this might be the right place to discuss them, I prefer to postpone such discussion until Chapter 46. This way, you first get a complete overview of reference types and then get real examples of the late binding technique.
In the “Understanding Reference Types” section, you saw how reference type assignments differ from value type assignments and how assignments are not enough to create a copy of a reference type. We also provided one basic solution to this problem, which was to create a new instance of a specified reference type and then assign each property of the target instance with values coming from the original one. But this is not enough, both because it is not complete and because it can be good only with small classes. To create a complete clone of a reference type, in the .NET development we can take advantage of two techniques: deep copy and shallow copy. Both techniques require the implementation of the ICloneable
interface. Although we discuss interfaces later, the concepts presented here are easy to understand. The ICloneable
interface provides a unique method named Clone
that enables developers to know that classes exposing such a method can easily be cloned. For example, consider the following implementation of the Person
class that also implements the ICloneable
interface:
Class Person
Implements ICloneable
Property FirstName As String
Property LastName As String
Property Work As Job
Public Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone
End Function
End Class
The most interesting thing in the code is the Clone
method required by the ICloneable
interface. In the method body, you should write code that performs the real copy of the reference type. Fortunately, the Object
class provides a method named MemberwiseClone
that automatically returns a shallow copy of your reference type. The keyword Me indicates the current instance of the class. Because Clone
must work with all possible types, it returns Object
. (We see later how to convert this result.) The class exposes two String
properties, FirstName
and LastName
. You might remember that, although String
is a reference type behind the scenes, you will actually treat it as a value type. The class also exposes another property named Work
of type Job
. This is a new reference type representing a person’s occupation. Job
is implemented in the following way:
Class Job
Property CompanyName As String
Property Position As String
End Class
Given this implementation, we can simply create a shallow copy.
A shallow copy creates a new instance of the current object and copies values of members of the original to the new one but does not create copies of children (referenced) objects. Continuing the example of the preceding implementation, Clone
creates a copy of the Person
class into a new instance and copies members’ values that are value types or Strings
. Because Job
is a pure reference type, the shallow copy provided by Clone
will not also create a clone of Job
. This is the explanation about what we said before, that a shallow copy creates a copy only of the specified instance but not of children objects. We can easily verify our assertions by writing the following code:
Sub Main()
'The original person
Dim firstPerson As New Person
firstPerson.FirstName = "Alessandro"
firstPerson.LastName = "Del Sole"
'Defines a work for the above person
Dim randomJob As New Job
randomJob.CompanyName = "Del Sole Ltd."
randomJob.Position = "CEO"
'Assignment of the new job
firstPerson.Work = randomJob
'Gets a shallow copy of the firstPerson object
Dim secondPerson As Person = CType(firstPerson.Clone, Person)
'Check if they are the same instances
'returns False, 2 different instances:
Console.WriteLine(firstPerson.FirstName Is secondPerson.FirstName)
'returns True (still same instance of Job!):
Console.WriteLine(firstPerson.Work Is secondPerson.Work)
Console.ReadLine()
End Sub
The preceding code first gets a new instance of the Person
class, setting some properties such as a new instance of the Job
class, too. Notice how the result of the Clone
method, which is of type Object
, is converted into a Person
instance using CType
. At this point we can check what happened. The Is
operator enables comparing two instances of reference types and returns True
if they are related to the same instance. For the FirstName
property, the comparison returns False
because the shallow copy created a new, standalone instance of the Person
class. But if we do the same check on the Work
property, which is a child reference type of the Person
class, the comparison returns True
. This means that firstPerson.Work
refers to the same instance of the Job
class as in secondPerson.Work
. And this also means that a shallow copy did not create a new copy of the Job
class to be assigned to the secondPerson
object. This is where the deep copy comes in.
Deep copy is something complex that can create perfect copies of an entire object’s graph. If you need to perform a deep copy, you have some alternatives. The easiest (and the one we can show at this point of the book) is to perform a shallow copy of the main object and then manually copy the other properties of children reference types. The best one instead recurs to serialization, which is an advanced topic discussed in Chapter 41, “Serialization.” At the moment we can focus on editing the previous implementation of the Clone
method for performing a simple deep copy. We could also implement the ICloneable
interface in the Job
class, as follows:
Class Job
Implements ICloneable
Property CompanyName As String
Property Position As String
Public Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone
End Function
End Class
Now we can modify the Clone
implementation inside the Person
class, as follows:
Class Person
Implements ICloneable
Property FirstName As String
Property LastName As String
Property Work As Job
Public Function Clone() As Object Implements System.ICloneable.Clone
Dim tempPerson As Person = CType(Me.MemberwiseClone, Person)
tempPerson.Work = CType(Me.Work.Clone, Job)
Return tempPerson
End Function
End Class
Implementing Icloneable
Implementing ICloneable
in referenced classes is not the only way of providing deep copies. We could also generate a new instance of the Job
class and manually assign values read from the original instance. But because we are discussing ICloneable
and Clone
, the example was completed this way.
The code obtains first a shallow copy of the current instance of the Person
class and then gets a shallow copy of the child instance of the Job
class.
Pay Attention to Recursive Clone
Cloning objects with recursive calls to the Clone
method could lead to a stack overflow if the hierarchy of your objects is particularly complex. Because of this, the previous implementation goes well with small classes and small objects graphs. If this is not the case, you should prefer serialization.
If we now try to run the same check again comparing the instances of firstPerson
and secondPerson
, the output will be the following:
False
False
This is because now the instances are different. We have two completely standalone instances of the Person
class.
Each time you create an instance of a class, the .NET runtime creates an instance behind the scenes of the System.Type
class that represents your object. Because in the .NET development you have the ability to inspect instances of the System.Type
class (known as Reflection) at runtime and to get a reference to that System.Type
, the Visual Basic programming language offers the GetType
keyword that enables you to accomplish both tasks. The GetType
keyword has two different behaviors: The first one is an operator, the second one is a method. GetType
is typically used to compare two instances of an object or to access metadata of a type at runtime. To understand GetType
, here are a few examples. Consider this first code snippet:
'GetType here is related to the type
Dim testType As Type = GetType(Integer)
For Each method As System.Reflection.MethodInfo
In testType.GetMethods
Console.WriteLine(method.Name)
Next
Console.ReadLine()
In a few words, the preceding code retrieves all the information and metadata related to the System.Int32
type; then it shows a list of all the methods exposed by such type, using Reflection. (MethodInfo
is an object representing the methods’ information.) This is useful if you need to retrieve information on a particular data type. If you instead need to retrieve information about metadata of a specific instance of a data type, you can use the GetType
method, as shown in the following code:
'GetType here is related to an instance
Dim testInt As Integer = 123456
Dim testType As Type = testInt.GetType
For Each method As System.Reflection.MethodInfo
In testType.GetMethods
Console.WriteLine(method.Name)
Next
Console.ReadLine()
In this particular situation, you retrieve information of an instance of the System.Int32
type and not of the type. You can also use GetType
to compare two types for equality:
'Comparing two types
If testInt.GetType Is GetType(Integer) Then
Console.WriteLine("TestInt is of type Integer")
End If
In the previous section we discussed converting between value types and reference types. These kinds of conversions are not the only ones allowed by the .NET Framework because you often need to perform conversions between two value types or two reference types. For example, imagine that you want to represent a number as a text message. In such a case, you need to convert an Integer
into a String
; you could also have an Integer
value that must be passed to a method that instead receives an argument of type Double
. Another common scenario is when you work with user controls in the user interface. Some controls such as ListBox
and DataGridView
on the Windows side or the DataGrid
on the web side can store any kind of object. If you show a list of Person
objects inside a ListBox
, the control stores a list of Object
, so you need to perform an explicit conversion from Object
to Person
each time you need to retrieve information on a single Person
. In this section we show how conversions between types can be performed.
With the exception of boxing and unboxing, conversions are of two kinds: widening conversions and narrowing conversions. Their type depends on whether the conversion is explicit or implicit. This is explained next.
Widening conversions occur when you try to convert a type into another one that can include all values of the original type. A typical example of a widening conversion is converting from an Integer
into a Double
, as in the following lines of code:
'Widening conversion
Dim i As Integer = 1
Dim d As Double = i
As you might remember from Table 4.2, Integer
represents a numeric value whereas Double
represents a large floating number. Therefore, Double
is greater than Integer
and can accept conversions from Integer
without loss of precision. Widening conversions do not need an explicit conversion operator, which instead happens for narrowing conversions.
The opposite of widening conversions, narrowing conversions occur when you attempt to convert a type into another that is smaller or with a loss of precision. For example, converting a Double
into an Integer
can cause a loss of precision because Double
is a large floating number and Integer
is just a numeric value, which is also smaller than Double
. There are several ways to perform narrowing conversions, depending on how the types you need to convert are implemented. Visual Basic 2012, continuing what was already available in previous versions of the language, provides an easy way to perform conversions between base types wrapped by Visual Basic keywords. For example, if we need to convert a Double
into an Integer,
we could write the following lines of code:
Dim d As Double = 12345.678
Dim i As Integer = CInt(d)
The CInt
function converts the specified Object
into an Integer
. In this particular case the value of i
becomes 12346 because the conversion caused a loss of precision and the Visual Basic compiler produced an integer number that is the closest to the original value. Another example is when you need to convert a number into a string representation of the number itself. This can be accomplished by the following line of code:
Dim s As String = CStr(i)
The CStr
function converts the specified Object
into a string. With particular regard to string conversions, all .NET objects expose a method named ToString
that performs a conversion of the original data into a new String
. The last line of code could be rewritten as follows:
Dim s As String = i.ToString()
ToString
is also useful because you can format the output. For example, consider the following line of code:
Dim i As Integer = 123456
Dim s As String = i.ToString("##,##.00")
The ToString
method enables specifying how the string should be formatted. On my machine the code produces the following output:
123,456.00
The output depends on regional settings for your system, with particular regard to separators.
Use ToString Instead of CStr
Because it enables you to format the result, ToString
should be preferred to CStr
. You can override the standard implementation of ToString
(provided by the Object
class) so that you can provide your own conversion logic. This is discussed later, with regard to inheritance.
Table 4.5 shows the complete list of conversion functions provided by the Visual Basic grammar.
Remember the Loss of Precision
Always take care when performing narrowing conversions because of the loss of precision, particularly with numeric values. Another particular case is when converting from String
to Char
. Such conversions retrieve only the first character of the specified string.
When narrowing conversions fail, an InvalidCastException
is thrown by the compiler. An example of this situation occurs when you attempt to convert a String
into an Integer
. Because String
is a valid object expression, you do not get an error at compile time, but the conversion would fail at runtime. Because of this, you should always enclose conversions within error handling code blocks (see Chapter 6, “Handling Errors and Exceptions,” for details). There are also alternatives that are independent from the Visual Basic language and that are provided by the .NET Framework. The first way is using the System.Convert
class that is available on objects that implement the IConvertible
interface. Convert
exposes a lot of methods, each for converting into a particular data type. For example, the following line of code converts a string representation of a number into an integer:
Dim c As Integer = System.Convert.ToInt32("1234")
If the string contains an invalid number, a FormatException
is thrown. Methods exposed by the Convert
class are well implemented because they do not only accept Object
expressions, but also specific types, such as Integer
, Boolean
, String
, and so on. IntelliSense can help you understand which types are supported. Table 4.6 provides an overview of the most common conversion methods exposed by System.Convert
.
Notice that System.Convert
also provides other methods that are not discussed here because they are related to particular situations that assume you have a deep knowledge of specific .NET topics and are out of the scope in a general discussion such as this one.
The Visual Basic programming language offers some other conversion operators that are probably the most commonly used because of their flexibility. The first one is CType
. It converts from one type to another and, if the conversion fails, the Visual Basic compiler throws an exception. The good news is that the conversion is also performed by the background compiler, so if the target type’s range exceeds the source one, you are immediately notified of the problem. For example, the compiler knows that converting a Date
into an Integer
is not possible, so in such a scenario you are immediately notified. If the conversion is legal (and therefore can compile) but types are populated at runtime with data that cannot be converted, an InvalidCastException
is thrown. For example, the following code converts an Integer
into a Short
:
Dim i As Integer = 123456
Dim s As Short = CType(i, Short)
CType
is also useful in unboxing. The following code converts from an Object
that contains an Integer
into a pure Integer
:
Dim p As Object = 1
Dim result As Integer = CType(p, Integer)
Of course, it can also be used for converting between reference types:
Dim p As Object = New Person
Dim result As Person = CType(p, Person)
CType
is specific to the Visual Basic runtime and enables widening and narrowing conversions from one type into another type that accepts the first one. For example, a Double
can be converted to an Integer
, although with loss of precision. Another couple of important operators, DirectCast
and TryCast
, can be used the same way, but they have different behavior. Both operators enable conversions when there is an inheritance or implementation relationship between the two types. For example, consider the following lines of code:
Dim d As Double = 123.456
Dim s As Short = CType(d, Short)
Converting from Double
to Short
using CType
will succeed. Now consider the usage of DirectCast
in the same scenario:
Dim d As Double = 123.456
Dim s As Short = DirectCast(d, Short)
This conversion will fail because Short
does not inherit from Double
and no implementation relationship exists between the two types. A conversion failure is notified via an InvalidCastException
. This means you should use DirectCast
only when you are sure that the inheritance or implementation conditions between types are satisfied. However, using DirectCast
has some advantages in terms of performances because it relies directly on the .NET runtime. DirectCast
conversions are also checked by the background compiler, so you are immediately notified if conversions fail via the Error List window. DirectCast
works with both value and reference types. If you work with reference types, it’s a best practice to check whether the two types are compliant so you can reduce the risks of errors:
'In this example P is of type Object but stores a Person
If TypeOf (p) Is Person Then
Dim result As Person = DirectCast(p, Person)
End If
The TypeOf
operator compares an object reference variable to a data type and returns True
if the object variable is compatible with the given type. As you might imagine, such checks can require performance overhead. Another operator that works only with reference types is known as TryCast
. This one works exactly like DirectCast,
but instead of throwing an InvalidCastException
in the case of conversion failure, it returns a null object (Nothing
). This can be useful because you can avoid implementing an exceptions check, simplifying your code (that will only need to check for a null value) and reducing performance overhead. The last code snippet could be rewritten as follows:
'In this example P is of type Object but stores a Person
Dim result As Person = TryCast(p, Person)
If the conversion fails, TryCast
returns Nothing
, so you will just need to check such a result.
There are special types that you will often work with, such as strings, date and time, and arrays. Although you will often work with collections, too (see Chapter 16, “Working with Collections and Iterators”), understanding how such objects work is an important objective. The .NET Framework 4.5 simplifies your developer life because objects provide methods to perform the most common operations on the data.
Note on Extension Methods
This section describes built-in methods from value and reference types. Because of the .NET infrastructure, all types provide the ability of invoking extension methods that could be potentially used for accomplishing some of the tasks proposed in this section. They will not be described because the scope of this chapter is to describe built-in members; you will need to understand extension methods, which are covered in Chapter 20.
Working with strings is one of the most common developer activities. In the .NET Common Type System, System.String
is a reference type. This might be surprising because strings actually behave like value types. The String
class cannot be inherited, so you can’t create a custom class derived from it. In addition, String
objects are immutable, like value types. What does this mean? It means that when you create a new String,
you cannot change it. Although you are allowed to edit a string’s content, behind the scenes the CLR does not edit the existing string; it instead creates a new instance of the String
object containing your edits. The CLR then stores such String
objects in the Heap and returns a reference to them. We discuss how to approach strings in a more efficient way later; at the moment you need to understand how to work with them. The System.String
classv provides lots of methods for working with strings without the need to write custom code. Assuming you understand the previous section relating to reference types, you can learn how to manipulate strings using the most common System.String
methods.
System.String
provides several methods for performing operations on strings. We discuss the most important of them. Each method comes with several overloads. Discussing every overload is not possible, so you learn how methods work; then you can use IntelliSense, the Object Browser, and the documentation for further information.
Comparing the content of two strings is an easy task. The most common way for comparing strings is taking advantage of the equality (=) operator, which checks whether two strings have the same value. The following is an example that compares strings for equality:
Dim stringOne As String = "Hi guys"
Dim stringTwo As String = "How are you?"
Dim stringThree As String = "Hi guys"
'Returns False
Dim result1 As Boolean = (stringOne = stringTwo)
'Returns True
Dim result2 As Boolean = (stringOne = stringThree)
You can also use the equality operator inside conditional blocks, as in the following snippet:
If stringOne = stringTwo Then
'Do something if the two strings are equal
End If
You instead check for strings’ inequality using the inequality operator (<>).
The Visual Basic Compiler and the Equality Operator
When using the equality operator for string comparisons, the Visual Basic compiler works differently from other managed languages. In fact, behind the scenes it makes a call to the Microsoft.VisualBasic.CompilerServices.Operators.CompareString
method, whereas other languages, such as C#, make an invocation to System.String.Equals
.
The String
class also exposes other interesting methods for comparing strings: Equals, Compare
, CompareTo,
and CompareOrdinal
. Equals
checks for strings equality and returns a Boolean value of True
if the strings are equal or False
if they are not (which is exactly like the equality operator). The following code compares two strings and returns False
because they are not equal:
Dim firstString As String = "Test string"
Dim secondString As String = "Comparison Test"
Dim areEqual As Boolean = String.Equals(firstString, secondString)
Equals has several signatures allowing deep control of the comparison. For example, you could check if two strings are equal according to the local system culture and without being case-sensitive:
Dim areCaseEqual As Boolean =
String.Equals(firstString, secondString,
StringComparison.CurrentCultureIgnoreCase)
The StringComparison
object provides a way for specifying comparison settings. IntelliSense provides descriptions for each available option. Then there is the Compare
method. It checks whether the first string is minor, equal, or greater than the second and returns an Integer
value representing the result of the comparison. If the first string is minor, it returns -1; if it is equal to the second one, the method returns zero; if the first string is greater than the second, it
returns 1. The following code snippet demonstrates this kind of comparison:
Dim firstString As String = "Test string"
Dim secondString As String = "Comparison Test"
Dim result As Integer = String.Compare(firstString, secondString)
In this case Compare
returns 1 because the second string is greater than the first one. Compare
enables specifying several comparing options. For example, you could perform the comparison based on case-sensitive strings. The following code demonstrates this:
Dim caseComparisonResult As Integer =
String.Compare(firstString, secondString, True)
For Equals
, Compare
also enables a comparison based on other options, such as the culture information of your system. The next method is String.CompareTo,
whose return values are the same as String.Compare
; however, String.CompareTo
is an instance method. You use it like in the following code:
Dim firstString As String = "Test string"
Dim secondString As String = "Comparison Test"
Dim result As Integer = firstString.CompareTo(secondString)
The last valuable method is String.CompareOrdinal
, which checks for casing differences via ordinal comparison rules, which means comparing the numeric values of the corresponding Char
objects that the string is composed of. The following is an example:
Dim firstString As String = "test"
Dim secondString As String = "TeSt"
'Returns:
'0 if the first string is equal to the second
'< 0 if the first string is less than the second
'> 0 if the first string is greater than the second
Dim result As Integer = String.CompareOrdinal(firstString, secondString)
The System.String
class provides a method named IsNullOrEmpty
that easily enables checking if a string is null or if it does not contain any characters. You can use such a method as follows:
If String.IsNullOrEmpty(stringToCheck) = False Then
'The string is neither null nor empty
Else
'The string is either null or empty
End If
Of course, you could also perform your check against True instead of False. In such situations both conditions (null or empty) are evaluated. This can be useful because you often need to validate strings to check whether they are valid. There could be situations in which you need to just ensure that a string is null or not empty. In this case you should use the usual syntax:
If stringToCheck Is Nothing Then
'String is null
End If
If stringToCheck = "" Then
'String is empty
End If
Often you need to send output strings according to a particular format, such as currency, percentage, and decimal numbers. The System.String
class offers a useful method named Format
that enables you to easily format text. Consider the following code example, paying attention to comments:
'Returns "The cost for traveling to Europe is $1,000.00
Console.WriteLine(String.Format("The cost for traveling to Europe is {0:C}
dollars", 1000))
'Returns "You are eligible for a 15.50% discount"
Console.WriteLine(String.Format("You are eligible for a {0:P} discount",
15.55F))
'Returns "Hex counterpart for 10 is A"
Console.WriteLine(String.Format("Hex counterpart for 10 is {0:X}", 10))
The first thing to notice is how you present your strings; Format
accepts a number of values to be formatted and then embedded in the main string, which are referenced with the number enclosed in brackets. For example, {0}
is the second argument of Format
, {1}
is the third one, and so on. Symbols enable the format; for example, C
stands for currency, P
stands for percentage, and X
stands for hexadecimal. Visual Basic 2012 offers the symbols listed in Table 4.7.
Roundtrip
Roundtrip ensures that conversions from floating point to String
and that converting back are allowed.
You can format multiple strings in one line of code, as in the following example:
Console.Writeline(String.Format("The traveling cost is" &
" {0:C}. Hex for {1} is '{1,5:X}'", 1000, 10))
The preceding code produces the following result:
The traveling cost is $1,000.00. Hex for 10 is ' A'
As you can see, you can specify a number of white spaces before the next value. This is accomplished by typing the number of spaces you want to add followed by a :
symbol and then the desired format symbol. String.Format
also enables the use of custom formats. Custom formats are based on the symbols shown in Table 4.8.
According to Table 4.7, we could write a custom percentage representation:
'Returns "Custom percentage %1,550"
Console.WriteLine(String.Format("Custom percentage {0:%##,###.##} ", 15.50))
Or you could write a custom currency representation. For example, if you live in Great Britain, you could write the following line for representing the Sterling currency:
Console.WriteLine(String.Format("Custom currency {0:£#,###.00} ", 987654))
Another interesting feature in customizing output is the ability to provide different formats according to the input value. For example, you can decide to format a number depending if it is positive, negative, or zero. At this regard, consider the following code:
Dim number As Decimal = 1000
Console.WriteLine(String.
Format("Custom currency formatting:
{0:£#,##0.00;*£#,##0.00*;Zero}",
number))
Here you specify three different formats, separated by semicolons. The first format affects positive numbers (such as the value of the number variable); the second one affects negative numbers, and the third one affects a zero value. The preceding example therefore produces the following output:
Custom currency formatting: £1,000.00
If you try to change the value of the number to −1000, the code produces the following output:
Custom currency formatting: *£1,000.00*
Finally, if you assign number = 0, the code produces the following output:
Custom currency formatting: Zero
Strings in .NET are reference types. Because of this, assigning a string object to another string to perform a copy will just copy the reference to the actual string. Fortunately, the System.String
class provides two useful methods for copying strings: Copy
and CopyTo
. The first one creates a copy of an entire string:
Dim sourceString As String = "Alessandro Del Sole"
Dim targetString As String = String.Copy(sourceString)
Copy
is a shared method and can create a new instance of String
and then put into the instance the content of the original string. If you instead need to create a copy of only a subset of the original string, you can invoke the instance method CopyTo
. Such method works a little differently from Copy
because it returns an array of Char
. The following code provides an example:
Dim sourceString As String = "Alessandro Del Sole"
Dim charArray(sourceString.Length) As Char
sourceString.CopyTo(11, charArray, 0, 3)
Console.WriteLine(charArray)
You first need to declare an array of Char
—in this case as long as the string length. The first argument of CopyTo
is the start position in the original string; the second is the target array. The third one is the start position in the target array, and the fourth one is the number of characters to copy. In the end, such code produces Del
as the output.
Clone Method
The String
class also offers a method named Clone
. You should not confuse this method with Copy
and CopyTo
because it will just return a reference to the original string and not a real copy.
When working with strings, you often need to inspect or evaluate their content. The System.String
class provides both methods and properties for inspecting strings. Imagine you have the following string:
Dim testString As String = "This is a string to inspect"
You can retrieve the string’s length via its Length
property:
'Returns 27
Dim length As Integer = testString.Length
Another interesting method is Contains
, which enables you to know whether a string contains the specified substring or array of Char
. Contains
returns a Boolean value, as you can see in the following code snippet:
'Returns True
Dim contains As Boolean = testString.Contains("inspect")
'Returns False
Dim contains1 As Boolean = testString.Contains("Inspect")
Just remember that evaluation is case-sensitive. In some situations, you might need to check whether a string begins or ends with a specified substring. You can verify both situations using StartsWith
and EndsWith
methods:
'Returns False, the string starts with "T"
Dim startsWith As Boolean = testString.StartsWith("Uh")
'Returns True
Dim endsWith As Boolean = testString.EndsWith("pect")
Often you might also need to get the position of a specified substring within a string. To accomplish this, you can use the IndexOf
method. For example, you could retrieve the start position of the first “is” substring as follows:
'Returns 2
Dim index As Integer = testString.IndexOf("is")
The code returns 2 because the start index is zero-based and refers to the “is” substring of the “This” word. You do not need to start your search from the beginning of the string; you can specify a start index or specify how the comparison must be performed via the StringComparison
enumeration. Both situations are summarized in the following code:
'Returns 5
Dim index1 As Integer = testString.IndexOf("is", 3,
StringComparison.InvariantCultureIgnoreCase)
StringComparison Enumeration
You can refer to IntelliSense when typing code for further details on the StringComparison
enumeration options. They are self-explanatory, and for the sake of brevity, all options cannot be shown here.
IndexOf
performs a search on the exact substring. You might also need to search for the position of just one character of a set of characters. This can be accomplished using the IndexOfAny
method as follows:
'Returns 1
Dim index2 As Integer = testString.
IndexOfAny(New Char() {"h"c, "s"c, "i"c})
The preceding code has an array of Char
storing three characters, all available in the main string. Because the first character in the array is found first, IndexOfAny
returns its position. Generally, IndexOfAny
returns the position of the character that is found first. There are counterparts to both IndexOf
and IndexOfAny
: LastIndexOf
and LastIndexOfAny
. The first two methods perform a search starting from the beginning of a string, whereas the last two perform a search starting from the end of a string. This is an example:
'Returns 5
Dim lastIndex As Integer = testString.LastIndexOf("is")
'Returns 22
Dim lastIndex1 As Integer = testString.LastIndexOfAny(New Char()
{"h"c, "s"c, "i"c})
Notice how LastIndexOf
returns the second occurrence of the “is” substring if you consider the main string from the beginning. Indexing is useful, but this only stores the position of a substring. If you need to retrieve the text of a substring, you can use the SubString
method that works as follows:
'Returns "is a string"
Dim subString As String = testString.Substring(5, 11)
You can also just specify the start index, if you need the entire substring starting from a particular point.
The System.String
class provides members for editing strings. The first method described is named Insert
and enables adding a substring into a string at the specified index. Consider the following example:
Dim testString As String = "This is a test string"
'Returns
'"This is a test,for demo purposes only,string"
Dim result As String = testString.Insert(14, ",for demo purposes only,")
As you can see from the comment in the code, Insert
adds the specified substring from the specified index but does not append or replace anything. Insert
’s counterpart is Remove
, which enables removing a substring starting from the specified index or a piece of substring from the specified index and for the specified number of characters. This is an example:
'Returns "This is a test string"
Dim removedString As String = testString.Remove(14)
Remember that you always need to assign the result of a method call that makes an edit to a string, because of the immutability of string types. In this case, invoking Remove
does not change the content of testString
, so you need to assign the result of the method call to a variable (new or existing).
Another common task is replacing a substring within a string with another string. For example, imagine you want to replace the “test” substring with the “demo” substring within the testString
instance. This can be accomplished using the Replace
method as follows:
'Returns
'"This is a demo string"
Dim replacedString As String = testString.Replace("test", "demo")
The result of Replace
must be assigned to another string to get the desired result. (See “Performance Tips” at the end of this section.) Editing strings also contain splitting techniques. You often need to split one string into multiple strings, especially when the string contains substrings separated by a symbol. For example, consider the following code in which a string contains substrings separated by commas, as in CSV files:
Dim stringToSplit As String = "Name,Last Name,Age"
You might want to extract the three substrings, Name
, Last Name
, and Age,
and store them as unique strings. To accomplish this, you can use the Split
method, which can receive as an argument the separator character:
Dim result() As String = stringToSplit.Split(","c)
For Each item As String In result
Console.WriteLine(item)
Next
The preceding code retrieves three strings that are stored into an array of String
and then produces the following output:
Name
Last Name
Age
Split
has several overloads that you can inspect with IntelliSense. One of these enables you to specify the maximum number of substrings to extract and split options, such as normal splitting or splitting if substrings are not empty:
Dim result() As String = stringToSplit.Split(New Char() {","c}, 2,
StringSplitOptions.RemoveEmptyEntries)
In this overload you have to explicitly specify an array of Char
; in this case there is just a one-dimension array containing the split symbol. Such code produces the following output, considering that only two substrings are accepted:
Name
Last Name, Age
Opposite to Split
, there is also a Join
method that enables joining substrings into a unique string. Substrings are passed as an array of String
and are separated by the specified character. The following code shows an example:
'Returns "Name, Last Name, Age"
Dim result As String = String.Join(",",
New String() {"Name", "Last Name", "Age"})
Another way to edit strings is trimming. Imagine you have a string containing white spaces at the end of the string or at the beginning of the string, or both. You might want to remove white spaces from the main string. The System.String
class provides three methods, Trim
, TrimStart
, and TrimEnd,
that enable you to accomplish this task, as shown in the following code (see comments):
Dim stringWithSpaces As String = " Test with spaces "
'Returns "Test with spaces"
Dim result1 As String = stringWithSpaces.Trim
'Returns "Test with spaces "
Dim result2 As String = stringWithSpaces.TrimStart
'Returns " Test with spaces"
Dim result3 As String = stringWithSpaces.TrimEnd
All three methods provide overloads for specifying characters different from white spaces. (Imagine you want to remove an asterisk.) Opposite to TrimStart
and TrimEnd
, System.String
exposes PadLeft
and PadRight
. The best explanation for both methods is a practical example. Consider the following code:
Dim padResult As String = testString.PadLeft(30, "*"c)
It produces the following result:
*********This is a test string
PadLeft
creates a new string, whose length is the one specified as the first argument of the method and that includes the original string with the addition of a number of symbols that is equal to the difference from the length you specified and the length of the original string. In our case, the original string is 21 characters long, although we specified 30 as the new length. So, there are 9 asterisks. PadRight
does the same, but symbols are added on the right side, as in the following example:
Dim padResult As String = testString.PadRight(30, "*"c)
This code produces the following result:
This is a test string*********
Both methods are useful if you need to add symbols to the left or to the right of a string.
Because of its particular nature, each time you edit a string you are not actually editing the string—instead, you are creating a new instance of the System.String
class. As you might imagine, this could lead to performance issues. That said, although it’s fundamental to know how you can edit strings; you should always prefer the StringBuilder
object, especially when concatenating strings. StringBuilder
is discussed later in this chapter.
Concatenation is perhaps the most common task that developers need to perform on strings. In Visual Basic 2012 you have some alternatives. First, you can use the addition operator:
Dim firstString As String = "Hello! My name is "
Dim secondString As String = "Alessandro Del Sole"
Dim result As String = firstString + secondString
Another, better, approach is the String.Concat
method:
Dim concatResult As String =
String.Concat(firstString, secondString)
Both ways produce the same result, but both ways have a big limitation; because strings are immutable, the CLR needs to create a new instance of the String
class each time you perform a concatenation. This scenario can lead to a significant loss of performance. If you need to concatenate 10 strings, the CLR creates 10 instances of the String
class. Fortunately, the .NET Framework provides a more efficient way for concatenating strings: the StringBuilder
object.
The System.Text.StringBuilder
class provides an efficient way for concatenating strings. You should always use StringBuilder
in such situations. The real difference is that StringBuilder
can create a buffer that grows along with the real needs of storing text. (The default constructor creates a 16-byte buffer.) Using the StringBuilder
class is straightforward. Consider the following code example:
'Requires an Imports System.Text directive
Function ConcatenatingStringsWithStringBuilder() As String
Dim result As New StringBuilder
'Ensures that the StringBuilder instance
'has the capacity of at least 100 characters
result.EnsureCapacity(100)
result.Append("Hello! My name is ")
result.Append("Alessandro Del Sole")
Return result.ToString
End Function
You simply instantiate the StringBuilder
class using the New
keyword and then invoke the Append
method that receives as an argument the string that must be concatenated. In the end, you need to explicitly convert the StringBuilder
to a String
invoking the ToString
method. This class is powerful and provides several methods for working with strings, such as AppendLine
(which appends an empty line with a carriage return), AppendFormat
(which enables you to format the appended string), and Replace
(which enables you to replace all occurrences of the specified string with another string). The EnsureCapacity
method used in the code example ensures that the StringBuilder
instance can contain at least the specified number of characters. You can find in the StringBuilder
class the same methods provided by the String
class (Replace
, Insert
, Remove
, and so on), so working with StringBuilder
should be familiar and straightforward.
Together with strings, you often need to handle dates and moments in time. To accomplish this, the .NET Framework 4.5 provides the System.DateTime
value type.
MinValue and MaxValue
Being a value type, System.DateTime
has two shared fields: MinValue
and MaxValue
. These store the minimum accepted date and the maximum date, respectively. The minimum date is 01/01/0001 00:00:00 a.m., and maximum date is 12/31/9999 11:59:59 p.m.
The Visual Basic grammar offers the Date
keyword, which is a lexical representation of the System.DateTime
object, so you can use both definitions. For consistency, we use the Date
reserved keyword, but keep in mind that this keyword creates (or gets a reference to) an instance of the System.DateTime
type. Working with dates is an easy task. You can create a new date by creating an instance of the DateTime
class:
Dim myBirthDate As New Date(1977, 5, 10)
The constructor has several overloads, but the most common is the preceding one, where you can specify year, month, and day. For example, such values could be written by the user and then converted into a DateTime
object. Another common situation in which you need to create a date is for storing the current system clock date and time. This can be easily accomplished using the DateTime.Now
property as follows:
Dim currentDate As Date = Date.Now
Such code produces the following result, representing the moment when I’m writing this chapter:
5/28/2012 10:02:35 PM
When you get an instance of a DateTime
, you can retrieve a lot of information about it. For example, consider the following code taking care of comments:
'Creates a new date; May 10th 1977, 8.30 pm
Dim myBirthDate As New Date(1977, 5, 10,
20, 30, 0)
'In 1977, May 10th was Tuesday
Console.WriteLine(myBirthDate.DayOfWeek.
ToString)
'8.30 pm
Console.WriteLine("Hour: {0}, Minutes: {1}",
myBirthDate.Hour,
myBirthDate.Minute)
'Is the date included within the Day Light Saving Time period?
Console.WriteLine("Is Day light saving time: {0}",
myBirthDate.IsDaylightSavingTime.
ToString)
'Is leap year
Console.WriteLine("Is leap: {0}",
Date.IsLeapYear(myBirthDate.Year).
ToString)
The code first creates the following date, representing my birth date: 5/10/1977 8:30:00 PM. Then it retrieves some information, such as the name of the day of the week (represented by the DayOfWeek
enumeration), the hours, the minutes (via the Hour
and Minute
integer properties), and the specified date within Daylight Saving Time. The DateTime
object also exposes a shared method named IsLeapYear
that can establish whether the specified year is a leap year. In our example, the year is not passed directly but is provided via the Year
property of the myBirthDate
instance. The following is the result of the code:
Tuesday
Hour: 20, Minutes: 30
Is Day light saving time: True
Is leap: False
Finally, you can declare dates with the so-called date literals. The following is an example of how you can customize the date format:
Dim customDate As Date = #5/25/2012 8:00:00 PM#
It is not unusual to ask the user to provide a date within an application. Typically, this is accomplished via the user interface and, if you do not provide a specific user control (such as the WPF DatePicker
or the Win Forms DateTimePicker
) for selecting dates in a graphical fashion, such input is provided in the form of a string. Because of this, you need a way of converting the string into a date, so that you can then manipulate the user input as an effective DateTime
object (unless the string is invalid; then you need validation). To accomplish this kind of conversion, the System.DateTime
class provides two methods that you already saw when discussing value types: Parse
and TryParse
. For example, consider the following code that receives an input by the user and attempts to convert such input into a DateTime
object:
Sub ParsingDates()
Console.WriteLine("Please specify a date:")
Dim inputDate As Date
Dim result As Boolean = Date.TryParse(Console.ReadLine, inputDate)
If result = False Then
Console.WriteLine("You entered an invalid date")
Else
Console.WriteLine(inputDate.DayOfWeek.ToString)
End If
End Sub
The TryParse
method receives the string to convert as the first argument (which in this case is obtained by the Console window) and the output object passed by reference (inputDate
); it returns True
if the conversion succeeds or False
if it fails. The conversion succeeds if the input string format is accepted and recognized by the DateTime
type. If you run this code and enter a string in the format 1977/05/10, the conversion succeeds because such format is accepted by DateTime
. You can then manipulate the new date as you like. (In the preceding example, the code shows the day of the week for the specified date, which in my example is Tuesday.)
Tip
In many cases you work with data from a database. The ADO.NET engine and layered technologies, such as LINQ, map dates from databases directly into a System.DateTime
object so that you will be able to work and manipulate such objects from and to data sources.
You need to present dates for several scenarios, and you might be required to perform this task in different ways. Fortunately, the System.DateTime
provides many ways of formatting dates. The easiest way is invoking the ToString
method, which accepts an argument that enables specifying how a date must be presented. For example, consider the following code snippet that writes the current date in both the extended (D
) and the short (d
) date formats:
Console.WriteLine(DateTime.Now.ToString("D"))
Console.WriteLine(DateTime.Now.ToString("d"))
Such code produces the following output:
Monday, May 28, 2012
5/28/2012
The result is based on the regional and culture settings of your system. Table 4.9 summarizes symbols you can use with the ToString
method.
ToString
also recognizes date literals. The following is an example of how you can customize and write a date:
Console.WriteLine(Date.Today.ToString("dd/MM/yyyy"))
The previous code prints the current date in the Day/Month/Year format. System.DateTime
provides a plethora of other useful methods you can use for formatting dates. Such methods also return different data types, depending on the scenario in which they have to be used in. Table 4.10 summarizes the most important methods you can always inspect with IntelliSense and the Object Browser.
The following code snippet uses all the preceding methods to demonstrate how the output differs depending on the method:
Console.WriteLine("Local time: {0}", Date.Now.ToLocalTime)
Console.WriteLine("Long date: {0}", Date.Now.ToLongDateString)
Console.WriteLine("Short date: {0}", Date.Now.ToShortDateString)
Console.WriteLine("Long time: {0}", Date.Now.ToLongTimeString)
Console.WriteLine("Short time: {0}", Date.Now.ToShortTimeString)
Console.WriteLine("Universal time: {0}", Date.Now.
ToUniversalTime.ToString)
Console.WriteLine("File time: {0}", Date.Now.
ToFileTime.ToString)
Console.WriteLine("File time UTC: {0}", Date.Now.
ToFileTimeUtc.ToString)
Console.WriteLine("OLE Automation date: {0}", Date.Now.
ToOADate.ToString)
The preceding code produces the following result, which you can compare with the methods described in Table 4.9:
Local time: 05/28/2012 19:27:22
Long date: Monday, May 28, 2012
Short date: 5/28/2012
Long time: 7:27:22 PM
Short time: 7:27 PM
Universal time: 5/28/2012 7:27:22 PM
File time: 129826997181439508
File time UTC: 129826997181439508
OLE Automation date: 41057.811552581
It’s not unusual to need to know the amount of time spent between two dates. The System.DateTime
enables this by invoking a Subtract
method, which returns a System.TimeSpan
value. For example, consider the following code that subtracts a date from another one:
Dim birthDate As Date = New Date(1977, 5, 10, 20, 30, 0)
Dim secondDate As Date = New Date(1990, 5, 11, 20, 10, 0)
Dim result As System.TimeSpan = secondDate.Subtract(birthDate)
'In days
Console.WriteLine(result.Days)
'In "ticks"
Console.WriteLine(result.Ticks)
You can subtract two DateTime
objects and get a result of type TimeSpan
(discussed next). You can then get information on the result, such as the number of days that represent the difference between the two dates or the number of ticks. The previous code produces the following result:
4748
4103124000000000
You can also add values to a date. For example you can edit a date by adding days, hours, minutes, seconds, or ticks or by incrementing the year. Consider the following code snippet:
Dim editedDate As Date = birthDate.AddDays(3)
editedDate = editedDate.AddHours(2)
editedDate = editedDate.AddYears(1)
Console.WriteLine(editedDate)
This code adds three days and two hours to the date and increments the year by one unit. In the end, it produces the following output:
5/13/1978 10:30:00 PM
You can use negative numbers to subtract values from a single date, which returns a Date
object instead of a TimeSpan
like in Subtract
. For instance, the following code subtracts a day from the date represented by the birthDate
variable:
'Returns 05/09/1977
Dim result As Date = birthDate.AddDays(-1)
Dates are important and, although they allow working with time, too, the .NET Framework provides an important structure specific for representing pieces of time: System.TimeSpan
.
Note About Operators
You can use standard operators such as the addition and subtraction operators when working with both DateTime
and TimeSpan
objects. This is possible because both objects overload the standard operators. Overloading operators is discussed in Chapter 11.
You often need to represent intervals of time in your applications, especially in conjunction with dates. The .NET Framework provides a structure—a value type named System.TimeSpan
. This structure can represent time from a minimum value (one tick) until a maximum value (one day). A tick is the smallest unit for time representations and is equal to 100 nanoseconds. TimeSpan
represents a summed amount of time between two given time values, and the time portion of a Date
object represents a single specific moment in time.
Minimum and Maximum Values
As for other value types, System.TimeSpan
also provides two shared properties named MinValue
and MaxValue. MinValue
returns -10675199.02:48:05.4775808, and MaxValue
returns 10675199.02:48:05.4775807. For the sake of clarity, both values are respectively equal to System.Int64.MinValue
and System.Int64.MaxValue
.
You can find several places in which using TimeSpan
is needed other than simply working with dates. For example, you might want to create your performance benchmarks using the StopWatch
object that returns a TimeSpan
. Or you might need such structure when working with animations in WPF applications. The following code example simulates a performance test; a System.StopWatch
object is started, an intensive loop is performed, and then the StopWatch
is stopped. The StopWatch
class offers an Elapsed
property that is of type TimeSpan
and that can be useful for analyzing the amount of elapsed time:
Dim watch As New Stopwatch
watch.Start()
For i = 0 To 10000
'Simulates intensive processing
System.Threading.Thread.SpinWait(800000)
Next
watch.Stop()
Console.WriteLine(watch.Elapsed.Seconds)
Console.WriteLine(watch.Elapsed.Milliseconds)
Console.WriteLine(watch.Elapsed.Ticks)
The preceding code produced the following result on my machine, but it will be different on yours, depending on your hardware:
49
374
493746173
The TimeSpan
structure is similar to the area of the DateTime
type that is related to time. Notice that TimeSpan
offers several similar properties, such as Days
; Hours
; Minutes
; Seconds
; Milliseconds
; and methods such as AddDays
, AddHours
, AddMinute
, and Subtract
. TimeSpan
is all about time; this means that although there are similarities, as mentioned before, with the time-related DateTime
members, you cannot (obviously) work with dates. The following code provides an example of creating a TimeSpan
instance starting from an existing date:
Sub TimeSpanInstance()
Dim currentDate As Date = Date.Now
'Because the System namespace is imported at project
'level, we do not need an Imports directive
Dim intervalOfTime As TimeSpan = currentDate.TimeOfDay
Console.WriteLine("My friend, in the current date " &
"there are {0} days; time is {1}:{2}:{3}",
intervalOfTime.Days,
intervalOfTime.Hours,
intervalOfTime.Minutes,
intervalOfTime.Seconds)
End Sub
The preceding code produces the following result:
My friend, in the current date there are 0 days; time is 19:30
In the specified interval, there is only the current day, so the first argument returns zero. Take a look back at the section “Subtracting Dates and Adding Time to Time” to see an example of TimeSpan
usage for an interval of time retrieved subtracting two dates.
You might often ask what people are doing on the other side of world when in your country it’s a particular time of the day. Working with time zones can also be important for your business if you need to contact people who live in different and faraway countries. The .NET Framework provides two types, TimeZone
and TimeZoneInfo
, which enable retrieving information on time zones. Both types are exposed by the System
namespace. For example, imagine you want to retrieve information on the time zone of your country. This can be accomplished as follows (assuming regional settings on your machine are effectively related to your country):
Dim zone As TimeZone = TimeZone.CurrentTimeZone
TimeZone
is a reference type and through its CurrentTimeZone
property it provides a lot of information such as the name of the time zone or the Daylight Saving Time period as demonstrated here:
Console.WriteLine(zone.DaylightName)
Console.WriteLine(zone.StandardName)
Console.WriteLine(zone.IsDaylightSavingTime(Date.Now))
This code produces the following result on my machine:
W. Europe Daylight Time
W. Europe Standard Time
True
The official MSDN documentation states that using the TimeZoneInfo
class should be preferred instead of TimeZone
. This is because TimeZoneInfo
also provides the ability of creating custom time zones. The following code shows how you can retrieve current time zone information using TimeZoneInfo
:
Dim tz As TimeZoneInfo = TimeZoneInfo.Local
'Shows the current time zone Identifier
Console.WriteLine(tz.Id)
Creating a custom time zone is also a simple task, which is accomplished by the following code:
Dim customZone As TimeZoneInfo = TimeZoneInfo.
CreateCustomTimeZone("CustomTimeZone",
Date.UtcNow.Subtract(Date.Now),
"Custom Zone", "Custom Zone")
All you need is to specify a custom identifier, the difference between the UTC time span and the local time span, a daylight identifier, and a standard identifier. TimeZoneInfo
also provides another useful method for enumerating time zones recognized by the system, named GetSystemTimeZones
; you can use it like this:
For Each timez As TimeZoneInfo In TimeZoneInfo.GetSystemTimeZones
Console.WriteLine(timez.DisplayName)
Next
An excerpt of the output provided by this simple iteration is the following:
(UTC-12:00) International Date Line West
(UTC-11:00) Midway Island, Samoa
(UTC-10:00) Hawaii
(UTC-09:00) Alaska
(UTC-08:00) Pacific Time (US & Canada)
(UTC-08:00) Tijuana, Baja California
(UTC-07:00) Arizona
(UTC-07:00) Chihuahua, La Paz, Mazatlan
(UTC-07:00) Mountain Time (US & Canada)
(UTC-06:00) Central America
(UTC-06:00) Central Time (US & Canada)
(UTC-05:00) Eastern Time (US & Canada)
(UTC-05:00) Indiana (East)
(UTC-04:30) Caracas
(UTC-04:00) Santiago
(UTC-03:30) Newfoundland
(UTC-01:00) Cape Verde Is.
(UTC) Casablanca
(UTC) Coordinated Universal Time
(UTC) Dublin, Edinburgh, Lisbon, London
(UTC) Monrovia, Reykjavik
(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
(UTC+02:00) Windhoek
(UTC+03:00) Baghdad
(UTC+03:00) Kuwait, Riyadh
(UTC+03:00) Moscow, St. Petersburg, Volgograd
(UTC+03:00) Nairobi
(UTC+06:00) Almaty, Novosibirsk
(UTC+06:00) Astana, Dhaka
(UTC+06:30) Yangon (Rangoon)
(UTC+07:00) Krasnoyarsk
(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi
(UTC+08:00) Perth
(UTC+08:00) Taipei
(UTC+09:00) Yakutsk
(UTC+09:30) Adelaide
(UTC+10:00) Vladivostok
(UTC+11:00) Magadan, Solomon Is., New Caledonia
(UTC+12:00) Auckland, Wellington
(UTC+12:00) Fiji, Kamchatka, Marshall Is.
(UTC+13:00) Nuku'alofa
Thanks to this information, you could use the TimeZoneInfo
class for converting between time zones. The following code demonstrates how to calculate the hour difference between Italy and Redmond, Washington:
'Redmond time; requires specifying the Time Zone ID
Dim RedmondTime As Date = TimeZoneInfo.
ConvertTimeBySystemTimeZoneId(DateTime.Now, "Pacific Standard Time")
Console.WriteLine("In Italy now is {0} while in Redmond it is {1}",
Date.Now.Hour,RedmondTime.Hour)
By invoking the ConvertTimeBySystemZoneId
method, you can convert between your local system time and another time, based on the zone ID. If you don’t know zone IDs, just replace the previous iterations for showing the content of the timez.Id
property instead of DisplayName
.
How many times do you need to represent something with a unique identifier? Probably very often. Examples are items with the same name but with different characteristics. The .NET Framework enables creating unique identifiers via the System.Guid
structure (therefore a value type). This lets you generate a unique, 128-bit string, identifier. To generate a unique identifier, invoke the Guid.NewGuid
method, which works as follows:
'Declaring a Guid
Dim uniqueIdentifier As Guid
'A unique identifier
uniqueIdentifier = Guid.NewGuid
Console.WriteLine(uniqueIdentifier.ToString)
'Another unique identifier,
'although to the same variable
uniqueIdentifier = Guid.NewGuid
Console.WriteLine(uniqueIdentifier.ToString)
If you run the preceding code, you notice that each time you invoke the NewGuid
method a new GUID is generated, although you assign such a value to the same variable. This makes sense because you use the GUID each time you need a unique identifier. On my machine the preceding code produces the following result:
46e0daca-db56-45b1-923e-f76cf1636019
6fd3a61d-ec17-4b6a-adec-6acfa9a0cb00
Examples of GUIDs
The Windows operating system makes a huge usage of GUIDs. If you try to inspect the Windows Registry, you’ll find lots of examples.
The Guid.NewGuid
method provides auto-generated GUIDs. If you need to provide your own GUID, you can use the New
constructor followed by the desired identifier:
'Specifying a Guid
uniqueIdentifier = New Guid("f578c96b-5918-4f79-b690-6c463ffb2c3e")
The constructor has several overloads, which enable generating GUIDs also based on byte arrays and integers. Generally, you use GUIDs each time you need to represent something as unique. Several .NET types accept GUIDs, so you need to know how you can create them in code, although this is not the only way.
The Visual Studio IDE provides a graphical tool for generating GUIDs. You can run this tool by selecting the Create GUID command from the Tools menu. Figure 4.11 shows the Create GUID window.
You can choose the format you need for your GUID, such as Windows-like GUIDs. When you get your GUID, you can copy it to the clipboard and then reuse it in your code.
As a developer, you know what arrays are. They are a place in which you can store a set of items, generally of the same type. In the .NET Framework 4.5 (as in older versions), arrays are reference types all deriving from the System.Array
class and can be both one-dimensional and multidimensional.
Arrays Versus Collections
Collections, especially generic ones, are more efficient than arrays. I always recommend working with collections instead of arrays, except when strictly needed. (For example, you might need jagged arrays.)
You can either declare an array and use it later in your code or declare it and assign it with objects. The following is an example of declaring an array of String
objects, meaning that it can store only objects of type String
:
Dim anArrayOfString() As String
In this line of code an array of String
is declared. This array has no predefined bounds, so it is flexible and useful if you cannot predict how many items it needs to store. There is also an alternative syntax, which allows placing parentheses at the end of the type, as follows:
'Alternative syntax
Dim anArrayOfString As String()
You are free to use whichever syntax you like. For the sake of consistency, the first one is used.
Is Option Strict On?
The preceding and the following code examples assume that Option Strict
is set to On
. If it is off, adding instances of System.Object
is also allowed but might cause errors at runtime. It is recommended to set Option Strict
to On
to avoid implicit conversion, and this is one of those cases.
You can initialize arrays directly when declaring them, as in the following code that declares an array of strings and stores three instances of the System.String
class:
'Inline initialization with implicit bounds
Dim anArrayOfThreeStrings() As String = New String() {"One", "Two", "Three"}
Notice how you assign an array using the New
keyword followed by the type name and a couple of parentheses. Brackets contain the items to store. The declared array has no bounds limits, but actually after the assignment its upper bound is 2, so its bounds are determined by the number of values it is initialized with.
Arrays Base
Arrays are zero-based. This means that an array with an upper bound of 2 can store three items (index of zero, index of one, and index of two). This also means that the upper bound will always be one less than the Count
property of the array.
This approach works with arrays of other .NET types, too, as in the following code in which Char
and Byte
are used:
Dim anArrayOfChar() As Char = New Char() {"a"c, "b"c, "c"c}
Dim anArrayOfByte() As Byte = New Byte() {1, 2, 3}
If you already know how many items the array can store, you can specify the bounds limits. For example, imagine you want to store three instances of System.Byte
into an array of Byte
. This can be accomplished via the following code:
Dim anExplicitBoundArrayOfByte(2) As Byte
anExplicitBoundArrayOfByte(0) = 1
anExplicitBoundArrayOfByte(1) = 2
anExplicitBoundArrayOfByte(2) = 3
Inline Initialization with Explicit Bounds
Inline initialization is not allowed against arrays declared with explicit bounds. In such situations the only allowed syntax is the one shown in the previous code snippet.
The upper limit is enclosed in parentheses. Storing items is accomplished through indices. (The first one is always zero.) As with assignment, you can retrieve the content of a particular item using indices:
'Outputs 2
Console.WriteLine(anExplicitBoundArrayOfByte(1).ToString)
You can also perform tasks on each element in the array using a For Each
loop, as in the following code snippet, which works the same on an array of Byte
and on array of String
:
For Each value As Byte In anExplicitBoundArrayOfByte
Console.WriteLine(value)
Next
For Each value As String In anArrayOfThreeStrings
Console.WriteLine(value)
Next
Another important task you should perform when working with not explicitly bound arrays is checking whether they contain something. You can accomplish this checking whether the array is Nothing
:
If anArrayOfString Is Nothing Then
'The array is not initialized
End If
This is because attempting to access a null array causes the runtime to throw an exception.
In some situations you need to increase the capacity of an array that you previously declared with explicit bounds. Let’s retake one of the previous arrays:
Dim anExplicitBoundArrayOfByte(2) As Byte
anExplicitBoundArrayOfByte(0) = 1
anExplicitBoundArrayOfByte(1) = 2
anExplicitBoundArrayOfByte(2) = 3
At runtime you might need to store an additional Byte
, so in this case you should first increase the array’s size. To accomplish this, the Visual Basic grammar provides a special keyword named ReDim
. ReDim
redeclares an array of the same type with new bounds. But this keyword would also clean all the previously stored items; so what if you just need to add a new item to an existing list without clearing? Fortunately, the Visual Basic grammar provides another keyword named Preserve
that is the best friend of ReDim
in such situations and enables maintaining the previously stored values, preventing cleaning. The following code redeclares the preceding array without cleaning previous values:
ReDim Preserve anExplicitBoundArrayOfByte(3)
At this point, you can add a new item using the new available index:
anExplicitBoundArrayOfByte(3) = 4
Notice how you do not specify again the type of the array when using ReDim
.
Arrays can have multiple dimensions. Two-dimensional (also known as rectangular) and three-dimensional arrays are the most common situations. The following code declares a two-dimensional array with four values, but with no explicit dimensions specified:
Dim multiArray(,) As Integer = {{1, 2}, {3, 4}}
You can also specify dimensions as follows:
Dim multiArrayWithExplicitBounds(5, 1) As Integer
You cannot initialize arrays inline in the case of multidimensional arrays. You can then access indices as follows:
multiArrayWithExplicitBounds(1, 0) = 1
multiArrayWithExplicitBounds(2, 0) = 2
multiArrayWithExplicitBounds(1, 1) = 3
Array Literals
Visual Basic also offers a specific feature for working with both multidimensional arrays and jagged arrays (discussed next), named Array Literals. This feature enables the compiler to infer the appropriate type for arrays. Because it requires that you are familiar with the Local Type Inference, Array Literals are discussed in Chapter 20.
Jagged arrays are arrays of arrays and are similar to multidimensional arrays. However, they differ because each item of a dimension is an array. Here you see examples of jagged arrays of Integer
. To declare a jagged array, you can use the following syntax:
'A 9-entry array on the left and
'an unbound array on the right
Dim firstJaggedArray(8)() As Integer
As you can see, a jagged array declaration is characterized by a double couple of parentheses. You can also declare a jagged array that is not explicitly bound, as in the following code snippet:
Dim unboundJaggedArray()() As Integer
Although you can perform inline initializations, this coding technique could become difficult with complex arrays. Because of this, it could be more convenient declaring the array and then assigning its indices as follows:
Dim oneIntArray() As Integer = {1, 2, 3}
Dim twoIntArray() As Integer = {4, 5, 6}
unboundJaggedArray = {oneIntArray, twoIntArray}
By the way, the following initialization is perfectly legal:
Dim unboundJaggedArray()() As Integer _
= {New Integer() {1, 2, 3}, New Integer() {4, 5, 6}}
As I explain in Chapter 20, the Array Literals feature makes inline initialization easier. You can then normally access arrays (that is, items) in a jagged array—for example, performing a For..Each
loop:
'Returns 1 2 3 4 5 6
For Each arr As Integer() In unboundJaggedArray
For Each item As Integer In arr
Console.WriteLine(item.ToString)
Next
Next
As I mentioned at the beginning of this section, all arrays derive from the System.Array
class and therefore are reference types. This is an important consideration because you have to know how to manipulate them. System.Array
provides several static and instance members for performing tasks on arrays. Here we discuss the most important members and method overloads; they should be self-explanatory. IntelliSense can help by showing the necessary information. First, here’s an example of an array of byte:
Dim anArrayOfByte() As Byte = New Byte() {1, 2, 3}
In this array, bounds are not explicit. Particularly at runtime, you might need to access array indices. To avoid IndexOutOfRange
exceptions, though, you do need to know at least the upper bound. Just for clarification, imagine you want to perform a For..Next
loop against an array. To accomplish this, you first need to know the bounds. The GetLowerBound
and GetUpperBound
methods enable retrieving the lower and upper bounds of an array, as shown in the following code:
'Returns 0 and 2
Console.WriteLine("Lower bound {0}, upper bound {1}",
anArrayOfByte.GetLowerBound(0).ToString,
anArrayOfByte.GetUpperBound(0).ToString)
Both methods receive the dimension of the array as an argument. This is because they can work both on one-dimensional arrays and on multidimensional arrays. A zero dimension means that you are working with a one-dimensional array or with the first dimension of a multidimensional array. Another common task is sorting arrays. There are two methods that you can use, Sort
and Reverse
. Sort
performs an ordering of arrays in an ascending way, whereas Reverse
performs the ordering in a descending way. Starting from the anArrayOfByte
array, the following code reverses the order:
'Array now contains 3, 2, 1
Array.Reverse(anArrayOfByte)
To sort the array back, you can simply invoke the Sort
method:
Array.Sort(anArrayOfByte)
Both methods perform ordering according to the IComparable(Of T)
interface. You can also search for a particular item within an array. For this purpose, you can use two methods: IndexOf
and BinarySearch
. Both return the index of the specified item, but the first one just stops searching when the first occurrence is found; the second one searches through the entire array, but only if the array is sorted according to the implementation of the IComparable
interface. Their usage is very straightforward:
'A conversion to Byte is required
'Both return 1
Dim position As Integer = Array.IndexOf(anArrayOfByte, CByte(2))
Dim position2 As Integer = Array.BinarySearch(anArrayOfByte, CByte(2))
Both methods receive an Object
as the second argument. But we have an array of Byte
. Because just writing 2 tells the compiler to recognize such a number as an Integer
, we need to explicitly convert it to Byte
.
Note on System.Array Methods
System.Array
also provides methods that take a lambda expression as arguments. Lambdas are discussed in Chapter 20, so we do not apply them to arrays in this chapter. A quick recap is done for your convenience in the appropriate place.
Another common task with arrays is copying. Because they are reference types, assigning an array to another just copies the reference. To create a real copy of an array, you can take advantage of the shared Copy
method and of the instance CopyTo
method. First, you need to declare a target array. Continuing the example about the anArrayOfByte
array, you could declare the new one as follows:
'Declares an array to copy to,
'with bounds equals to the source array
Dim targetArray(anArrayOfByte.GetUpperBound(0)) As Byte
To ensure the upper bound is the same as in the original array, an invocation to the GetUpperBound
method is made. Next, you can copy the array:
'Copies the original array into the target,
'using the original length
Array.Copy(anArrayOfByte, targetArray, anArrayOfByte.Length)
Array.Copy
needs you to pass the source array, the target array, and the total number of items you want to copy. Supposing you want to perform a complete copy of the source array, you can just pass its length. The alternative is to invoke the instance method CopyTo
:
anArrayOfByte.CopyTo(targetArray, 0)
The method receives the target array as the first argument and the index where copying must begin as the second argument. A third way for copying an array is invoking the Clone
method, which is inherited from System.Object
. Note that Copy
and CopyTo
provide more granularity and control over the copy process. The last scenario creates arrays on-the-fly. You could need to perform such a task at runtime given a number of items of a specified type—for example, when you receive several strings as the user input. The System.Array
class provides a shared method named CreateInstance
, which creates a new instance of the System.Array
class. It receives two arguments: the System.Type
that the array must be of and the upper bound. For example, the following code creates a new array of String
that can store three elements:
Dim runTimeArray As Array = Array.CreateInstance(GetType(String), 2)
Pay Attention to CreateInstance
You should use CreateInstance
with care because you can write code that is correctly compiled but that can cause runtime errors (for example, with regard to array bounds).
Because the first argument is the representation of the System.Type
you want to assign to the array, you must use the GetType
keyword to retrieve information about the type. You can assign items to each index invoking the SetValue
method, which is an instance method. The following line of code assigns a string to the zero index of the previous array:
runTimeArray.SetValue(CStr("Test string"), 0)
If you want to retrieve your items, simply invoke the GetValue
method specifying the index:
'Returns "Test string"
Console.WriteLine(runTimeArray.GetValue(0))
When working with data types, you often need to perform several tasks on them. Depending on which type you work with, the Visual Basic programming language offers different kinds of operators, such as arithmetic operators, logical and bitwise operators, and shift operators. In this section you learn about Visual Basic operators and how you can use them in your own code. Let’s begin by discussing arithmetic operators, which are probably the operators you will use most frequently.
Visual Basic 2012 provides some arithmetic operators, listed in Table 4.11.
The first three operators are self-explanatory, so I would like to focus on the other ones. First, an important consideration should be done on the division operators. As shown in Table 4.11, Visual Basic offers two symbols, the slash (/) and backslash (). The first one can be used in divisions between floating-point numbers (such as Double
and Single
types), and the second can be used only in divisions between integer numbers. This backslash is fast when working with integers and truncates the result in case it is a floating-point number. The backslash accepts and returns just integers. To understand this concept, consider the following division between Doubles
:
'Division between double: returns 2.5
Dim dblResult As Double = 10 / 4
The result of such calculation is 2.5. Now consider the following one:
'Division between integers: returns 2
Dim intResult As Integer = 10 4
The result of this calculation is 2. This is because the operator truncated the result, due to its integer nature. If you try to use such operators in a division involving floating-point numbers, the Visual Basic compiler throws an exception, which is useful for avoiding subtle errors. By the way, such an exception occurs only with
Option Strict On
, which you should always set as your default choice.
Supported Types
The integer division operator supports the SByte
, Byte
, Short
, UShort
, Integer
, UInteger
, Long
, and ULong
data types, which are all numeric types that do not support a floating point.
For divisions between floating-point numbers, it’s worth mentioning that divisions between Single
and Double
are also allowed but cause the compiler to perform some implicit conversions that should be avoided. In such situations, you should just perform an explicit conversion, as in the following code:
'Division between Single and Double
Dim singleValue As Single = 987.654
Dim doubleValue As Double = 654.321
Dim division As Single = singleValue / CSng(doubleValue)
The next interesting operator is the exponentiation operator. A simple example follows:
Dim result As Double = 2 ^ 4 'returns 16
The exponentiation operator returns a Double
value. Because of this, even if operands are other types (such as Integer
or Long
), they will be always converted to Double
. Behind the scenes, the ^ operator invokes the Pow
method exposed by the System.Math
class. So you could also rewrite the preceding line of code as follows:
Dim result As Double = System.Math.Pow(2,4) 'returns 16
The last built-in operator is Mod
(which stands for modulus) that returns the remainder of a division between numbers. The following lines of code show an example:
'Mod: returns 0
Dim remainder As Integer = 10 Mod 2
'Mod: returns 1
Dim remainder As Integer = 9 Mod 2
A typical usage of Mod
is for determining whether a number is an odd number. To accomplish this, you could create a function like the following:
Function IsOdd(ByVal number As Integer) As Boolean
Return (number Mod 2) <> 0
End Function
If the remainder is different from zero, the number is odd and therefore returns True. Mod
supports all numeric types, including unsigned types and floating-point ones. The .NET Framework offers another way for retrieving the remainder of a division, which is the System.Math.IEEERemainnder
method that works as follows:
'Double remainder
Dim dblRemainder As Double = System.Math.IEEERemainder(10.42, 5.12)
Although both Mod
and IEEERemainder
return the remainder of a division between numbers, they use different formulas behind the scenes. Thus, the result can differ. According to the MSDN documentation, this is the formula for the IEEERemainder
method:
IEEERemainder = dividend - (divisor * Math.Round(dividend / divisor))
This is instead the formula for the Modulus operator:
Modulus = (Math.Abs(dividend) - (Math.Abs(divisor) *
(Math.Floor(Math.Abs(dividend) / Math.Abs(divisor))))) *
Math.Sign(dividend)
You can see how calculations work differently, especially where Modulus gets the absolute value for dividend and divisor.
System.Math Class
This section provides an overview of the arithmetic operators built in to the Visual Basic 2012 programming language. The System.Math
class provides lots of additional methods for performing complex calculations, but this is beyond the scope here.
You can use operators shown in the previous paragraph for incremental operations. Consider the following code:
Dim value As Double = 1
value += 1 'Same as value = value + 1
value -= 1 'Same as value = value - 1
value *= 2 'Same as value = value * 2
value /= 2 'Same as value = value / 2
value ^= 2 'Same as value = value ^ 2
Dim test As String = "This is"
test &= " a string" 'same as test = test & " a string"
You can abbreviate your code using this particular form when performing operations or concatenations. Also notice that +=
assignment operator works on strings as well.
Visual Basic 2012 offers logical, bitwise, and shift operators. Logical operators are special operators enabling comparisons between Boolean values and returning Boolean values. Bitwise and shift operators enable performing operations bit by bit. Next let’s discuss both logical and bitwise operators.
Visual Basic 2012 has eight logical/bitwise operators: Not
, And
, Or
, Xor
, AndAlso
, OrElse
, IsFalse
, and IsTrue
. In this section you learn about the first four; the other ones are covered in the next section. The first operator, Not
, returns the opposite of the actual Boolean value. For example, the following lines of code return False
because, although the 43 number is greater than 10, Not
returns the opposite:
'Returns False
Dim result As Boolean = (Not 43 > 10)
Logical operators can also be used with reference types. For example, you can return the opposite of the result of a comparison between objects (see the section “Comparison Operators” for details):
Dim firstPerson As New Person
Dim secondPerson As New Person
'Returns True
result = (Not firstPerson Is secondPerson)
This code returns True
; the comparison between firstPerson
and secondPerson
returns False
because they point to two different instances of the Person
class, but Not
returns the opposite. The next operator is And
, which compares two Boolean values or expressions and returns True
if both values or expressions are True
; otherwise, if at least one value is False
, And
returns False
. Here is an example of And
:
'Returns False
result = 10 > 15 and 30 > 15
'Returns True
result = 20 > 15 and 30 > 15
'Returns True
result = 20 > 15 and 15 = 15
And
is also useful for comparing Boolean properties of objects. For example, you might want to check whether a text file exists on disk and that it is not zero-byte; you could write the following code:
If My.Computer.FileSystem.FileExists("C:MyFile.txt") = True And
My.Computer.FileSystem.ReadAllText("C:MyFile.txt").Length > 0 Then
'Valid file
End If
If both actions return True
, And
returns True
. In our example this should mean that we encountered a valid text file. The next operator is Or
. Such an operator works like this: if expressions or values are True
, it returns True
; if both are False
, it returns False
; and if one of the two expressions is True
, it returns True
. The following code demonstrates this scenario:
'Returns True
result = 10 > 15 or 30 > 15
'Returns True
result = 10 < 15 or 30 > 15
'Returns False
result = 10 > 15 or 30 < 15
The last operator is Xor
(eXclusive or). Such an operator compares two Boolean expressions (or values) and returns True
only if one of the two expressions is True
; in all other cases it returns False
. Continuing the first example, Xor
returns the values described inside comments:
'Returns True
result = 10 > 15 Xor 30 > 15
'Returns False
result = 20 > 15 Xor 30 > 15
'Returns False
result = 20 > 15 Xor 15 = 15
Sometimes you do not need to perform the evaluation of the second expression in a Boolean comparison because evaluating the first one provides the result you need. In such scenarios, you can use two short-circuiting operators, AndAlso
and OrElse
. Short-circuiting means that code execution is shorter and performances are improved. Such operators are particularly useful when you need to invoke an external method from within an If..Then
code block. Let’s consider again the previous example for the And
operator:
If My.Computer.FileSystem.FileExists("C:MyFile.txt") = True And
My.Computer.FileSystem.ReadAllText("C:MyFile.txt").Length > 0 Then
'Valid file
End If
The Visual Basic compiler performs both evaluations. What would happen if the file did not exist? It throws a FileNotFoundException
when the ReadAllText
method is invoked because the And
operator requires both expressions to be evaluated. You should implement error-handling routines for such code, but this example is just related to operators. You can prevent your code from encountering the previously described problem using AndAlso
. You need to replace And
with AndAlso
, as in the following code:
If My.Computer.FileSystem.FileExists("C:MyFile.txt") = True AndAlso
My.Computer.FileSystem.ReadAllText("C:MyFile.txt").Length > 0 Then
'Valid file
End If
AndAlso
evaluates the first expression; if this returns False
, the second expression is not evaluated at all. In this case, if the file does not exist, the code exits from the If
block. AndAlso
’s counterpart is OrElse
, which evaluates the second expression only when the first one is False. Finally, in Visual Basic are two other operators named IsTrue
and IsFalse
. The first one works in conjunction with the OrElse
operator, while the second works with the AndAlso
. You cannot explicitly invoke these operators in your code because it is the job of the Visual Basic compiler to invoke them within an evaluation expression. This means that the types you want to be evaluated via OrElse
or AndAlso
must expose both of them. The following is a simple sample:
Public Structure myType
Public Shared Operator IsFalse(ByVal value As myType) As Boolean
Dim result As Boolean
' Insert code to calculate IsFalse of value.
Return result
End Operator
Public Shared Operator IsTrue(ByVal value As myType) As Boolean
Dim result As Boolean
' Insert code to calculate IsTrue of value.
Return result
End Operator
End Structure
Performing bitwise operations means performing operations with two binary numbers, bit by bit. The problem here is that Visual Basic does not allow working directly with binary numbers, so you need to write code against decimal or hexadecimal numbers that the Visual Basic compiler will actually treat, behind the scenes, in their binary representation. However, you still need to write them in a comprehensible way.
Converting Between Decimal and Binary
You can use the Windows Calculator in scientific mode to perform conversions between decimal/hexadecimal and binary numbers.
The bitwise operators in Visual Basic are still And
, Or
, Not
, and Xor
. Unlike logical operations, though, in which such operators evaluate expressions, bitwise operations are related to bit manipulations. You might wonder why you would need to perform bitwise operations in the era of WPF, Silverlight, and other high-level technologies. You could get multiple answers to this question, but the most useful one is probably providing the example of applications that interact with hardware devices in which you still need to work in a bit-by-bit fashion. Another common situation in Visual Basic is the combination of Enum
flags. Let’s see some examples. The And
operator combines two operands into a result. Inside such a result, it puts a 1 value where both operands have 1 in a particular position; otherwise, it puts a zero. For a better explanation, consider the following code:
Dim result As Integer = 152 And 312
The binary counterpart for 152 is 10011000, whereas the binary counterpart for 312 is 100111000. The result variable’s value is 24, whose binary counterpart is 11000. If you observe the following representation
10011000
100111000
11000
You can notice how the third line, which represents the result of the And
operation, contains 1 only in positions in which both operands have 1. If you then convert the result back to a decimal number, you get 24. The Or
operator works similarly: It combines two operands into a result; inside such a result, it puts a 1 value if at least one of the operands has a 1 value in a particular position. Consider this code:
Dim result As Integer = 152 Or 312
Both 152 and 312 binary counterparts are the same as the previous example. The Or
operator produces 110111000 as a binary output, whose decimal counterpart is 440. To understand this step, take a look at this comparison:
10011000
100111000
110111000
It’s easy to see that the result contains 1 where at least one of the operands contains 1 in a particular position. The Xor
operator combines two operands into a result; inside such a result, it puts a 1 value if at least one of the operands has a 1 value in a particular position, but not if both have 1 in that position. (In such a case it places 0.) Consider this bitwise operation:
Dim result As Integer = 152 Xor 312
The 152 and 312 binary counterparts are the same as in the preceding example. But this line of code returns 416, whose binary counterpart is 110100000. So let’s see what happened:
10011000
100111000
110100000
As you can see, Xor
placed 1 where at least one of the operands has 1 in a particular position, but where both operands have 1, it placed 0. The Not
operator is probably the easiest to understand. It just reverses the bits of an operand into a result value. For example, consider this line of code:
Dim result As Integer = Not 312
In the following comparison, the second line is the result of the preceding negation:
100111000
011000111
This result has −313 as its decimal counterpart.
Binary Numbers
This book does not teach binary numbers, so the code shown in this and in the following section assumes that you are already familiar with binary representations of decimal numbers.
Shift operators are also something that makes more sense with binary numbers than with decimal or hexadecimal numbers, although you need to provide them via their decimal representations. With shift operators, you can move (that is, shift) a binary representation left or right for the specified number of positions. The left-shift operator is <<
, and the right-shift operator is >>
. For example, consider the following Integer
:
'Binary counterpart is
'101000100
Dim firstValue As Integer = 324
The binary representation for 324 is 101000100. At this point, we want to left-shift the binary for four positions. The following line accomplishes this:
'Returns 5184, which is
'1010001000000
Dim leftValue As Integer = firstValue << 4
With the left-shifting of four positions, the number 101000100 produces 1010001000000 as a result. Such binary representation is the equivalent of the 5184 decimal number, which is the actual value of the leftValue
variable. The right-shift operator works the same but moves positions on the right:
'Returns 20, which is
'10100
Dim rightValue As Integer = firstValue >> 4
This code moves 101000100 for four positions to the right, so the binary result is 10100. Its decimal equivalent is then 20, which is the actual value of the rightValue
variable.
Supported Types
Shift operators support Byte
, Short
, Integer
, Long
, SByte
, UShort
, UInteger
and ULong
data types. When using shift operators with unsigned types, there is no sign bit to propagate; therefore, the vacated positions are set to zero.
As in the previous versions, Visual Basic 2012 still offers concatenation operators—the +
and &
symbols. The main difference is that the +
symbol is intended for numeric additions, although it can also work with strings; the &
symbol is defined only for strings, and should be preferred when concatenating strings so that you can avoid possible errors. Listing 4.2 shows an example of concatenation.
Module ConcatenationOperators
Sub ConcatenationDemo()
Dim firstString As String = "Alessandro"
Dim secondString As String = "Del Sole"
Dim completeString As String = firstString & secondString
'The following still works but should be avoided
'Dim completeString As String = firstString + secondString
End Sub
End Module
As in its predecessors, Visual Basic 2012 still defines some comparison operators. Typically, comparison operators are of three kinds: numeric operators, string operators, and object operators. Let’s see these operators in detail.
You can compare numeric values using the operators listed in Table 4.12.
These operators return a Boolean value that is True
or False
. The following code snippet shows an example (comments within the code contain the Boolean value returned):
Sub NumericOperators()
Dim firstNumber As Double = 3
Dim secondNumber As Double = 4
Dim comparisonResult As Boolean = False
'False
comparisonResult = (firstNumber = secondNumber)
'True
comparisonResult = (secondNumber > firstNumber)
'False
comparisonResult = (secondNumber <= firstNumber)
'True
comparisonResult = (secondNumber <> firstNumber)
End Sub
String comparison was discusses in the section “Working with Strings,” so refer to that topic.
You can compare two or more objects to understand whether they point to the same instance or what type of object you are working with. The three operators for comparing objects are Is
, IsNot
, and TypeOf
. Is
and IsNot
are used to understand whether two objects point to the same instance. Consider the following code:
Dim firstPerson As New Person
Dim secondPerson As New Person
'Returns True, not same instance
If firstPerson IsNot secondPerson Then
End If
'Returns False, not same instance
If firstPerson Is secondPerson Then
End If
'Returns True, same instance
Dim onePerson As Person = secondPerson
If secondPerson Is onePerson Then
End If
firstPerson
and secondPerson
are two different instances of the Person
class. In the first comparison, IsNot
returns True
because they are two different instances. In the second comparison, Is
returns False
because they are still two different instances. In the third comparison, the result is True
because you might remember that simply assigning a reference type just copies the reference to an object. In this case, both secondPerson
and onePerson
point to the same instance. The last example is related to the TypeOf
operator. Typically, you use it to understand whether a particular object has inheritance relationships with another one. Consider the following code snippet:
'Returns True
Dim anotherPerson As Object = New Person
If TypeOf anotherPerson Is Person Then
End If
We have here an anotherPerson
object of type Object
, assigned with a new instance of the Person
class. (This is possible because Object
can be assigned with any .NET type.) The TypeOf
comparison returns True
because anotherPerson
is effectively an instance of Person
(and not simply object). TypeOf
is useful if you need to check for the data type of a Windows Forms or WPF control. For example, a System.Windows.Controls.Button
control in WPF inherits from System.Windows.Controls.FrameworkElement,
and then TypeOf x
is FrameworkElement
and returns True
.
Operators Precedence Order
Visual Basic operators have a precedence order. For further information, refer to the MSDN Library: http://msdn.microsoft.com/en-us/library/fw84t893(v=vs.110).aspx.
Hundreds of programming techniques are based on loops and iterations. Both loops and iterations enable the repetition of some actions for a specific number of times or when a particular condition is True
or False
. All these cases are discussed next.
Iterations in Visual Basic 2012 are performed via the For..Next
and For Each
loops. Let’s analyze them more in details.
A For..Next
loop enables repeating the same action (or group of actions) for a finite number of times. The following code shows an example in which the same action (writing to the Console window) is performed 10 times:
For i As Integer = 1 To 10
Console.WriteLine("This action has been repeated {0} times", i)
Next
In such loops, you need to define a variable of a numeric type (i
in the preceding example) that acts as a counter.
Tip
You can also assign the variable with another variable of the same type instead of assigning a numeric value.
The previous code produces the following result:
This action has been repeated 1 times
This action has been repeated 2 times
This action has been repeated 3 times
This action has been repeated 4 times
This action has been repeated 5 times
This action has been repeated 6 times
This action has been repeated 7 times
This action has been repeated 8 times
This action has been repeated 9 times
This action has been repeated 10 times
Notice that you can also initialize the counter with zero or with any other numeric value.
Tip
You can use the Integer
or UInteger
variables as counters in For..Next
loops. This is because these data types are optimized for the Visual Basic compiler. Other numeric types are also supported but are not optimized, so you are encouraged to always use Integer
or UInteger
.
You can also decide how the counter must be incremented. For example, you could decide to increment the counter of two units instead of one (as in the previous example). This can be accomplished via the Step
keyword:
For i As Integer = 1 To 10 Step 2
Console.WriteLine("Current value is {0}", i)
Next
This code produces the following output:
Current value is 1
Current value is 3
Current value is 5
Current value is 7
Current value is 9
Step can also work with negative numbers and lets you perform a going-back loop:
For i As Integer = 10 To 1 Step -2
Console.WriteLine("Current value is {0}", i)
Next
You can also decide to break a For
loop when a particular condition is satisfied and you do not need to still perform the iteration. This can be accomplished with the Exit For
statement, as shown in the following example:
For i As Integer = 1 To 10
Console.WriteLine("Current value is {0}", i)
If i = 4 Then Exit For
Next
In the preceding example, when the counter reaches the value of 4, the For
loop is interrupted and control is returned to the code that immediately follows the Next
keyword. There is also another way for controlling a For
loop, such as when you need to pass the control directly to the next iteration of the loop when a particular condition is satisfied (which is the opposite of Exit For
). This can be accomplished with the Continue For
statement, as shown in the following code snippet:
For i As Integer = 1 To 10
If i = 4 Then 'Ignore the 4 value
i += 1 'Increments to 5
Continue For 'Continues from next value, that is 6
End If
Console.WriteLine("Current value is {0}", i)
Next
In the preceding example we are doing some edits on the counter. Notice that each time you invoke a Continue For
, the counter itself is incremented one unit.
A For Each
loop allows performing an action or a group of actions on each item from an array or a collection. Although collections are discussed in Chapter 16, I’m providing a code example with them because this is the typical usage of a For Each
loop. For example, consider the following code:
'A collection of Process objects
Dim procList As List(Of Process) = Process.GetProcesses.ToList
For Each proc As Process In procList
Console.WriteLine(proc.ProcessName)
Console.WriteLine(" " & proc.Id)
Next
In the preceding code snippet is a collection containing references to all the running processes on the machine. Each process is represented by an instance of the System.Diagnostics.Process
class; therefore, List(Of Process)
is a collection of processes. Supposing we want to retrieve some information for each process, such as the name and the identification number, we can iterate the collection using a For Each
statement. You need to specify a variable (also known as control variable) that is the same type of the item you are investigating. In the preceding code you are just performing reading operations, but you can also edit items’ properties. For example, you might have a collection of Person
objects and could retrieve and edit information for each Person
in the collection, as in the following code:
'A collection of Person objects
Dim people As New List(Of Person)
'Populate the collection here..
'....
For Each p As Person In people
p.LastName = "Dr. " & p.LastName
Console.WriteLine(p.LastName)
Next
This code will add the Dr
. prefix to the LastName
property of each Person
instance.
For Each Availability
Behind the scenes, For Each
can be used against objects that implement the IEnumerable
or IEnumerable(Of T)
interfaces. Such objects expose the enumerator that provides support for For Each
iterations.
You can still use Exit For
when you need to break out from a For Each
statement. A For Each
loop has better performances with collections than with arrays, but you can use it in both scenarios.
Iterators
Visual Basic 2012 introduces a new feature, the iterators. With iterators, you can return elements from the collection or the array to the caller code while the iteration is still in progress, so that such elements can be used by the caller without having to wait for the iteration to be completed. Iterators are typically used with collections, so this feature is discussed in Chapter 16.
Until Visual Basic 2010, if you wrote a For..Each
loop to iterate the result of a lambda expression or the content of a variable of type IQueryable
or IQueryable(Of T)
, you had to declare a temporary variable to avoid the following error message:
Using the iteration variable in a query might have unexpected results
The following code shows an example:
Private Function SearchProducts(ParamArray keywords As String()) As _
IQueryable(Of Product)
Dim query As IQueryable(Of Product) = dataContext.Products
For Each keyword As String In keywords
query = query.Where(Function(p) p.Description.Contains(keyword))
Next
Return query
End Function
As you can see, a lambda expression exists inside the loop, whose result is assigned to the query
variable. Such a lambda is using the control variable (keyword
) directly, and this was not allowed because only one slot of memory was allocated and every lambda expression or query was referring to the same variable. Because of this, until Visual Basic 2012 you had to rewrite the previous code as follows:
Private Function SearchProducts(ParamArray keywords As String()) As _
IQueryable(Of Product)
Dim query As IQueryable(Of Product) = dataContext.Products
For Each keyword As String In keywords
Dim temp As String = keyword
query = query.Where(Function(p) p.Description.Contains(temp))
Next
Return query
End Function
By declaring a temporary variable (temp
in the example), you could solve the problem. The Visual Basic 2012 compiler solves the memory allocation problem and makes it possible to use the control variable directly, without the need of declaring an intermediate variable. So, in Visual Basic 2012 you could write the previous example as in the first code snippet.
As in the previous versions of the language, Visual Basic 2012 offers two kinds of loops: Do..Loop
and While..End While
. In this section we take a look at both loops.
The Do..Loop
is the most frequently used loop in Visual Basic and the most flexible. This loop can have two behaviors: repeating a set of actions until a condition is false and repeating a set of actions until a condition is true. The first scenario is accomplished using a Do While
statement, as demonstrated in Listing 4.3.
Sub LoopWhileDemo()
Dim max As Integer = 0
Do While max < Integer.MaxValue
max += 1
'Do something else here
If max = 7000000 Then Exit Do
Loop
Console.WriteLine("Done: " & max.ToString)
End Sub
The code is quite easy: Whereas the value of max is less than the maximum value of the Integer type, increment max itself is one unit. Do While
evaluates a False
condition. (The loop goes on because max is less than Integer.MaxValue
.) The code also demonstrates how you can exit from a loop using an Exit Do
statement. This passes the control to the next statement after the Loop
keyword. The other scenario is when you need to evaluate a True
condition. This can be accomplished via a Do Until
loop. Listing 4.4 demonstrates this.
Sub LoopUntilDemo()
Dim max As Integer = 0
Do Until max = Integer.MaxValue
max += 1
If max = 7000000 Then Exit Do
Loop
Console.WriteLine("Done: " & max.ToString)
End Sub
The difference here is that the loop ends when the condition is True
—that is, when the value of max equals the value of Integer.MaxValue
. As before, Exit Do
can end the loop. The interesting thing in both cases is that you can evaluate the condition on the Loop
side instead of the Do
one. Listing 4.5 shows how you could rewrite both examples.
'Loop is executed at least once
Sub LoopUntilBottomDemo()
Dim max As Integer = 0
Do
max += 1
If max = 7000000 Then Exit Do
Loop Until max = Integer.MaxValue
Console.WriteLine("Done: " & max.ToString)
End Sub
'Loop is executed at least once
Sub LoopWhileBottomDemo()
Dim max As Integer = 0
Do
max += 1
If max = 7000000 Then Exit Do
Loop While max < Integer.MaxValue
Console.WriteLine("Done: " & max.ToString)
End Sub
Both loops behave the same way as previous ones, with one important difference: Here the loop is executed at least once.
Performance Tips
The For
loops are faster than Do
ones. Because of this, you should use For
loops, particularly when you know that you will do a finite number of iterations.
A While..End While
loop performs actions when a condition is False
. Listing 4.6 shows an example.
Sub WhileEndWhileDemo()
Dim max As Integer = 0
While max < Integer.MaxValue
max += 1
If max = 7000000 Then Exit While
End While
Console.WriteLine("Done: " & max.ToString)
End Sub
The loop behaves the same as Do While
because both evaluate the same condition.
Note
The While..End While
loop is less efficient than a Do While
and is deprecated, although it is still supported. You should therefore always use a Do While
loop.
The If..Then..Else
is the most classical block for conditionally executing actions. An If
evaluates an expression as True
or False
and, according to this, allows specifying actions to take place. Listing 4.7 shows an example.
Sub IfThenElseDemo()
Console.WriteLine("Type a number")
'Assumes users type a valid number
Dim number As Double = CDbl(Console.ReadLine)
If number >= 100 Then
Console.WriteLine("Your number is greater than 100")
ElseIf number < 100 And number > 50 Then
Console.WriteLine("Your number is less than 100 and greater than 50")
Else
'General action
Console.WriteLine("Your number is: {0}", number)
End If
End Sub
If
checks whether the condition is True
; if so, it takes the specified action. You can also specify to evaluate a condition for False
(for example, If something = False Then
). You can also use an ElseIf
to delimit the condition evaluation. If no expression satisfies the condition, the Else
statement provides an action that will be executed in such a situation.
Coding Tip
The Visual Studio IDE offers a useful feature known as code blocks delimiters selection. Because you can nest different If..Then
blocks or can have a long code file, when you place the cursor near either the If/Then keywords or the End If
statement, the IDE highlights the related delimiter (End If
, if you place the cursor on an If,
and vice versa).
Notice how the code uses an And
operator to evaluate the condition. You can use other operators such as logical and short-circuit operators as well. Another typical example is when you need to check whether a condition is false using the Not
operator. The following is an example:
If Not number >= 100 Then
'Number is False
End If
Not
also requires the same syntax when working with reference types, but in this case you can also use the IsNot
operator. The following example checks whether the instance of the Person
class is not null:
Dim p As Person 'p is actually null
'You can check with IsNot
If p IsNot Nothing Then
'p is not null
Else
'p is null
End If
IsNot
is not available with value types.
The IIf
operator evaluates an expression and returns one of two objects, according to the evaluation result which can be true or false. The syntax for the IIf
operator is the following:
Dim result As Object = IIf(Expression As Boolean, TruePart As Object,
FalsePart As Object)
So the first argument of the operator is a Boolean expression. If the expression result is True
, the operator returns the second argument (TruePart
) otherwise it returns the third one (FalsePart
). The following code demonstrates how to evaluate if the user input is null and show a text message according to the result:
Dim input As String = Console.ReadLine
Dim result As Object = IIf(input Is Nothing, "Enter something please",
String.Concat("You entered ", input))
Console.WriteLine(CStr(input))
Remember that the IIf
operator and its arguments must return Object
, so you will have to make the appropriate type conversions when needed.
Select Case
is a statement that allows evaluating an expression against a series of values. Generally, Select Case
is used to check whether an expression matches a particular value in situations evaluated as True
. Listing 4.8 provides an example.
Sub SelectCaseDemo()
Console.WriteLine("Type a file extension (without dot):")
Dim fileExtension As String = Console.ReadLine
Select Case fileExtension.ToLower
Case Is = "txt"
Console.WriteLine("Is a text file")
Case Is = "exe"
Console.WriteLine("Is an executable")
Case Is = "doc"
Console.WriteLine("Is a Microsoft Word document")
Case Else
Console.WriteLine("Is something else")
End Select
End Sub
The code in Listing 4.8 simply compares the string provided by the user with a series of values. If no value matches the string, a Case Else
is used to provide a general result. Comparison is performed with the Is
operator and the equality operator. The following syntax is also accepted:
Case "txt"
IntelliSense adds the Is =
symbology by default. You can also break from a Select Case
statement in any moment using an Exit Select
statement. Select..Case
also offers another syntax to apply when you want to check whether a value falls within a particular range. To accomplish this, you use the To
keyword instead of the Is =
operators, as in the following code that waits for the user to enter a number and then checks what range the number falls in:
Console.WriteLine("Enter a number from 1 to 50:")
Dim result As Integer = CInt(Console.ReadLine)
Select Case result
'The user entered a number in the range from 1 to 25
Case 1 To 25
Console.WriteLine("You entered {0} which is a small number",
result.ToString)
'The user entered a number in the range from 26 to 50
Case 26 To 50
Console.WriteLine("You entered {0} which is a high number",
result.ToString)
'The user entered a number < 1 or > 50
Case Else
Console.WriteLine("You entered a number which is out of range")
End Select
In other words, considering the preceding example, Case 1 To 25
means in case the value to check is in the range between the left value (1) and the right value (25), then take the nested action. You can also check for multiple items not ranged with the following syntax:
Select Case result
'The user entered 1 or a value between 5 and 10 or 12
'All other values are excluded
Case Is = 1, 5 To 10, 12
'...
End Select
Coding Tip
For the If..End If
block, the code blocks delimiters selection feature is also available for Select..End Select
blocks.
Performance Tips
The Visual Basic compiler evaluates expressions as a sequence. Because of this, in Select Case
statements it evaluates all conditions until the one matching the value is found. Consequently, the first Case
instructions in the sequence should be related to values considered the most frequent.
Constants provide a way for representing an immutable value with an identifier. There could be situations in which your applications need to use the same value (which can be of any .NET type); therefore, it can be convenient to define an easy-to-remember identifier instead of a value. What would happen if such a value were a Long
number? You declare constants as follows:
Const defaultIntegerValue As Integer = 123456789
Const aConstantString As String = "Same value along the application"
Constants are read-only fields that can be declared only at the module and class level or within a method and must be assigned with a value when declared. Constants within methods have public visibility by default, whereas constants at the module and class level can have one of the .NET scopes, as in the following lines:
Private Const defaultIntegerValue As Integer = 123456789
Public Const aConstantString As String= "Same value along the application"
The reason constants must be assigned when declared is that the expression is evaluated at compile time. Starting from Visual Basic 2008, there are a couple of things to consider. Look at the following line of code:
Private Const Test = "Test message"
The type for the Test
variable is not specified. Until Visual Basic 2005, with Option Strict Off,
such a declaration would assign Object
. In later versions of the language, if Option Infer
is On
, the compiler assigns String
; if it is Off
, the compiler goes back to assigning Object
.
Visual Basic provides an alternative way for invoking object members—the With..End With
statement. Consider the following code block, in which a new Person
class is instantiated and then properties are assigned while methods are invoked:
Dim p As New People.Person
p.FirstName = "Alessandro"
p.LastName = "Del Sole"
Dim fullName As String = p.ToString
Using a With..End With
statement, you just need to specify the name of the class once and then type a dot so that IntelliSense shows members you can use, as follows:
Dim p As New People.Person
With p
.FirstName = "Alessandro"
.LastName = "Del Sole"
Dim fullName As String = .ToString
End With
There is no difference between the two coding techniques, so feel free to use the one you like most. With..End With
offers the advantage of speeding up the code writing a little and can be useful if you have a lot of members to invoke or assign at one time.
With..End With
has no equivalent in other .NET languages, so if you have to interoperate, assigning members the normal way can be a good idea. Although the compiler translates the With..End With
blocks as single members’ invocations, in such scenarios the best approach is a .NET-oriented coding style instead of a VB-oriented one.
Every development environment relies on data types. The .NET Framework relies on two kinds of data types: value types and reference types. Both kinds of types are managed by the Common Type System, which provides a common infrastructure to .NET languages for working with types. In this chapter, you learned the important basics of the .NET development and the Visual Basic language, which can be summarized as follows:
• Common Type System
• Value types and reference types
• System.Object
and inheritance levels in value types and reference types
• Memory allocation of both value types and reference types
• Converting between types and conversion operators
• Most common value types and reference types
• Common operators
You often need to work with and analyze data types. Visual Basic 2012 provides several ways for performing work on types and the data they store. To accomplish this, you can use
• Iterations, such as For..Next
and For..Each
• Loops, such as Do..Loop
• Conditional code blocks, such as If..End If
and Select Case..End Select
It’s important to understand all the preceding features because they often recur in your developer life; these features appear extensively in the rest of the book. But you also might encounter errors when working with types. The next two chapters discuss two fundamental topics in the .NET development with Visual Basic: debugging and handling errors.
3.138.119.106