为什么返回自己作为默认属性的对象会挂起excel并导致调试器崩溃?

时间:2018-11-26 02:19:18

标签: excel vba class default

我最近遇到了“属性值。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)返回私有集合的第ith个元素,Lst(i)=6设置第ith个元素。 (为清楚起见,删除了错误处理和索引检查代码)。

我注意到,从默认属性返回自身的对象可以从变量的函数中返回(例如下面的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

1 个答案:

答案 0 :(得分:2)

无法避免递归。

根据VBA语言规范的5.6.2.2部分:

  
      
  • 如果表达式的值类型是特定类别:      
        
    • 如果源对象具有公共默认设置属性获取或公共默认功能,并且此默认成员的参数列表为   与包含0个参数的参数列表兼容,简单   数据值的值是将该默认成员评估为   一个简单的数据值。
    •   
  •   

请注意,对于您的示例类,此行代码满足所有这些条件:

If L = 6 Then DoEvents

表达式L = 6的类型为布尔型,左侧为Lst,右侧为Integer。这意味着比较的类型为Integer,因此运行时将检查是否存在默认的Property Get,您可以在此处提供:

Public Property Get item(Optional index)
'Attribute Values.VB_UserMemId = 0

参数列表与包含0个参数的参数列表兼容,因为index是可选的。因此,它的值为L.item() = 6。您在属性内进行的唯一测试是If IsMissing(index),如果被称为默认成员,则 保证为真 -请记住,它为 < em>不能 要求传递参数。如您所知,这将导致您...

  

5.6.2.3默认成员递归限制

     

评估其默认属性获取或默认功能的对象   返回另一个对象可以导致递归评估过程,如果   返回的对象还有另一个默认成员。通过递归   如果评估为一个默认成员链,则可能是隐式的   简单数据值,每个默认成员都有一个空的参数列表,   或显式(如果指定的索引表达式专门   参数化每个默认成员。

如何处理是特定于实现的。但是,Office VBA实施不限制递归深度,当主机用尽堆栈空间时,它将使主机崩溃。


也就是说,您的其余问题只是一个x-y problem,尽管我的建议是取消该问题。使用默认成员会隐藏代码的意图,并且健壮,可维护的代码应该可读。