Is scoping broken in VBA?
Asked Answered
J

1

11

Say you have this code in a module called Module1:

Option Explicit

Private Type TSomething
    Foo As Integer
    Bar As Integer
End Type

Public Something As TSomething

In equivalent C# code if you made the Something field public, the code would no longer compile, because of inconsistent accessibility - the type of the field being less accessible than the field itself. Which makes sense.

However in VBA you could have this code in Module2:

Sub DoSomething()
    Module1.Something.Bar = 42
    Debug.Print Module1.Something.Bar
End Sub

And you get IntelliSense while typing it, and it compiles, and it runs, and it outputs 42.

Why? How does it even work, from a COM standpoint? Is it part of the language specs?

Jakoba answered 23/3, 2016 at 20:2 Comment(9)
In Module2, can you define a variable of type Module1.TSomething and assign that to Module1.Something ?Barreto
@SimonForsberg Compile error (as expected) - "User-definted type not defined"... in other words, the type is visible to usages, but not to declarations...Jakoba
Sounds exaclty how Java works, not that this question is about Java, but anyway. It makes sense to me, but then again... I'm a Java-guy.Barreto
VBA exposes the private type, just like it exposes a private enum. It assumes you can make use of the TypeInfo in the using context, but it won't allow you to declare instances of those types or enums.Radioman
It just doesn't make much sense to try to map Visual Basic behavior onto C#. VB is a "make it work if you can" language, C# is hard-ass.Veracruz
@HansPassant so, VB (and Java?) basically doesn't care about the Private access modifier as far as using a type goes - and happily lets you "leak" private types to an interface, even though the client code doesn't know about the type involved, yet it does know enough to use it, but isn't allowed to declare variables of that type... but runtime will happily dump it into a Variant... I need a drink.Jakoba
Well, why not, late binding is a VB feature so it isn't like client code could not access the members. That is not different in C# either, you can use Reflection to access private members.Veracruz
While I can't speak authoritatively, but I suspect that it has something to do with VBA and COM's extensive use of IDispatch and IUnknown. It would appear that the compiler is accessing Module1.Something by treating it as an Object instead of a Namespace, then querying the interface to see if it can successfully call Something.Bar. Something is exposed by being Public, so it doesn't need access to the type - it queries it's interface to see if it supports the property Bar. Note that without doing this (kind of like duck typing), late binding would be much more difficult.Teak
Add Debug.Print TypeName(Module1.Something) provides an additional clue that COM is examining the interfaces - the error it produces is (my emphasis) "Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions". Debug.Print TypeName(Module1.Something.Bar) happily spits out "Integer".Teak
R
3

As per my comment, VBA exposes a private Type, just like it exposes a Private Enum.

VBA assumes you can make use of the TypeInfo in the consuming context, but it won't allow you to declare or create instances of those types or enums.

This C++ answer is partly informative:

Access Control is applied to names

The access specifier for the name has nothing to do with it's type

But it's perhaps useful to think of a Private Type in a standard module, as something like a "PublicNotCreatable" class. If you provide a public wrapper, then the type is accessible outside the host module.

But VBA handles things differently when the Type is in a Public Class Module!

Here's your Module1 expanded:

Option Explicit

Private Type TSomething
    Foo As Integer
    Bar As Integer
End Type

Public Type TOtherThing
    Foo As Integer
    Bar As Integer
End Type

Public Type TWrapperThing
  Something As TSomething
End Type

Public Something As TSomething
Public Otherthing As TOtherThing
Public Wrapperthing As TWrapperThing

Public Function GetSomething() As TSomething
  GetSomething.Foo = 1
End Function

Public Function GetOtherthing() As TOtherThing
  GetOtherthing.Foo = 1
End Function

And Module2 expanded:

Option Explicit

Sub DoThings()

'Compile Error: User-defined type not defined
  'Dim oSomething As TSomething
  Dim vSomething As Variant
  
  Dim oOtherthing As Module1.TOtherThing
  Dim vOtherthing As Variant
  Dim oWrapperthing As Module1.TWrapperThing
  
  Module1.Something.Foo = 42
  Module1.Otherthing.Foo = 42
  Module1.Wrapperthing.Something.Foo = 42

  'Compile Error: Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions
  'vSomething = Module1.Something
  'vOtherthing = Module1.Otherthing
  
  oOtherthing = Module1.Otherthing
  oOtherthing.Foo = 43
  
  'Is 43 > 42?
  Debug.Assert oOtherthing.Foo > Module1.Otherthing.Foo
  
'Compile Errors: "GetSomething" User-defined type not defined
  'Module1.GetSomething.Foo = 42
  'Module1.GetSomething().Foo = 42
  
  Module1.GetOtherthing.Foo = 42
  Module1.GetOtherthing().Foo = 42

End Sub
Radioman answered 23/3, 2016 at 23:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.