Polymorphism

Polymorphism is often considered to be directly tied to inheritance. In reality, it is independent. Polymorphism in software engineering means that you can have two classes with different implementations or code, but with a common set of methods, properties, or events. You can then write a program that operates upon that interface and does not care about which type of object it operates at runtime.

Method Signatures

To properly understand polymorphism, you need to explore the concept of a method signature, sometimes also called a prototype. All methods have a signature, which is defined by the method's name and the data types of its parameters. You might have code such as this:

Public Function CalculateValue() As Integer
        
End Sub

In this example, the signature is as follows:

f()

If you add a parameter to the method, the signature will change. For example, you could change the method to accept a Double:

Public Function CalculateValue(ByVal value As Double) As Integer

Then, the signature of the method is as follows:

f(Double)

Polymorphism merely says that you should be able to write client code that calls methods on an object, and as long as the object provides your methods with the method signatures you expect, it doesn't matter from which class the object was created. The following sections look at some examples of polymorphism within Visual Basic.

Implementing Polymorphism

You can use several techniques to achieve polymorphic behavior:

  • Inheritance
  • Multiple interfaces
  • Late binding
  • Reflection

Much of this chapter has illustrated the concepts around using inheritance and multiple interfaces. These techniques are common implementations that help you achieve polymorphism. These methods provide the least performance penalty and can be easy to implement. Late binding enables you to implement “pure” polymorphism, although it comes with a slight performance cost. Additionally while it is deceptively easy to implement, it can introduce difficult-to-debug runtime issues.

Reflection enables you to use either late binding or multiple interfaces, but against objects created in a very dynamic way, even going so far as to dynamically load a DLL into your application at runtime so that you can use its classes. Reflection is neither performant nor easy to implement. Since inheritance and multiple interfaces have been discussed in detail, you will now start with a look at late binding.

Polymorphism with Inheritance

Inheritance, discussed earlier in this chapter, can also be used to enable polymorphism. The idea is that a subclass can always be treated as though it were the data type of the parent class.

As described earlier, the Person class is an abstract base class, a class with no implementation of its own. The purpose of an abstract base class is to provide a common base from which other classes can be derived. Each of these child classes exhibits polymorphism because it can be treated using methods common to its parent—Person.

To implement polymorphism using inheritance, you do not need to use an abstract base class. Any base class that provides overridable methods (using either the MustOverride or Overridable keywords) will work fine, as all its subclasses are guaranteed to have that same set of methods as part of their interface, and yet the subclasses can provide custom implementation for those methods.

Polymorphism Through Late Binding

Typically, when you interact with objects in Visual Basic, you are interacting with them through strongly typed variables. For example, in Form1 you interacted with the Person declaring and instance with the variable name emp. The emp variable is declared using a specific type—meaning that it is strongly typed or early bound.

You can also interact with objects that are late bound. Late binding means that your object variable has no specific data type, but rather is of type Object. To use late binding, you need to use the Option Strict Off directive at the top of your code file (or in the project's properties). This tells the Visual Basic compiler that you want to use late binding, so it will allow you to do this type of polymorphism.

By default, Option Strict is turned off in a new Visual Basic project. With Option Strict off, the Object data type is late bound and you will be able to attempt to call an arbitrary method, even though the Object data type does not implement those methods. Note Visual Basic isn't alone in leveraging late binding. C# has introduced this concept, which it originally didn't support, and it is leveraged heavily by all of the implementations of LINQ.

When late binding is enabled you get the same result as you did before, even if at compile time the environment was unsure if a class included a given method as part of its interface. The late-binding mechanism, behind the scenes, dynamically determines the real type of your object and invokes the appropriate method.

When you work with objects through late binding, neither the Visual Basic IDE nor the compiler is always certain at compile time if you are calling a valid method. It just assumes that you know what you are talking about and compiles the code.

At runtime, when the code is actually invoked, it attempts to dynamically call the method. If that is a valid method, then your code will work; otherwise, you will get an error.

Obviously, there is a level of danger when using late binding, as a simple typo can introduce errors that can be discovered only when the application is actually run. However, it also offers a lot of flexibility, as code that makes use of late binding can talk to any object from any class as long as those objects implement the methods you require.

There is a performance penalty for using late binding. The existence of each method is discovered dynamically at runtime, and that discovery takes time. Moreover, the mechanism used to invoke a method through late binding is not nearly as efficient as the mechanism used to call a method that is known at compile time.

Polymorphism with Multiple Interfaces

Another way to implement polymorphism is to use multiple interfaces. This approach avoids late binding, meaning the IDE and compiler can check your code as you enter and compile it. Moreover, because the compiler has access to all the information about each method you call, your code runs much faster.

Visual Basic not only supports polymorphism through late binding, it also implements a stricter form of polymorphism through its support of multiple interfaces.

With multiple interfaces, you can treat all objects as equals by making them all implement a common data type or interface.

This approach has the benefit that it is strongly typed, meaning the IDE and compiler can help you find errors due to typos, because the names and data types of all methods and parameters are known at design time. It is also fast in terms of performance: Because the compiler knows about the methods, it can use optimized mechanisms for calling them, especially compared to the dynamic mechanisms used in late binding.

Polymorphism through Reflection

You have learned how to use late binding to invoke a method on any arbitrary object as long as that object has a method matching the method signature you are trying to call. You have also walked through the use of multiple interfaces, which enables you to achieve polymorphism through a faster, early-bound technique. The challenge with these techniques is that late binding can be slow and hard to debug, and multiple interfaces can be somewhat rigid and inflexible.

Enter reflection. Reflection is a technology built into the .NET Framework that enables you to write code that interrogates an assembly to dynamically determine the classes and data types it contains. Using reflection, you can load the assembly into your process, create instances of those classes, and invoke their methods.

When you use late binding, Visual Basic makes use of the System.Reflection namespace behind the scenes on your behalf. The System.Reflection namespace can give you insight into classes by enabling to traverse information about an assembly or class.

You can choose to manually use reflection as well. However, this means that just as there is a performance impact for the Visual Basic runtime, there will be a performance impact in your own applications when using Reflection. More important, misused Reflection can have significant negative performance implications. Howerver, the presence of reflection gives you even more flexibility in terms of how you interact with objects.

While this section will introduce Reflection, fuller coverage of Reflection is handled in Chapter 17. With that in mind, suppose that the class you want to call is located in some other assembly on disk—an assembly you did not specifically reference from within your project when you compiled it. How can you dynamically find, load, and invoke such an assembly?

Reflection enables you to do this, assuming that the assembly is polymorphic. In other words, it has either an interface you expect or a set of methods you can invoke via late binding.

To see how reflection works with late binding, create a new class in a separate assembly (project) and use it from within the existing application. Add a new Class Library project to your solution. Name it “VB2012_Objects.” It begins with a single class module that you can use as a starting point. Rename the default Class1.vb file to External.vb and change the code in that class to match this:

Public Class External
  Public Function Multiply(x As Double, y As Double) As Double
    Return x * y
  End Function
End Class

Now compile the assembly. Next, bring up the code window for Form1. Add an Imports statement at the top for the System.Reflection namespace:

Imports System.Reflection

Remember that because you are using late binding your project ProVB2012_Ch04 must have Option Strict Off. Otherwise, late binding isn't available.

Add a button to Form1 and label it Multiply. Rename the button as ButtonMultiply and implement an event handler for the Click event with the following code. Remember, you have to have imported the System.Reflections namespace for this to work (code file: Customer.vb):

Private Sub ButtonMultiply_Click(sender As Object, 
e As EventArgs) Handles ButtonMultiply.Click
        Dim obj As Object
        Dim dll As Assembly
        dll = Assembly.LoadFrom(
             "......VB2012_ObjectsinDebugVB2012_Objects.dll")
        obj = dll.CreateInstance("VB2012_Objects.External")
        MessageBox.Show(obj.Multiply(10, 10))
End Sub

There is a lot going on here, so a step-by-step walk-through will be helpful. First, notice that you are reverting to late binding; your obj variable is declared as type Object. You will look at using reflection and multiple interfaces in a moment, but for now you will use late binding.

Next, you have declared a dll variable as type Reflection.Assembly. This variable will contain a reference to the VB2012_Objects assembly that you will be dynamically loading through your code. Note that you are not adding a reference to this assembly via Project ⇒ Add Reference. You will dynamically access the assembly at runtime.

You then load the external assembly dynamically by using the Assembly.LoadFrom method:

dll = Assembly.LoadFrom(
             "......VB2012_ObjectsinDebugVB2012_Objects.dll")

This causes the reflection library to load your assembly from a file on disk at the location you specify. Once the assembly is loaded into your process, you can use the dll variable to interact with it, including interrogating it to get a list of the classes it contains or to create instances of those classes.


Note
You can also use the AssemblyLoad method, which scans the directory containing your application's .exe file (and the global assembly cache) for any EXE or DLL containing the Objects assembly. When it finds the assembly, it loads it into memory, making it available for your use.

You can then use the CreateInstance method on the assembly itself to create objects based on any class in that assembly. In this case, you are creating an object based on the External class:

obj = dll.CreateInstance("VB2012_Objects.External")

Now you have an actual object to work with, so you can use late binding to invoke its Multiply method. At this point, your code is really no different from any late-binding method call, except that the assembly and object were created dynamically at runtime, rather than being referenced directly by your project. Note from a performance standpoint this can be an important difference. You should be able to run the application and have it dynamically invoke the assembly at runtime.

Polymorphism via Reflection and Multiple Interfaces

You can also use both reflection and multiple interfaces together. You have seen how multiple interfaces enable you to have objects from different classes implement the same interface and thus be treated identically. You have also seen how reflection enables you to load an assembly and class dynamically at runtime.

You can combine these concepts by using an interface shared in common between your main application and your external assembly, using reflection to load that external assembly dynamically at runtime. Within this method there is no reason to have Option Strict disabled, as you will now be working with strongly typed method calls.

In fact this is the primary advantage of using interfaces with Reflection. By adding an interface definition that is shared across otherwise unrelated assemblies; it is possible to enforce strong typing with Reflection. Keep in mind that this does not change the performance characteristics of Reflection nor does it mean that the assembly you load will in fact have an object that hosts a given interface.

This technique is still very nice, as the code is strongly typed, providing all the coding benefits; but both the DLL and the object itself are loaded dynamically, providing a great deal of flexibility to your application. Note Reflection is covered in much more detail in Chapter 17.

Polymorphism Summary

Polymorphism is a very important concept in object-oriented design and programming, and Visual Basic provides you with ample techniques through which it can be implemented.

Table 4.3 summarizes the different techniques covered and touches on key pros and cons. It also looks to provide some high-level guidelines about when to use each method of polymorphism. While most applications typically leverage some combination of inheritance and multiple interfaces, there are places for all of the polymorphic techniques listed. For example, LINQ is built around a late-bound implementation.

Table 4.3 Methods of Implementing Polymorphism.

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

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