Working with Objects

In the .NET environment in general and within Visual Basic in particular, you use objects all the time without even thinking about it. As noted earlier, every variable, every control on a form—in fact, every form—inherits from System.Object. When you open a file or interact with a database, you are using objects to do that work.

Objects Declaration and Instantiation

Objects are created using the New keyword, indicating that you want a new instance of a particular class. There are numerous variations on how or where you can use the New keyword in your code. Each one provides different advantages in terms of code readability or flexibility.

The most obvious way to create an object is to declare an object variable and then create an instance of the object:

Dim obj As TheClass
obj = New TheClass()

The result of this code is that you have a new instance of TheClass ready for use. To interact with this new object, you use the obj variable that you declared. The obj variable contains a reference to the object, a concept explored later.

You can shorten the preceding code by combining the declaration of the variable with the creation of the instance, as illustrated here:

Dim obj As TheClass = New TheClass()
Dim obj As New TheClass()
Dim obj = New TheClass()

Note
At run time there is no difference between the first example and this one, other than code length.

The preceding code shows three ways to both declare the variable obj as data type TheClass and create an instance of the class. In all cases you are immediately creating an object that you can use. It is up to you how you create these instances, as it is really a matter of style.

Such flexibility is very useful when working with inheritance or multiple interfaces. You might declare the variable to be of one type — say, an interface — and instantiate the object based on a class that implements that interface. You will revisit this syntax when interfaces are covered in detail in Chapter 4.

So far, you've been declaring a variable for new objects, but sometimes you simply need to pass an object as a parameter to a method, in which case you can create an instance of the object right in the call to that method:

DoSomething(New TheClass())

This calls the DoSomething method, passing a new instance of TheClass as a parameter. This can be even more complex. Perhaps, instead of needing an object reference, your method needs an Integer. You can provide that Integer value from a method on the object:

Public Class TheClass
  Public Function GetValue() As Integer
    Return 42
  End Function
End Class

You can then instantiate the object and call the method all in one shot, thus passing the value returned from the method as a parameter:

DoSomething(New TheClass().GetValue())

Obviously, you need to carefully weigh the readability of such code against its compactness. At some point, having code that is more compact can detract from readability, rather than enhance it.

Object References

Typically, when you work with an object, you are using a reference to the memory that contains that object's data. Conversely, when you are working with simple data types, such as Integer, you are working with the actual value, rather than a reference. Let's explore these concepts and see how they work and interact.

When you create a new object using the New keyword, you are allocated memory on the heap and store a reference to the memory for that object in a variable on the stack, as shown here:

Dim obj As New TheClass()

This code creates a new instance of TheClass. You gain access to this new object via the obj variable. This variable points to a memory location on the stack that holds a reference to the object. You might then do something like this:

Dim another As TheClass
another = obj

Now, you have a second variable, another, which also has a memory location on the stack that references the same object data on the heap. You can use either variable interchangeably, as they both reference the exact same object. Remember that the variable you have is not the object itself but just a reference, or pointer, to the object.

As noted earlier in this chapter, objects are reference type, meaning the values are stored on the managed heap with only pointers kept on the stack.

Early Binding versus Late Binding

One of the strengths of Visual Basic has long been that it provides access to both early and late binding when interacting with objects. Early binding means that at compile time, the compiler recognizes the type of each variable and prepares the code to directly interact with a specific type of object by directly calling its methods. Because the Visual Basic compiler knows the object's data type ahead of time, it can directly compile code to invoke the methods on the object. Early binding also enables the IDE to use IntelliSense to aid development efforts by enabling the compiler to ensure that you are referencing methods that exist and are providing the proper parameter values.

Late binding means that your code interacts with an object dynamically at run time. This provides a great deal of flexibility, because the code doesn't care what type of object it is interacting with as long as the object supports the methods you want to call. Because the type of the object is not known by the IDE or compiler, neither IntelliSense nor compile-time syntax checking is possible, but in exchange you get unprecedented flexibility.

Visual Basic provides a switch for you to decide if you want to enable late binding within your application. Enabling strict type checking by using Option Strict On in the project's Properties dialogue or at the top of the code modules, disables late binding. When Option Strict is on, the IDE and compiler enforce early binding behavior.

By default, Option Strict is turned off, so you have easy access to the use of late binding within the code. Chapter 1 discusses Option Strict. You can change this default directly in Visual Studio 2012 by selecting Tools ⇒ Options from the VS menu. The Options dialog is shown in Figure 3.11. Expanding the Projects and Solutions node reveals the VB defaults. Feel free to change any of these default settings.

Figure 3.11 Resetting the default settings for new projects in Visual Studio

3.11

Implementing Late Binding

Late binding occurs when the compiler cannot determine the type of object that you'll be calling. This level of ambiguity can be achieved using the Object data type. A variable of data type Object can hold virtually any value, including a reference to any type of object. Thus, code such as the following could be run against any object that implements a DoSomething method that accepts no parameters:

Option Strict Off
        
Module LateBind
  Dim x as Integer = 20
  Dim y = DoWork(x)

  Public Function DoWork(ByVal obj As String) as String
   Return obj.substring(0, 3)
  End Function
End Module

If the object passed into this routine cannot be converted to a string then method, then an exception will be thrown. Note, however, that you are passing an integer into the method. Late binding allows Visual Basic to determine at run time that the inbound parameter needs to be converted to a string, and Visual Basic will automatically handle this conversion. Keep in mind, however, that late binding goes beyond what is done by Option Strict. For example, it plays into the ability to determine the exact type associated with a LINQ query at run time.

While late binding is flexible, it can be error prone and is slower than early-bound code. To make a late-bound method call, the .NET run time must dynamically determine whether the target object is actually compatible with what you are trying to do. It must then invoke that a conversion on your behalf. This takes more time and effort than an early-bound call, whereby the compiler knows ahead of time that the method exists and can compile the code to make the call directly. With a late-bound call, the compiler has to generate code to make the call dynamically at run time.

Data Type Conversions

When developing software, it is often necessary to take a numeric value and convert it to a string to display in a text box. As you've seen this can be done implicitly via late binding. Similarly, it is often necessary to accept input from a text box and convert this input to a numeric value. These conversions, unlike some, can be done in one of two fashions: implicitly or explicitly.

Implicit conversions are those that rely on the runtime system to adjust the data via late binding to a new type without any guidance. Often, Visual Basic's default settings enable developers to write code containing many implicit conversions that the developer may not even notice.

Explicit conversions, conversely, are those for which the developer recognizes the need to change a variable's type and assign it to a different variable. Unlike implicit conversions, explicit conversions are easily recognizable within the code. Some languages such as C# require that all conversions that might be type unsafe be done through an explicit conversion; otherwise, an error is thrown.

It is therefore important to understand what a type-safe implicit conversion is. In short, it's a conversion that cannot fail because of the nature of the data involved. For example, if you assign the value of a smaller type, Short, into a larger type, Long, then there is no way this conversion can fail. As both values are integer-style numbers, and the maximum and minimum values of a Short variable are well within the range of a Long, this conversion will always succeed and can safely be handled as an implicit conversion:

Dim shortNumber As Short = 32767
Dim longNumber As Long = shortNumber

However, the reverse of this is not a type-safe conversion. In a system that demands explicit conversions, the assignment of a Long value to a Short variable results in a compilation error, as the compiler doesn't have any safe way to handle the assignment when the larger value is outside the range of the smaller value. It is still possible to explicitly cast a value from a larger type to a smaller type, but this is an explicit conversion. By default, Visual Basic supports certain unsafe implicit conversions. Thus, adding the following line will not, when Option Strict is Off, cause an error under Visual Basic:

shortNumber = longNumber

One of the original goals of Visual Basic is to support rapid prototyping. In a rapid prototyping model, a developer is writing code that “works” for demonstration purposes but may not be ready for deployment. This distinction is important because in the discussion of implicit conversions, you should always keep in mind that they are not a best practice for production software.

Performing Explicit Conversions

Keep in mind that even when you choose to allow implicit conversions, these are allowed only for a relatively small number of data types. At some point you'll need to carry out explicit conversions. The following code is an example of some typical conversions between different integer types when Option Strict is enabled:

Dim myShort As Short
Dim myUInt16 As UInt16
Dim myInt16 As Int16
Dim myInteger As Integer
Dim myUInt32 As UInt32
Dim myInt32 As Int32
Dim myLong As Long
Dim myInt64 As Int64
        
myShort = 0
myUInt16 = Convert.ToUInt16(myShort)
myInt16 = myShort
myInteger = myShort
myUInt32 = Convert.ToUInt32(myShort)
myInt32 = myShort
myInt64 = myShort
myLong = Long.MaxValue
        
If myLong < Short.MaxValue Then
  myShort = Convert.ToInt16(myLong)
End If
myInteger = CInt(myLong)

The preceding snippet provides some excellent examples of what might not be intuitive behavior. The first thing to note is that you can't implicitly cast from Short to UInt16, or any of the other unsigned types for that matter. That's because with Option Strict the compiler won't allow an implicit conversion that might result in a value out of range or lead to loss of data. You may be thinking that an unsigned Short has a maximum that is twice the maximum of a signed Short, but in this case, if the variable myShort contained a -1, then the value wouldn't be in the allowable range for an unsigned type.

Just for clarity, even with the explicit conversion, if myShort were a negative number, then the Convert.ToUInt32 method would throw a runtime exception. Managing failed conversions requires either an understanding of exceptions and exception handling, as covered in Chapter 6, or the use of a conversion utility such as TryParse, covered in the next section.

The second item illustrated in this code is the shared method MaxValue. All of the integer and decimal types have this property. As the name indicates, it returns the maximum value for the specified type. There is a matching MinValue method for getting the minimum value. As shared properties, these properties can be referenced from the class (Long.MaxValue) without requiring an instance.

Finally, although this code will compile, it won't always execute correctly. It illustrates a classic error, which in the real world is often intermittent. The error occurs because the final conversion statement does not check to ensure that the value being assigned to myInteger is within the maximum range for an integer type. On those occasions when myLong is larger than the maximum allowed, this code will throw an exception.

Visual Basic provides many ways to convert values. Some of them are updated versions of techniques that are supported from previous versions of Visual Basic. Others, such as the ToString method, are an inherent part of every class (although the .NET specification does not define how a ToString class is implemented for each type).

The following set of conversion methods is based on the conversions supported by Visual Basic. They coincide with the primitive data types described earlier; however, continued use of these methods is not considered a best practice. That bears repeating: While you may find the following methods in existing code, you should strive to avoid and replace these calls.

  • Cbool()
  • Cchar()
  • CDbl()
  • Cint()
  • Cobj()
  • CSng()
  • Cbyte()
  • Cdate()
  • Cdec()
  • CLng()
  • Cshort()
  • CStr()

Each of these methods has been designed to accept the input of the other primitive data types (as appropriate) and to convert such items to the type indicated by the method name. Thus, the CStr class is used to convert a primitive type to a String. The disadvantage of these methods is that they only support a limited number of types and are specific to Visual Basic. If you are working with developers who don't have a long Visual Basic history they will find these methods distracting. A more generic way to handle conversions is to leverage the System.Convert class shown in the following code snippet:

Dim intMyShort As Integer = 200
Dim int = Convert.ToInt32(intMyShort)
Dim dt = Convert.ToDateTime("9/9/2001")

The class System.Convert implements not only the conversion methods listed earlier, but also other common conversions. These additional methods include standard conversions for things such as unsigned integers and pointers.

All the preceding type conversions are great for value types and the limited number of classes to which they apply, but these implementations are oriented toward a limited set of known types. It is not possible to convert a custom class to an Integer using these classes. More important, there should be no reason to have such a conversion. Instead, a particular class should provide a method that returns the appropriate type. That way, no type conversion is required. However, when Option Strict is enabled, the compiler requires you to cast an object to an appropriate type before triggering an implicit conversion. Note, however, that the Convert method isn't the only way to indicate that a given variable can be treated as another type.

Parse and TryParse

Most value types, at least those which are part of the .NET Framework, provide a pair of shared methods called Parse and TryParse. These methods accept a value of your choosing and then attempt to convert this variable into the selected value type. The Parse and TryParse methods are available only on value types. Reference types have related methods called DirectCast and Cast, which are optimized for reference variables.

The Parse method has a single parameter. This input parameter accepts a value that is the target for the object you want to create of a given type. This method then attempts to create a value based on the data passed in. However, be aware that if the data passed into the Parse method cannot be converted, then this method will throw an exception that your code needs to catch. The following line illustrates how the Parse function works:

result = Long.Parse("100")

Unfortunately, when you embed this call within a Try-Catch statement for exception handling, you create a more complex block of code. Note that exception handling and its use is covered in Chapter 6; for now just be aware that exceptions require additional system resources for your running code that impacts performance. Because you always need to encapsulate such code within a Try-Catch block, the .NET development team decided that it would make more sense to provide a version of this method that encapsulated that exception-handling logic.

This is the origin of the TryParse method. The TryParse method works similarly to the Parse method except that it has two parameters and returns a Boolean, rather than a value. Instead of assigning the value of the TryParse method, you test it as part of an If-Then statement to determine whether the conversion of your data to the selected type was successful. If the conversion was successful, then the new value is stored in the second parameter passed to this method, which you can then assign to the variable you want to hold that value:

Dim converted As Long
If Long.TryParse("100", converted) Then
    result = converted
End If

Using the CType Function

Whether you are using late binding or not, it can be useful to pass object references around using the Object data type, converting them to an appropriate type when you need to interact with them. This is particularly useful when working with objects that use inheritance or implement multiple interfaces, concepts discussed in Chapter 4.

If Option Strict is turned off, which is the default, then you can write code using a variable of type Object to make an early-bound method call (code file: MainWindow.xaml.vb):

     Public Sub objCType(ByVal obj As Object)
         Dim local As String
         local = obj
         local.ToCharArray()
    End Sub

This code uses a strongly typed variable, local, to reference what was a generic object value. Behind the scenes, Visual Basic converts the generic type to a specific type so that it can be assigned to the strongly typed variable. If the conversion cannot be done, then you get a trappable runtime error.

The same thing can be done using the CType function. If Option Strict is enabled, then the previous approach will not compile, and the CType function must be used. Here is the same code making use of CType (code file: MainWindow.xaml.vb):

     Public Sub CType1(ByVal obj As Object)
         Dim local As String
         local = CType(obj, String)
         local.ToLower()
    End Sub

This code declares a variable of type TheClass, which is an early-bound data type that you want to use. The parameter you're accepting is of the generic Object data type, though, so you use the CType method to gain an early-bound reference to the object. If the object isn't of the type specified in the second parameter, then the call to CType fails with a trappable error.

Once you have a reference to the object, you can call methods by using the early-bound variable local. This code can be shortened to avoid the use of the intermediate variable. Instead, you can simply call methods directly from the data type (code file: MainWindow.xaml.vb):

  Public Sub CType2(obj As Object)
     CType(obj, String).ToUpper()
  End Sub 

Even though the variable you are working with is of type Object and therefore any calls to it will be late bound, you use the CType method to temporarily convert the variable into a specific type — in this case, the type String.


Note
If the object passed as a parameter is not of type specified as the second parameter, then you get a trappable error, so it is always wise to wrap this code in a TryCatch block.

As shown in Chapter 4, the CType function can also be very useful when working with objects that implement multiple interfaces. When an object has multiple interfaces, you can reference a single object variable through the appropriate interface as needed by using CType.

Using DirectCast

Another function that is very similar to CType is the method DirectCast. The DirectCast call also converts values of one type into another type. It works in a more restrictive fashion than CType, but the trade-off is that it can be somewhat faster than CType:

Dim obj As TheClass
        
obj = New TheClass
DirectCast(obj, ITheInterface).DoSomething()

This is similar to the last example with CType, illustrating the parity between the two functions. There are differences, however. First, DirectCast works only with reference types, whereas CType accepts both reference and value types. For instance, CType can be used in the following code:

Dim int As Integer = CType(123.45, Integer)

Trying to do the same thing with DirectCast would result in a compiler error, as the value 123.45 is a value type, not a reference type.

Second, DirectCast is not as aggressive about converting types as CType. CType can be viewed as an intelligent combination of all the other conversion functions (such as CInt, CStr, and so on). DirectCast, conversely, assumes that the source data is directly convertible, and it won't take extra steps to convert it.

As an example, consider the following code:

Dim obj As Object = 123.45
        
Dim int As Integer = DirectCast(obj, Integer)

If you were using CType this would work, as CType uses CInt-like behavior to convert the value to an Integer. DirectCast, however, will throw an exception because the value is not directly convertible to Integer.

Using TryCast

A method similar to DirectCast is TryCast. TryCast converts values of one type into another type, but unlike DirectCast, if it can't do the conversion, then TryCast doesn't throw an exception. Instead, TryCast simply returns Nothing if the cast can't be performed. TryCast works only with reference values; it cannot be used with value types such as Integer or Boolean.

Using TryCast, you can write code like this (code file: MainWindow.xaml.vb):

   Public Sub TryCast1 (ByVal obj As Object)
      Dim temp = TryCast(obj, Object)     
      If temp Is Nothing Then
        ' the cast couldn't be accomplished
        ' so do no work
      Else
        temp.DoSomething()
      End If
  End Sub

If you are not sure whether a type conversion is possible, then it is often best to use TryCast. This function avoids the overhead and complexity of catching possible exceptions from CType or DirectCast and still provides you with an easy way to convert an object to another type.

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

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