Careful What You Watch For

I came across this answer on StackOverflow the other day and wanted to bring some attention to it (emphasis mine):

I had this problem manifest itself while debugging when I had a watch that attempted to return the "missing" key's item. Actually, further frustrated debugging had the same problem when I literally had a watch for the [scriptingdictonaryObject].exists() condtional); I suggest that the "missing" key is added because of the watch.  When I removed the watch and instead created a temporary worksheet to copy the array to while running, the unwanted keys were no longer added.
Dictionary object adding items before .add() is called
I am using a dictionary object from the MS Scripting Runtime library to store a series of arrays and perform operations on the array cells as necessary. There is a for loop to go through the proces...

What's going on here?

One of the "features" of a Dictionary object is that it will implicitly create new items without needing to call the .Add method explicitly.  What's the difference between implicit and explicit?

Note: Early-bound usage of the Dictionary object requires a reference to the "Microsoft Scripting Runtime" (details here).

Dim MyDict As New Dictionary

'Explicit add
MyDict.Add "KeyA", "Item A"

'Implicit add
MyDict.Item("KeyB") = "Item B"

Debug.Print MyDict("KeyA"); vbNewLine; MyDict("KeyB")

Here's the relevant portion of the documentation regarding implicit key creation:

Remarks
If key is not found when changing an item, a new key is created with the specified newitem. If key is not found when attempting to return an existing item, a new key is created and its corresponding item is left empty.

Reproducing the problem

Let's reproduce the problem to see exactly where things go sideways.

Expected behavior

Create the following sample routine:

Sub WatchOut()
    Dim MyDict As Dictionary
    Set MyDict = New Dictionary
    
    Debug.Print "KeyA exists? "; MyDict.Exists("KeyA")
End Sub

Run the above routine from the immediate window and it should return False:

WatchOut
KeyA exists? False

Add a watch

Now, let's add a watch of the "KeyA" item:

Let's try running the WatchOut routine again:

WatchOut
KeyA exists? False

So far, so good.  Maybe this isn't a problem after all.

Step through the code

Let's add a Stop statement to force the code to break:

Sub WatchOut()
    Dim MyDict As Dictionary
    Set MyDict = New Dictionary
    
    Stop
    Debug.Print "KeyA exists? "; MyDict.Exists("KeyA")
End Sub

Now, let's try running the WatchOut routine:

WatchOut
KeyA exists? True

Aha!  The combination of the watch and breaking into the debugger is enough to force the "bug" to appear.  I put bug in scare quotes because it's actually expected behavior for the debugger.  But it's almost certainly unexpected behavior for the developer.  

(Note that there is nothing special about the Stop command that causes this behavior.  You can remove the Stop line and set a breakpoint on the code and the same behavior will occur.)

You can see where this sort of thing could cause you to pull your hair out while debugging.  Anytime the behavior of your program is different while running normally versus while debugging, you've got the makings of one aggravating debugging session.

Summary

Steps to reproduce the problem:

  1. Create a Watch for a specific Dictionary item
  2. Break into the debugger while executing the code

This will probably only ever help one or two developers.  But it will potentially save those developers hours of frustration.  And, if I'm being honest, I'm as likely as anyone to be one of those developers ;-).

Image by WikiImages from Pixabay