问题
We all know there can only ever be one error object in VBA.
While helping a co-worker with error handling and why he shouldn't use On Error Resume Next
I had an idea:
Store the error object somewhere to later reference back to it.
Consider this piece of test code:
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
It will print 0 to the immediate window because On Error GoTo 0
resets the error object and then prints 1 since it still holds a reference to the only error object (?).
If we create a new class and give it some properties pertaining to the ErrObject like so:
(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
And create our instance like this:
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
We still get 0 and 1 as output respectively.
This bringst me to my question: What is the use of declaring a variable as ErrObject when we cannot create a new object itself but it simply becomes another pointer to the only error object in VBA?
回答1:
None whatsoever.
Err
is often treated as some kind of global ErrObject
instance, but the truth is, it's a function that returns one - as revealed in the object browser:
And that function is implemented in such a way, that you always get the same object.
Objects need to expose an interface to be usable, and so the object returned by the Err
function exposes that of the ErrObject
class - it doesn't mean the ErrObject
class exists so that it can be instantiated or encapsulated by user code: it merely provides an interface to access the properties of the current run-time error state.
When you encapsulate an ErrObject
like you did, you're essentially just giving yourself another way (besides the Err
function) to access the ErrObject
instance - but it's still the exact same object holding the properties of the current run-time error state.
And when an object's properties change, your encapsulated copy that points to that object is going to start reporting the new values, and the old ones you meant to "remember" are overwritten.
Note that this is true for any object, not just ErrObject
.
Say I have a class that does what you're doing with the ErrObject
reference, but with a 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
If I make an instance of that class (let's call it Class1
) and assign c
to its InternalCollection
, and then add items to 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
The output is 2
, because c
and InternalCollection
(/the encapsuated coll
reference) are the very same object, and that's what's happening with your encapsulated ErrObject
.
The solution is to not encapsulate the ErrObject
itself, but rather pull its values into backing fields for get-only properties that encapsulate the state of the 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
'...
Now, whether that's useful is up for debate - IMO if the state is consumed at a moment where the global error state already contains the same information, there's no need to do this.
The class could pretty easily be [ab]used as a return type for a Function
that returns Nothing
to indicate success, and the encapsulated error state in case of failure - the problem is that the language is designed around raising errors rather than returning them; it's too easy to "fire-and-forget" such a function without verifying its return value, and since at the call site the actual runtime error state isn't going to trip an On Error
statement, carrying error state as program data isn't idiomatic, it makes a "surprising" API that can easily result in code that ends up ignoring all errors.
Idiomatic error handling deals with the global runtime error state as soon as possible, and either recovers in the same scope, or lets the error state bubble up the call stack to where it can be handled. And until the error is handled, the ErrObject
state is accessible through the global Err
function.
来源:https://stackoverflow.com/questions/55066376/what-is-the-use-of-declaring-an-errobject-variable-if-there-can-only-ever-exist