使用With语句释放Variant类型对象数组元素

7
如果将一个对象数组声明为变量类型(以便使用IsEmpty函数轻松检查它是否已初始化),那么如果随后定义的数组元素被引用作为With语句的对象表达式(例如:With VariantObjArray(i)...),那么该对象变量数组元素将被错误释放(尽管With语句对对象变量的隐式复制在单个执行Through With语句范围的执行过程中功能正确)。 此外,数组元素对象变量的错误释放可能是内存泄漏,因为它发生在执行With表达式的同时,而不是作为任何标准的释放机制(如退出With语句或从子例程返回或显式设置为Nothing)的结果。
Sub DemoVariantObjArrayBug()
    Dim i As Integer
    Dim NextWkSh As Worksheet
    Static VariantObjArray As Variant

    If IsEmpty(VariantObjArray) Then 'Check to avoid unnecessary re-allocation of static or global array variable
        ReDim VariantObjArray(1 To ThisWorkbook.Worksheets.Count)
        For Each NextWkSh In ThisWorkbook.Worksheets
            i = i + 1: Set VariantObjArray(i) = ThisWorkbook.Worksheets(i)
        Next NextWkSh
    End If
    
Stop 'and, to observe the bug, open the Locals window, expand its view of VariantObjArray, single step through
     'the following code and observe each VariantObjArray element being deallocated with each cycle's execution
     'of the "With" statement:
      
    For i = LBound(VariantObjArray) To UBound(VariantObjArray)
           With VariantObjArray(i) 'The bug workaround is to, instead of this, do something like the following...
'            Dim SomeWkSh As Object: Set SomeWkSh = VariantObjArray(i)
'            With SomeWkSh
                Debug.Print """" & .Name & """: CodeName = " & .CodeName & ", Index = " & .Index
            End With
    Next i
End Sub

一种解决方法是明确使用中间对象变量,如上面的替代代码所示(最初被注释掉)。

  1. 我无法找到有关此错误的任何网络讨论,那么这真的是一个没有人遇到过的错误吗?
  2. 还是一个新的错误,最近引入,包括我的当前版本的Excel,Microsoft 365 MSO(16.0.14326.21052)64位?
  3. 它只发生在64位Office中吗?
  4. 这实际上是分配的对象内存泄漏还是仅仅是对象指针丢失?

1
2、3:不,只能在32位的Excel 2003上重现。 - GSerg
1
你的复现可以大大简化。您不需要使用static或数组来保存对象。 ReDim VariantObjArray(1到3),为每个元素设置一个数字,在with中触摸一个元素而没有任何内容,它将变为空值而不是数字。 https://pastebin.com/0SJFELZk - GSerg
1
我强烈建议将循环体有系统地提取到它们自己的过程范围中(并通过ByVal传递参数)- 我非常确定这可以解决问题(现在无法测试)。 - Mathieu Guindon
2
@Mathieu Guindon (#1,2):对于某些算法,您的建议非常合理,但对于其他算法来说则不然。通过将细节推入子过程,高度的算法碎片化可能会干扰对整个算法目的和机制的理解,并且实际上可能会增加通过未来的天真修改引入错误的可能性。我的经验是,这种问题是好的编程变成了一种艺术而不是一种规定性算法的方式之一。 - pstraton
@Cristian Buse:你说得对!太棒了。很抱歉在没有实际测试你的解决方法之前做出了错误的假设。下次我去伦敦时我请你喝啤酒。然而,潜在的问题仍然是一个bug,而且你的干净解决方法并不明显。微软在听吗?(是的,这是几十年来的幻想。) - pstraton
显示剩余8条评论
1个回答

1
一个更加干净的解决方案是由Cristian Buse提供的(请参见上面的评论)。这是我的演示代码和他的解决方案:
Sub DemoVariantObjArrayBug()
    Dim i As Integer
    Dim NextWkSh As Worksheet
    Static VariantObjArray As Variant 'NOTE: can't declare it as "Static VariantObjArray() As Variant"!

    If IsEmpty(VariantObjArray) Then 'Check to avoid unnecessary re-allocation of static or global array variable
    
        '*** The bug workaround is to, instead of this naive ReDim that sets the stage for the bug...
        ReDim VariantObjArray(1 To ThisWorkbook.Worksheets.Count)
        
        '*** ...use Cristian Buse's workaround which explicitly defines the array elements' type via the ReDim:
'        ReDim VariantObjArray(1 To ThisWorkbook.Worksheets.Count) As Worksheet

        For Each NextWkSh In ThisWorkbook.Worksheets
            i = i + 1: Set VariantObjArray(i) = ThisWorkbook.Worksheets(i)
        Next NextWkSh
    End If
    
Stop 'and, to observe the bug, open the Locals window, expand its view of VariantObjArray, single step through
     'the following code and observe each VariantObjArray element being deallocated with each cycle's execution
     'of the "With" statement:
      
    For i = LBound(VariantObjArray) To UBound(VariantObjArray)
           With VariantObjArray(i) 'The bug workaround is to, instead of naively re-dimensioning the array as
                                   'an (implicit) Variant, above, re-dimension it as a specific type (Worksheet).
                                   
                Debug.Print """" & .Name & """: CodeName = " & .CodeName & ", Index = " & .Index
            End With
    Next i
End Sub

一个微妙的细节是,为了编译,数组的初始声明必须作为单个变量。
Static VariantObjArray As Variant

不是变量数组:

Static VariantObjArray() As Variant

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