如果只能存在一个错误对象,那么声明 ErrObject 变量有什么用呢?

Nac*_*rid 1 error-handling vba ms-office

我们都知道 VBA 中只能有一个错误对象。
在帮助同事处理错误以及为什么他不应该使用错误时,On Error Resume Next我有一个想法:
将错误对象存储在某处以便稍后引用它。

考虑这段测试代码:

Sub Test()
    Dim t As ErrObject
    On Error Resume Next
    Err.Raise 1
    Set t = Err
    On Error GoTo 0
    Debug.Print t.Number
    On Error Resume Next
    Err.Raise 1
    Debug.Print t.Number
End Sub
Run Code Online (Sandbox Code Playgroud)

它将在立即窗口中打印 0,因为On Error GoTo 0重置错误对象,然后打印 1,因为它仍然保留对唯一错误对象的引用(?)。

如果我们创建一个新类并为其赋予一些与 ErrObject 相关的属性,如下所示:

(TestClass)
Option Explicit
Public oError As ErrObject
Private Sub Class_Initialize(): End Sub
Private Sub Class_Terminate()
    If Not oError Is Nothing Then Set oError = Nothing
End Sub
Public Property Get Error()
    Error = oError
End Property
Public Property Set Error(ByVal ErrorObject As ErrObject)
    Set oError = ErrorObject
End Property
Run Code Online (Sandbox Code Playgroud)

并像这样创建我们的实例:

Sub Test2()
    Dim t As TestClass
    On Error Resume Next
    Set t = New TestClass
    Err.Raise 1
    Set t.Error = Err
    On Error GoTo 0
    Debug.Print t.oError.Number
    On Error Resume Next
    Err.Raise 1
    Debug.Print t.oError.Number
End Sub
Run Code Online (Sandbox Code Playgroud)

我们仍然分别得到 0 和 1 作为输出。

这让我想到了我的问题:当我们无法创建新对象本身但它只是成为指向 VBA 中唯一错误对象的另一个指针时,将变量声明为 ErrObject 有什么用?

Mat*_*don 5

没有任何。

Err通常被视为某种全局实例,但事实是,它是一个返回ErrObject1 的函数- 正如对象浏览器中所示:

在此输入图像描述

这个函数的实现方式是这样的,你总是得到相同的对象。

对象需要公开一个接口才能使用,因此函数返回的对象Err公开了该类的ErrObject对象 - 这并不意味着该类ErrObject存在以便可以由用户代码实例化或封装:它只是提供一个接口访问当前运行时错误状态的属性。

当你像你一样封装一个ErrObject时,你本质上只是给自己提供了另一种方式(除了函数之外Err)来访问实例 - 但它仍然是保存当前运行时错误状态ErrObject属性的完全相同的对象。

当对象的属性发生变化时,指向该对象的封装副本将开始报告新值,并且您想要“记住”的旧值将被覆盖。

请注意,这对于任何对象都适用,而不仅仅是ErrObject

假设我有一个类可以执行您对引用所做的操作ErrObject,但使用Collection

Private coll As Collection

Public Property Set InternalCollection(ByVal c As Collection)
    Set coll = c
End Property

Public Property Get InternalCollection() As Collection
    Set InternalCollection = coll
End Property
Run Code Online (Sandbox Code Playgroud)

如果我创建该类的一个实例(我们称之为Class1)并分配c给它InternalCollection,然后将项目添加到c...

Dim c As Collection
Set c = New Collection
With New Class1
    Set .InternalCollection = c

    c.Add 42
    .InternalCollection.Add 42

    Debug.Print .InternalCollection.Count
End With
Run Code Online (Sandbox Code Playgroud)

输出是2,因为cInternalCollection(/封装的coll引用)是同一个对象,这就是封装的ErrObject.

解决方案是不封装ErrObject本身,而是将其值拉入封装状态的仅获取属性的支持字段ErrObject

Private errNumber As Long
Private errDescription As String
'...

Public Sub SetErrorInfo() 'note: an ErrObject argument would be redundant!
    With Err
        errNumber = .Number
        errDescription = .Description
        '...
    End With
End Sub

Public Property Get Number() As Long
    Number = errNumber 
End Property

Public Property Get Description() As String
    Description = errDescription
End Property

'...
Run Code Online (Sandbox Code Playgroud)

现在,这是否有用还有待争论 - IMO 如果在全局错误状态已经包含相同信息的时刻消耗状态,则无需这样做。

该类可以很容易地被用作 a 的返回类型,Function返回Nothing表示成功,并在失败时封装错误状态 - 问题在于该语言是围绕引发错误而不是返回错误而设计的;在不验证其返回值的情况下“一劳永逸”这样的函数太容易了,并且由于在调用站点,实际的运行时错误状态不会触发On Error语句,因此携带错误状态作为程序数据并不惯用,它创建了一个“令人惊讶”的 API,很容易导致代码最终忽略所有错误。

惯用的错误处理会尽快处理全局运行时错误状态,并在同一范围内恢复,或者让错误状态在调用堆栈中向上冒泡到可以处理的位置。在处理错误之前,ErrObject可以通过全局函数访问状态Err