VBScript Default Property Semantics

VBScript Default Property Semantics

  • Comments 11

Here’s a question I recently got about VBScript, where by "recently" I mean August 28th, 2003. This code works just fine:

Set myObject = CreateObject("myObject")
myObject.myName = "Eric"
WScript.Echo myObject ' myName is the default property, so prints "Eric"

But myObject = "Robert" doesn't set the default property, it sets the variable to the string. Why does reading work but writing fail? This works in VB6, why not VBScript? 

In a strongly typed language such as VB6 the compiler can look at the compile-time types of the left and right sides of an assignment and determine that the type of the left hand side is an object, the right hand side is a string, and the object has a default property which is a string.  The VB6 compiler can then generate the appropriate code to assign the string to the default property.

But what if you wrote a VB6 program using only variants?  When the compiler sees foo = bar typed as variants it cannot tell whether this means "set the default property of foo to bar" or "set foo to bar".  The VB compiler chooses the latter every time. VBScript is a weakly typed subset of VB -- in VBScript, everything is a variant.  So in VBScript, all assignments are treated as though the value is actually being assigned to the variable, not the default property. Therefore this is by design - this is for compatibility with VB6's behaviour.  Had we written the same program in weakly-typed VB6, we'd get the same result. 

I hear you exclaiming "The fact that a difference in available type information leads to a difference in run-time behaviour violates a basic principle of programming language design!  Namely, the principle that late-bound calls have exactly the same semantics as early-bound calls!" 

Indeed, as I've mentioned before, VB6 and VBScript violate this principle in several places.  Default properties were, in my opinion, a bad idea all around, for this and other reasons.  They make the language harder to parse and hence harder to understand.  In particular, parameterless default properties make very little sense in VBScript.  However, we are stuck with them now.

However, I should call out that there are some things we can do at runtime. Suppose blah is an object with a property fred that is an object, and fred has a default property which is a string. If you say foo = blah.fred then foo is assigned the default value of fred even though we lack the compile-time type information.  foo isn't set to the object fred. Of course, in this case we know to fetch the default property at runtime because we know the runtime type of fred and also know that there is no Set keyword.  These two facts are sufficient to cause the runtime engine to always fetch the default property.

Or suppose you have an object Baz with a property Table, Table has a default parameterized property Item which returns an object that has a string property Blah: Then this works just fine:

x = Baz.Table(1).Blah

But why? In this case we do not have enough compile-time information to determine that we really should call Baz.Table.Item(1).Blah.  The script engine has every reason to believe that Table is a function or parameterized property of Baz. This problem is solved by pushing it off to the implementation! The rule for implementers of IDispatch::Invoke is if all of the following are true:

  • the caller invokes a property
  • the caller passes an argument list
  • the property does not actually take an argument list
  • that property returns an object
  • that object has a default property
  • that default property takes an argument list

then invoke the default property with the argument list. Strange but true.

Perhaps unsurprisingly, not very many people know about that rule!  This is yet another reason to never, ever write your own implementation of IDispatch::Invoke.  It also leads to some mind-numbingly complex code in the VBScript IntelliSense engine that Visual Studio uses.  Making default property IntelliSense work with such a dynamically typed language was a piece of work, lemme tell ya. As I mentioned earlier, there are in fact some odd corner cases that are slightly different between VB6 and VBScript IntelliSense.  I also talked a bit about left-hand-side default property semantics earlier.

  • "Default properties were, in my opinion, a bad idea all around, for this and other reasons. They make the language harder to parse and hence harder to understand."

    A-men, brother.

    Something that just struck me...in at least two places (default properties and With statements) VB/VBScript uses *nothing at all* in the source code to represent some important thing. This is strange because in most other places VB is liberally verbose compared to other languages.

    There is dangerous potential for ambiguity when interpreting the meaning of nothing. Like when I ask my wife, "What's wrong?"
  • Ah, but the "with" statement at least calls out "I'm doing something odd". When you see

    with foo
    .bar

    you can just by reading the code easily figure out that this means foo.bar. The "with" statement is understandable LEXICALLY.

    Default properties are different. Lexically there is nothing to show that x = y is going to call the default property of y -- this fact can only be known if you possess information about what the runtime type of y will be.

    Now, that's kind of irksome, I admit. But don't forget that default properties were invented in the first place for things like collections. It really does kind of make sense to be able to say something like mycollection(98105) = "University Avenue Seattle" instead of MyCollection.Item(98105)...

    Abstraction is all about hiding things. Good abstractions make plain the semantics and hide the implementation. Lots of languages hide stuff that ought to be made more plain. For example, what does this do in C#?

    }

    Could do anything! That brace can call arbitrary destructors, totally invisibly. Those destructors may have semantic import!


  • Just to be pedantic, C# doesn't have destructors. Some objects do have finalizers, but the } doesn't actually invoke them. It just allows the GC to invoke the finalizer if there are no root objects referencing those objects when the GC decides to run. Since the GC only runs when it receives pressure, this could technically be quite a while after the code in question is run.
    Off the top of my head, the only time the } actually forces any type of cleanup is if you use them with a using() statement, but you are explicitly requesting .Dispose() to be called in that case.

    Or did you mean to put C++ instead, and put C# there on accident since you just recently joined that team?

    <TongueInCheek>
    But if you're going to point out that ".bar" is easily determinable what it does by looking up to see "with foo", the same thing could be said for }. Just look up to the most recent { and figure it out.
    </TongueInCheek>
  • Yeah, I meant C++, you are correct.
  • I used to think that default properties were stupid because they necessitate a distinction between 'set' and 'let'. However, I now miss being able to say 'value = SomeControl' instead of C#'s 'if (SomeControl is CheckBox) value = (SomeControl as CheckBox).Checked.toString() else value = (SomeControl as TextBox).Text'.
  • I found this site very useful.

  • we can learn a lot about VBScript.

  • Thanks for shedding a little light on a mysterious subject.  Where can I find more detailted information on how the default property semantics in VB6?  I am trying to interpret some code that has a subroutine with arguments of type Control being passed to functions that take Variant.  

    Public Function foo(ctl As Control) As Boolean

       If IsNumeric(ctl) = False Then

    ...

      ctl = Format$(ctl, s_precision)

    ...

    End Function

    How does VB6 decide when to pass a reference to the object and when to pass a reference the default property.

    Is there a formal specification for the VB6 language anywhere?

  • Thanks for this information - it has really helped me understand some issues.

    I am trying to upgrade a VB6 app to dot net - and the new app gets called from VBScript - so the behaviour between VB6 and .NET must match - but this will not work and it relates to issues that you have detailed above.

    I have created a class in dot net with a default property based on iCollection interface - this works fine - I can enumarete fine and referance it like NewCollection(1).TestProp - without issues - but when this collection is returned as a property from another object it does not work. - ie.

    ApplicationClass.NewCollection(1).TestProp does not work - but ApplicationClass.NewCollection.Item(1).TestProp does work - and so does "Set its = ApplicationClass.NewCollection" and than its(1).TestProp

    Also noticed that ApplicationClass.NewCollection()(1).TestProp does work.

    Is there anyway of getting .NET to behave the same way as what VB6 does - are there some attributes that I can apply to correct this behaviour??

Page 1 of 1 (11 items)