Chapter 17: Introduction to Application Advancement

Once the VB6 upgrade has been completed, the time is right for a Visual Basic .NET application advancement, that is, the process of adding new features to your upgraded VB application. Chapters 17, 18, 19, and 20 are provided to give you ideas about the kinds of advancements you can make to your applications: VB.NET object-oriented features,VB.NET application layering, VB.NET code refactoring, VB.NET data serialization, and more. Remember that by studying Visual Basic .NET and the .NET Framework in detail, you will discover even more technologies and features that you can add to your application to increase their value to your business for years to come.

What is application advancement?

Application advancement is the process of adding new features, or improving the existing features, of an application. It is moving beyond functional equivalence after upgrading an application.

What are the 3 main aspects of application advancement?

Any advancement plan will require a new architecture, design and implementation. Architecture topics will impact the overall system and what it depends on, along with relationships to other systems. The design advancements will organize the code and help you determine which portions of your system can be reused and which must be recreated anew. Implementation advancements will improve the performance, readability, and maintainability of the code.

What are the main Visual Basic .NET Object-Oriented features that I can take advantage of?

Visual Basic .NET is a fully object-oriented language that supports features like the following:

  • Encapsulation: This feature allows a group of class members to be treated as a single conceptual unit. It enables developers to hide implementation details while exposing a well defined set of functionality to clients.
  • Inheritance: It is the ability to create new classes based on previously defined classes or interfaces. The new class (named the derived class) extends the functionality of the class it inherits from (named the base class) to solve a more specific problem. The derived class will have all existing members (including variables, methods, and properties) of the base class. If an inherited method’s behavior is inadequate or incorrect for the derived class then the derived class can change the definition of an inherited method to make it more suitable. The name of this process is overriding.
  • Interfaces: Interfaces specify the properties, methods, and events that classes must implement. They allow developers to separate the information that clients need to know (such as the names, return types, and parameter lists of methods) from the implementation details. This reduces compatibility risks because enhanced implementations can be developed for interfaces without jeopardizing existing code. In Visual Basic 6.0, it is possible to implement existing interfaces, but it is not possible to define them yourself. The Interface statement has been introduced in Visual Basic .NET to allow developers to define interfaces as entities that are different from classes. The Implements keyword is used to implement interfaces in a class. It is important to note that .NET Framework classes can implement more than one interface to be applicable in different contexts. It is also important to note that implementing an interface establishes an inheritance relationship between the interface (which serves as the base) and the implementing class (which is derived from the interface).
  • Polymorphism: This feature, in conjunction with inheritance, allows a program to invoke the proper version of a method depending on the object being used to invoke it. Conceptually, a base class provides some core set of functionality. Derived classes build on this core set by specializing some of the functionality. A base class reference variable can be used to refer to any object of the base class or any derived class. When an overridden method is invoked through this reference, the actual method invoked will be based on the type of the object, not the type of the variable. Polymorphism allows very generic programming, where developers can write solutions for problems based on what functionality will be provided without having to worry about how that functionality will be specialized in derived classes.
  • Overloading Functionality: A member is overloaded when it is declared more than one time with the same name but different arguments. This quality allows developers to program functionality in a generic way. For example, the Encrypt function can be designed to receive different data types and return an encrypted value. In this way, the Encrypt function can be used in all the places that an encrypted variable is required. In Visual Basic 6.0, you would have to define a function with a different name for each of the expected data types. Overloading, overriding, and shadowing are similar concepts applicable to the declaration of members with the same name, but there are important differences that make them appropriate to certain circumstances: overloading is applicable when different members have the same name but accept a different number of arguments or arguments with different data types; overriding is applicable when a member defined in a derived class must accept the same data type and number of arguments as a member in the ancestor; and shadowing is applicable when an inherited member has to be locally replaced.
  • Visual Inheritance: Another scenario where inheritance has been proven to be especially useful is in visual form inheritance. This type of inheritance allows you to apply the layout, controls, and code for existing forms to new forms. Through this type of inheritance, developers can build standard base forms that can be inherited by applications to give them a similar look and feel, but still giving the freedom to expand on the form by adding new controls or changing a particular control’s behavior. The result of visual inheritance is that developers can take existing forms and customize them for new applications, instead of having to recreate new forms for each application.

What are the main recommendations when implementing layering in Visual Basic .NET?

Layers represent a build-up of functionality. The higher layers are dependant on the lower layers to supply the required information and methods. A single layer represents a logical group of functionality that is contained in an application tier that usually corresponds to functionality that is bound to a physical resource such as a database server. In this way, application tiers are composed of one or more logical layers that build the functionality provided by the tier.

It is important to keep in mind that lower layers should almost never rely on higher layers for operation. A group of layers usually present a coherent whole. For example, you may be familiar with the concept of dividing an application among various tiers that are composed of groups of layers, such as the presentation tier (UI), the business logic tier (functionality), the data tier (storage and retrieval), and so on. Each tier represents a coherent unit of the application and serves a single purpose, such as displaying information to the user or representing business logic.

Careful layer design is important. Unnecessary layers can harm performance.

So, what are the pros and cons of layering?

An advantage to layers is that substitution is possible. This means that you can pull out a layer and provide a different layer that adheres to the interface but provides a completely different level of functionality. This is very helpful for unit tests; it is also helpful for applications that may require different business logic or database access but no other changes.

A disadvantage of layers is that cascading interface changes may lead to more work. Adding a field to a screen or page may require a change not only in the layers of the presentation tier, but it may also require a change in the business and data tiers.

What is refactoring and what is the best approach for implementing it?

Refactoring is an iterative process whose goal is to transform existing applications according to modern software engineering quality criteria. When refactoring is applied to an application, the characteristics of the application are improved in terms of clarity, maintainability, and redundancy reduction, amongst others.

Refactoring may involve small changes, such as changing variable or subroutine names, or it may require large changes, such as consolidating functionality into a single component or breaking apart an existing component into multiple components that more logically isolate functionality.

The best approach to refactoring is to attempt to do so in small steps. This will help to ensure that the refactoring process does not introduce defects. Smaller refactoring processes, such as identifying poor variable names, may take only minutes. Larger refactoring processes, such as identifying functionality that can be consolidated (or separated) can take hours, days, or even weeks. However, even for extensive or intensive refactoring, it is still best to proceed in small steps.

What are the main reasons to apply code refactoring?

The following motivations are among the most common:

  • To make it easier to modify and add new code. The grouping and separation of common pieces of code allows for centralization of functionality. If functionality needs to be changed, it can be done in one single place.
  • To improve the design of existing code. Refactoring tends to produce clearer code and eases the adoption of enterprise development standards. It also allows the developer to easily develop and test different system models and designs to identify which one best fits the enterprise needs.
  • To gain a better understanding of code. The clarity and organization gain obtained with refactoring allows the human reader to identify and understand the key concepts of the functionality.
  • Improved growth and integration capabilities. The code can grow in an organized and systematic way; this allows for the definition of standard interfaces that ease integration and reusability.

What lies behind the concept of refactoring to patterns?

It implies the transformation of existing applications according to software design patterns. A pattern is based on an abstraction of a group of similar software designs that is further refined and at the end constitutes a model that contains the most relevant characteristics of the original applications. Patterns are obtained with a logical inductive process that has been applied to real-life application designs. Software design patterns are also enriched with good design principles and standards that will allow the user to obtain a better product when the pattern is applied.

Patterns can assist users with creating large-scale applications and with resolving recurring design problems with proven and highly effective guidance. When an application is refactored according to design patterns, the resulting structure of the application can be understood as a derivation of the pattern(s) applied; this reduces the future development and maintenance risks.

Why should I consider replacing API Calls with .NET Framework Intrinsic Functions?

Because of some limitations in Visual Basic 6.0, many programmers have been forced to use the different functions that are available in the Windows API. For operations such as registry manipulation, finding window names, or finding the names of special folders, Visual Basic 6.0 developers were forced to use the Windows API because there was no other way to carry out these tasks.

In Microsoft Visual Basic .NET, all the barriers that held back Visual Basic 6.0 developers and forced them to resort to the Windows API are completely gone. For every function that accomplished a specific task in the Windows API, there is a way to achieve the same result in .NET using intrinsic functions.

You can still use Windows API calls by means of using interop services, but your .NET applications are usually be better off using the functions included in the .NET Framework. The amount of work required to get your .NET application working with the same API calls from Visual Basic 6.0 may be more than what you would expect. Calling these APIs in .NET is not as simple as it is in Visual Basic 6.0, and there may be some additional overhead making the necessary corrections so that changes in data types from Visual Basic 6.0 will adhere to the type specifications of the APIs. Furthermore, by using API calls in your .NET application, you are immediately limiting your application to be specific to a particular version of the Windows operating system.

Even though there is no harm in using Windows APIs in Visual Basic .NET, your code may be more portable and compatible if you replace your API calls by using .NET intrinsic functions. The end result may be the same, but if you ever need to move part of your code toward another .NET language or operating environment you know that you will not be held back by adhering to .NET functions instead of using the Windows API.

Why should I consider replacing Registry API with .NET Intrinsic Functions?

Built-in registry manipulation from within Visual Basic 6.0 has been somewhat limited to the following four functions: SaveSetting, GetSetting, GetAllSettings, and DeleteSettings. Although these functions allow registry editing, they are limited in the subtrees they can access: HKEY_CURRENT_USER\Software\VB and VBA. Because of this limitation, many programmers use the Windows API to perform registry modification tasks from within Visual Basic 6.0.

Two new classes that offer the same registry manipulation functionality as the Windows Registry API have been added to the .NET Framework. These classes are the Registry class and the RegistryKey class; they are located in the Microsoft.Win32 namespace. The Registry class provides the set of standard base keys found in the registry. Using it, you can define the root key on which you will be working.

After the base key is defined, you can use the RegistryKey methods to perform the necessary registry actions, like adding, deleting or changing subkey values. There are equivalent .NET registry method calls for the most common operations performed using the Windows registry API functions. For example, if you used the RegCreateKey, RegOpenKeyEx, or RegDeleteKey API calls, you can now use the RegistryKey class methods CreateSubKey, OpenSubKey, or DeleteSubKey respectively. These .NET methods offer the same functionality as their API counterparts and are easier to use.

By using the Registry and RegistryKey classes, you will be able to manipulate the registry from within Visual Basic .NET. The end result will be exactly the same as when using the Windows API registry functions from within Visual Basic 6.0. In most cases, the necessary code to perform these operations will be smaller and more understandable than the API counterpart.

Note that Registry access has been simplified in Visual Studio 2005 with the addition of the My.Computer.Registry object and the addition of two new methods to the Microsoft.Win32.Registry object that My.Computer.Registry points to. These objects provide access to the registry on the local computer. The two new methods are GetValue() and SetValue(); they allow you to read from and write to arbitrary keys in the registry without having to first navigate the registry tree.

How is Serialization implemented in Visual Basic .NET?

When coding applications that do not have or need access to a database, there is frequently a need to store data used in the program for later retrieval. Before .NET, any attempt to serialize data required custom code. For example, if the programmer needed to serialize the information from a class or data structure, a procedure could have been written that would write each instance of the class/structure to an XML document. Programming this code usually required a great deal of work and testing to make sure that the implemented serialization worked correctly. Fortunately, carrying out this task with .NET does not require writing, testing, or debugging custom code because it has been carefully planned and is very straightforward to implement.

What techniques can be employed when implementing serialization?

Using .NET, there are different serialization schemes that can be used in your application. Each technique has its own positive and negative aspects; choosing the correct technique depends on the requirements that you may have for storing your data.

The first technique, shallow serialization, uses classes found in the System.Xml.Serialization namespace, specifically the XmlSerializer class. Using this class, you can easily serialize and deserialize data in your code to XML format. You must first create an XmlSerializer instance and pass to the constructor the type of the class that you want to serialize. You can then create an object of type FileStream to store the data and then call the Serialize method of the XmlSerializer instance that was previously defined.

On the other hand, binary serialization allows the storage of both private and public properties. This technique is found in the System.Runtime.Serialization namespace and requires a bit more coding than the XMLSerializer method. The class to be serialized using binary serialization must have the <Serializable()> attribute declared. This communicates to the serialization object that it can go ahead and try to serialize the object. Then you need to first declare a BinaryFormatter object to store the data and use a FileStream object and the Serialize method of the BinaryFormatter class to store this information to a file.

How can .NET Collections be used for optimizing performance?

In the System.Collections namespace, you can find a series of classes and interfaces that define a series of objects that can be used to implement sets of closely related objects in your classes. This namespace includes collections that can be used immediately, such as ArrayList, Stack,Hashtable, and Queue, but it also includes interfaces such as IEnumerable that allow you to create your own collection classes.

By having classes implement collections instead of dealing with groups of instances by means of arrays, all the caveats that using arrays usually bring, such as (but not limited to) run-time errors, invalid indexes, or array length tracking, are no longer concerns. By implementing collections, you are also following good object-oriented practice in the sense that you can easily employ other concepts such as inheritance and polymorphism in the future. Following this approach will also aid you in avoiding errors in the future by re-utilizing stable code.