Home
Foreword
Preface
Class Idioms
Collections
Implements
Constructors
Terminate
Forms
On Error
Frameworks
F.A.Q.
Value-added
FSMs
Constants
GOTO
Hungarian
Nothing
Properties
Big EXEs

Maybe it's just me, but Visual Basic's lack of a proper typecast operator seems to put an unreasonable burden on the conscientious programmer, particularly when dealing with object variables. Here we'll look at type-casting using polymorphic variables created with the 'Implements' keyword.

Act I: Creating Polymorphic Variables

Let's say we have a class CTextFile that Implements CFile, where CFile has properties called PathName and FileName. We have the implementation of these methods in CFile itself, and CTextFile delegates them to a private CFile object. CTextFile is a collection class (the House of Bricks) that implements a collection of lines.

This all works fairly well, except that the only way we can get to the FileName and PathName properties of CTextFile is through a CFile variable. This makes for some cumbersome coding:

Dim oTextFile As CTextFile
Dim oAliasForSameFile As CFile  ' Sinking feeling already...
Set oTextFile = New CTextFile
Set oAliasForSameFile = oTextFile
' Do CFile stuff...

oAliasForSameFile.PathName = "c:\somwhere"
oAliasForSameFile.FileName = "markyfile.bat"

' Now do CTextFile stuff...

oTextFile.Add "@echo off"
oTextFile.Add "echo What a carry on..."
' ...etc
 

What we really want, of course, is to be able to write something like this:

Dim oTextFile As CTextFile
Set oTextFile = New CTextFile

((CFile) oTextFile).PathName = "c:\somwhere"
((CFile) oTextFile).FileName = "markyfile.bat"

oTextFile.Add "@echo off"
oTextFile.Add "echo Now that's what I call music!"
' ...etc

(I'm borrowing a bit of syntax from C here: (CFile) oTextFile is supposed to convert the CTextFile reference into a CFile reference for the duration of the statement. It's just like VB's CInt et al, except that I'm imagining it works for user-defined types).

Okay, there is a way around this, but is it really the right way? To get around it we need to implement the PathName and FileName properties again in CTextFile, delegating them to the mandatory CFile interface functions. For example, here's FileName:

Private Property Let CFile_FileName(ByVal RHS As String)
    ' Implements CFile forces this one
    m_oFile.FileName = RHS  ' Delegate to a real CFile
    ' Extra stuff here if necessary...
End Property

Public Property Let FileName(ByVal RHS As String)
    CFile_FileName = RHS  ' Yawn
End Property

Well, you get the idea. This works very well and I know this makes sense in terms of COM Interfaces, but it's so tedious. Is this really the only practical way to make early-bound polymorphic variables? Maybe not...

An alternative way to do this is to forget all about those duplicate properties and methods by writing a function that converts a reference to the derived type into a reference to the base type - a typecast, in C/++ parlance. This works well in practice, and initially it looks like we can even give it the same name as the class:

Public Function CFile(ByVal oThisFile As CFile) As CFile
    Set CFile = oThisFile
End Function

...
CFile(oTextFile).FileName = "markh.txt"    ' <--- okay!

Actually we can't call it the same as the class because it causes a runtime error in files that contain 'Implements CFile', although it works elsewhere. If it's going to be a global function we could call it CCFile, which ties up nicely with VB's CInt, CStr, etc. It would be nice to attach this conversion function to the class though (CTextFile in this case), so how about doing it as a property:

Public Property Get CFile() As CFile
    Set CFile = Me
End Property

and then

oTextFile.FileName = "markh.txt"        ' Nope
oTextFile.CFile.FileName = "markh.txt"  ' Yes!

This is good, although once again it can't really be called CFile (compile-time error this time) so we have to choose something less elegant like CCFile (doesn't look as good when it's a property) or oCFile.

You may think we could do this more easily by making the internal CFile object a public property of CTextFile. This would allow the following, which LOOKS superfically the same:

oTextFile.oFile.FileName = "markh.txt"

This isn't right though, because it circumvents the CFile interface altogether, bypassing any modified behaviour we've coded for the CFile_ interface's properties and methods. For example, let's say that CTextFile modifies the behaviour of CFile's FileName property by shifting the name to upper case. First the right way:

Private Property Get CFile_FileName() As String
    CFile_FileName = UCase(m_oFile.FileName)
End Property

Public Property Get FileName() As String
    FileName = CFile_FileName
End Property

and, just to remind ourselves of the 'shortcut' way,

oTextFile.oFile.FileName = "markh.txt"

Now we exercise this property with the following code:

Dim oFile As New CFile
oFile.FileName = "markh.txt"

Dim oTextFile As New CTextFile
Set oFile = oTextFile

Debug.Print oFile.FileName
Debug.Print oTextFile.FileName

Assuming the first version of CTextFile, this code prints

    MARKH.TXT
    MARKH.TXT

but with the second version, it prints

    MARKH.TXT
    markh.txt

To summarise, we now have four different ways of making early-bound polymorphic variables:

(1) Multiple Variables

    Set oFile = oTextFile
    oFile.FileName = "markh.txt"

    ---> Awful.

(2) Duplicate All Properties and Methods

    oTextFile.FileName = "markh.txt"

    ---> Looks the best, but lots of code.

(3) Write a Global Conversion Function

    CCFile(oTextFile).FileName = "markh.txt"

    ---> Looks pretty good (like a C++ cast) and we only write it once, but requires a spurious BAS file to hold the conversion routines.

(4) Write a Conversion Property

    oTextFile.oCFile.FileName = "markh.txt"

    ---> A bit ugly, but still quite good.

The unspoken issue, of course, is efficiency. My own view on efficiency is that it's the least important thing until it actually becomes a problem. What I'd be interested to know is how much of the extra function calls and type conversions gets optimized out by the compiler.

In conclusion, I suppose Implements is better than nothing, but for doing inheritance-type things it really IS more painful than a proper OO language (NOTE: the 'delegation wizard' argument isn't even worth addressing). The real problem seems to be twofold:

(1) There is no typecast operator, so I can't do this, which is permissible in Java:

    ((CFruit) oApple).Eat

(2) All Implemented (base) classes in VB are effectively, in one sense, pure virtual base classes - i.e. if you derive from (Implement) a class you have no choice but to override all of its properties and methods. The act of delegating to a private instance of the base class (and actually putting code in that class - so the virtualness isn't really 'pure') is just laboriously doing by hand what real inheritance gives you for free. Which is why Java has inheritance AND Implements. The question is, why doesn't Visual Basic?

Act II: Promoting Polymorphic Variables

Moving on from our discussion about creating derived classes (eg. deriving CTextFile from CFile), I've discovered that you can't assign an object reference from a base type variable to a variable of a derived type:

Dim oTextFile As CTextFile
Dim oAnyFile As CFile  ' Can reference any object that Implements CFile

Set oAnyFile = … ' Something

Set oTextFile = oAnyFile    ' <--- NOT ALLOWED!

This is fair enough, of course, since oAnyFile doesn't necessarily contain all the required information (it may be a CFile object, a CTextFile object, or any other object that Implements CFile). However, in practice it's useful to be able to do this kind of assignment - say, to attach an already-initialised CFile object to a CTextFile one. I guess in C++ you'd write a constructor for CTextFile that accepts a CFile as an argument.

One way is to add a new property called CFile to CFile's interface, so every class that Implements CFile has to have a property called CFile_CFile. This is a 'pure virtual property': it contains no code in the CFile class itself, and it exists only to ensure that each derived class provides a way of assigning a CFile object to itself. By providing specific versions of CFile_CFile for each class, we can now write code like this:

Dim oNewFile As CFile
For Each oThisFile In colListOfFiles

    Select Case oThisFile.Extension
        Case "EXE": Set oNewFile = New CExecFile
        Case "TXT": Set oNewFile = New CTextFile
        Case "NET": Set oNewFile = New CNetFile
        Case Else: Set oNewFile = oThisFile  ' Stick with a generic CFile
    End Select

    Set oNewFile.CFile = oThisFile    <--- The clever bit
    m_colFiles.Add oNewFile

Next oThisFile

This is good because oNewFile knows what type of object it contains and hence potentially calls a different version of CFile_CFile each time around the loop. The (mandatory) CFile_CFile property is different for each derived class, and it tacks whatever extra bits area needed after the assignment of the CFile object to the contained object. For example, here is CTextFile's version of CFile_CFile:

Private Property Set CFile_CFile(ByVal RHS As CFile)
    Set m_oFile = RHS    ' <--- This bit's common to all derived classes
    ' Extra code here to, say, open the file and read
    ' the text into a private collection of lines.
End Property

The nice part, of course, is that the property name (CFile) is the same as the class name of the object being assigned - although devising a convenient way to access this overridden function revisits all the problems described earlier! If I want to add a public CFile property to the derived class (CTextFile), for example, I can't call it 'CFile' because it conflicts with the 'Implements CFile' statement.

(NOTE: I considered CFile_, but although this works, once again poor old VB gets confused and the property doesn't appear in the code window's drop-down list. Worse than this, if I make a class the Implements CTextFile it just doesn't work because Implements can't handle underscores in property and method names.)
 

Key Spinner

© 1998 - 2009 Mark Hurst. All rights reserved.   Updated March 01, 2009