Types in Visual Basic .NET

I l @ ve RuBoard

Types are an integral part of the CLR. By extension, this obviously makes types an important part of Visual Basic .NET and, indeed, of any CLR language. Types can be broken down into two distinct categories: value types and reference types. Value types are typically stored directly on the stack, inside an array, or within another type. If a value type is contained by a reference type, it is stored on the heap along with that reference type's other member variables. Value types are accessed directly. Reference types are stored in the runtime heap and can be accessed only indirectly, through a reference to that type. When you assign one reference variable to another, you do not copy the underlying reference type ”you merely copy the reference to the object. (Both variables point to the same object.) When you assign a value type to another value type, you in effect copy the value of the object.

Aside from the primitive types ( Boolean, Byte, Short, Integer, Long, Single, Double, Decimal, Date, Char, String, and Object ), Visual Basic .NET defines five main types: Enum , Structure , Module , Interface , and Class . Structures, enumerations, and all the primitive data types except String and Object are value types. All other types are reference types.

Note

All value types inherit from System.ValueType. At run time, you can use the Microsoft.VisualBasic.IsReference method to determine whether an object is a value type or a reference type. Alternatively, you can use the object resolution syntax with the TypeOf operator, as in If TypeOf(obj) Is System.ValueType Then .


Whenever any member of a reference type is accessed, the runtime must first dereference the pointer to the heap in order to perform the desired operation. This is intuitively slower than accessing values directly from the stack without the pointer redirection. What this really means is that you should carefully consider whether a reference or value type is most advantageous when you build your applications. The reality is that developers tend to prefer reference types (classes) over value types (structures) because classes offer more flexibility. In most cases, the additional overhead of a reference type is negligible and becomes significant only when you perform intensive calculations (as in graphics applications and games ).

Type Magic: Boxing and Unboxing

Riddle me this: when is a reference type not a reference type? Answer: When it's a value type, of course! Crummy jokes aside, the CLR does allow a value type to be treated as a reference type. What makes this possible is System.Object and a process known as boxing .

All objects (including System.ValueType ) inherit from System.Object , which is itself a reference type. System.Object is the base of all reference and value types and can be a container for both types. The framework is designed this way because in some situations it is advantageous for a value type to behave like a reference type. To help put this in perspective, consider the Console.WriteLine method and the Date data type. There is no specific overload of Console.WriteLine that accepts a Date type (which is a value type). There is, however, an overload that accepts an Object parameter. If it were possible to coerce the Date type into an Object , Console.WriteLine would work just fine, right? Exactly.

What actually happens is that a wrapper for the value type, in this case a Date , makes it look like a reference type. This wrapper is allocated on the heap, and the value type's value is copied into the wrapper (so a copy of the date is stored in the heap). The system marks the wrapper to identify it as a value type masquerading as a reference type. This process of moving a value type onto the heap and providing a reference type wrapper is known as boxing, and the reverse process is known as unboxing. Essentially, the process of boxing and unboxing allows any type to be treated as an object.

The one caveat to all of this wonderful type wizardry is performance. Loading value types onto the heap and generating a wrapper does not come without cost. As you can see, not understanding the impact of boxing and when it occurs can introduce lots of performance bugs . Consider the following code:

 SubMain() DimiAsInteger=5 Console.WriteLine("{0},{1}",i,i) 'ManuallyboxtheInteger DimoAsObject=i Console.WriteLine("{0},{1}",o,o) EndSub 

Console.WriteLine provides a lot of overloads, none of which defines Date as the parameter type, so one of the overloads using System.Object is used. In the above code, the first Console.WriteLine boxes the Integer twice: once for each parameter. A more efficient approach would be to box the Integer only once and pass it to Console.WriteLine . This is exactly what's happening with the second call to Console.WriteLine . Storing the value of any value type in a variable of type System.Object is an explicit way to perform the boxing operation. When I assign i to o , the runtime boxes the value of i . By passing the o variable to Console.WriteLine , I avoid an additional boxing call because the boxing was done explicitly. That's boxing in a nutshell .

Note

Manual boxing is not always more efficient. There is a performance difference if you're dealing with an immutable value type ( Date ) versus a mutable type (such as Integer or Double ). If the type to be boxed is immutable, you're better off just passing the variable to a method and allowing the runtime to box it as normal. You'll see a benefit to manually boxing your variables if you need to pass them repeatedly to functions that would otherwise result in a boxing operation each time the variable was marshaled. (To investigate this subject further, you can check out the MSIL and CLR specifications at http://www.msdn.microsoft.com.)


Let's take a closer look at each type that's available through Visual Basic .NET.

Classes and Modules

Classes are the definitive reference type. They are extremely flexible in that they can contain data members (constants, variables, and events), function members ( methods , properties, indexers, operators, and constructors), events, and nested types. Class types support implementation inheritance ”the heart and soul of all object oriented programming. All Visual Basic .NET classes by definition derive from the System.Object class.

Modules are also reference types, but that distinction isn't very helpful. Behaviorally, modules don't work any differently than their Visual Basic 6.0 counterparts, with the exception of namespace resolution. In other words, Visual Basic 6.0 does not support hierarchical modules or resolving members through complete names such as Module1.MyFunction . Modules are part of a flat structure. By contrast, you can think of Visual Basic .NET modules as restricted classes. All of their members are Shared ; cannot be nested inside other classes, structures, or modules; can never be instantiated ; do not support inheritance; and cannot implement interfaces.

Structures

The Structure type in Visual Basic .NET is an evolution of the user -defined type (UDT) from Visual Basic 6.0. Structures can now have public methods and can even implement interfaces. Structures are similar to classes; the key difference is that structures are value types and classes are reference types. Being a value type offers structures certain performance advantages over classes, but structures lack other OOP functionality that make classes so versatile. Structures work best when you use them to design your own composite data types. Consider, for example, the Point structure from the System.Drawing namespace, which might look something like this:

 PublicStructurePoint PublicXAsInteger PublicYAsInteger PublicSubNew(ByValxAsInteger,ByValyAsInteger) Me.X=x Me.Y=y EndSub PublicOverloadsFunctionEquals(ByValobjAsObject)AsBoolean DimptasPoint IfTypeOfobjIsPointThen pt=CType(obj,Point) Return(X=pt.X)And(Y=pt.Y) EndIf EndFunction PublicOverridesFunctionToString()AsString ReturnString.Format("(X={0},Y={1})",X,Y) EndFunction EndStructure 

Notice that I defined not only a constructor, but two overloaded methods (overriding the base System.ValueType class methods). In this way, I customized the behavior of the structure and made it more user friendly. But that's not the half of it. Read on.

Structures and Interfaces

It might seem odd that a structure can implement interfaces, but any doubts you have will be quickly swept away when you see what you can do with it. Let's extend the Point structure example for our own nefarious purposes. Let's say that we're developing a graphics application. We're using the MyPoint structure to store coordinate pairs, but we want to sort these pairs in order of distance from the origin (coordinates x=0 , y=0 ). The calculation itself is pretty simple, but we don't want to have to implement a sort algorithm along with all of the logic. It would look ugly and be a pain to maintain. The solution can be found in the IComparable interface. Check this out:

 PublicStructureMyPoint ImplementsIComparable PublicXAsInteger PublicYAsInteger PublicFunctionCompareTo(objAsObject)_ AsIntegerImplementsIComparable.CompareTo DimptAsMyPoint=CType(obj,MyPoint) Dimd1AsDouble=(X^2)+(Y^2) Dimd2AsDouble=(pt.X^2)+(pt.Y^2) ReturnCInt(d1d2) EndFunction EndStructure 

This sample demonstrates that we can now compare two MyPoint types using the IComparable interface. This is only part of the trick. Let's say we also have an array of MyPoint structures like this:

 Dimpts()AsNewMyPoint(100) 

How can we sort these values efficiently ? It turns out that this is easy. The System.Array class defines a Sort method that will do the work for us. All we need to do in advance is implement the IComparable interface. (This works for classes, too.) Sorting the array is as simple as the following line:

 System.Array.Sort(pts) 

Doesn't get much easier than that, does it?

Remember that structures are very efficient storage objects and that you can extend them to provide very flexible behavior, but they are no substitute for classes. Furthermore, using the IComparable interface when implemented on a structure would cause a lot of boxing and unboxing. In fact, with the previous example, the boxing and unboxing overhead could very easily make a reference type a far better solution. In other words, because of the boxing overhead a class-based implementation of the point structure would be more efficient if you expect the IComparable interface to be used frequently.

Choosing between structures and classes is not always easy; both types have their own strengths and weaknesses. Consider not only how your objects will be stored but also how they will be used. This will greatly affect your choice of a class versus a structure implementation. Remember that when efficiency of storage and access is required, structures are more likely to fit the bill. When flexibility, inheritance and all of those other object-oriented niceties are needed, a class implementation is probably what you need.

Interfaces

COM developers are no strangers to interfaces, and rightly so. COM wouldn't be much use without interfaces ”the entire concept of COM revolves around interface-based programming. In Visual Basic .NET, an Interface is a specific reference type and provides some interesting twists on the classic COM idea of an interface:

  • Interfaces can define properties, methods, and events.

  • Interfaces can derive from other interfaces.

  • Interfaces support multiple inheritance.

  • Only classes and structures can implement interfaces.

  • Classes and structures can implement any number of interfaces.

Note

You might have noticed that interfaces are similar to abstract classes in that the implementation details are left to the child class. The major limitation of classes is that only single inheritance is supported, whereas a class can implement a virtually unlimited number of interfaces.


The fundamental reason for using interfaces over classes is the same in Visual Basic .NET. They allow you to separate definition from implementation, which reduces the risk of breaking existing applications as your objects evolve . Interfaces help group together functionality that can be implemented by many objects, and they provide a useful distribution method. If you design your objects to implement specific interfaces, client objects only need to know about the interface to use those objects. You don't need to distribute the classes themselves .

Naming Conventions

When you create your own interfaces, try to follow the conventions used by the .NET Framework. Start the name with a capital I , followed by a capital letter of the first part of the name, and then follow the Pascal casing rules described in Chapter 1. Examples include IComparable , IComboBox , IControl , and IListBox .

You'll see more examples of interfaces being used in different contexts throughout this book. This should start you thinking about how they might fit into your applications. Interfaces are an important part of Visual Basic .NET, and you should familiarize yourself with them.

Enumeration Types

Enumerations are a special kind of value type that inherits from the System.Enum type. They symbolically represent a set of values of one of the primitive integral types ( Byte , Short , Integer , or Long ). In other words, enumerations are a way of giving nice names to a list of constants. They are also special in that they are strongly typed, which prevents unintentional misuse. The standard syntax for declaring enumerations looks like the following:

 EnumIdentifier[AsIntegralTypeName] EnumMember1[=Value] EnumMember2[=Value] ... EndEnum 

Let's start with a simple example of declaring enumerations:

 EnumDayOfWeek Sunday Monday Tuesday Wednesday Thursday Friday Saturday EndEnum EnumDayOfWeekA Sunday=0 Monday=1 Tuesday=2 Wednesday=3 Thursday=4 Friday=5 Saturday=6 EndEnum 

It is interesting to note in the above code that the two declared enumerations ( DayOfWeek and DayOfWeekA ) are functionally equivalent. If you do not assign a value to each member of the enumeration, Visual Basic .NET will implicitly do it for you. When Visual Basic .NET assigns values to enumeration members, it assigns them incrementally. This means that an enum with four members and no explicit value assignments will be numbered 0, 1, 2, and 3. The general rule is that the value assigned to enum members is either 0 (if it is the first member) or the value of the immediately preceding member incremented by one.

Enumerations offer a type-safe way to provide constant values throughout an application. In your implementations of enumerations, it is generally good design practice to group them according to their function and intended use. After all, that is the point behind using them in the first place. Misusing enumerations will lead to general confusion in your projects, and they won't be worth the bother.

The Enum declaration also gives you control over how the values are stored. By using the optional As argument, you can control the size of the underlying value type. As I already mentioned, you can use enumerations to represent Byte , Short , Integer , or Long values. You can make your memory allocations more efficient by selecting an appropriate integral type for the Enum .

Enumerations do have other uses, however. If it is important that the underlying value represent a specific number, you must select an appropriate storage type. Consider the following example:

 PublicEnumTestAsInteger A=&H4000 B=&HB0000000 C=3242938 D=-213784383 EndEnum 

Here we needed to store values that required at least 4 bytes, so we defined the Enum to have an underlying type of Integer . If you were to select a storage type that is too small to properly represent the values (for example, a Short ), you would get a compile error that states "Constant expression not representable in type 'Short'." The default storage type for enumerations is Integer . For the most part, I'd recommend sticking to the default underlying type for most of your enums. If size is an issue, going with a smaller type is perfectly reasonable.

Neat Enum Tricks

The System.Enum class has many useful members that can help you make your code more robust as well as make your life easier. For example, you can use enumerations to perform input validation. System.Enum defines the IsDefined method to check whether a constant is a legal value for that Enum . Using the previous example of the DayOfWeek Enum , check out the following example:

 'ThisreturnsFalse.Theinteger10isnotavalidDayoftheWeek System.Enum.IsDefined(GetType(DayOfWeek),10) 'ThisreturnsTrueforanyintegerbetween0and6 System.Enum.IsDefined(GetType(DayOfWeek),5) 

Using IsDefined makes input validation easier, but there's plenty more where that came from. In all, the System.Enum type defines the shared methods described in Table 2-5 for common use with all enumerations.

Table 2-5. System.Enum Methods

Method

Description

Format

Converts the specified value of a specified enumerated type to its equivalent string representation based on the specified format.

GetName

Retrieves the name of the constant in the specified enumeration that has the specified value.

GetNames

Retrieves an array of the names of the constants in a specified enumeration.

GetType (inherited from Object )

Gets the Type of the current instance.

GetUnderlyingType

Returns the underlying type of the specified enumeration.

GetValues

Retrieves an array of the values of the constants in a specified enumeration.

IsDefined

Indicates whether a constant with a specified value exists in a specified enumeration.

Parse

Overloaded. Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.

ToObject

Overloaded. Returns an instance of the specified enumeration type that's set to the specified value.

Go ahead and play with the Enum type. There's a great deal that you can do with it to make your life easier.

Delegates and Events

Unlike procedural languages, Visual Basic is based on an event-driven programming model. The application responds to user-generated or system-generated events. An event is simply a signal sent by an object that informs all the interested parties that something has occurred. In the nomenclature of events, we talk of publishers and subscribers. Applications become subscribers by signing up for events provided by publishers. Events are implemented in Visual Basic .NET using delegates. Delegates are a way of defining a method signature that a subscriber must adhere to.

Note

You can think of a delegate as a strongly typed function pointer.


Defining Your Delegates

The syntax for defining a delegate is simple. Take the following example from the .NET Framework that defines the MouseEventHandler delegate:

 PublicDelegateSubMouseEventHandler(senderAsObject,_ eAsMouseEventArgs) 

This example defines the signature of a method that can receive mouse events. When you publish events using this delegate type, clients must define their event handler methods so that the signatures match. Note that delegates are not limited to publishing events. You can use delegates in many ways. Here's a creative example that uses an external class to provide custom sort methods for another class:

 'Thisisaclassthatimplementsasortmethod.Thespecificsort 'methodusedontheinternalarraym_Arrayiscustomizableby 'settingthesortmethodpropertytotheaddressofamethodthat 'hasthesamesignatureastheSortHandlerdelegate. PublicClassMyStuff PublicSubNew() m_Array=NewString(5){"0", "1", "2", "3", "4", "5"} SortMethod=AddressOfMe.DefaultSort EndSub 'DefiningtheDelegate PublicDelegateSubSortHandler(ByValaryAsSystem.Array) 'Thisisavariablethatstoresareferencetoadelegate Privatem_SortMethodAsSortHandler Privatem_Array()AsObject PrivateSubDefaultSort(ByValaryAsSystem.Array) Console.WriteLine("CallingDefaultSort") EndSub PublicSubSort() m_SortMethod(m_Array) EndSub PublicWriteOnlyPropertySortMethod()AsSortHandler Set(ByValmethodAsSortHandler) m_SortMethod=method EndSet EndProperty EndClass 'Thisisaclassthatimplementscustomsortalgorithmsforanarray 'eachofthesortmethodssignaturesmatchthesignatureoftheSortHandler 'delegate.Thepowertodoingthisisitallowscustombehaviortobe 'specifiedcompletelyoutsideofthe PublicClassCustomSortAlgorithms PublicSubQuickSort(ByValaryAsSystem.Array) 'Implementaquicksortalgorithm Console.WriteLine("CallingQuickSort") EndSub PublicSubBubbleSort(ByValaryAsSystem.Array) 'Implementabubblesortalgorithm Console.WriteLine("CallingBubbleSort") EndSub PublicSubSimpleSort(ByValaryAsSystem.Array) 'Implementasimplesortalgorithm Console.WriteLine("CallingSimpleSort") EndSub EndClass PublicModuleModule1 PublicSubMain() DimmAsNewMyStuff() DimsAsNewCustomSortAlgorithms() 'Let'stryouteachofthesortmethods,starting 'withthedefaultone.We'llreplacethesortmethod 'withourthreecustomones. m.Sort() m.SortMethod=AddressOfs.BubbleSort m.Sort() m.SortMethod=AddressOfs.SimpleSort m.Sort() m.SortMethod=AddressOfs.QuickSort m.Sort() Console.ReadLine() EndSub EndModule 

Notice that you can use delegates to define a variable that represents a method. You can replace the contents of that variable at any time without having to fundamentally modify your code. Because the method signature is guaranteed by the delegate, you can dynamically change the actual method being called, by replacing the contents of the delegate. Pretty darn cool, huh?

Declaring and Publishing Events

Continuing with the example of the MouseEventHandler delegate, let's look at how publishing and subscribing to an Event is not much more difficult than declaring your delegates. Declaring the events is easy. You can declare events on your interfaces and/or classes. The syntax is the same either way and looks like this:

 PublicClassMouseEventClass ... PublicEventMouseUpAsMouseEventHandler PublicEventMouseDownAsMouseEventHandler EndClass 

Raising the event requires only a little more work and another keyword: RaiseEvent . A typical practice is to create a protected class method to actually raise the event (to ensure that only the class and its derived types can actually fire the event). If the base class does not provide any method to raise an event, that event can be raised only by that base class; none of its derived classes will have that capability. Ultimately, this is a design decision you have to make, but providing protected members for raising events is a common design practice that's used extensively throughout the .NET Framework. Here is an example:

 ProtectedSubOnMouseUp(eAsMouseEventArgs) RaiseEventMouseUp(Me,e) EndSub ... 'FiretheEvent OnMouseUp(newMouseEventArgs(MouseButtons.Left,1,0,0,0)) 

That pretty much covers the publisher side of the event process. Now we just need to look at how to subscribe to these events.

Note

You can create an event without first declaring a delegate, by using an alternate form of the Event declaration. The problem is that this results in the creation of a separate delegate type for each event. Defining your delegates separately, as we just did, allows them to be reused and is better overall design practice. If you're interested in investigating this further, see Section 7.3 of the Visual Basic .NET Language Specification for more information about declaring events.


Subscribing to the event is simple. First, you must declare the variable containing the event using the WithEvents keyword. Your class's methods only have to match the signature of the delegate (the types, not the variable names ”the variable names are not important) and use the Handles keyword on your methods, as in the following example:

 PublicSubMyMouseHandler(senderAsObject,_ eAsMouseEventArgs)_ HandlesSampleClass.MouseUp ... EndSub 

An alternative method of subscribing to events is available through the use of the AddHandler and RemoveHandler statements. AddHandler allows you to mirror the functionality of the Handles clause ”hooking up an event handler ”and RemoveHandler allows you to unsubscribe from an event. These two statements give you much greater flexibility over how you subscribe to events in an application. Instead of the more traditional static compile-time event subscription, you can handle everything at run time and provide much more sophisticated behaviors. For instance, you can subscribe one method to multiple events (as long as the delegate signatures are identical), subscribe multiple methods to the same event, or anything in between. Even better, you do not need to use the WithEvents clause on your variable declarations if you use AddHandler and RemoveHandler .

More Info

I haven't provided a sample showing AddHandler and RemoveHandler , but the syntax is very easy to understand and the Visual Basic .NET documentation is an excellent reference.


I l @ ve RuBoard


Designing Enterprise Applications with Microsoft Visual Basic .NET
Designing Enterprise Applications with Microsoft Visual Basic .NET (Pro-Developer)
ISBN: 073561721X
EAN: 2147483647
Year: 2002
Pages: 103

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