How do I avoid down-casting to the interface class? - vba

I'm using Excel VBA (Excel 2010) and I've run into a problem when attempting to use inheritance. Basically, I have an interface MyInterface and an implementing class MyImplementation. In the VBA code, when I refer to a Dim of type MyInterface I can only access members defined on that interface - this is expected. When I refer to a Dim of type MyImplementation I cannot access members defined on the interface it implements - not expected.
Why can I not call the interface property directly on the implementation class?
MyInterface
Option Explicit
Public Property Get Text() As String
End Property
MyImplementation
Option Explicit
Implements MyInterface
'The implementation of the interface method'
Private Property Get MyInterface_Text() As String
MyInterface_Text = "Some Text"
End Property
Public Property Get MoreText() As String
MoreText = "Yes, some more text!"
End Property
MainModule - usage examples
Function Stuff()
Dim impl As New MyImplementation
Dim myInt As MyInterface: Set myInt = impl
'The following line is fine - displays "Yes, some more text!"
MsgBox impl.MoreText
'This is also fine - displays "Some text"
MsgBox DownCast(impl).Text
'This is also fine - displays "Some text"
MsgBox myInt.Text
'This is *not* fine - why??
MsgBox impl.Text
End Function
Function DownCast(ByRef interface As MyInterface) As MyInterface
Set DownCast = interface
End Function
The main question is how can I avoid down-casting?
Note - the example above is intentionally contrived. I realize it's generally bad practice to refer directly to implementation classes.

When I refer to a Dim of type MyImplementation, I cannot access members defined on the interface it implements - not expected.
The solution is to change your expectation. That's the way things work in VBA: VBA classes implement COM interfaces (such as IUnknown) without exposing them publicly.
If you want to expose your interface's members from the class, you have to do so explicitly:
Option Explicit
Implements MyInterface
'The implementation of the interface method'
Private Property Get MyInterface_Text() As String
MyInterface_Text = "Some Text"
End Property
Public Property Get MoreText() As String
MoreText = "Yes, some more text!"
End Property
Public Property Get Text() As String
Text = MyInterface_Text
End Property

Simply declare the implementing methods as Public instead of Private will do:
Option Explicit
' Class MyImpl
Implements MyInterface
'The implementation of the interface method'
'Notice the Public here instead of private'
Public Property Get MyInterface_Text() As String
MyInterface_Text = "Some Text"
End Property
The only thing to keep in mind is that to invoke the methods on the implementation you'll need to use a longer name:
Dim instance as MyImpl
' initialize your instance
instance.MyInterface_Text
' instead of instance.Text
That's all.

Related

Is it possible to change the appearance of a custom class's object in the VBA editor's locals and watch windows? [duplicate]

Although an experienced VBA programmer it is the first time that I make my own classes (objects). I am surprised to see that all properties are 'duplicated' in the Locals Window. A small example (break at 'End Sub'):
' Class module:
Private pName As String
Public Property Let Name(inValue As String)
pName = inValue
End Property
Public Property Get Name() As String
Name = pName
End Property
' Normal module:
Sub Test()
Dim objTest As cTest
Set objTest = New cTest
objTest.Name = "John Doe"
End Sub
Why are both Name and pName shown in the Locals Window? Can I in some way get rid of pName?
As comments & answers already said, that's just the VBE being helpful.
However if you find it noisy to have the private fields and public members listed in the locals toolwindow, there's a way to nicely clean it up - here I put the Test procedure inside ThisWorkbook, and left the class named Class1:
So what's going on here? What's this?
Here's Class1:
Option Explicit
Private Type TClass1
Name As String
'...other members...
End Type
Private this As TClass1
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
The class only has 1 private field, a user-defined type value named this, which holds all the encapsulated data members.
As a result, the properties' underlying fields are effectively hidden, or rather, they're all regrouped under this, so you won't see the underlying field values unless you want to see them:
And as an additional benefit, you don't need any pseudo-Hungarian prefixes anymore, the properties' implementations are crystal-clear, and best of all the properties have the exact same identifier name as their backing field.
All the Inspection windows not only show the public interface of the objects to you, but also their private members. AFAIK there is nothing you can do about it.
Consider it a nice feature to get even more insights while debugging.
In my experience this is less of an issue in real world objects as they tend to have more fields and properties. Assuming a consistent naming (as your example shows), fields and properties are nicely grouped together.
If you really dont want to see even Mathieu's This you could wrap it into a function. This is a bit more involved, and can be achieved using
a second class that stores the data in public variables. This will be marginally slower then Mattieu's implementation
a collection object that accesses the data using keys. This does not require additional clutter in the project exporer's 'class module' list but will be a little slower if you call the This repeatedly in fast sucession
An example for each is given below. If you break in the Class's Initialisation function, you can add me to the watch window and only the Name property will be listed
Using 2 Objects example
insert a classmodule and name it: InvisibleObjData
Option Explicit
Public Name As String
Public plop
Private Sub Class_Initialize()
Name = "new"
plop = 0
End Sub
insert a classmodule and name it: InvisibleObj
Option Explicit
Private Function This() As InvisibleObjData
Static p As New InvisibleObjData 'static ensures the data object persists at successive calls
Set This = p
End Function
Private Sub Class_Initialize()
This.Name = "invisible man": Debug.Print Name
Me.Name = "test": Debug.Print Name
This.plop = 111: Debug.Print This.plop
End Sub
Property Let Name(aname As String): This.Name = aname: End Property
Property Get Name() As String: Name = This.Name: End Property
'_______________________________________________________________________________________
' in the immediate window type
'
' set x=new invisibleObj
If you dont like splitting the class over two objects, a similar behaviour can be generated using a 'wrapped' collection object:
insert a classmodule and name it: InvisibleCol
Option Explicit
Private Function This() As Collection
Static p As New Collection
'static ensures the collection object persists at successive calls
'different instances will have different collections
'note a better dictionary object may help
Set This = p
End Function
Private Function add2this(s, v)
'a better dictionary object instead of the collection would help...
On Error Resume Next
This.Remove s
This.Add v, s
End Function
Private Sub Class_Initialize()
add2this "name", "invisible man": Debug.Print Name
Me.Name = "test": Debug.Print Name
add2this "plop", 111
Debug.Print This("plop") ' use the key to access your data
Debug.Print This!plop * 2 ' use of the BANG operator to reduce the number of dbl quotes
' Note: This!plop is the same as This("plop")
End Sub
Property Let Name(aname As String): add2this "name", aname: End Property
Property Get Name() As String: Name = This!Name: End Property
'_______________________________________________________________________________________
' in the immediate window type
'
' set x=new invisibleCol

Late Binding: Can it be achieved with object variables other than OBJECT type?

Generally late binding happens when an instance of an OBJECT type tries to access a member of a derived class. I was wondering if this definition can be extended that is do we get late binding error when an object variable of a class tries to access a member of a more derived class. So tried the following: Instance "v" of the base class VEHICLE tries to access the MAKE property of the CAR class. MAKE is not present in VEHICLE. The compiler does not allow this but error message is not that of late Binding. It just says that "Make is not a member of Vehice". So clearly this was not late binding. Code i wrote is given below:
Option Strict On
Module Module1
Sub Main()
Dim c As New Car()
Dim v As Vehicle = New Car()
c.Make = "Ford" 'Early Binding
v.make = "Nissan" 'Late Binding. Hence Disallowed
Console.ReadLine()
End Sub
End Module
Class Vehicle
'Some code
End Class
Class Car
Inherits Vehicle
Public Property Make As String
Public Property price As Integer
End Class
So my question is is there a way to achieve late binding without using an instance of OBJECT type?

VBA inheritance

I heard a lot of that VBA is lacking inheritance. I did a little workaround and now it seems to me that it is exactly what inheritance does. I'm far from pro =) and may be missing something. So I would really appreciate your thoughts about possible downsides.
I was very surprised when found that you still can make a full realization of a function in an interface class (not only a signature), which led me to the following below. I saw that some people did similar with help of composition, but they used only a signature in the interface.
IBird - class
Public Sub SayFromInterface()
Debug.Print "Tweet from IBird"
End Sub
Public Sub SayFromInstance()
End Sub
Crow - class
Implements IBird
Private pBird As IBird
Private Sub Class_Initialize()
Set pBird = New IBird
End Sub
'let you use already implemented code from "abstract class", to avoid
'duplicating your code, which is the main point of inheritance
'in my opinion
Public Sub IBird_SayFromInterface()
pBird.SayFromInterface
End Sub
'you can override the IBird function (common use)
Public Sub IBird_SayFromInstance()
Debug.Print "Tweet from Crow"
End Sub
Test - module
Sub testIBird()
Dim Bird As IBird
Set Bird = New Crow
Bird.SayFromInterface
Bird.SayFromInstance
Debug.Print TypeName(Bird)
Debug.Print TypeOf Bird Is IBird
End Sub
Output:
Tweet from IBird
Tweet from Crow
Crow
True
That's composition, not inheritance - and yes, with composition you can essentially emulate inheritance. And if the class implements the interface of the encapsulated object, then things start looking like some kind of a decorator pattern.
Except you wouldn't be having any implementation code in IBird. An interface should be purely abstract. Making a New instance of what's supposed to be an interface, makes the class no longer be an interface: now it's just another class exposing a default interface that any other class can implement, and the I prefix becomes rather confusing:
Set pBird = New IBird
It's rather weird that the client code now needs to wonder whether they want that bird to chirp FromInstance or FromInterface - these are very "meta" identifiers that make things not work like inheritance.
If we had a Crow : Bird where Bird had this implementation for IBird.Chirp:
public virtual string Chirp() => "Chirp!";
...and then Crow had this:
public override string Chirp() => "Craaaw!";
Then which method is invoked depends on what the runtime type is - this should feel pretty obvious:
IBird bird1 = new Bird();
bird1.Chirp(); // "Chirp!"
IBird bird2 = new Crow();
bird2.Chirp(); // "Craaaw!"
However picture a method that receives an IBird parameter:
public void DoSomething(IBird bird)
{
Debug.Print(bird.Chirp());
}
If bird is a Bird, it prints "Chirp!"; if bird is a Crow, it prints "Craaaw!": the method that gets to run, is the most derived override, which isn't necessarily defined on the most derived type.
Inheritance would allow a GiantCrow : Crow to inherit the Chirp method from Crow and not necessarily override it. And that is what you can't simulate with VBA classes: you are forced to write the equivalent of...
public override string Chirp() => base.Chirp();
...which is technically redundant, and gets very repetitive if you have to do it every time just to get the "base" members visible on your default interface.
Instead of inheriting base members, we actually wrap calls to the encapsulated object. The decorator pattern does exactly that, and gives you a non-intrusive way of extending a VBA class or interface.
A decorator implements the interface it's extending, and encapsulates a private instance of that type. So basically with a decorator the "crow inheritance hierarchy" setup looks like this:
Dim bird As IBird
Set bird = Crow.Create(New BaseBird)
A perhaps more suitable decorator pattern example might be:
Dim repository As IRepository
Set repository = LoggingRepository.Create(BirdRepository.Create(connectionString), New DebugLogger)
Where a BirdRepository is responsible for abstracting the database operations relating to some Birds table (BirdRepository implements IRepository), and where LoggingRepository is a decorator that also implements IRepository, but also wraps an IRepository instance (in this case a BirdRepository) to add its own functionality - something that might look like this:
'#PredeclaredId
Implements IRepository
Private loggerInternal As ILogger
Private wrappedInternal As IRepository
Public Function Create(ByVal internal As IRepository, ByVal logger As ILogger) As IRepository
Dim result As LoggingRepository
Set result.Wrapped = internal
Set result.Log = logger
Set Create = result
End Function
Public Property Get Wrapped() As IRepository
Set Wrapped = wrappedInternal
End Property
Public Property Set Wrapped(ByVal value As IRepository)
If Not wrappedInternal Is Nothing Then Err.Raise 5, TypeName(Me), "Instance is already initialized."
Set wrappedInternal = value
End Property
Public Property Get Log() As ILogger
Set Log = loggerInternal
End Property
Public Property Set Log(ByVal value As ILogger)
If Not loggerInternal Is Nothing Then Err.Raise 5, TypeName(Me), "Instance is already initialized."
Set loggerInternal = value
End Property
Private Function IRepository_SelectAll() As Object
Log.Info "Starting IRepository.SelectAll"
Dim t As Double
t = Timer
Set IRepository_SelectAll = wrappedInternal.SelectAll
Log.Info "IRepository.SelectAll completed in " & Timer - t & " seconds."
End Function
Private Sub IRepository_Delete(ByVal id As Long)
Log.Info "Starting IRepository.Delete(" & id & ")"
Dim t As Double
t = Timer
wrappedInternal.Delete id
Log.Info "IRepository.Delete completed in " & Timer - t & " seconds."
End Sub
Private Sub IRepository_Save(ByVal entity As Object)
'...
wrappedInternal.Save entity
'...
End Sub
'...
A method that is given a IRepository object cannot (and absolutely should not) know whether it's given a plain BirdRepository, a LoggingRepository wrapping a BirdRepository, or a FakeRepository that encapsulates a Collection instead of access to a database table - and this polymorphism is the entire point.
It's one way to extend a type without using inheritance, that VBA can absolutely leverage without bastardizing the pattern too much. But it's not inheritance.

"Class.Property" is not accessible in this context because it is 'Private'

I'm looking access private member of a class in myproject.vb file. My function header in myproject.vb looks like this,
Public Function MyVbFunction(ByVal objRequest As BookRequest) As Integer
Try
For Each book As Book In objRequest.m_Books
If Myvbfucntion2(book) = 1 Then
Return True
End If
Next
End Try
End Function
Book Request Class has property m_Books as Private of type BookCollection class
Public Class BookRequest
Private m_Books As ExamCollection
'
'
'
End Function
It is not allowing me to access 'book', showing as m_Books as private member. How can i access m_Books to pass to Myvbfucntion2.
The compiler tells you the answer. It can't access m_exams because it is private. So make it public, that will fix the problem:)
Private means it is only accessible from the same class.
Public means it is accessible from anywhere.
You could make a public property and use the get and set functions to tie your property to a private member if you wish.
Public Property Books() As BookCollection
Get
Return m_Books
End Get
Set(ByVal Value As BooksCollection)
m_Books = Value
End Set
End Property
Apologies for any code formatting issues. I wrote this on my mobile phone

VBA inheritance, analog of super

For example I have class A which implements class B
---class A----
implements B
public sub B_do()
end sub
--class B----
public sub do()
end sub
How can I call do() from A? (super.do()) So, how I can define some common variable for both classes? Now I can inherit only functions, sub and properties...
added: same question http://social.msdn.microsoft.com/Forums/en-US/vbgeneral/thread/5a83d794-3da1-466a-83d3-5d2eb0a054b2
added: It is not possible to share variable across hierarhy of classes. You should implements property (same way as functions).
The usual way to do this in VBA is to have A contain an instance of B as well as having A implement B's interface, and then delegate calls to the B interface of A to the internal B.
This is old stuff, but see the Visual Studio 6.0 Programmer's Guide:
http://msdn.microsoft.com/en-us/library/aa716285(VS.60).aspx
There is a chapter on "The Many (Inter)Faces of Code Reuse" that describes this convention:
http://msdn.microsoft.com/en-us/library/aa240846(v=VS.60).aspx
The way MS describes it is:
In addition to implementing abstract
interfaces, you can reuse your code by
implementing the interface of an
ordinary class, and then selectively
delegating to a hidden instance of the
class.
This means that implementation inheritance requires lots of explicit delegation methods. There's even a chapter subheading: "Doesn't This Get Tedious?". Yet another reason why OOP in VBA is a PITA (TM)...
EDIT THAT WON'T FIT IN A COMMENT:
To answer the question you posed in your comment, well, an A is a B. When you make A implement B's interface, you are essentially saying that you can treat an instance of A as if it is actually of type B. In VBA, the way you do that is by declaring a variable of type B, and then setting it to an instance of A. VBA will know what to do when you call it like a B:
Dim usedAsB as B
Dim anA as A
Set anA = New A
Set usedAsB = anA 'fine since A implements B
usedAsB.something() 'will call B_something() defined in class A
As far as what you see in the debug window, I don't why it appears that way. And as far as forced delegation, I'm not sure what you mean. VBA automatically dispatches calls to the B interface to the right methods in the A class. If you mean automatically generating the code to inherit B's implementation in the manner described above, there's nothing like that I know of for VBA. I think the various "professional" versions of VB6 could do that, but I've never used VB6 so I don't know.
This is the way I have used it a long time to simulate an abstract class through the interface.
'class module: IBase
'We define a base interface
'
Sub go(): End Sub
Sub gogo(): End Sub
Now let's define the other classes, beginning with the abstract class 'B'.
'
'class module: B
'
Implements IBase
'Daughter classes should implement 'go'
'Note that the method is 'Public'
Public Sub go(): End Sub
'Let's agree that every daughter class implements the method
'abstract 'super' that returns the IBase interface
Public Property Get super() As IBase: End Property
'
'The signature of other abstract methods can be stated here
'
'Public Sub goGoChild(): End Sub
'Public Function goGoGoChild2(): End Function
'
'
'Note that the methods are accessible through the IBase interface
'
Private Sub IBase_go()
Debug.Print "B: super.go()"
End Sub
Private Sub IBase_gogo()
Debug.Print "B: super.gogo()"
End Sub
Let's create class 'A' which implements the abstract class 'B'
'
'class module: 'A'
'
'We started implementing the abstract class B
Implements B
'we define a private type 'myType'
Private Type myType
'variable that references an instance of 'B'
objB As B
'variable that references the interface of 'B'
objIB As IBase
End Type
'VBA version of 'this'
Private this As myType
'
'Every class that implements 'B' (abstract class)
'you must initialize in your constructor some variables
'of instance.
'
Private Sub Class_Initialize()
With this
'we create an instance of object B
Set .objB = New B
'the variable 'objIB' refers to the IBase interface, implemented by class B
Set .objIB = .objB
End With
End Sub
'Visible only for those who reference interface B
Private Property Get B_super() As IBase
'returns the methods implemented by 'B', through the interface IBase
Set B_super = this.objIB
End Property
Private Sub B_go()
Debug.Print "A: go()"
End Sub
'==================================================
'Class 'A' local method
Sub localMethod1()
Debug.Print "A: Local method 1"
End Sub
And finally, let's create the 'main' module.
Sub testA()
'reference to class 'A'
Dim objA As A
'reference to interface 'B'
Dim objIA As B
'we create an instance of 'A'
Set objA = New A
'we access the local methods of instance 'A'
objA.localMethod1
'we obtain the reference to interface B (abstract class) implemented by 'A'
Set objIA = objA
'we access the 'go' method, implemented by interface 'B'
objIA.go
'we go to the 'go' method of the super class
objIA.super.go
'we access the 'gogo' method of the super class
objIA.super.gogo
End Sub
And the output, in the verification window, will be:
A: Local method 1
A: go()
B: super.go()
B: super.gogo()
One can pull a trick to mimic inheritance. It works by using the default member property.
If you give your derived class a property called Super whose type is the superclass then make that the default member (by exporting and editing file to include Attribute Item.VB_UserMemId = 0, re-importing) then you can reach the superclass by just a pair of round brackets (which resolves to the default member).
This blog post gives the full details but author (disclosure, me) there uses 'Base' instead of 'Super'
Hopefully that syntax is tight enough for you.
I also point out that this does not expose all the base class's internal guts like C# does. This means my method does not suffer from the fragile base class problem. I.e. my method retains encapsulation making it better IMHO.