【问题标题】:Generic way to check if a key is in a Collection in Excel VBA检查密钥是否在 Excel VBA 中的集合中的通用方法
【发布时间】:2016-10-26 17:53:32
【问题描述】:

我的代码中有不同的集合。有些持有对象(各种类型),有些则在其中包含类型(如 Long)。

有没有办法检查集合中是否包含适用于类型和对象的键?

到目前为止,我有两个功能。

第一个函数:

Private Function ContainsObject(objCollection As Object, strName As String) As Boolean
    Dim o As Object
    On Error Resume Next
    Set o = objCollection(strName)
    ContainsObject = (Err.Number = 0)
    Err.Clear
End Function

第二个功能:

Private Function ContainsLong(AllItems As Collection, TheKey As String) As Boolean
    Dim TheValue As Long
    On Error Resume Next
    TheValue = AllItems.Item(TheKey)
    ContainsLong = (Err.Number = 0)
    Err.Clear
End Function

这两个函数的原因是,如果我传递一个包含 Longs 对的 Co​​llection,ContainsObject 似乎不起作用(该函数总是返回 False。)

P.S.:第一个函数是Test or check if sheet exists第三个答案的副本

【问题讨论】:

  • 似乎有拼写错误。我认为Contains = (Err.Number = 0) 应该是ContainsObject = (Err.Number = 0) 同样CheckForKeyInCollection = (Err.Number = 0) 应该是ContainsLong = (Err.Number = 0)
  • 感谢编辑!我接受了可以快速解释并解决我的问题的答案。也感谢其他答案,解决方案也在那里 - 但更微妙......

标签: excel vba excel-2010


【解决方案1】:

您应该在第一个函数中使用Variant。您可以将Object 分配给Variant,例如这不会出错:

Sub Test()
    Dim var As Variant
    Dim obj As Object
    Set obj = Application
    var = Application
    Debug.Print var
End Sub

但这会产生Type Mismatch 编译错误,即尝试将Long 分配给Object

Sub Test()
    Dim obj As Object
    Dim lng As Long
    lng = 3
    Set obj = lng
End Sub

因此,对于一个通用函数(沿着您的代码行)来检查 Collection 键是否有效,您可以使用:

Function HasKey(coll As Collection, strKey As String) As Boolean
    Dim var As Variant
    On Error Resume Next
    var = coll(strKey)
    HasKey = (Err.Number = 0)
    Err.Clear
End Function

测试代码:

Sub Test()
    Dim coll1 As New Collection
    coll1.Add Item:=Sheet1.Range("A1"), Key:="1"
    coll1.Add Item:=Sheet1.Range("A2"), Key:="2"
    Debug.Print HasKey(coll1, "1")

    Dim coll2 As New Collection
    coll2.Add Item:=1, Key:="1"
    coll2.Add Item:=2, Key:="2"
    Debug.Print HasKey(coll2, "1")
End Sub

MSDN 上有一篇关于此的有用文章。上下文是 VB6,但与 VBA 相关。

【讨论】:

  • 感谢您的事实和中肯的回答,我已投赞成票。
  • @skkakkar - 感谢您的回答建议使用 Dictionary 对象,我同意这是首选方法。 OP 正在使用Collection 对象,可能没有机会切换到Dictionary。正如他们所说,并非总是可以“中途换马”:)
【解决方案2】:

在编辑您的帖子期间,根据 cmets 的一些拼写错误已经得到纠正。 在回答您的问题时,我想介绍相关方面。
虽然在集合中使用键主要有三个优点
- 如果订单更改,您的代码仍将访问正确的项目 - 您可以直接访问该项目而无需通读整个 收藏
- 它可以让你的代码更具可读性。

*但同时在使用keys中主要存在三个问题 合集

  • 无法检查密钥是否存在

  • 您无法更改密钥

  • 您无法检索密钥

根据 Pearsons 的文章,集合的键是只写的 - 无法获取集合的现有键的列表。进一步阅读引用的段落:-

这里,Col 是一个 Collection 对象,我们将在其中存储多个 CFile 对象。 CollKeys Collection 用于存储密钥 存储在 Coll Collection 中的 CFile 对象。我们需要这一秒 集合,因为集合的键是只写的——有 无法获取现有集合键的列表。中的一个 CFiles 提供的增强功能是能够检索列表 集合的键。

Custom Collection Classes

一种方法是遍历集合的成员,看看是否有匹配您正在寻找的东西,另一种方法是捕获 Item not in collection 错误,然后设置一个标志来表示该项目不存在.对这些方法的看法不同,而有些人认为这不是捕获错误的好方法,而其他部分则认为对于任何中型到大型集合而言,这将比迭代快得多。
因此,如果我们采用一种方法来捕获错误,那么我们得到的错误编号取决于导致错误的确切原因。我们需要一个代码例程来检查错误。可以用最简单的方式。

'c1 is the collection
 For i = 1 To c1.Count
     Debug.Print Err.Number, Err.Description
     If Err.Number <> 0 Then Err.Clear
 Next i

各种专业人士提出的错误捕获例程在他们认为重要并包含在其例程中的错误编号上有所不同。与收集对象相关的各种常见错误编号是:-

  • Error 5无效的过程调用或参数。这个错误也可能发生 如果尝试调用在 当前平台。例如,某些程序可能仅适用于 Microsoft Windows 或 Macintosh 等。
  • error 438"对象不支持这个属性或方法一个对象 是一个类实例。类实例支持一些属性 在该类类型定义中定义,不支持这个。
  • Error 457 此键已与此元素关联 集合。您为集合成员指定了一个键 标识集合的另一个成员。选择不同的键 为该会员。
  • Error 91 对象变量或未设置块变量。有两个 创建对象变量的步骤。首先你必须声明 对象变量。然后您必须为该对象分配一个有效的引用 变量使用 Set 语句。你试图使用一个对象 尚未引用有效对象的变量。
  • Error 450 参数数量错误或属性无效 分配。过程调用中的参数数量 与预期的所需参数的数量不同 过程。如果您尝试为只读属性赋值,

在上述错误中,错误编号 438 被认为是重要的,另一个是 5。我在我的示例测试程序中加入了一个函数例程,该例程由 Mark Nold 在 7 年前于 2008 年发布,参见 SO 问题 Determining whether an object is a member of a collection in VBA应归功于他。

在程序测试运行时不允许出现错误 457 等错误。我试图填充重复的键数据,它在程序测试时给出了错误,如快照所示。

删除后显示正确的输出,如快照所示。

如果不将键值存储在独立数组中,可能无法使用普通集合获取集合的键列表。最简单的替代方法是添加对 Microsoft 脚本运行时的引用并改用功能更强大的字典。 我已经包含了这种方法来获取我的程序中的键列表。
在填充 Collection 时,要确保 key 是第二个参数,并且必须是唯一的字符串。

我的程序的完整代码是。

Sub Generic_key_check()
    Dim arr As Variant
    Dim c1 As New Collection
    Dim dic As Object
    With Application
    .ScreenUpdating = False
    End With


    Set dic = CreateObject("Scripting.Dictionary")
    dic.CompareMode = vbTextCompare

    'Populate the collection
    c1.Add "sheet1", "sheet1"
    c1.Add "sheet2", "sheet2"
    c1.Add "sheet3", "sheet3"
    c1.Add "sheet4", "sheet4"
    c1.Add "sheet5", "sheet5"
    c1.Add 2014001, "Long1"
    c1.Add 2015001, "Long2"
    c1.Add 2016001, "Long3"
    c1.Add 2015002, "Long4"
    c1.Add 2016002, "Long5"

    'Populate the dictionary
    dic.Add "sheet1", "sheet1"
    dic.Add "sheet2", "sheet2"
    dic.Add "sheet3", "sheet3"
    dic.Add "sheet4", "sheet4"
    dic.Add "sheet5", "sheet5"
    dic.Add "Long1", 2014001
    dic.Add "Long2", 2015001
    dic.Add "Long3", 2016001
    dic.Add "Long4", 2015002
    dic.Add "Long5", 2016002
    ' Get a list of key items by Dictionary Method
    Dim N As Variant
    For Each N In dic.Keys
    Debug.Print "Key: " & N, "Value: " & dic.item(N)
    Next
    'Test for two types of data whether key exists or not.
    If InCollection(c1, "Long1") Then
    'If Exists("Long1", c1) Then
    Debug.Print "Good"

    Else
    ' If there is error then print out the error number and its description.
    Debug.Print Err.Number, Err.Description
    Debug.Print "Not Good"
    End If
    If InCollection(c1, "sheet2") Then
    Debug.Print "Good"

    Else
    Debug.Print Err.Number, Err.Description
    Debug.Print "Not Good"
    End If

    'Checking whether desired key has populated correctly
    Debug.Print c1("Sheet1")
    Debug.Print c1("Long3")



    'Listing out collection items to check theyexist in the collection.
    For i = 1 To c1.Count
    Debug.Print c1.item(i)
    Next i
    With Application
    .ScreenUpdating = True
    End With
    Set c1 = Nothing
End Sub
Public Function InCollection(col As Collection, key As String) As Boolean
    Dim var As Variant
    Dim errNumber As Long

    InCollection = False
    Set var = Nothing

    Err.Clear
    On Error Resume Next
    var = col.item(key)
    errNumber = CLng(Err.Number)
    On Error GoTo 0

    '5 is not in, 0 and 438 represent incollection
    If errNumber = 5 Then ' it is 5 if not in collection
    InCollection = False
    Else
    InCollection = True
    End If

End Function

快照中显示了即时窗口中显示的每个程序的最终输出。

【讨论】:

  • +1 用于显示来自脚本运行时库的Dictionary 的应用程序。很遗憾,这不是 Excel VBA 的默认部分。
【解决方案3】:

我想指出,如果你想让 PaulE 的函数更灵活一点,你可以将字符串参数更改为 Variant,这意味着你现在也可以使用它来检查项目键或货号,很方便。如果您要检查大量集合,变体会稍微慢一些,但在大多数情况下,这两个函数的作用相似。

Function HasItem(col As Collection, ItemKeyOrNum As Variant) As Boolean
    Dim v As Variant
    On Error Resume Next
    v = IsObject(col.Item(ItemKeyOrNum))
    HasItem = Not IsEmpty(v)
End Function

这里接受的答案是错误的(我也注意到了很多其他问题的情况,因此请注意并阅读所有答案)。 Apostle 和 PaulE 在那里合作,为所提出的具体问题提供最正确的答案。我尝试使用已接受的答案,但没有成功。

问题明确指出,“有没有办法检查集合中是否包含适用于类型和对象的键?”

接受的答案不适用于对象。 PaulE 的答案是最终的正确答案。我只是在这里添加一点细微差别,以使该功能更加一刀切。

【讨论】:

  • 我的关于改进答案和改进 Stack Overflow 的问题,以这个主题为例,被删除了。但是有什么新东西?在这里或直接提出的关于改进 Stack Overflow 的评论几乎总是被删除或忽略,这就是 SO 继续出现如此多故障的原因之一——在这种情况下,最错误的答案靠近顶部,分配的分数最多,并且最正确的答案在底部,分数最少。似乎没有人在乎。我们知道这条评论很可能会直接进入记忆漏洞,永远不会再被考虑。
  • 删除的 cmets 也没有留下任何痕迹:所以,直接进入内存洞。
【解决方案4】:

Apostle 的回答几乎是正确的。罗宾的答案不适用于通用对象,但可以按书面方式工作,因为 Excel 的 Range 对象将返回单元格的值。我喜欢 Apostle 对 IsObject 的使用(主要是因为这也是我想出来的)。不过代码有点过于复杂了。

如果集合中存在键,IsObject 会将变体设置为 True 或 False,否则将忽略错误,使变体为空。

Function HasKey(col As Collection, Key As String) As Boolean
    Dim v As Variant
  On Error Resume Next
    v = IsObject(col.Item(Key))
    HasKey = Not IsEmpty(v)
End Function

【讨论】:

    【解决方案5】:

    一个字符串中的短变体:

    Function keyExists(coll As Collection, key As String) As Boolean
        On Error Resume Next: keyExists = IsObject(coll(key)) Or True
    End Function
    

    首先,keyExists = false。错误捕获器设置为忽略错误。如果表达式(始终为 TRUE)的计算没有错误(具有键的元素存在),则 keyExists 为 TRUE。

    用法(集合中包含各种类型的值):

    Sub testExist()
        Dim coll As New Collection
        coll.Add New Collection, "1"
        coll.Add Array(1, 1), "3"
        coll.Add 1, "5"
        coll.Add "1111", "9"
        
        For i = 1 To 10
            Debug.Print "key " & i & " is " & IIf(keyExists(coll, CStr(i)), "Exists", "Absent")
        Next
    End Sub
    

    【讨论】:

      【解决方案6】:

      如果 Collection 包含对象而不是原始类型,则 Robin 的方法将失败,因为它们需要使用 Set 分配,否则会生成错误,导致方法返回 False。这是一个小的调整:

      'Test if a key is available in a collection
      Public Function HasKey(coll As Collection, strKey As String) As Boolean
          On Error GoTo IsMissingError
              Dim val As Variant
      '        val = coll(strKey)
              HasKey = IsObject(coll(strKey))
              HasKey = True
              On Error GoTo 0
              Exit Function
      IsMissingError:
              HasKey = False
              On Error GoTo 0
      End Function
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-04
        • 1970-01-01
        • 1970-01-01
        • 2018-06-09
        • 1970-01-01
        • 2011-05-17
        • 2014-09-14
        相关资源
        最近更新 更多