ByVal 是个谎言吗?

Is ByVal a lie?

我想我最近(终于)开始了解 ByValByRef 在 VBA 中的工作方式。

观察

在我迄今为止阅读的所有教程和参考资料中,ByRef 始终是 "passing the object",ByVal 始终是 "passing a copy of the object"。对我来说,后者意味着对象在内存中的位置被复制,指向新位置的指针被 returned。我现在意识到情况并非总是如此,事实上,据我所知,这种情况很少发生:相反,大多数对象和 classes 实际上都通过了 ByRef,即使指定了 ByVal在例程的签名中。

A System.Collections.ArrayList 通过 ByRef 静默传递,如以下代码所示:

Sub test()
    Dim list1 As Object, list2 As Object
    Set list1 = CreateObject("System.Collections.ArrayList")
    list1.Add "foo"
    Set list2 = RemoveItem(list1)
    Debug.Assert list2.Contains("foo") = False 'as expected
    Debug.Assert list1.Contains("foo") = True  'raises error, meaning list1 was passed byref not byval
End Sub

Function RemoveItem(ByVal list As Object) As Object 'ByVal
    list.Remove "foo" 'expect to remove from a copy and return that
    Set RemoveItem = list
End Function

考虑到我对 ByVal 的了解,这让我很惊讶。进一步挖掘表明,要从 ByVal 获得我想要的副本,我需要我传递的对象具有启用此功能的方法。对于 ArrayList.Clone 方法进行 浅拷贝 。所以我的函数变成了:

Function RemoveItem(ByRef list As Object) As Object 'ByRef or ByVal, makes no difference
    Dim listCopy As Object
    Set listCopy = list.Clone 'make a shallow copy of the object
    listCopy.Remove "foo" 'actually remove from a copy and return that
    Set RemoveItem = listCopy
End Function

A VB Array 在传递 ByVal 时引发编译器错误,可能只是针对此

的警告

问题

所有让我想到的:

  1. 对于 classes/objects
  2. ByValByRef 之间有什么区别(如果有)
  3. 区分可以传递 ByVal 的某些类型(BooleanLong)与 class 和不能传递的类型
  4. 在图书馆参考方面 - VB6 中可能没有写的东西
    • 是否可以创建具有默认 ByVal 响应的对象?
      • 在 Python 中,您可以指定 class 如何对不同的关键字做出反应。我怀疑你是否可以在 VBA 中原生地做到这一点,但是一个用更通用的语言编写的对象是否可以检测它是否被传递 ByVal 和 return a .Clone 本身。
      • 换句话说,VBA 的内置数据类型如何传递 ByVal 以及任何其他对象是否可以模仿此行为(同样,在 Python 中,一切都是对象,因此一切行为都可以复制*)。还是我不必担心这种情况会发生?
    • 是否可以模仿 Array 的行为并在传递时引发编译器错误 ByVal - 因为数组没有克隆方法并且无法轻易复制,所以总是 ByRef
  5. 是否有 Sub、Function 或方法可以让我创建对象的深度副本?也就是说,复制一个对象使用的内存并创建对象的第二个副本的一种通用方法。

*轶事,我对Python

很陌生

In all the tutorials and references I've been through to date, ByRef was always "passing the object", ByVal was "passing a copy of the object".

你有link上面的例子吗?

  1. 对于对象(从 classes 创建)ByVal 类似于 *fooByRef 类似于 **foo

  2. Boolean 和 Long 是原语,因为原语 ByVal 类似于 barByRef 类似于 *bar.

    • 没有
      • 对象无法判断它是否已通过 ByRefByVal
      • VBA 'built-in' 数据类型在 C/C++ 中有类似物 所以 Long 是一个 32 位整数 所以 Boolean 实际上也是,但只能取两个值之一(0=False,-1=True)。您不必担心,VBA 以安全的名义设置了限制。
    • 如果您想为您的对象强制传递 ByRef,请创建一个类型而不是 class。
  3. 对于 classes,您将不得不编写自己的浅层和深层复制构造函数。但是类型可以通过浅层和深层使用 = 来复制。

数组和类型被传递 ByRef 因为它们是在堆栈上创建的。

对于 VBA 中的对象和 类,传递 byval 或 byref 没有真正的区别。通过byref ony意味着传递指针'byref',对象保持不变

如果你想到它,它对 (Excel) 个对象有意义;你是否通过 Range("A1") byref 或 byval,它仍然是我们正在寻址的同一个单元格。