excel - 为什么返回自身作为默认属性的对象会挂起 Excel 并使调试器崩溃?

标签 excel vba class default

我最近遇到了“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 上,调试器就会崩溃,并且我会得到绿色死亡三角形,我担心这会让学生感到非常困惑: green triangle of death...

请注意,如果类中的 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/

相关文章:

vba - 如何在 MS Access 表单上动态保持控件居中(相对位置)?

ruby - 类在需要时运行

vba - 如何使用 VBA 用户窗体循环访问工作簿中的多个工作表

vba - Excel VBA 或 VSTO - 如何循环数据透视表上的字段?

javascript - 使用类而不是使用抛出对象作为返回值的函数有什么优点?

php - 替代 PHP 的 __autoload 函数?

excel - 使用公式,从变量字符串中提取操作系统版本号

vba - Excel VBA 优化 - 转置数据

python - XlsxWriter - 设置网格线颜色

java - 有办法获取 XSSFSheet 哈希密码吗?