Dim As New和Dim / Set有什么区别?

17
在VBA中,我可以通过以下两种方式之一创建对象:
'First way
Dim myCol1 As New Collection

'Second way
Dim myCol2 As Collection
Set myCol2 = New Collection

myCol1.Add "AAA"    'Works
myCol2.Add "BBB"    'Works as well

第二种方式只是第一种方式的更冗长的版本吗?还是myCol1和myCol2对象之间实际上存在差异?


第二个版本只是将操作分成了两行。如果在编译的操作中足够深入,事件序列发生的顺序没有任何区别,这并不奇怪。 - David
我认为是这样的。我更喜欢第一种方式(更短),但让我困惑的是互联网上有很多例子使用第二种方式?! - Combinatix
4
as new 和直接创建对象的区别在于,使用 as new 延迟对象的创建,直到第一次(以及之后所有)跨引用变量调用时才创建。具体原因可以参考这个链接:为什么不在声明时实例化对象? - Alex K.
3
很遗憾,实现方式存在巨大差异。实际上,有两个不同的操作码和两个不同的函数在MSVBVM中。 - ThunderFrame
1
@ThunderFrame:我承认错误,我猜每天都会学到新东西。 (不过,作为VB,我真诚地希望我永远不需要应用这些知识。只是说说而已。) - David
2个回答

31

有几个关键的区别。你应该绝对偏好第二个 Dim/Set 方法。

原因1 - 用 As New,对象只在该对象的一个属性或方法被调用时创建,但是看下这个例子,在将对象设置为 Nothing,然后调用一个属性/方法会导致对象重新实例化:

Sub ShortcutInstantiation()

  Dim x As New Collection

  x.Add "FOO", "BAR"
  Set x = Nothing

  'This line implicitly recreates a new Collection
  Debug.Print x.Count

  Debug.Print x Is Nothing 'Prints False

End Sub

Sub SafeInstantiation()

  Dim x As Collection
  Set x = New Collection

  x.Add "FOO", "BAR"
  Set x = Nothing

  'Throws error because x is nothing
  Debug.Print x.Count

End Sub

原因2 使用As New方式较慢,因为VBA需要在每次属性或方法调用之前检查对象是否已被实例化。

看一下这个伪代码,就能明白VBA在幕后做了什么:

Sub NotSoShortcutInstantiation()

  Dim x As New Collection

  If x Is Nothing Then Set x = New Collection
  x.Add "FOO", "BAR"

  If x Is Nothing Then Set x = New Collection
  x.Add "FIZZ", "BUZZ"

  If x Is Nothing Then Set x = New Collection
  x.Add "CAR", "DOOR"

  If x Is Nothing Then Set x = New Collection
  Debug.Print x.Count

End Sub

原因 3 如果您的对象构造函数在您期望之后执行某些操作,而不是在显式实例化时执行操作,则可能存在关键时间差:

比较以下代码的结果:

Sub InstantiationTiming()

  Dim foo As String

  Dim x As New Class1
  Debug.Print Format(Now(), "hh:mm:ss") & " x should be ready"
  foo = x.foo

  Dim y As Class1
  Set y = New Class1
  Debug.Print Format(Now(), "hh:mm:ss") & " y should be ready"
  foo = y.foo

End Sub

As New方法输出:

06:36:57 x should be ready
06:36:57 Class Initialized

Set y = New方法会输出:

06:36:57 Class Initialized
06:36:57 y should be ready

2
在循环中经常看到对as new的困惑,这让人们认为他们正在获得一个新对象,而实际上每次循环他们使用的是完全相同的引用(以及它的状态)。 - Alex K.
Dim As New 的目的是什么?它似乎只有缺点? - Combinatix
打字少了!也就是说,它永远不值得这种权衡。 - ThunderFrame
2
答案说:“只有在调用该对象的属性或方法时才会创建对象”,但实际上是指代码中对该对象的任何引用,而不仅仅是调用其成员。例如,即使是Debug.Print x is Nothing这一行也会触发对象的创建,以便始终打印“False”。 - C Perkins

7
< p > As New 结构具有合法的用途。在一个类中,当您不知道哪个方法将被首先调用时,使用模块级变量可以节省一些代码。因此,这里提供了一些我在类中存放的代码片段

Option Explicit

Private mdicQueryStringParams As New Scripting.Dictionary

Function SafeItem(ByVal sKey As String, ByRef pvItem As Variant) As Boolean

    If mdicQueryStringParams.Exists(sKey) Then
        pvItem = mdicQueryStringParams.Item(sKey)
        SafeItem = True
    End If

End Function

假设有很多方法都依赖于初始化的mdicQueryStringParams,你将不得不编写保护代码以确保在所有这些方法中都创建了它。
现在你可能会说可以使用Sub Class_Initialize在类创建时进行New。就像这样:
Private Sub Class_Initialize()
    Set mdicQueryStringParams = New Scripting.Dictionary
End Sub

假设我想要回收/重置类的一部分状态,那么我可以编写一个Clear方法,将mdicQueryStringParams设置为Nothing。在这种情况下,Sub Class_Initialise不会再次运行。在此处, Mat's Mug 在SO上教会了我,静态类在VBA中是可能存在的(谢谢!),因此有时Sub Class_Initialise只会运行一次。
(^不可否认的是,在Clear方法中我可以将其设置为新实例,是的,是的,我知道,我知道)
关键是使用As New语法,您将获得一个“复活”的变量以及“自动初始化”。作为开发人员,我们应该利用这个工具箱中的另一种技术/模式,而不是禁止它。
事实上,我很少使用它,但我就是不喜欢禁止东西。

1
为什么要有一个静态类来存储需要“重置”的可变状态?这让我感到矛盾。 - Comintern
这很有趣。我能够看到为什么应该使用Dim/Set方法(感谢您提供出色的答案)。但在日常编程中,当您创建函数时,通常会创建对象,然后使用它,最后销毁它。我的意思是,ThunderFrame答案中的原因1几乎不会发生,而原因3只会在非常特定的代码中发生。而且,当您使用很少的调用来创建一个Excel函数时,性能差异微不足道。然而,我理解需要注意的点,这就是我提出这个问题的原因。 - Combinatix
Dictionary的情况下,似乎你的Clear方法应该调用Dictionary.RemoveAll()。更一般地说,如果你正在创建一个字段/私有成员,我仍然更喜欢类的作者管理创建/销毁。让Class_Initilalize调用一个SetupNewDictionary帮助程序,然后如果你真的必须销毁原始字典并创建另一个字典,则销毁方法调用SetupNewDictionary - ThunderFrame
@Combinatix 1和3可能是边缘情况(但它们很容易陷入陷阱,如果您不理解"As New"的含义,则调试它们可能非常困难),但2将始终是性能问题。在循环中访问对象属性将加剧这种性能问题。 - ThunderFrame
@Thunderframe:在VBA的底层,它正在检查一个空指针,在CPU级别比较寄存器是否为零是一种优化的CPU操作。这几乎无关紧要。不过,如果是在数千个循环中,那就有点重要了。 - S Meaden
显示剩余2条评论

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