Getting Started

Combine Static and Dynamic Types

Late binding can bring great flexibility to Visual Basic programmers, but it has a serious downside in eliminating static-type checking and IntelliSense support. Use a static wrapper for dynamic code to enjoy the best of both worlds.

Technology Toolbox: Visual Basic

Late binding can give you a great deal of flexibility as a Visual Basic programmer, but it has a serious downside: It eliminates static-type checking and disables IntelliSense support.

Late binding essentially means that the program doesn't know the variable type at compile time. As a consequence, type checking is performed at run time. This is why late binding is also often referred to as dynamic typing. However, the type checking is limited to verifying that a certain class member is present in late-bound object. Consider this example of late binding (see Listing 1).

You can compile this code successfully, but a MissingMemberException is thrown once you try to run it. The Tree class lacks a Start method, so the last line in Main causes an error. If you activate the Strict option by replacing Option Strict Off with Option Strict On, you get a compile-time error with this message: "Option Strict On disallows late binding." And you are no longer able to compile such code.

Using this approach can help you avoid typos and similar errors. For example, assume you write "engine.Stat()" by mistake. The compiler can now help you identify the error right away, during compilation, with this message: "Stat is not a member of Engine".

Long-time Visual Basic programmers have a lot of experience using late binding, dating back to Visual Basic' pre-.NET days. In the COM era (Visual Basic 6 and prior), late binding often presented a workaround for COM versioning and binary compatibility issues. When performing early binding in referencing COM components, Visual Basic would bind to a specific version of a component. In COM, you used a different set of GUIDs to identify a component. Each time the component interface changed—that is, each time the signature of any non-private member, a method or a property changed—VB created a completely new GUID for the component whose interface changed. This version of the component had no relationship to the previous version of that same component. This means that if an application performed early binding, any other binary, non-compatible version of the component would provoke the infamous exception colloquially referred to as DLL hell: "Error 429 - ActiveX component can't create object."

In the case of late binding, the application would continue to work as long as a new version of a component had all the same methods that previous versions had, even if it contained new, non-private methods. You accomplished this by declaring a variable as Object. You also had to create the variable using the CreateObject function with ProgId as a parameter. This meant that the Visual Basic runtime would check for existence of a method or a property only at run time, during the execution. The code would execute without any problems as long as the object supported the function or property in question.

Duck Typing
The style of programming where the type of the method is determined by the inspection of the signature of its methods and properties, rather then by explicit static relationships between objects, is known as duck typing. The name derives from the cliché: "If it walks like a duck and quacks like a duck, it must be a duck."

Everything works fine, as long as the object has the member that corresponds to what client code expects. Several modern languages, such as Ruby and Python, as well as older languages like Smalltalk, permit this style of programming. Devotees of dynamically-typed languages emphasize the flexibility and productivity you can obtain from this approach, and praise it over the benefits that static-type checking can bring. People who promote late binding usually cite unit testing as the best guard against introducing programming errors. In dynamically-typed languages, function calls are resolved at run time, which permits treating objects in polymorphic manner, but without any interface or implementation inheritance relationship between the objects.

The .NET versions of VB permit a similar style of programming; however, they also let you disable late-bound programming by using the Option Strict On statement. Microsoft introduced this option with the first version of VB.NET. In pre-.NET versions of VB, anything you declare as Object is late bound. Placing Option Strict On at the top of a file effectively turns off late binding in that file.

This snippet illustrates an example where you might find this dynamic style of typing handy:

Option Strict Off
'. . . 
Public Sub CheckSpellingAndSave(ByVal _
officeObject As Object)
officeObject.CheckSpelling()
officeObject.SaveAs(FileName)
End Sub

You need to set Option Strict to Off for this code to compile. This code executes fine as long as the officeObject parameter was created as an Excel.Worksheet or Word.Document, due to the fact that both of these objects have both CheckSpelling and SaveAs methods (see Figure 1).

It's also possible to write this code with Option Strict on. However, doing so would make it far more verbose code-wise, to accomplish the same behaviour. Consider this code, which is based on reflection:

Option Strict On
'...
Public Sub CheckSpellingAndSave(ByVal officeObject _
As Object)
Dim saveAsParameters As Object() = {FileName}
officeObject.GetType.GetMethod("CheckSpelling"). _
Invoke(officeObject, Nothing)
officeObject.GetType.GetMethod("SaveAs") _
Invoke(officeObject, saveAsParameters)
End Sub

Reflection enables you accomplish everything you could do in pre-.NET versions of VB with late binding—and more. However, it does require that you write substantially more code to do so. In this case, the entire sample has only two lines, but the difference in quantity of code is significant. Now imagine you have to write a lot of code similar to this. The less verbose variant would be far preferable. If you didn't use reflection, the only solution in strict mode would be to overload a pair of methods:

Option Strict On
'...
Public Sub CheckSpellingAndSave(ByVal officeObject _
As Worksheet)
officeObject.CheckSpelling()
officeObject.SaveAs(FileName)
End Sub
Public Sub CheckSpellingAndSave(ByVal officeObject _
As Document)
officeObject.CheckSpelling()
officeObject.SaveAs(FileName)
End Sub

The problem with this solution is that you have identical code in both methods, a situation you want to avoid.

In a more common situation, you could resolve this by making both Document and Worksheet inherit the same base class or implement the same interface. Extract Interface is a standard refactoring technique. In this case, you don't have source for Office Automation Libraries, so the Extract Interface approach won't work here. You also cannot modify the Document or Worksheet class, so you need to find a different solution.

Reset VB's Behavior
Visual Basic behaves like a statically-typed language once you activate Option Strict, much as C# or Java do. A unique feature of VB when compared to other static or dynamic languages is that VB lets you specify this option at the file level. This means that you can mix files written in static and dynamic style in the same project.

This gives you a great deal of flexibility when you write code. You can sum up Microsoft's approach to typing with this sentence: "Static typing where possible, dynamic typing when needed." This leads us to an interesting way of considering the problem of how to approach the static versus dynamic typing issue: If you can isolate the dynamic elements from the static ones into separate files, you can keep code strict in all but the parts of your application that absolutely require it.

Before I go into more detail about how to do this, it might be helpful to review some aspects of VB as dynamic language. Some dynamic languages go further than Visual Basic with the dynamic paradigm. For example, they will also let you modify types by adding properties and methods "on the fly" at run time. You cannot implement similar functionality in VB without using the services provided by the Reflection.Emit namespace. This means you can be fairly certain that all the types you will encounter exist at the moment of compiling your program. Even in cases where you load some types dynamically, these types were probably compiled and created as static types, and there already exists a physical assembly or DLL containing these types. It is highly probable that these types were created intentionally to represent a certain defined entity, along usual OO design and analysis guidelines.

These facts lead you to an interesting conclusion: In Visual Basic, you interact with static types underneath, even when writing dynamic code. Now let's return to the CheckSpellingAndSave code snippet. This code interacts with two types: Word.Document and Excel.Worksheet, both from the Office automation library. Knowing this, it makes sense to provide a statically-typed wrapper over the dynamic code. This means the code that interacts with the wrapper never knows what happens underneath, nor does it need to. Client code treats your wrapper like any other static type, remaining oblivious to the fact that you used the wrapper to encapsulate dynamic code.

The sample handles two types: Document and Worksheet. The types exhibit some common behavior—they both implement CheckSpelling and SaveAs methods—but they don't share any common types, except the ultimate base type, Object, which is common to every other type in .NET. You cannot access the services these objects provide in a common way.

You could simply declare an interface if you had the source code for these two classes. For example, you could name it IOfficeWrapper, and then declare all common methods for Document and Worksheet in that interface. You would also make Document and Worksheet implement the IOfficeWrapper interface. This is typical Extract Interface refactoring. This approach enables you to see Document and Worksheet through a common interface and treat them in a uniform manner. This code creates an IOfficeWrapper interface, enabling it to contain all the methods used in a common manner:

Option Explicit On
Option Strict On
Public Interface IOfficeWrapper
Sub CheckSpelling()
Sub SaveAs(ByVal fileName As String)
End Interface

You can't change either the Excel.Worksheet or the Word.Document classes, so you need to create a new class that implements this interface. You need to make this new class delegate calls to the original officeObject. You also need to write this class in a dynamically-typed style so it can treat both the Worksheet and Document objects in the same way. Finally, place the class in a separate file and deactivate Option Strict.

p>You provide a wrapper with a constructor, so it can receive an officeObject and keep a reference to the officeObject in a private field. CheckSpelling and SaveAs method delegate calls only to a reference of original officeObject maintained as a field in our OfficeWrapper class (see Listing 2).

Basically, wrapper class delegates calls only to original Document or Worksheet objects. The Document or Worksheet object is passed to the wrapper at the moment of wrapper creation. Another interesting detail is that you deactivate Option Strict in the wrapper class, which enables you to treat Document and Worksheet in a polymorphic manner. However, you set Option Strict to On in the wrapper interface.

The final step is to put the wrapper to use. Refactor the original CheckSpellingAndSave method so the code is not duplicated and strict typing is maintained:

Option Strict On
'...
Public Sub CheckSpellingAndSave(ByVal officeObject _
As Object)
Dim officeWrapper as IOfficeWrapper = _ 
New OfficeWrapper(officeObject)
officeWrapper.CheckSpelling()
officeWrapper.SaveAs(FileName)
End Sub

The only code you need to add to the method is the code that creates the wrapper. You also need to redirect calls from the officeObject parameter to the officeWrapper object. Assuming this isn't the only place you'll use your office automation objects, the cost of creating and using the wrapper to activate strict typing is quite reasonable. Now you can place "Option Strict On" on any file, except the file containing OfficeWrapper source.

At this point, you are now able to isolate dynamic code from the rest of the application, minimizing its impact, while also preserving the benefits of dynamic code and avoiding code duplication.

This article is adapted from chapter 5 of Refactoring in Visual Basic by Danijel Arsenovski, coming from Manning Publications in late 2006, ISBN 1-932394-89-3.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube