excel - 为什么将自身作为默认属性返回的对象会挂起 excel 并使调试器崩溃?
问题描述
我最近遇到了`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)
返回私有集合的第 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 函数,返回 a Lst
,为哪个项目被调用,等等。导致不需要的和未被检测到的无限重复(不完全递归)。取消注释DoEvents
get item 属性中的语句可以让您停止而不会崩溃
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
解决方案
递归是无法避免的。
从VBA 语言规范的第5.6.2.2节:
- 如果表达式的值类型是特定的类:
- 如果源对象具有公共默认Property Get或公共默认函数,并且此默认成员的参数列表与包含 0 个参数的参数列表兼容,则简单数据值的值是将此默认成员评估为简单数据值的结果.
请注意,对于您的示例类,这行代码满足所有这些条件:
If L = 6 Then DoEvents
表达式的类型L = 6
是布尔型,Lst
左边是an Integer
,右边是an。这意味着比较的类型是Integer
,因此运行时检查是否存在Property Get
您在此处提供的默认值:
Public Property Get item(Optional index) 'Attribute Values.VB_UserMemId = 0
参数列表与包含 0 个参数的参数列表兼容,因为index
是可选的。因此,它评估为L.item() = 6
。您在属性中进行的唯一测试是If IsMissing(index)
,如果它被称为默认成员,则保证为真- 请记住,它不能要求传递参数。如您所见,这会导致您...
5.6.2.3默认成员递归限制
如果返回的对象具有进一步的默认成员,则对其默认Property Get或默认函数返回另一个对象的对象的求值可能会导致递归求值过程。如果评估为简单数据值并且每个默认成员都有一个空参数列表,则通过此默认成员链的递归可能是隐式的,或者如果指定了专门参数化每个默认成员的索引表达式,则递归可能是显式的。
如何处理是特定于实现的。但是,Office VBA 实现不会限制递归深度,并且在堆栈空间用完时只会使主机崩溃。
也就是说,您的问题的其余部分只是一个xy 问题,尽管我的建议是放弃这个。使用默认成员隐藏了代码的意图,并且健壮、可维护的代码应该是可读的。
推荐阅读
- html - 当它的文本分成两行时,如何折叠这个弹性项目的空白
- nginx - 如何在域/第一/第二/之后将前 2 个字符串(子目录)作为 lang 和页面参数传递到 nginx 中的域/index.php 文件(css、js 文件除外)
- javascript - Angular - 将值从 HTML 传递到组件不起作用
- c# - 为什么三元运算符和 if 语句返回不同的结果?
- javascript - 路由新组件和道具
- json - single-spa axios 调用重定向到 single-spa 端口
- java - Spring Boot 不断询问登录凭据
- php - 在vuejs上上传文件时获取“未定义的偏移量:1” - php
- python - 如何检查一个列表是否是另一个列表的子集(有容差)
- sql - PL/SQL 从 Oracle 包中读取定义的游标,返回 SYS_REFCURSOR,使用 cx_Oracle