在类模块内限制集合中的类型

5
我有一个类模块内的集合。我想限制可以“添加”的对象类型,也就是说,该集合只应接受给定类型的对象,不接受其他任何类型的对象。
是否有任何方法可以强制执行向集合中添加对象的类型?
据我所知,没有内置的方法来实现这一点。那么解决方案是将此集合设置为私有,并为通常可访问集合的方法(即Add、Remove、Item和Count)构建包装器函数吗?
我有点讨厌必须编写3个包装器函数,这些函数没有添加任何功能,只是为了能够将某些类型强制加入到Add方法中。但如果这是唯一的方法,那就只能这样做了。
4个回答

5

无法避免包装器函数。这只是VBA使用的“通过包含/委托进行专业化”的模型固有的。

不过,您可以构建“自定义集合类”。您甚至可以使用For...Each使它可迭代,但这需要离开VBA IDE并直接编辑源文件。

首先,请参阅旧版Visual Basic 6.0程序员指南中的“创建自己的集合类”部分:

http://msdn.microsoft.com/en-us/library/aa262340(v=VS.60).aspx

这里在stackoverflow上也有一个解答,描述了相同的事情:

vb6 equivalent to list<someclass>

然而,这些都是针对VB6编写的,而不是VBA。在VBA中,你无法在IDE中执行“procedure attributes”部分。你必须将类模块导出为文本,并使用文本编辑器添加它。Dick Kusleika的网站Daily Dose of Excel(Dick是一个常规的stackoverflow贡献者,你可能知道)有一篇来自Rob van Gelder的文章,展示了如何做到这一点:

http://www.dailydoseofexcel.com/archives/2010/07/04/custom-collection-class/

在你的情况下,为每个“自定义集合”类创建自己的模块可能不值得这么麻烦。(如果你只需要一次使用,并且它被埋在另一个类中,你可能会发现你并不希望暴露Collection的所有功能。)

每个“自定义集合”为什么需要自己的模块?我不能只制作一个通用的CustomCollection.cls类,从现在开始使用它来代替Collection吗?无论如何,好答案,+1。 - Jean-François Corbett
没错,我第一次就明白了。我的意思是:为什么不创建一个通用的UniformCollection类,它可以包含任何对象类型,只要该集合中所有项目的类型相同即可?我实际上已经做到了;稍后会在答案中展示。 - Jean-François Corbett

3
这是我所做的事情。我喜欢Rob van Gelder的示例,正如@jtolle所指出的那样,但我为什么要满足于创建一个只接受一种特定对象类型(例如People)的“自定义集合类”呢?这太烦人了。

相反,我将这个想法泛化,并创建了一个名为UniformCollection的新类,它可以包含任何数据类型——只要在UniformCollection的任何给定实例中所有项目都是相同类型的。

我添加了一个私有变体,它是给定UniformCollection实例可以包含的数据类型的占位符。

Private mvarPrototype As Variant

创建一个UniformCollection实例后,在使用之前,必须通过指定它将包含的数据类型来进行初始化。
Public Sub Initialize(Prototype As Variant)
    If VarType(Prototype) = vbEmpty Or VarType(Prototype) = vbNull Then
        Err.Raise Number:=ERR__CANT_INITIALIZE, _
            Source:=TypeName(Me), _
            Description:=ErrorDescription(ERR__CANT_INITIALIZE) & _
                TypeName(Prototype)
    End If
    ' Clear anything already in collection.
    Set mUniformCollection = New Collection
    If VarType(Prototype) = vbObject Or VarType(Prototype) = vbDataObject Then
        ' It's an object. Need Set.
        Set mvarPrototype = Prototype
    Else
        ' It's not an object.
        mvarPrototype = Prototype
    End If
    ' Collection will now accept only items of same type as Prototype.
End Sub

Add方法将只接受与原型(无论是对象还是基本变量...尚未测试UDT)相同类型的新项。
Public Sub Add(NewItem As Variant)
    If VarType(mvarPrototype) = vbEmpty Then
        Err.Raise Number:=ERR__NOT_INITIALIZED, _
            Source:=TypeName(Me), _
            Description:=ErrorDescription(ERR__NOT_INITIALIZED)
    ElseIf Not TypeName(NewItem) = TypeName(mvarPrototype) Then
        Err.Raise Number:=ERR__INVALID_TYPE, _
            Source:=TypeName(Me), _
            Description:=ErrorDescription(ERR__INVALID_TYPE) & _
                TypeName(mvarPrototype) & "."
    Else
        ' Object is of correct type. Accept it.
        ' Do nothing.
    End If

    mUniformCollection.Add NewItem

End Sub

其余内容和示例基本相同(加上一些错误处理)。遗憾的是RvG没有走到底!更糟糕的是,微软没有将这种功能作为内置功能包含在内...


1
对于一个 VBA 特定问题的有趣解决方案,点赞 +1。 - jtolle
1
我认为,如果您将第一个添加的内容视为“原型”,可能会更加简洁。但我喜欢这种方式。在集合内部进行运行时类型检查并不能让您获得编译时类型安全性,但如果您能够找到方法,它仍然可以让您实现Collection接口(https://dev59.com/km025IYBdhLWcg3w4Z-u)。 - jtolle
我喜欢使用第一个添加的项目作为原型定义的想法!周二一定要记住这个。 - Jean-François Corbett

1

我几乎做了与Jean-François Corbett相同的代码,但是由于某种原因没有起作用,因此我进行了适应性调整。

Option Explicit

Public pParametro As String
Private pColecao As New Collection

Public Sub Inicializar(ByVal parametro As String)
    pParametro = parametro
End Sub

Public Sub Add(NewItem As Object)
If TypeName(NewItem) <> pParametro Then
    MsgBox "Classe do objeto não é compatível à coleção"
Else
    pColecao.Add NewItem
End If
End Sub

Public Property Get Count() As Long
  Count = pColecao.Count
End Property

Public Property Get Item(NameOrNumber As Variant) As Variant
  Set Item = pColecao(NameOrNumber)
End Property

Sub Remove(NameOrNumber As Variant)
  pColecao.Remove NameOrNumber
End Sub

然后,当我想要从CCollection创建一个实例时,我会像下面的代码一样操作:
    Set pFornecedores = New CCollection
    pFornecedores.Inicializar ("CEmpresa")

CEmpresa是我想要的对象的类类型


0

是的。解决方案是将您的集合设为私有,然后创建公共包装函数来添加、删除、获取项和计数等操作。

可能会觉得编写额外代码很麻烦,但这是一种更健壮的封装集合的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接