2

I am going crazy with VBA dictionaries, as the Exists() method makes no sense.

I thought you can use the dict.Exists(key) method to check if a key is in the dictionary without further actions. The problem is that when checking it, the key is automatically added into the dictionary. It really makes no sense!

Here's my code. Am I doing something wrong?

Function getContracts(wb As Workbook) As Dictionary
   Dim cData As Variant, fromTo(1 To 2) As Variant
   Dim contracts As New Dictionary, ctrDates As New Collection
   Dim positions As New Dictionary, p As Long, r As Long
   Dim dataSh As String, i As Long
   
   dataSh = "Export"
   
   cData = wb.Worksheets(dataSh).UsedRange
   
   For i = LBound(cData) To UBound(cData)
      fromTo(1) = cData(i, 1)
      fromTo(2) = cData(i, 2)
      Set ctrDates = Nothing
      If IsDate(fromTo(1)) And IsDate(fromTo(2)) Then
         If Not contracts.Exists(cData(i, 3)) Then ' Here it detects correctly that the key doesn't exist
            ctrDates.Add fromTo 
            contracts.Add cData(i, 3), ctrDates ' And here it fails because the key just got added by .Exists()
         Else
            Set ctrDates = contracts(cData(i, 3))
            ctrDates.Add fromTo
            contracts(cData(i, 3)) = ctrDates
         End If
      Else
         Debug.Print "Not a valid date in line " & i
      End If
      
   Next i
   
End Function
Ryan M
  • 18,333
  • 31
  • 67
  • 74
David
  • 45
  • 5
  • Does it fail for all values of `cData(i, 3)` or only for a specific value? – FunThomas Apr 05 '22 at 10:44
  • Code works for me with *my* test data. What is the excat error message and what is the value of `cdata(i,3)`? BTW, Often it is not neccessary to check for existance as the change operation will add the key if it does not exist. – Storax Apr 05 '22 at 10:49
  • FunThomas: Fails for all. Even when entering it from the Inmediate Window Storax: it contains a string with a contract number (i.e.: "054831") VBasic2008: I'll try that – David Apr 05 '22 at 10:58
  • Have you tried `Set contracts(cData(i, 3)) = ctrDates`? And in the Else statement `contracts(cData(i,3)).Add FromTo`? – VBasic2008 Apr 05 '22 at 10:58
  • 1
    Please, add the line `contracts.RemoveAll` before the loop (don't ask why...) and transform `contracts(cData(i, 3)) = ctrDates` in `Set contracts(cData(i, 3)) = ctrDates`, as has already been suggested... – FaneDuru Apr 05 '22 at 11:00
  • Ok so.... After fixing the error pointed out by @VBasic2008, I still got the stupid .Exists() behaviour. Frustrated, I restarted Excel and NOW it seems to work... I am a bit reluctant though, as I can't allow that the method fails again out of nothing when I deliver the macro. Does anybody know if this is a known bug? – David Apr 05 '22 at 11:06
  • @FaneDuru thanks! I'm very tempted to ask why :) but I'll do that just to avoid this happening again. – David Apr 05 '22 at 11:07
  • Please, try my above suggestion (adding `contracts.RemoveAll`). Theoretically, when the code stops, the dictionary will be empty, but it happened in my case to not all the time being empty... I could/can not understand why. – FaneDuru Apr 05 '22 at 11:08
  • @FaneDuru despite the weird behaviour in my PC (fixing itself when restarting Excel) I think I should also take your suggestion as an answer for others that may run into the same problem, so if you want to post it as answer I'll accept it. – David Apr 05 '22 at 11:23
  • No, I cannot place an answer not knowing how to explain the weird behavior... I could not even reproduce the context of those cases. I think it happened no more than two or three times. I am using very often dictionaries but usually they do not behave in this strange way... – FaneDuru Apr 05 '22 at 11:31
  • There is a known issue when using the Watch window to debug code involving a Dictionary object. https://stackoverflow.com/questions/48437731/dictionary-is-populated-with-an-empty-item-after-checking-dictionary-item-in-wat – Tim Williams Apr 05 '22 at 16:10
  • It happened to me a couple of times, the thing is, restarting the file fixed the issue. While working with C# dicitonaries, I realized that maybe we should use ``` for each dKey in dDictionary.keys``` instead of ```dDictionary```. But i'm not sure if this helps, because again, restarting the file fixed it. – Dumitru Daniel Feb 16 '23 at 20:41

3 Answers3

1

You can shorten your code to

   For i = LBound(cData) To UBound(cData)
      fromTo(1) = cData(i, 1)
      fromTo(2) = cData(i, 2)
      Set ctrDates = Nothing
      If IsDate(fromTo(1)) And IsDate(fromTo(2)) Then
            If Not IsEmpty(contracts(cData(i, 3))) Then Set ctrDates = contracts(cData(i, 3))
            ctrDates.Add fromTo
            Set contracts(cData(i, 3)) = ctrDates

      Else
         Debug.Print "Not a valid date in line " & i
      End If
      
   Next i

If one changes a value at a key it will automatically add the key if it does not exist.

Further reading on dictionaries

PS: This might also circumvent the strange behaviour described in the comments as you do not use the exist method. But on the other hand I have never experienced such a strange behaviour when using dictionaries

Storax
  • 11,158
  • 3
  • 16
  • 33
1

Collections of Date Pairs in a Dictionary

  • A reference to the Microsoft Scripting Runtime library is necessary for this to work.
Option Explicit

Sub GetContractsTEST()

    Const dName As String = "Export"

    Dim wb As Workbook: Set wb = ThisWorkbook
    Dim dws As Worksheet: Set dws = wb.Worksheets(dName)

    Dim Contracts As Scripting.Dictionary: Set Contracts = GetContracts(dws)
    If Contracts Is Nothing Then Exit Sub

    Dim Key As Variant, Item As Variant
    For Each Key In Contracts.Keys
        Debug.Print Key
        For Each Item In Contracts(Key)
            Debug.Print Item(1), Item(2)
        Next Item
    Next Key

End Sub

Function GetContracts(ByVal ws As Worksheet) As Scripting.Dictionary
    Const ProcName As String = "GetContracts"
    On Error GoTo ClearError

    Dim cData As Variant: cData = ws.UsedRange.Value
    Dim fromTo(1 To 2) As Variant

    Dim Contracts As New Scripting.Dictionary
    Contracts.CompareMode = TextCompare
    
    Dim r As Long

    For r = LBound(cData) To UBound(cData)
        fromTo(1) = cData(r, 1)
        fromTo(2) = cData(r, 2)
        If IsDate(fromTo(1)) And IsDate(fromTo(2)) Then
            If Not Contracts.Exists(cData(r, 3)) Then
                Set Contracts(cData(r, 3)) = New Collection
            End If
            Contracts(cData(r, 3)).Add fromTo
        Else
            Debug.Print "Not a valid date in line " & r
        End If
    Next r

    Set GetContracts = Contracts

ProcExit:
    Exit Function
ClearError:
    Debug.Print "'" & ProcName & "' Run-time error '" _
        & Err.Number & "':" & vbLf & "    " & Err.Description
    Resume ProcExit
End Function
VBasic2008
  • 44,888
  • 5
  • 17
  • 28
0

Possible Solution:

I had the same issue, this tends to happen when the compare more has not been set. I have not dug any deeper into this as the issue cannot always be replicated and the documentation around .Exists() and .CompareMode isn't that thorough source.

(as everyone has said you should enable the Microsoft Scripting Runtime reference for early binding)

When creating a new dictionary set its .CompareMode to vbBinaryCompare this will set a more strict compare mode and also in my case fixes the .Exists() bug. Do note that you can only set .CompareMode on an empty dictionary

Dim NewDictionary As New Scripting.Dictionary
NewDictionary.CompareMode = vbBinaryCompare

If NewDictionary.Exists(key) Then
'do things
End If
Ricards Porins
  • 384
  • 1
  • 7