我最近遇到了“Attribute Values.VB_UserMemId = 0”。我喜欢列表,所以我想构建一个定制的集合类型对象。
可以重现错误的类的最少代码是:
类Lst
Option Explicit
Public c As New Collection
'this is the default property
Public Property Get item(Optional index)
'Attribute Values.VB_UserMemId = 0
If IsMissing(index) Then
Set item = Me
'DoEvents
Else
item = c(index)
End If
End Property
Public Property Let item(Optional index, itm)
If IsMissing(index) Then 'assume itm is list
If IsObject(itm) Then Set c = itm.c Else c.add itm
Else
c.add itm, , index
c.Remove index + 1
End If
End Property
本质上,lst(i)
返回私有(private)集合的第 i 个元素,Lst(i)=6
设置第 i 个元素。 (为了清楚起见,删除了错误处理和索引检查代码)。
我注意到从默认属性返回自身的对象可以从变体中的函数返回(例如下面的LstFunc=L
),而不需要set
消除学生眼中的复杂性...(你不能用集合对象做到这一点)
不幸的是,我遇到了两个挑战......这些挑战的最低代码是:
问题
Function LstFunc() As Variant
Dim L As New Lst
L = 4 'replaces L.item=3
LstFunc = L 'this is not normally allowed, but desirable (for me!)
End Function
Sub try()
Dim L As New Lst
L = LstFunc 'replaces L.item=LstFunc-->L.c: [4]
L = 3 'L.c: [4,3]
If L = 6 Then DoEvents
End Sub
这是发生的事情
1) 当计算表达式 L = 6
时,Excel 会挂起。有时 ESC
会让您恢复正常,但我的经验是 Excel 停止响应并需要重新启动。
为了计算表达式,首先调用 L.item 函数,返回一个 Lst
,为哪个项目调用等等。导致不需要的、未被检测到的无限重复(不完全是递归)。取消注释 get item 属性中的 DoEvents
语句可以让您停止而不会崩溃
2) 取消注释 DoEvents
后,我一步步在调试器模式下运行。如果我现在(意外地..)将鼠标悬停在变量 L 上,调试器就会崩溃,并且我会得到绿色死亡三角形,我担心这会让学生感到非常困惑:
请注意,如果类中的 DoEvents
语句再次被注释掉,此行为是可以恢复的。名副其实的陷阱 22...
这有点复杂,但是任何关于如何以低计算成本捕获 (1) 中不需要的重复并且不失去像变体一样传递对象的能力的建议都会受到热烈欢迎。
PS 这是一段代码片段,提供了下面评论中讨论的不安全的解决方法:
Public Property Get item(Optional index)
'Attribute Values.VB_UserMemId = 0
static i
If IsMissing(index) Then
Set item = Me
i=i+1:if i>1000 then item="":exit property
'DoEvents
Else
item = c(index)
i=0
End If
End Property
最佳答案
递归是不可避免的。
来自部分5.6.2.2 VBA语言规范:
- If the expression’s value type is a specific class:
- If the source object has a public default Property Get or a public default function, and this default member’s parameter list is compatible with an argument list containing 0 parameters, the simple data value’s value is the result of evaluating this default member as a simple data value.
请注意,对于您的示例类,这行代码满足所有这些条件:
If L = 6 Then DoEvents
表达式L = 6
的类型是 bool 型,左侧是Lst
,右侧是Integer
边。这意味着比较的类型是 Integer
,因此运行时会检查是否存在默认的 Property Get
(您在此处提供):
Public Property Get item(Optional index) 'Attribute Values.VB_UserMemId = 0
参数列表与包含 0 个参数的参数列表兼容,因为 index
是可选的。因此,它的计算结果为L.item() = 6
。您在属性内执行的唯一测试是 If IsMissing(index)
,如果将其作为默认成员调用,则保证为 true -请记住,它不能要求传递参数。正如您所发现的,这会导致您...
5.6.2.3 Default Member Recursion Limits
Evaluation of an object whose default Property Get or default function returns another object can lead to a recursive evaluation process if the returned object has a further default member. Recursion through this chain of default members may be implicit if evaluating to a simple data value and each default member has an empty parameter list, or explicit if index expressions are specified that specifically parameterize each default member.
如何处理此问题是特定于实现的。然而,Office VBA 实现不会限制递归深度,并且当主机耗尽堆栈空间时只会导致主机崩溃。
也就是说,你的问题的其余部分只是一个 x-y problem ,尽管我的建议是放弃这个。使用默认成员会隐藏代码的意图,并且健壮、可维护的代码应该具有可读性。
关于excel - 为什么返回自身作为默认属性的对象会挂起 Excel 并使调试器崩溃?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53473985/