简体   繁体   中英

VBA dictionary <out of context> after Sub is run

I am trying to run the following code to create a dictionary that will then be used in a function to assign the functions parameter values depending on the dictionary key.

Option Explicit

Public b1 As Object
Public var1 As Variant
Public var2 As String
Public var3 As Variant

Sub CreateDictionaries()
    Set b1 = CreateObject("Scripting.Dictionary")
    b1.Add "key1", 0.009
    b1.Add "key2", 0.011
    b1.Add "key3", 0.014
    b1.Add "key4", 0.025
    b1.Add "key5", 0.045
End Sub

Public Function MyFunction(var1, var2, var3)
    If var1 <= 5 Then
        MyFunction = b1.Item(var2) * var1* var3
    ElseIf var1 > 5 And var1 <= 10 Then
        MyFunction = b1.Item(var2) * (var1 - 5) * var3
    ElseIf var1 > 10 Then
        MyFunction  = b1.Item(var2) * (var1 - 10) * var3    
    End If
End Function

Although this worked initially, after I temporarily changed the Sub to Static Sub it stopped working. Changing it back to what it was in its last working condition has not fixed the problem. Restarting VBA and running the code as a new module also haven't worked.

While debugging I can see in the Watch window that the dictionary b1 is created as it should, however after the Sub finishes it takes the value "out of context". It doesn't make any sense, and it's now driving me crazy! Can anyone help?

A couple of points:

  • Add a reference to the Microsoft Scripting Runtime library; this will allow you to use the actual types, instead of plain Object:

     Public b1 As Scripting.Dictionary 
  • You could initialize the dictionary using the New syntax:

     Public b1 As New Scripting.Dictionary 

    although this will only create the dictionary; it won't fill it; which brings us the the next point.

  • AFAIK, in VBA there is no constructor method for modules. So you'll have to verify that the dictionary has been filled, and perhaps test for initialization as well, before you try to use it:

     Public b1 As Scripting.Dictionary Sub InitializeDictionary() If Not b1 Is Nothing Then Exit Sub Set b1 = New Scripting.Dictionary b1.Add "key1", 0.009 b1.Add "key2", 0.011 b1.Add "key3", 0.014 b1.Add "key4", 0.025 b1.Add "key5", 0.045 End Sub Public Function MyFunction(var1, var2, var3) InitializeDictionary If var1 <= 5 Then MyFunction = b1.Item(var2) * var1* var3 ElseIf var1 > 5 And var1 <= 10 Then MyFunction = b1.Item(var2) * (var1 - 5) * var3 ElseIf var1 > 10 Then MyFunction = b1.Item(var2) * (var1 - 10) * var3 End If End Function 
  • The Static keyword won't make a difference here. According to the documentation , the Static keyword on a Function or Sub :

    indicates that the Sub procedure's local variables are preserved between calls. The Static attribute doesn't affect variables that are declared outside the Sub , even if they are used in the procedure.

    and in this case the b1 variable has been declared outside of the Sub .

    Using the Static statement to declare module-level variables (such as b1 ) will also have no effect -- in either case the value will last until the code is reset. It only makes a difference when declaring variables within a procedure.

  • Regarding the Watch window and the variables <out of context> , when you add a Watch in the VBA IDE, you need to specify the context as <all procedures> and <all modules> . Otherwise, the Watch variable will only be evaluated when the debugger is stopped within the selected module, and within the selected procedure.

Create a Static Dictionary inside the function. The dictionary will be populated the first time you use the function but after that the values will be retained and the relevant code skipped.

Option Explicit

Public Function MyFunction(var1, var2, var3)

    Static b1 As Object   
    If b1 Is Nothing Then
        Set b1 = CreateObject("Scripting.Dictionary")
        b1.Add "key1", 0.009
        b1.Add "key2", 0.011
        b1.Add "key3", 0.014
        b1.Add "key4", 0.025
        b1.Add "key5", 0.045
    End If

   If var1 <= 5 Then
        MyFunction = b1.Item(var2) * var1* var3
    ElseIf var1 > 5 And var1 <= 10 Then
        MyFunction = b1.Item(var2) * (var1 - 5) * var3
    ElseIf var1 > 10 Then
        MyFunction  = b1.Item(var2) * (var1 - 10) * var3    
    End If
End Function

As others have pointed out, it's usually better to early-bind your variables. Which means if you know you want a Dictionary object, avoid declaring a Object first and then converting it to a Dictionary when you can just declare it as a Dictionary right away. To do this you need to include the reference to the Scripting Runtime Library and use:

Dim b1 As Scripting.Dictionary
Set b1 = New Scripting.Dictionary

Do not use the single As New statement below because it can cause unwanted errors (more here)

Dim b1 As New Scripting.Dictionary

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM