用自定义 VBA 类包装 .Net ArrayList 获取迭代器

Posted

技术标签:

【中文标题】用自定义 VBA 类包装 .Net ArrayList 获取迭代器【英文标题】:Wrap .Net ArrayList with custom VBA class get iterator 【发布时间】:2014-10-24 05:00:30 【问题描述】:

我刚刚发现我可以使用可以创建 COM 类的 CreateObject 方法从 VBA 创建一些 .Net 类。这很酷,但是创建的类是后期绑定的,所以你不会得到任何智能感知等。所以我希望做的是编写 VBA 包装类并将所有方法调用委托给 .Net 对象的内部引用。

所以这对于一个 ArrayList 对象来说很有效,除了试图引用枚举器之外。 VBA 有一个 hack,它允许您创建自己的集合并使用 For Each ... 语法来枚举您的集合。下面是一个例子:

Public Property Get NewEnum() As IUnknown
    Attribute NewEnum.VB_UserMemId = -4
    Attribute NewEnum.VB_MemberFlags = "40"
    Set NewEnum = <<< pass a reference to the enumerator here.
End Property

大多数实现都持有对 VBA Collection 对象的引用,并为 Collection 对象传递枚举器。但是,由于我持有一个 .Net ArrayList,我想将其传递出去。但是,当我尝试时,我得到“无效的过程调用或参数”。

我目前的尝试是这样的:

Public Function NewEnum() As IUnknown
    Dim enumerator As Object
    Set enumerator = internalList.GetEnumerator()    <<<< Error occurs here.
    Set NewEnum = enumerator
End Function

我很确定它可以完成这项工作,因为可以在没有包装器的情况下直接迭代 ArrayList 集合。例如

Public Sub TestCreateArrayList()
    Dim list As Object
    Set list = CreateObject("System.Collections.ArrayList")

    list.Add "an item."
    list.Add "another item."

    Dim Item As Variant
    For Each Item In list
        Debug.Print Item
    Next
End Sub

我可以在没有 For Each 功能的情况下生活,但如果我能让它工作,尤其是当它看起来快要完成的时候,那就太好了。

编辑: 可能值得一提的是,实例化 ArrayList 然后调用 GetEnumerator 会产生相同的错误,即使在包装类之外也是如此。

进一步编辑: 请注意,尝试将 IUnknown 类型的变量设置为 GetEnumerator 方法的结果仍然会产生相同的错误。

Public Sub TestCreateArrayList()
    Dim list As Object
    Set list = CreateObject("System.Collections.ArrayList")

    Dim iterator As IUnknown
    Set iterator = list.GetEnumerator()   <<<< error occurs here.
End Sub 

最终编辑: 感谢下面@RegEdit 的 cmets,我能够让它工作并认为我会添加代码:

Private internalList As Object

Private Sub Class_Initialize()
    ' create a .Net Array list for the internal data store.
    Set internalList = CreateObject("System.Collections.ArrayList")
End Sub

Private Sub Class_Terminate()
    If Not internalList Is Nothing Then
        On Error Resume Next
        internalList.Dispose
        Err.Clear
    End If
End Sub

Public Function NewEnum() As IUnknown
Attribute NewEnum.VB_UserMemId = -4
        Dim enumerator As IUnknown
        Set enumerator = internalList.GetEnumerator(0, internalList.Count)
        Set NewEnum = enumerator
End Function

' ... other wrapped methods elided

【问题讨论】:

您是否尝试过使用类似这样的额外方法从 ArrayList 创建派生类:[DispId(-4)] public IEnumerator GetMyEnumerator() return GetEnumerator(); ,并获取 MyEnumerator 而不是 GetEnumerator? @SimonMourier VBA 不支持继承。恐怕你不能从其他类派生。 我说的不是VBA 方面,而是.NET 方面。此外,将 ComVisible(true) 属性添加到新的派生类。 你帮的太多了。 System.Collections.IEnumerator 会自动映射到 COM 枚举器 (IEnumVARIANT)。只需公开一个返回 IEnumerator 的方法(在其实现中调用 GetEnumerator),您就可以设置为 For Each。 @HansPassant 是的,您是对的,IEnumerable 接口具有正确的 COM 设置。但是,使用上述方法调用 GetEnumerator 失败。请参阅上面的编辑代码。 【参考方案1】:

当你使用

 Dim enumerator As Object

您正在声明一个 COM 对象。但是 COM 将 IEnumerator 实现为IEnumVARIANT,它直接继承了IUnknown,并且COM 不乐意尝试将GetEnumerator 返回值设置为Object。相反,您可以使用

Dim enumerator As IEnumVARIANT

编辑:请注意,尽管ArrayList 使用采用indexcount 的方法实现IEnumerable、it overloads GetEnumerator。所以当你想调用不带参数的重载时,你必须在VBA中使用Nothing关键字。

Set iterator = list.GetEnumerator(Nothing, Nothing)

您可以看到它正在工作,因为如果您包含将两个项目添加到列表中的原始代码行,然后使用诸如

之类的调用
Set iterator = list.GetEnumerator(1, 3) ' 3 is out of bounds

您现在收到一个新的(预期的)错误,通知您偏移量/长度超出范围。

【讨论】:

酷,我认为你是对的。但是,将变量类型更改为 IEnumVARIANT 或 IUnknown 仍然失败。 Try:Public Sub TestCreateArrayList() Dim list As Object Set list = CreateObject("System.Collections.ArrayList") Dim iterator As IEnumVARIANT Set iterator = list.GetEnumerator() End Sub @Rossco 啊,忘了说,那是因为ArrayList 有一个重载的GetEnumerator。我已经更新了我的答案来解释这一点。 @RegEdit 你是个传奇。这正是问题所在!我什至没有考虑过 VBA 可能会尝试使用重载方法。我想如果我不提供任何参数,它会尝试使用没有参数的那个。所以我使用: Set iterator = list.GetEnumerator(0, internalList.Count)

以上是关于用自定义 VBA 类包装 .Net ArrayList 获取迭代器的主要内容,如果未能解决你的问题,请参考以下文章

我们可以用自定义函数扩展现有的类吗? (无继承)

access 查询条件 能否用自定义的函数

C# 用自定义错误完成后台工作人员

用自定义注解做点什么

用自定义注解做点什么

弃用自定义颤振小部件