By restructuring we mean arranging the code in logical modules and classes, grouping related functions and data together, and using scope rules to achieve better legibility and maintainability.
Goals
·Add modularity
·Go object-oriented
·Encapsulate data access
·Limit visibility by scoping
Restructuring the code
Spaghetti may be delicious, but not when you find it in your code. It's often necessary to rewrite code to reuse it, to understand how it works, to modify it, or to throw away unnecessary code and write new functionality. Let's learn a few simple ways to restructure code to achieve better quality.
PA This sign denotes that Project Analyzer contains a feature to help with a particular task. The use of this utility is not required for restructuring, but it will make it easier. Project Analyzer helps you to understand how your code works. It also reviews source code for some issues mentioned in this article.
Add modularity
Think of a module as a group of related procedures. What you'd want to do is to find groups of procedures that belong together, and put them all to a single module.
The goal is to minimize the number of dependencies between modules. When a module calls procedures from another module, these modules are coupled. Coupling also occurs when a procedure uses variables, constants, declare statements or other elements of another module. You want to keep coupling to a minimum, to make it easier to read the code, and also to make the module a better candidate for reuse in another program.
So how do you find a group of related procedures? That's a tough question. If you know your code (and you should), you probably already have an idea which procedures are related. Now, find out which procedures call these procedures, and where the calls eventually lead to. A call tree is your primary source of calls/called by information. Another thing to consider are the variables, constants and other elements required. PA
There's no single solution to the module division problem. It's more like a trade-off between minimal coupling and optimal module size. You don't want to group all of your procedures into a single giant module, even though that minimizes coupling. It's better to get an idea of a good module size. You'd want modules to contain, say, max 30 procedures (that's not a recommendation, just an example). If the module becomes larger, you might want to consider splitting it into two - even if it increases coupling.
Go object-oriented
Once you've found a group of related procedures, it's time to decide whether you really want a build a new module or rather a new class. You'd want to use a class if the procedures work intensively on a certain set of data. A module is better if the procedures don't work on the same data, or if you can live by passing the data as procedure parameters.
Object-oriented programming may be a way of life, but it's also a practical means to improve the quality of code. We're not going into the details when to use or not to use object-oriented programming. When you suspect you should go object-oriented, you should try it.
Convert a module to a class
Even though classes and modules are different, going from a module to a class may actually be quite simple, provided that the code is suitable.
Before creating your class you should verify that most, if not all, data access is already limited to happen inside the module. By this we mean that no other parts of your program will read or write the variables declared in the module (that will become your class). You ensure this by declaring all variables private. Verify that your project compiles. If it doesn't, you're accessing the variables from outside. You'll need to provide accessor methods to set or get the data. In VB, you build Public (or Friend) properties to get/set the values of private variables.
If your code doesn't compile when your variables are Private, turn them back to Public for now. Write the properties once you have your class ready.
You also need to get rid of public constant and type declarations. Classes don't define public constants or data types. Either change them to Private, or move them to a module that you keep for project-level declarations.
Public Enums are good to leave in the class. That's true if they're required by public methods or properties of your class, but not used in other ways outside of the class. If you can, turn your Enums to Private.
Steps to create a class
1.Create a new empty class
2.Copy all the code "as is" from the module to the new class
3.Remove the module from the project - but don't delete it yet!
4.Compile & fix repeatedly until your code works. You'll need to fix all the places that reference your old module to references to your new class.
The last step should take the most time. You'll need to create object variables to hold instances of your new class, create the instances with the New keyword, and turn procedure calls to object.method syntax.
If you're working in VB classic (6.0), try not to declare your new object variables like this:
Dim obj As New Class
This is a slow way to program. Everywhere you use the variable obj, VB will check if it's already been instantiated or not. This slows down your program. Instead, declare the variable without the New keyword, and instantiate it separately in a sensible place.
Dim obj As Class
Set obj = New Class
You may find out that it did not make sense to go object-oriented. If this happens, just revert back to your original module. You did take a backup copy, didn't you?
Encapsulate data access
Classes shouldn't usually allow variable read/write from outside of the class. Instead, you should provide accessor properties that will set and get the variable values. PA
Encapsulated variables should be declared Private. The use of inheritance in .NET might also justify a Protected variable. The accessor properties are usually Public or Friend depending on your project.
What's so good about encapsulated data? First of all, you can make data read-only (or write-only). So how do you make data read-only (or write-only)? In VB classic, set the Property Let/Set part Private, and you have property that only gets its values from inside the class. In VB.NET you can use the special ReadOnly keyword. Similarly, if you want to create a write-only property, create a Private Property Get, or use VB.NET's WriteOnly keyword.
It's advisable to check the data in the Property Set block before you store it. For example, you can make sure that a numeric value is within acceptable bounds.
So what if you need to change the way you store your data? With properties, no problem. You just rewrite the properties to access the new data storage. No need to touch the code that accesses the properties.
Encapsulation example
Here you can see one example of variable encapsulation. Here the data is read/write, and the acceptable values are from 0 to 130.
Private mAge As Integer ' Encapsulated variable
' Accessor property to retrieve the value
Public Property Get Age() As Integer
Age = mAge
End Property
' Accessor property to set the value, verifying that
' the value is in pre-defined limits
Public Property Let Age(ByVal newAge As Integer)
If newAge >= 0 And newAge <= 130 Then
mAge = newAge
Else
' Age not in limits, raise a customer error
Err.Raise 12345, "MyClass.Age [Let]", "Invalid age"
End If
End Property
Limit visibility by scoping
Not all code should be accessible from everywhere in your project(s). You should hide the details and only give a limited number of points of access to any module or class.
Scoping rules are simple. There are only 2:
1.Use as limiting scope as possible. A limited scope enforces modular programming and encapsulation. You access code only via well-defined interfaces and get rid of unpleasant suprises caused by someone accessing code that's not written to be accessed from outside of its context. Use Private whenever possible. PA
2.Always declare a scope. VB's default scoping rules are somewhat complicated. If you don't give a scope the declaration can be anything from Public to Private, depending on where you wrote it. When you always write a scope you can't misdefine or misunderstand it. So instead of Dim write Private, instead of Sub write Public or Private Sub, instead of Class write Public/Friend/Private Class. The use of Dim is particularly worth noting. You should use Dim only inside procedures, but never outside of them. PA
Scope - Availability- Use
Public - Everywhere- Use when you need to give access to the whole project, or expose the code to other projects.
Friend - In the declaring project - Friend is required when building projects that expose an interface to other projects. Use Friend to keep other projects from accessing the code.
Private - In the declaring class/module - Use for implementation procedures, all data, and enum/user-defined type definitions required only inside the class/module.
Protected (.NET only) - In the declaring class and its descendants - Use in base classes, but only when required. Exposed to other projects via inheritance.
Protected Friend (.NET only) - In the declaring project, and in descendant classes - Use in base classes, but only when required. Exposed to other projects via inheritance.
PA This sign denotes that Project Analyzer contains a feature to help with a particular task. The use of this utility is not required for restructuring, but it will make it easier. Project Analyzer helps you to understand how your code works. It also reviews source code for some issues mentioned in this article.
.NET Programming Standards and Naming Conventions Common .NET Naming Conventions
These are the industry-accepted standard naming conventions for J#, C# and VB.NET programs. For additional information, please see the MSDN help documentation and FX Cop. While individual naming conventions at organizations may vary (Microsoft only suggests conventions for public and protected items), the list below is quickly becoming the de-facto standard in the industry.Please note the absence of Hungarian Notation except in visual controls. These naming standards should find their way into all of your .NET development, including ASP.NET Web applications and .NET Windows Forms applications.)
Namespaces - Pascal Case, no underscores. Use CompanyName.TechnologyName as root. If you don't have a company, use your domain name or your own initials. Note that any acronyms of three or more letters should be pascal case (Xml instead of XML) instead of all caps. - AppliedIS.TimeCard.BusinessRules
IrritatedVowel.Controllers
PeteBrown.DotNetTraining.InheritanceDemo PeteBrown.DotNetTraining.Xml
Assemblies - If the assembly contains a single name space, or has an entire self-contained root namespace, name the assembly the same name as the namespace. - AppliedIS.TimeCard.BusinessRules.dll
IrritatedVowel.Controllers.dll
Classes and Structs - Pascal Case, no underscores or leading "C" or "cls". Classes may begin with an "I" only if the letter following the I is not capitalized, otherwise it looks like an Interface. Classes should not have the same name as the namespace in which they reside. Any acronyms of three or more letters should be pascal case, not all caps.Try to avoid abbreviations, and try to always use nouns.
Widget
InstanceManager
XmlDocument
MainForm
DocumentForm
HeaderControl
CustomerListDataSet (typed dataset)
Collection Classes - Follow class naming conventions, but add Collection to the end of the name
WidgetCollection
Delegate Classes - Follow class naming conventions, but add Delegate to the end of the name
WidgetCallbackDelegate
Exception Classes - Follow class naming conventions, but add Exception to the end of the nameInvalidTransactionException
Attribute Classes - Follow class naming conventions, but add Attribute to the end of the name
WebServiceAttribute
Interfaces - Follow class naming conventions, but start the name with "I" and capitalize the letter following the I
IWidget
Enumerations - Follow class naming conventions. Do not add "Enum" to the end of the enumeration name. If the enumeration represents a set of bitwise flags, end the name with a plural.
SearchOptions (bitwise flags)
AcceptRejectRule (normal enum)
Functions and Subs - Pascal Case, no underscores except in the event handlers.Try to avoid abbreviations. Many programmers have a nasty habit of overly abbreviating everything. This should be discouraged.
Functions and subs must differ by more than case to be usable from case-insensitive languages like Visual Basic .NET
VB: Public Sub DoSomething(...)
C#: public void DoSomething(...)
Properties and Public * Member Variables - Pascal Case, no underscores. Try to avoid abbreviations. Members must differ by more than case to be usable from case-insensitive languages like Visual Basic .NET.
VB: Public Property RecordID As Integer
C#: public int RecordID
Parameters - Camel Case.Try to avoid abbreviations. Parameters must differ by more than case to be usable from case-insensitive languages like Visual Basic .NET.VB: ByRef recordID As Integer
C#: ref int recordID
Procedure-Level Variables - Camel Case
VB: Dim recordID As Integer
C#: int recordID ;
Class-Level Private and Protected Variables - Camel Case with Leading Underscore. In VB.NET, always indicate "Protected" or "Private", do not use "Dim". Use of "m_" is discouraged, as is use of a variable name that differs from the property by only case, especially with protected variables as that violates compliance, and will make your life a pain if you program in VB.NET, as you would have to name your members something different from the accessor/mutator properties.
Of all the items here, the leading underscore is really the only controversial one. I personally prefer it over straight underscore-less camel case for my private variables so that I don't have to qualify variable names with "this." to distinguish from parameters in constructors or elsewhere where I likely will have a naming collision. With VB.NET's case insensitivity, this is even more important as your accessor properties will usually have the same name as your private member variables except for the underscore.
As far as m_ goes, it is really just about aesthetics. I (and many others) find m_ ugly, as it looks like there is a hole in the variable name. It's almost offensive. I used to use it in VB6 all the time, but that was only because variables could not have a leading underscore. I couldn't be happier to see it go away.
Microsoft recommends against the m_ (and the straight _) even though they did both in their code. Also, prefixing with a straight "m" is right out. Of course, since they code mainly in C#, they can have private members that differ only in case from the properties. VB folks have to do something else. Rather than try and come up with language-by-language special cases, I recommend the leading underscore for all languages that will support it.
If I want my class to be fully CLS-compliant, I could leave off the prefix on any C# protected member variables. In practice, however, I never worry about this as I keep all potentially protected member variables private, and supply protected accessors and mutators instead.
VB: Private _recordID As Integer
C#: private int _recordID ;
Controls on Forms - Modified Hungarian Using .NET Class Names, or a common "control" or "ui" prefix for all controls. This keeps the controls together in intellisense and makes UI programming much easier.
In recent projects (the past couple years), I have taken to a single prefix for all my UI controls. I typically use "ux" (I used to use "ui", but it wasn't set apart well in intellisense). "ux" comes from my usual design abbreviations where it means "User eXperience". I have found this to be extremely helpful in that I get the desired grouping in the intellisense even better than if I use "txt", "lbl" etc. It also allows you to change combo boxes to text boxes etc. without having to change the names.
txtUserID, lblHeader, lstChoices, btnSubmit
or "ux" prefix (preferred!)
uxUserIDText, uxHeaderLabel, uxChoiceList, uxSubmitButton
* Public class-level variables are universally frowned upon. It is considered to be a much better practice to use property procedures (accessors and mutators) to provide read and/or write access to a private member variable. If you must expose a member variable to other classes using "Public", follow the property naming conventions, but don't complain if your guilty conscience keeps you up at night ;-).
Please don't copy and paste these conventions on your own site. Feel free instead to link directly to this page. That way you get the ability to automatically get updates when I make them, as well as get that warm fuzzy you get by not copying someone else's work. Schools and accredited educational institutions can paste these conventions on their own sites if and only if they include a direct link to this page an an attribution for the source of the information from "Pete Brown's irritatedVowel.com". Thank you for respecting my wishes on this.
You Can find the standards for publicly exposed classes/properties etc at MSDN . If you want to run a tool to validate your code for public standards and required practices, download FXCop.
Sidebar: Database Naming Conventions
I've found the following naming conventions helpful when working in SQL Server.
Let's face it: when you work in SQL Server you already know what is a table, what is a view and what is a stored proc. All the GUI tools put them in separate buckets. So instead of going by type, name your stored procedures by function and the object they work on, not with some standard prefix like "P" (or even worse, "sp_" - something that is contra-indicated by Microsoft for performance reasons). When you name your procedures use "by" for the sort order, and "for" for criteria and start them off with the logical object name that the procedure works with. I also see no real reason for prefixes like "usp_" as all they do is add more characters to the name. Keep it simple and informative.
"Select" Stored Procedure Name Examples
CustomerGetSingleForID
retrieves a customer by ID
CustomerGetListAllByName
Retrieves a list of customers ordered by name. I usually use "lists" to populate drop-downs, pick lists etc, but not for updating. For that reason, lists often only have a few fields.
OrderGetListForCustomerIDByDate retrieves a list of orders for the customer, sorted by date)
Insert / Update / Delete Procedures
These are your typical CRUD procs that work on a single row in a dataset
CustomerUpdateSingle - update a single customer.
CustomerDeleteSingle - delete a single customer.
CustomerInsertSingle - insert a single customer.
Some people put an underscore after the object name in the stored proc: Customer_UpdateSingle. That looks fine to me as well. Either way, all the procedures related to a logical entity show up in the same location in the listings and trees in the various SQL Server management programs and add-ins.
So what if the stored procedure updates multiple objects? No problem, just name it for what you would logically bundle those objects as. For example, if you have a procedure that updates an invoice and invoice line items all at once (admittedly a contrived example), you can logically consider it all to be an "invoice" and name it accordingly.
Others
Other stored procedures that are not simply CRUD-based are named based on what they do:
ProcessPendingOrders
CleanTempTables
MungeMyDatabase
Tables are named based on what they include. Table names and columns are Pascal case with no underscores. I won't get into the plural/non-plural bit as I unfortunately agree completely with both sides on that argument. heh.
NOTE: If you use strongly-typed datasets, you will find it makes a lot of sense to follow the PascalCase naming convention with your database tables and fields as well. That is because strongly-typed datasets are simply classes with properties for table field names.
Why Hungarian Has Fallen Out of Favor with .NET
With his simple question in my guestbook, Microsoft mid-atlantic's Andrew Coupe inspired me to write this little blurb here explaining why I feel that Hungarian Notation has outlived its usefulness. If you're sticking to Hungarian Notation in .NET, waffling, or just plain curious -read on.
True Hungarian Notation is a naming convention invented by Charles Simonyi from Microsoft back in the 70s. (For more information on its origins, see this funny article) Back in the day, one of the more promising versions of Hungarian Notation was used in a way that indicated the purpose or logical type of the variables rather than the language-specific type. For example, instead of indicating something was an int, it would be specified that it was an ID or a Quantity. When Hungarian was used this way, it was very useful, especially in languages where variable names were limited in length.
Shortly after the notation started being used, developers came up with standard language-specific data-type based prefixes. I remember when I worked in C/C++, we had things such as "rgsz" meaning an array of zero-terminated strings (rg is short for"reference to a graph" - an array in C/C++). This was useful for a while, but the prefixes became unwieldy in both in length and in understanding the permutations and combinations; you ended up with things not too far from rgszxyzpdqOrderNo. Think that's a stretch? Imagine what the name of a variable that contained a long pointer to an array of arrays of long pointers to some particular structure looked like when you mapped out the full prefix. Yes, we really did have the occassional variable like that in our programs, especially the database engine I worked on.
Then along came Visual Basic. This great rapid application development tool introduced a lot of programmers to Windows programming. Many them were brand new to programming, and gave their variables meaningless or less than helpful, and almost always inconsistent names. In response to this influx of bad code, good VB developers started to use two different variations of the Hungarian naming conventions. One was a traditional "as many letters as needed" set with prefixes such as "s" for String, "i" for Integer, and sometimes things like "arrl" for an array of longs. The second school standardized on three letters for all prefixes "str" for String, and "int" for Integer being two examples.
Some people took the conventions to extremes, and did silly things like prefix database table names with "tbl", stored procedures with "P", VB classes with "C" or "cls", and function names with the Hungarian for their return type. That was and is still just pure silliness, IMHO. I do, however, think that "I" in front of an interface is important as interfaces need to be treated very differently from classes.
During this time, VB4 and eventually VB5 and VB6 came out. VB4 introduced classes and some object-oriented language features to these developers. Developers needed to remember to use "Set" when assigning object variables, and had to remember to set them to "Nothing" when they were done with them. To assist in remembering to do this, developers would prefix all their object variables with "obj" or "o". Typical code around this time was nothing but a ton of variables with names like objResults and oHenry. It looked just a bit odd, and wasn't particularly helpful.
Those of us who worked a lot with COM objects in Visual Basic realized that the prefixes made no sense in public interfaces. In fact, they looked plain awful. Many of us adopted conventions that stated that anything exposed via COM would not have any prefixes whatsoever. That included parameters, functions, classes, etc. This was inline with what Microsoft and most 3rd-party vendors did with all the COM libraries and ActiveX controls they provided.
At the same time, those of us working on large projects noticed a definite laziness on the part of some other programmers (you know who you are! <g>) when it came to updating the variable prefixes when they changed a variable from, say, an Integer to a Long. This caused far more confusion than was worth. When coupled with the huge multi-page lists of "all the prefixes" for every possible type you could think of, Hungarian started to get in the way more than it helped.
Along comes .NET and just about everything is an object or can be boxed as such, and everything public is exposed to other languages - languages which may not use the same names for the variables we use (this was the same issue we had when writing for COM). If I want to know a variable's type, I can hover over it with the mouse in the VS.NET IDE. If I want to know a variable's use, I simply look at the well-worded variable name.
What's New in Visual Basic .NET?
The number of language enhancements in Visual Basic. NET nearly justifies the creation of a new language. Let's look at a few of the more visible changes.
Option Explicit
Forcing the explicit declaration of all variables reduces potential bugs. Classic VB required an Option Explicit statement in the declaration section of code if we wanted the Visual Basic compiler to enforce variable declaration which held the potential for problems because variables were often misspelled. When using Option Explicit, an entirely new variable is created if not already declared. Unlike classic VB, Visual Basic .NET implements Option Explicit by default, preventing the accidental creation of new variables and protecting the use of declared ones.
Option Strict
Option Strict is similar to Option Explicit in that it tells the compiler to require a variable declaration and requires all data conversions to be explicit. In classic Visual Basic, implicit conversions are not possible when Option Strict is on. (This setting is off by default.)
Option Compare
Option Compare, as you might guess, determines how strings will be evaluated. The two possible parameter values are binary and text. Binary compares the literal binary values of the two values being compared. A binary compare would mean the upper- and lower- case values cannot be equal, in effect enforcing case-sensitive compares. Text allows the evaluation of two variables to be case - insensitive. Your application requirements will determine which Option Compare option you will use.
Option Base
Option Base is a retired option of classic Visual Basic that allowed developers to determine whether or not arrays will be 0 or 1 based. Visual Basic .NET no longer recognizes this option and sets all arrays to base 0.
Variables
Variables in .NET come in two flavors: value types and reference types. All primitive data types with the exception of the string data type are value type variables. All classes including the string data type are reference types.
The most significant difference between the types is in how they are stored in memory. Value types are stored in a stack (which requires a smaller memory footprint), while reference types are stored in a heap.
Boxing
Boxing occurs when a value type is converted to a reference type and recreated on the heap. Boxing should be used sparingly as the ability to move values from the stack to the heap is performance intensive.
The most common occurrence of boxing is when a value type variable is passed to a procedure that accepts the System.Object data type. System.Object is the equivalent of the classic Visual Basic variant data type.
ReDim
The ReDim statement, available in classic Visual Basic, is still available in Visual Basic .NET. Classic V isual Basic no t only allowed developers to rediminish an array, but also initialize the array. Visual Basic .NET allows the use of ReDim to rediminish an array but not to initialize an array.
StringBuilder
The StringBuilder class is an impressive class optimizing string manipulation. You'll better understand its advantages once you understand how string manipulation has historically worked.
Classic Visual Basic hid the actual implementation code supporting functions available in the Visual Basic library, and string manipulation was no exception. One common string function is the concatenation of two strings. Unfortunately, Visual Basic doesn't simply add the two strings together; instead, the windows system determines the space required for the new string, allocates memory, and places the new concatenated value into the newly allocated memory. The StringBuilder class is implemented as an array of characters. This allows it to implement methods to manipulate what appears to be a string without the overhead incurred by an actual string. The Insert method of the StringBuilder class is used to add to the character array in a way that is much more efficient than classic string manipulation, increasing performance of many common programming scenarios. (You'll find the StringBuilder class in the System.Text namespace.)
Using the StringBuilder
This example will show you how to use the StringBuilder class and will compare its performance against the performance of classic Visual Basic string concatenation. To begin, follow these steps:
1 Create a windows project and build a window that looks the same as Figure 7-1, using the parameters in Table 7-1.
Click for a full image.
Figure 7-1: Using the StringBuilder class.
Table 7-1: Parameters for the StringBuilder Class
Control Property Value
Button Name btnString
Text strString = strString & "string value"
Button Name BtnStringBuilder
Text objStringBuilder = objStringBuilder.Append("string value")
Label Name lblStringDisplay
Label Name lblStringBuildingDisplay
Label Text The first button concatinates strings the classic VB way while the second button used the StringBuilder class. Each will loop through each concatination 15000 times.
2 Add the following code segment to the click event of the btnString button.
3.Dim dateStart As Date
4.Dim strString As String
5.Dim i As Integer
6.dateStart = DateAndTime.TimeOfDay
7.For i = 1 To 15000
8.strString = strString & "string value "
9.Next i
10.lblStringDisplay.Text = DateAndTime.DateDiff(DateInterval.Second, _
11.dateStart, DateAndTime.TimeOfDay) & " Seconds"
12.Add the following code segment to the click event of the btnStringBuilder button.
13.Dim dateStart As Date
14.Dim objString As New System.Text.StringBuilder()
15.Dim i As Integer
16.dateStart = DateAndTime.TimeOfDay
17.For i = 1 To 15000
18.objString = objString.Append("string value ")
19.Next i
20.lblStringBuilderDisplay.Text = DateAndTime.DateDiff( _
21.DateInterval.Second, dateStart, _
22.DateAndTime.TimeOfDay) & " Seconds"
23.Now run the example and press each button. You will see a significant difference between the performances of the two methods of string concatenation.
NOTE
Previously, strings were built by simply adding one onto the end of another. This only seems to be what is happening. What is actually occurring is something different. When adding one string to another, you begin with the original string in memory, then a new string is allocated in memory for the string being added. Next, a new string representing the new concatenated string is created and the new string placed into it, and finally, the original string and the added string are de-allocated, leaving only the newly concatenated string in memory.
As you might imagine, this is a very inefficient process for simply adding two string values together. The StringBuilder class is a collection of characters. The StringBuilder character collection can allow values to be added and removed without the need to re-allocate and de-allocate memory blocks. As you will see, the performance difference is significant.
Structures
Classic Visual Basic allowed developers to create their own data types called User Defined Data Types or UDTs, which were implemented using the Type keyword. Visual Basic .NET has retired the Type keyword and replaced it with the keyword Structure, like so:
Public Structure Person
Dim strFirstName as String
Dim strLastName as String
End Struct
Variable Scope
All variables have a predefined scope that is assigned during initialization. Listed below are a few of the most common scope declarations and their definitions.
· Private scope: Defines a variables scope as restricted to the current method. A variable defined as having private scope is referred to as a member variable and is commonly prefixed with an "m".
· Public scope: Allows the parent class, or calling class, access to the data held by a public variable or method.
· Friend scope: Similar to public scope as far as all code within a project is concerned. The difference between the public scope and friend scope is that variables or methods that are defined with the friend scope cannot be accessed by a parent class outside of the project.
· Protected scope: A new scope declaration that allows access to classes that inherit from the variables class.
Regions
The #Region directive allows you to organize your code into collapsible blocks which help to make the code window easier to work with by displaying only those functions you are working with. Each region can be defined with a name helping each region to be more easily identifiable, as shown here
#Region "MyRegion"
'some code
#End Region
When you are done writing "some code," you can collapse the region and begin working on the next segment of code.
Windows Forms
Visual Basic .NET implements Windows Forms as classes that inherit windows functionality from the Form class found in the System.Windows.Forms namespace. Developing Win32 applications in Visual Basic .NET is still very similar to classic Visual Basic windows development in that windows controls can be dragged and dropped onto the form designer. The difference is that none of the implementation code is hidden.
For example, here's the implementation code for the Windows Form discussed in the previous example of the StringBuilder class. While this type of code must be implemented in classic Visual Basic forms, it is hidden. As you can see, the code is no longer hidden; however, I would strongly recommend leaving this code alone unless you really know what you are doing and have a specific need to fill. Take a look at t he code below and notice that the entire form is actually a class that inherits the System.Windows.Forms.form class. As mentioned earlier in this book, everything in .NET is a class. There are no exceptions.
Public Class Form1
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'
End Sub
#End Region
End Class
The implementation code for all the controls on the form were stored in the "Windows Form Designer generated code" region. (This information has been removed so you won't be distracted from the Windows Form's own implementation.)
Project Structure
While the Visual Basic compiler used a file's extension to determine what type of project file it was, Visual Basic .NET implements all code through classes. All Visual Studio .NET needs to know is the language the file is written in. Project groups in Visual Studio have proved to be a powerful tool for managing, building, and debugging multiple project solutions. Visual Studio .NET replaces the Group Project with a Project Solution, which is one or more projects and the supporting files. Because solutions actually contain projects and items, they are often referred to as Solution Containers. And, because projects also contain files, it should come as no surprise that projects are referred to as Project Containers.
Solutions and project files each have their own extensions so that Visual Studio .NET knows what kind of container they are:
· sln: The file extension of a solution file which maintains all solution specific information.
· suo: The file extension of all Solution User Options files which maintains all of the user's preference information for the solution.
· vbproj: The file extension of all Visual Basic project files.
· vb and .cs: The file extensions of Visual Basic .NET and C# files, respectively. This is a significant improvement from previous versions of Visual Studio when forms, classes, and other components were given component specific file which offered no clue as to the language used to build the file. Project items built using a specific language will always have that language's file extension, thus allowing Visual Studio .NET to know which compiler it must use. (For additional information on file extensions of project items, refer to the MSDN article entitled, "File Types and File Extensions in Visual Basic and Visual C#".)
ErrorProvider
One of the more interesting Windows and Web Form improvements is the ability to alert the user of exceptions without interrupting them until they press a button that performs validation, providing better overall user experience. The ErrorProvider component is a non-visual component that allows you to perform data validation on form controls. If a data violation occurs, you can set a message to be displayed as a tool tip near the offending control.
Ideally, you should implement data validation, a type of business rule enforcement, at the lowest common layer. The most ideal place to do so is at the database level because this is the only application layer that cannot be bypassed. Furthermore, rules enforced here are not duplicated as they would be if you implemented business rules in the presentation layer. For example, if a name can be equal to or less than 20 characters and the rule is implemented in the presentation layer, then every form that supports the use of the name must implement the same rule. If the rule is changed, it must be changed in every form that uses the name. This is both sloppy and error prone.
Current technology does not lend itself to this level of data validation very easily; however, over time Microsoft will devise better validation schemes and developers will build custom solutions. The challenge is to provide solid data validation without compromising user experience. To implement data validation in the presentation layer means that we are duplicating business rule enforcement because the same rules are surely implemented in the database as well. Of course the risk is that when the database schema changes, we may miss making the same changes in the presentation layer.
If all business rules are implemented in the Component layer, it is possible to bypass the rules by ignoring the Component layer or building another one that does not implement the rule. In such a case, you have the potential of corrupting data that will be more expensive to repair then it would have cost to devise a sound business rules enforcement schema.
The XML Schema is excellent place to begin looking for sound business rule implementation. By pulling the database schema from the database and persisting it in memory, you can leverage XML Schema to enforce data types and constraints. Also, when building your web page dynamically you can enforce these data specific rules through the ErrorProvider. In this case, you are implementing rules at the business level layer, which were defined in the database at design time; the violation can be made known through the ErrorProvider at the presentation layer. This is the ideal way to enforce data specific business rules; all other programmatic business rules should be implemented in the business level layer. Never enforce business rules in the presentation layer.
Implementing Namespaces
Namespaces make it easy to organize classes, functions, data types, and structures into a hierarchy. Namespaces allow you to quickly access classes and methods buried in the .NET Framework Class Library or any other application that provides a namespace. The .NET Framework Class Library provides hundreds of classes and thousands of functions as well as data types and structures. Use the Imports statement to import a namespace for easy access to its classes and methods. Once imported it is no longer necessary to use a fully qualified path to the desired class or method. For example:
Imports system.text ' Give access to the StringBuilder class
Table 7-2 lists the namespaces that are used most commonly and their general functionality.
System.AppDomainUnloadedException Thrown when attempting to use an unloaded application domain.
System.ApplicationException Thrown when a non-fatal application error has occurred.
System.ArgumentException Thrown when an argument passed is not valid.
System.ArgumentNullException Thrown when a null is passed as a method parameter that does not accept null values.
System.ArgumentOutOfRangeException Thrown when a passed value is outside the range of a methods parameter.
System.ArithmeticException Thrown when an error occurs while performing arithmetic and conversion operations.
System.ArrayTypeMismatchException Thrown when adding a value of the incorrect data type to an array.
System.DivideByZeroException Thrown whenever a value is divided by zero.
System.DllNotFoundException Thrown when a DLL referenced as imported is not available.
System.IndexOutOfRangeException Thrown when trying to access an invalid index in an array.
System.InvalidCaseException Thrown when an invalid conversion attempt is made.
System.NullReferenceException Thrown when attempting to dereference a null object reference.
System.OutOfMemoryException Thrown when memory is not available to perform the specified task.
System.OverflowException Thrown when an operation overflow occurs.
System.RankException Thrown when an array with the wrong number of dimensions is passed to a methods parameter.
System.SystemException Is the base class for all exception classes in the System namespace.
System.Data.ConstraintException Thrown when a constraint is violated.
System.Data.DataException Thrown when an ADO.NET component generates an error.
System.Data.DBConcurrencyException Thrown when the number of rows affected in an update procedure is zero.
System.Data. DeletedRowInaccessibleException Thrown when attempting to perform data manipulation operations on a data row that has been deleted.
System.Data. InvalidConstraintException Thrown when a data relationship is violated.
System.Data. NoNullAllowedException Thrown when inserting a null value where one is not accepted.
System.IO. DirectoryNotFoundException Thrown when a specified directory cannot be found.
System.IO.FileLoadException Thrown when a file cannot be loaded.
System.IO.IOException Thrown when an I/O error occurs.
System.IO.PathToLongException Thrown when a path or file name are too long.
System.Runtime.Remoting. Thrown when an error occurs during a remote RemotingException operation.
System.Runtime.Remoting. RemotingTimeoutException Thrown when the response of a server or client exceed a predefined interval.
System.Runtime.Remoting.ServerException Thrown when an error occurs while working with a remote component that is an unmanaged application incapable of throwing an exception.
System.Runtime.Serialization.SerializationException Thrown when an error occurs during the serialization or deserialization of a component.
System.Web.HttpException Allow an http exception to be thrown.
System.XML.XmlException Provides exception information about the last XML exception.
Basic Rules
Basic rules govern the use of a Try block. (They will quickly become obvious after using Try blocks a few times.)
· All Try blocks must employ at least one catch or finally clause.
· A Catch clause with no other parameters will catch all unhandled exceptions.
· The finally clause always executes when available except when an Exit Try occurs. As such, the finally clause is a good place to perform component cleanup. If an Exit Try statement is used anywhere in the Try block then it is better to perform cleanup after the End Try block statement.
· Developers familiar with the On Error statement may still perform error handling as they did in Visual Basic only when a Try block does not exist in the procedure.
Exception Handling Examples
For the following examples we will create a single Windows Form and add a button for each example. To begin, create a new project and name it "Exception Handler".
Try...Catch
One popular and easy way to understand an example of error handling has always been the divide by zero error, or in .NET terms "exception." You will use the divide by zero exception wherever possible so as to not distract you from what the chapter is trying to convey. (You will examine a few other specific exception conditions later in this chapter.)
This example demonstrates the simplest of all exception structures:
1. Drag a button onto your Windows Form and label it "Try# Catch".
2. Change the buttons name to "btnTryCatch".
3. Apply the following code in the click event of the button.
Dim intResult as Integer"
Dim int1 as Integer = 5
Dim int2 as Integer = 0
Try
intResult = int1 / int2
Catch objException as System.OverflowException
Messagebox.Show("Divide by zero has occurred")
End Try
The preceding example evaluates the exception object by using the catch clause. The catch clause checks to see if the exception object contains an "OverflowException" exception. If so, and in this case it will, the code in the catch clause executes.
The Finally Clause
This example employs the same code as the one previously, except it demonstrates that the Finally clause always executes:
1. Drag another button onto your Windows Form and label it "Finally".
2. Change the buttons name to "btnFinally".
3. Apply the following code in the click event of the button.
Dim intResult as Integer
Dim int1 as Integer = 5
Dim int2 as Integer = 0
Try
intResult = int1 / int2
Catch objException as System.OverflowException
Messagebox.Show("Divide by zero exception has occurred")
Finally
Dim obj As New System.Text.StringBuilder()
obj = obj.Append("Regardless of whether or not an ")
obj = obj.Append("exception occurs, the Finally clause ")
obj = obj.Append("will execute.")
MessageBox.Show(obj.ToString)
obj = Nothing
End Try
Feel free to remove the errant code with "intResult = int1 / 1" and observe that the finally clause still executes.
obj = Nothing
End Try
You will notice when the exception occurs, the exceptions message is displayed and the Try block is exited. In this case, the Finally block does not execute and is not a good place to clean up objects in memory.
Multiple Catch Statements
It is often preferable to use Multiple Catch statements in a single Try block, although the placement of Catch statements can impact performance. Once an exception is caught, the processing of the remaining Catch statements is aborted. Subsequently, once the Catch clause completes processing, the Finally clause is processed if available. To increase the performance of your Try blocks, place the most likely one to error or more common exceptions in the first Catch blocks while placing the least likely errors toward the end. Here's how to use Multiple Catch statements:
1. Drag another button onto your Windows Form and label it "Multiple Catch".
2. Change the button name to "btnMultipleCatch".
3. Cut and paste code from the Finally button to the Multiple Catch button.
4. Make the following changes as highlighted in the code below:
Dim intResult As Integer
Dim int1 As Integer = 5
Dim int2 As Integer = 0
Dim str1 As String
Try
str1 = int1 / int2
Throw (New Exception("A different exception"))
Catch objException As System.OverflowException
MessageBox.Show("Divide by zero exception has occurred")
Catch
MessageBox.Show("Some other exception has occured.")
Finally
Dim obj As New System.Text.StringBuilder()
obj = obj.Append("Regardless of whether or not an ")
obj = obj.Append("exception occurs, the Finally clause ")
obj = obj.Append("will execute.")
MessageBox.Show(obj.ToString)
obj = Nothing
End Try
As you step through the procedure you will notice that you no longer receive an overflow exception. You will also notice that the string value receiving the results of the calculation has the value "infinity" when dividing a number by zero. The overflow exception does not occur, however, when we threw an exception to the calling method. The first Catch clause is completely ignored, but the second Catch clause is not looking for any specific exception; as a result, the second Catch clause catches all exceptions not already caught.
Historically, when you were building COM components, the best practice was to ensure that all methods and components handled their own exceptions. The best practice with .NET components is to pass the exception to the client and allow the client to determine the next course of action. This is due in part because all languages now understand how to deal with each other's languages exceptions, therefore, it is no longer critical for the language catching the exception to also deal with the exception. Also, exceptions don't always correlate to an error that occurred. Often an exception simply indicates an application state that is not what the method requires. This could be as simple as the database is not available. In this care, no error has occurred in the code; however, because the database is unavailable, the method cannot complete its assigned task.
The exception class has several properties and methods. You'll learn about a few of the more notable ones here and then examine an example of each:
· Source property: The Source property of the exception class is intended to hold the application or object name generating the exception. It can also be programmatically set, but if it is not set, the property returns the assembly name where the exception occurred.
· Message property: The Message property is a string containing a description of the current exception.
· TargetSite property: The TargetSite property is a string containing the name of the procedure where the exception occurred.
· GetType method: The GetType method is inherited from the System.Object class and returns the type of exception that has occurred.
· ToString method: The ToString method returns a string describing the current exception including information provided by several other exception class properties and methods.
Exception Class Properties and Methods Example
This example demonstrates the use of some exception class properties and methods:
1. Drag another button onto your Windows Form and label it "Properites/Methods."
2. Change the button name to "btnPropMeth."
3. Cut and paste code from the Finally button to the Properties/Methods button.
4. Add the following code to the newly pasted code:
Dim intResult as Integer
Dim int1 as Integer = 5
Dim int2 as Integer = 0
Try
intResult = int1 / int2
Catch objException as System.OverflowException
Messagebox.Show("Divide by zero exception has occurred")
'Source, Message, TargetSite, GetType, ToString
MessageBox.Show("Source: " & objException.Source())
MessageBox.Show("Message: " & objException.Message())
MessageBox.Show("TargetSite: " & amp; objException.TargetSite.Name)
MessageBox.Show("GetType.Name: " & amp; objException.GetType.Name)
MessageBox.Show("ToString: " & amp; objException.ToString())
Finally
Dim obj As New System.Text.StringBuilder()
obj = obj.Append("Regardless of whether or not an ")
obj = obj.Append("exception occurs, the Finally clause ")
obj = obj.Append("will execute.")
MessageBox.Show(obj.ToString)
obj = Nothing
End Try
Object-Oriented Programming (OOP)
As mentioned earlier, Visual Basic did not meet the test as a true object-oriented language that implements true object-oriented programming as defined by abstraction, encapsulation, polymorphism, and inheritance. Visual Basic .NET not only supports inheritance, but also supports a variety of inheritance implementations including interface, forms, and implementation or polymorphism inheritance.
Before you continue any further, let's briefly discuss the four main concepts of object orientation and the implementation code for each. Each brief discussion is followed with an example that demonstrates the discussed OOP concept.
Abstraction
Abstraction is the easiest of the OOP concepts to understand and is often something we implement naturally without realizing it. In short, abstraction is the implementation of code to mimic the actions and characteristics of any realworld entity.
The most commonly-used example for describing abstraction is the abstraction of a person. Imagine that we want to create an object from a class that represents a person. A person class will need to describe its characteristics through the implementation of properties. Actions of the person class are performed by methods.
Encapsulation
Encapsulation is the programmatic equivalent of a black box. An actual black box may have a switch and dials. Inside the box would be the mechanisms to perform the actions provided by the black box.
We expose properties and methods through abstraction, but we implement the actual workings of our component through encapsulation. A few encapsulated actions might include data access, data validation, calculations, adding data to an array or collection, or calling other methods or other components. Exposing our components interface while hiding the component's implementation code effectively separates interface implementation from our black box implementation. This separation helps to modularize components to perform a more specific task while requiring minimal knowledge of how the black box actually works.
One of the more useful applications of encapsulation is in making a complex component. For example, your program may require interaction with a third party system, but interaction with this system can only be achieved through a complex API. Rather than requiring all developers on a project to spend valuable time figuring out how to correctly use the third party API or even find ways to misuse it, one developer could study the API then encapsulate it in a component that exposes a less complex interface. This is a common practice that saves time and reduces potential bugs.
Like abstraction, encapsulation isn't as much a technology as it is a method of code implementation. In the case of encapsulation, our method of implementation separates the exposed interface from the actual implementation code.
Polymorphism
Abstraction is an interface implemented to represent a real-world object; encapsulation is the implementation of a black box through interface and implementation separation; and polymorphism is the ability to implement the interface of another class into multiple classes or to implement multiple interfaces on a single class. This method of implementation is referred to as interface-based programming. A vehicle is a good example of polymorphism. A vehicle interface would only have those properties and methods that all vehicles have, a few of which might include paint color, number of doors, accelerator, and ignition. These properties and methods would apply to all types of vehicles including cars, trucks, and semi-trucks.
Polymorphism will not implement code behind the vehicle's properties and methods. (That's the job of inheritance covered in the next section.) Instead, polymorphism is the implementation of an interface. If the car, truck, and semitruck all implement the same vehicle interface, then the client code for all three classes can be exactly the same.
Implementing the vehicle interface only requires the declaration of properties and methods. To create a new interface, use t he Interface keyword in place of the Class keyword. The client implementing the new interface can do so by using the Implements keyword as shown in the example:
Implements Ivehicle
After using the Implements keyword, you will notice that Intellisense displays the properties and methods of the IVehicle interface. Using the Implements keyword will only give access to the properties and methods of the IVehicle interface; however, you must provide your own code behind the methods and property declarations to match the interface.
Inheritance
Inheritance is the ability to apply another class's interface and code to your own class. Remember, with polymorphism, you got the interface; however, you must apply your own code. The power of inheritance is the ability to inherit code, saving developers time. This type of inheritance is called implementation inheritance. To inherit another class, use the Inherits keyword.
Visual inheritance is the ability to inherit another form's look and feel onto another. Remember, everything in .NET is a class, including forms. If you create a project that exists in the MyApp namespace, create a form name MyBaseForm. The following code will inherit the MyBaseForm within our new form:
Public Class MyNewForm
Inherits MyApp.MyBaseForm
End Class
Properties
Properties are part of a program's interface and describe the characteristics of a class. These properties hold information about a class or, when loaded into memory, an object. Properties, as they exist in classes, are often referred to as "data." When a reference is made to a class's data, you will know that the reference is actually directed toward a class's property.
To create a property, use t he Property keyword and t hen define the type of property you are implementing. Properties can be read-only, write-only, or read and writable. To define the characteristics of properties, use t he keywords ReadOnly, WriteOnly, or supply no definition at all to implement both read and write ability.
Visual Studio .NET makes properties easier to implement by adding the basic shell of property code based on the property's scope definition. Unlike Visual Basic, Visual Basic .NET automatically supplies code for both read and write functionality: "Get" for read ability method and "Set" for write access to a property.
Create a new class, type the following code, and press ENTER:
Public Property FName() As String
Visual Studio .NET will automatically fill in the rest of the code that is required by the FName property:
Get
Return m_FName
End Get
Set(ByVal Value As String)
m_FName = Value
End Set
End Property
Methods
Methods are the actions exposed by a class in the form of either functions or sub-procedures. Sub-procedures and functions both execute code on behalf of the calling application, but sub-procedures simply execute code while functions execute code, then return a value.
The .NET Framework provides at least two new changes to how you can use procedures. In Visual Basic, you could call a procedure without the use of parameters, including procedures that required no parameters at all. The .NET Framework requires parenthesis to follow all methods even when parameters are not required. For example:
· Visual Basic 6 method call:
· intResult = DoSomething
· Visual Basic .NET method call:
· intResult = DoSomething()
Another change is the addition of the Return keyword. When returning a value for a function in Visual Basic, you set the function's name equal to the value being returned. With Visual Basic .NET, you can se t the keyword Return equal to a value and the value will be returned with the function. This is very useful when making code more generic. For instance, you can easily cut and past a method's code without regard to another method's function name because the keyword Return is used for setting the method equal to a return value. Examples of the old versus new method for returning values of a function are:
· Visual Basic 6 method call:
· Public Function DoSomething() as Int32
· DoSomething = 10
· End Function
· Visual Basic .NET method call:
· Public Function DoSomething() as Int32
· Return = 10
· End Function
If you look closely at a function's supporting properties you will find that the Return keyword is used by default. You can set the function name equal to a given value.
The third significant change is in how parameters are passed. Visual Basic passed a parameter value ByRef by default. The preferred method for passing parameter values is to explicitly define whether a value is passed by ByRef or ByVal. Finally, when using the Option keyword, you must define a default value similar to how C has worked for many years now.
Declaration Options
We have covered a few of the most common declaration methods including those that describe the scope of a property or method. Several description options will extend or restrict scope.
Here is a list of the most commonly-used declaration options with brief descriptions of each:
· Private: The Private keyword defines a variable or method as accessible only by code within the context of where the declaration occurred; outside code is not permitted access. Variables and methods defined as private are often referred to as member variables or methods, and commonly prefixed with an "m".
· Public: The Public keyword declares a property or method as accessible by anyone within the calling application or within the class itself.
· Friend: The Friend keyword defines a property or method as accessible by members within the class it is declared in.
· Protected: The Protected keyword defines a property or method as accessible only by members of its class or by members of an inheriting class.
· Default: A Default property is a single property of a class that can be set as the default. This allows developers that use your class to work more easily with your default property because they do not need to make a direct reference to the property. Default properties cannot be initialized as Shared or Private and all must be accepted at least on argument or parameter. Default properties do not promote good code readability, so use this option sparingly.
. Overloads:The Overloads property allows a function to be described using deferent combinations of parameters. Each combination is considered a signature, thereby uniquely defining an instance of the method being defined. You can define a function with multiple signatures without using the keyword Overloads, but if you use the Overloads keyword in one, you must use it in all of the function's Overloaded signatures.
· Shared:The Shared keyword is used in an inherited or base class to define a property or method as being shared among all instances of a given class. If multiple instances of a class with shared properties or methods are loaded, the shared properties or methods will provide the same data across each instance of the class. When one class alters the value for a shared property, all instances of that class will reflect the change. Shared properties of all instances of the class point to the same memory location.
· Overridable:The Overridable keyword is used when defining a property or method of an inherited class, as overridable by the inheriting class.
· Overides: The Overides keyword allows the inheriting class to disregard the property or method of the inherited class and implements its own code.
· NotOverridable: The NotOverridable keyword explicitly declares a property or method as not overridable by an inheriting class, and all properties are "not overridable" by default. The only real advantage to using this keyword is to make your code more readable.
· MustOverride: The MustOverride keyword forces the inheriting class to implement its own code for the property or method.
· Shadows: The Shadows keyword works like the Overloads keyword except that with shadows we do not have to follow rules such as implementing the same signature. The Shadows keyword does not require the consent (override ability) of the inherited class to replace the property or method's implementation code. A method does not have to be defined as overridable for the Shadows keyword to work.
Object Instantiation
When you drag and drop controls onto a Windows Form, you are using objects. When you observe your code, you are looking at a class; when that code is loaded into memory, at runtime, it is considered an object. The importance of the distinction is simply to describe that a class is a template, while an object is an instance of that template in memory. Also, many copies of the template can exist in memory at the same time as objects.
Fortunately, we do not have to depend on the component designer to work with classes; we can build our own classes and components. This is nothing new for a moderately experienced developer; what is new is how Visual Basic .NET permits us to instantiate classes.
Classic COM relied on the Windows Registry to store its exposed properties, methods, events, and enumerations; a client application could only access these exposed interfaces through the Registry. As a result, the way you instantiate classes when using classic COM components in COM+ is very important. Visual Basic .NET accepts a number of instantiation methods without performance impacts, although all variables must first be diminished and then instantiated before they can be used.
The two methods for instantiating classic COM are the CreateObject and New keywords. CreateObject uses the Windows Registry to obtain the interface of the class being instantiated. Because CreateObject depends on windows for access to the register, COM+ can apply a context for use by the COM+ services. The New keyword in classic COM also depends on Windows for access to the Windows Registry. The catch is that it doesn't always have to access the Windows Registry to discover a class's interface if the class resides inside the same component as the calling class. Because the New keyword has no problem accessing a class's interface within the same component, a class can be instantiated by passing COM+ services that would normally add a context or other component service. While this will not prevent you from loading a class into COM+, to take full advantage of COM+ services you should use the CreateObject keyword.
Having said all that, the CreateObject keyword cannot be used to instantiate .NET classes, although it can be used to instantiate classes that exist within classic COM components. Because .NET components don't rely on the Windows Registry, the New keyword is used when loading all .NET components. Here are several examples of how you might define and load classes into memory. First, the variable is diminished as MyClass:
Dim obj As MyClass
Second, you load the class "MyClass" into memory. An instance of a class loaded into memory is referred to as an object. Notice there is no "Set" keyword used.
Obj = New MyClass
Another method is to declare and instanciate an object in a single line:
Dim obj2 As MyClass = New MyClass()
Finally, you can implicitly diminish a variable with a class you are attempting to load. This is the shortest method and is perfectly acceptable:
Dim obj3 As New MyClass
Early and Late Binding
Binding is something we do when diminishing a variable, though many developers may not realize the importance of how they bind a class.
Early binding, often referred to as strong typing, refers to explicitly declaring the class used to define a variable. Early binding has several benefits. For example, when programming, Visual Studio .NET can give access to the class's interface with Intellisense which greatly reduces potential for typos and promotes rapid development. Also, when early binding a class, the Visual Basic compiler can enforce the proper use of a class's interface by providing warnings and refusing to complete the compile until the error is resolved. But performance gains are probably the most important reason to bind early: Early binding allows your program to access your class's interface directly, rather than through the Windows Registry or at runtime. If the compiler knows ahead of time which classes you will be using in your application, it can make the appropriate compilation optimizations.
Late binding can be useful when developing against non-existent components or ones that are being developed. Late binding allows you to continue compiling your code until the component is available; once the class is available, you can modify your code to early bind. You might also use late binding when you truly don't know the object type that will be passed to your function, in which case it is perfectly acceptable to accept any type of object. Before late binding can occur, the Option Strict option must be set to off. Option strict is off by default:
Option Strict Off
To declare a variable as lat e bound, simply diminish t he variable as type
object:
Option Strict Off
Dim obj As Object
'or
Dim obj As System.Object
The System.Object class is the class from which all other classes are derived. While it has no specific characteristics that prevent it from acting as any other class, it is used for late binding.
Components
A class defines something that can exist in memory. It defines an object's interface including properties, methods, events, and enumerations as well as implementation code. An object is an instance of a class in memory; while a class may exist only once, multiple instances of that class may reside in memory as objects. When adding items to a project, you can add a "class" or a "component class". In essence, these are the same thing with one exception: a component class implements the IComponent interface, enabling Visual Studio .NET to drag non-visual controls, such as the timer control, onto a component designer. Visual Studio .NET provides a designer for building components, which allows you to drag visual controls onto your class or component, and begin coding. For example, if you want to program a delay into your class, you can use a component item with the designer to drag the timer control onto your component. The implementation of a graphical designer (IE Component Designer) and container are available to you when you selected "component class" as a new item. Visual Studio creates your class by adding a line of code that inherits everything your class needs to be a component, as follows:
Inherits System.ComponentModel.Component
Then the designer creates a components object using the IContainer interface so that the designer can allow drag and drop capabilities:
Private components As System.ComponentModel.Icontainer
Simple OOP Examples
Now you'll build an example application that employs abstraction, encapsulation, polymorphism, and inheritance:
1. Create a new class library project named "PersonProj".
2. Rename the Class1.vb file to "Person.vb".
3. Add a new Windows Application project to the solution named "TestClient".
4. Right- click on t he TestClient project and select Se t as StartUp Project.
Now add abstraction:
1. Replace the default class in Person.vb with the following code:
Public Class clsPerson
Public FName As String
Public LName As String
Public FullName As String
Public BirthDate As Date
Public Age As Integer
Public TotalHours As Integer
Public Sub Work(ByVal intHours As Integer)
TotalHours += intHours
End Sub
End Class
2. Create a form that looks like Figure 7-2, and name it "frmAbstraction.vb." Use the parameters in Table 7- 4 to build the new Windows Form.
Creating a new Windows Form.
Table 7-4: Form Parameters
Control Property Value
Button Name btnSubmit
Text Submit
Textbox Name txtFName
Textbox Name txtLName
Textbox Name txtFullName
Textbox Name txtBirthDate
Textbox Name txtAge
Textbox Name txtHoursWorked
Label Name lblTotalHoursWorked
3. Right- click on t he TestClient project and select Properties. Change the Startup object to "frmAbstraction.vb".
4. Add a r eference to the PersonProj. Right - click on t he TestClient project and select Add Reference. Select the Projects tab and press the "Select" button then press OK.
5. Add the following object declaration to the initialization of frmAbstraction form:
' Here we are initializing the Person class. Normally this would be done
' when the class was needed for data access but in this case we are using
' the Person class to maintain our data.
Dim objPerson As New PersonProj.clsPerson()
6. Add the following code to the submit buttons click event:
objPerson.FName = txtFName.Text
objPerson.LName = txtLName.Text
objPerson.FullName = txtFullName.Text
If IsDate(txtBirthDate.Text) Then
objPerson.BirthDate = CDate(txtBirthDate.Text)
End If
If IsNumeric(txtAge.Text) Then
objPerson.Age = CInt(txtAge.Text)
End If
If IsNumeric(txtHoursWorked.Text) Then
objPerson.Work(CInt(txtHoursWorked.Text))
End If
lblTotalHoursWorked.Text = "Total Hours Worked: " _
& objPerson.TotalHours.ToString
7. Now run the application.
You should be able to place any value you wish into the First and Last name fields, then completely contradict yourself when filling in your full name. The same should hold true for entering your birth date and age. This example abstracts a person but does not hide any implementation; each time you press the Submit butt on, the Total Hours Worked is summed and displayed.
Adding Encapsulation
The encapsulation example implements the clsPerson class and encapsulated code, hiding the implementation code for all the properties and methods. In the abstraction example, the user has to enter both their birthday and age. As you encapsulate the implementation for the Person object, you will hide the implementation of their age. Age will be derived from the person's birth date, thus preventing a user from creating an invalid age and birth date values. Properties are also encapsulated, allowing the class to derive the full name from the first and last names that have been entered.
1. Add a new Windows Form item named "frmEncapsulation.vb".
2. Add the controls and parameters listed in Table 7- 5 to the frmEncapsulation.vb form.
Table 7-5: Controls for the Form
Control Property Value
Button Name btnSubmit
Text Submit
Textbox Name txtFName
Textbox Name txtLName
Label Name lblFullNameDisplay
Textbox Name txtBirthDate
Label Name lblAgeDisplay
Textbox Name txtHoursWorked
Label Name lblTotalHoursWorked
3. Create a new class in the Person class using the following code:
Public Class clsPerson2
Private m_FName As String
Private m_LName As String
Private m_BirthDate As Date
Private m_TotalHours As Integer
Public Property FName() As String
Get
Return m_FName
End Get
Set(ByVal Value As String)
m_FName = Value
End Set
End Property
Public Property LName() As String
Get
Return m_LName
End Get
Set(ByVal Value As String)
m_LName = Value
End Set
End Property
Public ReadOnly Property FullName() As String
Get
Return m_FName & " " & m_LName
End Get
End Property
Public Property BirthDate() As Date
Get
Return m_BirthDate
End Get
Set(ByVal Value As Date)
If IsDate(Value) Then
m_BirthDate = Value
End If
End Set
End Property
Public ReadOnly Property Age() As Integer
Get
If DatePart(DateInterval.Year, m_BirthDate) = 1 Then
Exit Property
End If
Return DateDiff(DateInterval.Year, m_BirthDate, Now)
End Get
End Property
'Method for adding hours to m_TotalHours worked.
Public Sub Work(ByVal intHours As Integer)
m_TotalHours += intHours
End Sub
Public ReadOnly Property TotalHoursWorked() As Integer
Get
Return m_TotalHours
End Get
End Property
End Class
4. Add the following object declaration to the initialization of frmAbstraction form:
' Here we are initializing the Person class. Normally this would be done
' when the class was needed for data access but in this case we are using
' the Person class to maintain our data.
Dim objPerson As New PersonProj.clsPerson2()
5. Right- click and select Properties then change the StartUp object to "frmEncapsulation".
6. Add the following code to the Submit button:
objPerson.FName = txtFName.Text
objPerson.LName = txtLName.Text
If IsDate(txtBirthDate.Text) Then
objPerson.BirthDate = CDate(txtBirthDate.Text)
Else
MsgBox("Please provide a valid Birth Date.")
End If
If IsNumeric(txtHoursWorked.Text) Then
objPerson.Work(CInt(txtHoursWorked.Text))
txtHoursWorked.Text = ""
End If
lblFullNameDisplay.Text = objPerson.FullName.ToString
lblAgeDisplay.Text = objPerson.Age.ToString
lblTotalHoursWorked.Text = "Total Hours Worked: " _
& objPerson.TotalHoursWorked.ToString
7. Now run the application and enter the information.
You'll notice that your age is calculated for you so that it cannot contradict what the age should be based on the birth date, and the full name is derived from the first and last name.
Adding Polymorphism or Interface-based Inheritance
This example features an interface called IPerson and a class named Employee that uses the IPerson interface:
1. First create a new Windows Form with the same controls as used in the encapsulation example and name it "frmPolymorphism".
2. Add a new Module to the PersonProj project and name it "MyInterfaces.vb".
3. Apply the following code to the MyInterface.vb module. The code defines
the interface.
Public Interface IPerson
Property FName() As String
Property LName() As String
ReadOnly Property FullName() As String
Property BirthDate() As Date
ReadOnly Property Age() As Integer
'Method for adding hours to m_TotalHours worked.
Sub Work(ByVal intHours As Integer)
ReadOnly Property TotalHoursWorked() As Integer
End Interface
4. Right- click and select Properties, then change the StartUp object to "frmPolymorphism".
5. Create a new class to the Person.vb module. You will use it to inherit the new interface:
Public Class clsPerson3
Implements IPerson
Private m_FName As String
Private m_LName As String
Private m_BirthDate As Date
Private m_TotalHours As Integer
Private m_HrRate As Integer
Private m_TotalPay As Double
Public Property FName() As String _
Implements IPerson.FName
Get
Return m_FName
End Get
Set(ByVal Value As String)
m_FName = Value
End Set
End Property
Public Property LName() As String _
Implements IPerson.LName
Get
Return m_LName
End Get
Set(ByVal Value As String)
m_LName = Value
End Set
End Property
Public ReadOnly Property FullName() As String _
Implements IPerson.FullName
Get
Return m_FName & " " & m_LName
End Get
End Property
Public Property BirthDate() As Date _
Implements IPerson.BirthDate
Get
Return m_BirthDate
End Get
Set(ByVal Value As Date)
If IsDate(Value) Then
m_BirthDate = Value
End If
End Set
End Property
Public ReadOnly Property Age() As Integer _
Implements IPerson.Age
Get
If DatePart(DateInterval.Year, _
m_BirthDate) = 1 Then Exit Property
Return DateDiff(DateInterval.Year, _
m_BirthDate, Now)
End Get
End Property
'Method for adding hours to m_TotalHours worked.
Public Sub Work(ByVal intHours As Integer) _
Implements IPerson.Work
m_TotalHours += intHours
End Sub
Public ReadOnly Property TotalHoursWorked() As Integer _
Implements IPerson.TotalHoursWorked
Get
Return m_TotalHours
End Get
End Property
'Additional Properties:
'HrRate
Public WriteOnly Property HrRate()
Set(ByVal Value)
m_HrRate = Value
End Set
End Property
'TotalPay
Public ReadOnly Property TotalPay() As Double
Get
Return m_HrRate * m_TotalHours
End Get
End Property
End Class
6. Now run this example just as you ran the previous ones. You will not notice a difference in how the application runs, although the plumbing has changed quite a bit.
With this simple example, it is easy to question the usefulness of polymorphism. However, if you were to continue building an application that dealt with several aspects of a person, you might find polymorphism more helpful to use if you had to deal with Employees, Customers, Managers, and Contractors.
Working with Inheritance
This inheritance example inherits the interface and implementation code of the clsPerson3 class:
1. Add a new Windows Form item named "frmInheritance.vb". Use the same controls as we used in the encapsulation example.
2. Add the following code to the initialization section of the form:
' Here we are initializing the Person class. Normally this would be done
' when the class was needed for data access but in this case we are using
' the Person class to maintain our data.
Dim objPerson As New Person.clsEmployee()
3. Add the following class to the Person.vb module of the PersonProj project:
Public Class clsEmployee
Inherits clsPerson3
End Class
4. Right- click and select Properties, then change the StartUp object to "frmInheritance".
5. Now Run the inheritance example as you did the previous ones. Notice that the clsEmployee class inherits the functionality of the clsPerson3 class, which in turn implements the IPerson interface. This example demonstrates both polymorphism and inheritance that have been combined to form a single solution.
Summary
In this chapter, you learned that Visual Basic has come a long way from a reduced featured-set language that promoted RAPID application development to a full featured language. Now employing full inheritance, Visual Basic promises to aid in the delivery of enterprise level applications that may previously have been better delivered in another OOP language.
Object Oriented Programming Language Features
(Written 10/30/2000)
This article describes the basic features involved in Object Oriented Programming (OOP) languages in general and how they are implemented in Visual Basic. It is by no means an all inclusive, definitive reference but does serve to describe some of the typical features considered to comprise an object oriented language. It may be a good starting point for less experienced programmers.
Object oriented features are typically lumped into 2 categories. The first includes the biggies: Polymorphism, Inheritance and Encapsulation (PIE). The second includes other features such as operation overloading, parameterized constructors and class-level attributes and operations.
Inheritance
Inheritance comes in two forms. Interface and Implementation inheritance. Interface inheritance is available in Visual Basic since VB6's introduction of the Implements keyword. Implementation inheritance is available as of VB.NET with the Inherits keyword.
The main difference between the two inheritances is that interface inheritance should specify only a contract of desired behavior. It should not allow any corresponding implementation code. It is up to other classes, which realize that interface, to provide the implementation.
Implementation inheritance lets subclasses share common code and attributes (properties). Also, implementation inheritance's ability to declare an operation abstract enables it to act similar to interface inheritance and force subclasses to implement the operation instead.
A VB.NET class can only enter into an implementation inheritance relationship with one superclass (also called base or ancestor class). However, VB.NET, like VB6, lets that same class enter into as many interface inheritance relationships as it chooses.
An example may help. Say you are developing an app in which the Store object wants to ask an instance of the Stereo class to calculate availability inventory. However, the Stereo class wants to borrow the functionality already provided by its superclass, Product. The Store object might implement such functionality like this:
Private myStereo as Stereo
Private Function getInventory() As Integer
getInventory = myStereo.calcInventory()
End Function
The real implementation of this behavior is in Product's calcInventory() method. Because VB6 doesn't support implementation inheritance you need to put some code in the Stereo class:
Implements Product
Private myProductObject as New Product
Private Function Product_calcInventory() As Integer
Product_calcInventory = myProductObject.calcInventory()
End Function
VB6 allows the interface, the Product class, to implement the actual behavior. Stereo keeps an instance of Product (containment) then asks that reference to do some work for it (delegation). This type of interface inheritance is not a true interface because it allows you to add code to Stereo to provide the actual behavior.
With VB.NET you can remove the containment and delegation code by using implementation inheritance. For example, in the Stereo class:
Inherits Product'
' any unique Stereo code'
The Store class makes a calculation request similar to the way the Stereo class makes this request, but Stereo carries out the work much differently in VB.NET. The Stereo instance, myStereo, doesn't contain a calcInventory() method so VB looks up its superclass, Product, and executes its calcInventory() procedure.
Implementation inheritance also allows you to override superclass operations. An Items class might use this to provide its own implementation of calcInventory() which would cancel out the behavior provided by its Product superclass. For instance:
Overrides Function calcInventory() As Integer'
' any unique Items code
'
End Function
All of the above may sound a bit confusing if you have not used inheritance in the past. Take a second to reread the above and you will see that it is really not that difficult. Interface inheritance tends to be a little harder to grasp for some but, again, it is not that though of a concept.
When to Use Inheritance
Implementation inheritance allows you to reduce your code base drastically since subclasses gain the methods of their superclass. This is a mixed blessing. If you start with a poor design you will end up with code that is more difficult than ever to debug and maintain.
Code and attributes once common in the superclass may not remain common as the app's business needs evolve over time. Eventually many subclasses may end up overriding the behavior of the superclass. Worse, the subclass may override the superclass, do its own work, then call the same operation again on the superclass. Believe me, I've seen it happen in other languages.
VB.NET requires you explicitly use the Overrides and Inherits keywords. Most languages do not mandate this and are smart enough to know on their own. I believe this is a good thing. It provides self documentation and instant visual clue as to whether a method is overridden or not simplifying maintenance and debugging.
Use Interface Inheritance When:
·The base or superclass represents a generic facility such as a table lookup, unique algorithm,...
·The number of common operations is small.
·The base class has few, if any, attributes.
·Classes realizing the interface are diverse, with little or no common code.
Use Implementation Inheritance When:
·The base class is an entity class (business or domain class) of primary interest to the application (not a utility or control class).
·The implementations are complex with a large number of operations.
·Many operations and attributes are common across the subclasses.
Polymorphism
Polymorphism means many forms. It gives classes the ability to define the same operation but provide unique implementations of that operation. In other words, each of the class' methods is different. You can use polymorphism with either interface or implementation inheritance.
Polymorphism is more than overriding a superclass's operations which is a small part of polymorphism's power. For an operation to be polymorphic it must implement an operation defined in an interface it implements or it must implement an operation defined in a superclass. The superclass' operation is typically abstract, which means it has no implementation code.
For example, you might encounter the getInformation() operation defined in Product as abstract. If another object refers to either a Stereo or Items object and wants to send it a message, it is best not to hard code that class name:
Public Function getProductInfo(myProduct as Product) As Boolean
myProduct.getInformation()
End Function
The input parameter's data type is Product. VB is smart enough to know whether the function is asking Stereo or Items to get its information.
Encapsulation
Encapsulation is also called information hiding and ensures that no other class has knowledge about attributes and/or behavior unless the class publishes them expressly.
VB lets you declare attributes and behaviors with the Private keyword inside a class module. This hides them from the world outside of the class. Conversly, if declared with the Public keyword other objects can access those properties and methods yet know nothing of their actual implementation.
Overloading
Overloading lets a function vary its behavior based on its input parameters. VB.NET lets you have multiple functions with the same name but with different input parameters. The language knows which version to execute based on what is pased in. An example:
Overloads Function calcInventory() As Integer
'
' any code unique to this no parameter version.
'
End Function
Overloads Function calcInventory(dteDate as Date) As Integer
'
' code unique to this date driven version.
'
End Function
Overloads Function calcInventory(StartDate as Date, EndDate as Date) As Integer
'
' code unique to the date range version.
'
End Function
Shared Members
Sometimes you need to provide behavior on behalf of all objects in a particular class and maintaining attributes that all instances of a class can have access to. VB.NET uses shared members to resolves this issue. Other languages call these feature static or instance operations.
If the Product class needs to maintain an attribute and operation that are available to all of its instances, you might implement the Product class like this:
Shared TotalProductCount As Integer
Shared Function calcTotalProductCount As Integer
'
' get count of all products.
'
End Function
These attributes do not require an object instance to use them. In VB6 you can create a variable in a .bas module that is acessible from all objects of a given class. This achieves the same function but is not very object-oriented.
Glossary
Constructors
Constructors allow you to create an object and provide it with an initial set of data so it can initialize properly. Without constructors, you create an object, then need to all a separate method to initialize it. Two steps.
Encapsulation
Is data hiding. You can create a set of procedures or methods and properties that form an interface. Other code can then use these methods with any knowledge of the code within the methods. The procedures or methods you write is called an implementation. The implementation is encapsulated within the interface.
Inheritance
The concept that an object can gain the interface and actual behaviors (implementation) of another object, then extend that interface or those behaviors. Say you create a generic Product object that handles things common to all your products. From it you may create specialized Perishable and Non-Perishable objects. Both objects inherit the original Product object's interface and behaviors but can extend or change some of those behaviors.
Initializers
An initializer allows you to declare a variable and assign it an initial value all in one statement.
Object-Based
This loosely describes a language that interacts with objects easily and directly.
Object-Oriented
Object oriented languages must support polymorphism, inheritance and encapsulation (PIE).
Overloading
This allows you to declare multiple procedures with the same name in the same scope each having different input parameter specifications. For instance, you many define a function CreateTotal that totals the values in its array argument and another CreateTotal function that takes 5 long arguments and returns their sum. You call CreateTotal and pass it the proper parameters and the language will know which version of the function to use.
Overriding
When using inheritance your new class gets all the methods from its parent or super class. However you may want a different implementation for one of these methods. You do so by overriding the original (inherited) method with your own code. Your new code may even call the original method in the parent class.
Polymorphism
This is the ability to have two different objects of two different types both implement the same method. It lets you write code that calls that method regardless of which type of object is in use at the moment.
Shared Members
Otherwise know as class, static or instance members. Shared members are methods or variables that are equally available to all instances of a class. Every object that you create, based on a given class, shares these same variables and routines.
User Interface Inheritance
Means you can create a VB form template then derive all your other forms from this template. All other forms will inherit the look and code from the template form. A change to the original form will propagate out to all the child forms.

