5

我已经追踪这个问题好几天了,所以我想我会在这里发布它以帮助其他有同样问题的人,并了解更多关于原因的信息。我已将问题代码简化为本文末尾的两个类模块。

基本上,简化的场景是这样的:两个类模块,Parent 和 Child,其中 Child 实现 Parent。Parent 中的某处是 line TypeOf Me Is Child,其中Me可能是任何 Object。

据我了解,当该TypeOf...Is行编译为 P 代码(调试 > 编译,或调用方法)并保存到文件(.xlsm 或 .xlsb)时,会导致文件无法正确打开。代码可以正常运行,但是当文件被保存、关闭和重新打开时,在打开(或打开 VBE)时会报错Invalid data formator Error accessing file. Network connection may have been lost,并且父模块无法再打开,也无法运行任何 VBA (?1=1在立即窗口中尝试,它会给出相同的错误)。

如果使用TypeName()而不是检查类型TypeOf...Is,则不会出现此问题(这是我在项目中使用的解决方案)。

任何人都可以进一步了解这里到底出了什么问题,或者至少确认我在导致问题的原因(P代码)方面处于正确的轨道上吗?

PS 是的,我知道父母对孩子的了解是糟糕的设计,但我已经接近一次性项目的尾声,不值得花时间重新设计。

有用的链接:

类模块:

家长:

Option Explicit
' Class: Parent

' The problem (so far as I can tell):
'   When the compiled version of the method below is saved to the file, the file
'   will no longer load properly. Upon saving and reopening the file, I get a
'   "Invalid data format" error, and the code for this class module can no longer be
'   accessed. Furthermore, no VBA code will run after this happens. Try typing "?1=1"
'   into the Immediate Window - you'll get another "Invalid data format" window.
'   Alternatively, the error will be "Error accessing file. Network connection may
'   have been lost." if the code is changed from using "Me" to "tmp" as noted in the
'   comments in DoSomething().

' Steps to replicate:
'   1. Debug > Compile VBAProject.
'   2. Save file.
'   3. Close Excel.
'   4. Reopen file (and may need to open VBE).

Public Sub DoSomething()
    ' The TypeOf...Is statement seems to be what causes the problem.
    ' Note that checking "Me" isn't the cause of the problem (merely makes
    '   for shorter demo code); making a "Dim tmp as Object; set tmp = new Collection"
    '   and checking "TypeOf tmp Is Child" will cause the same problem.
    ' Also note, changing this to use TypeName() resolves the issue.
    ' Another note, moving the TypeOf...Is to a "Private Sub DoSomethingElse()" has
    '   no effect on the issue. Moving it to a new, unrelated class, however, does
    '   not cause the issue to occur.
    If TypeOf Me Is Child Then
        Debug.Print "Parent"
    End If
End Sub

孩子:

Option Explicit
' Class: Child

Implements Parent

Private Sub Parent_DoSomething()
    Debug.Print "Child"
End Sub
4

2 回答 2

2

IMPLEMENTS 语句导致循环依赖

问题不在于TypeOf声明本身。问题是您设置了 VBA 无法解决的循环依赖项。正如提到的,VBA 并没有真正实现多态性。您创建的循环引用是您的接口的定义 "Parent"包括(需要存在)您的对象"Child"和您的类"Child" 实现的定义(需要存在)"Parent"。因此,VBA 无法在编译时正确创建接口,并且下次保存、关闭并重新打开工作簿和 VB 编辑器时,接口类将损坏且无法访问。

OP 可能会被误解为暗示该声明TypeOf .. Is应受到某种指责。但是,TypeOf 语句并不特殊。接口类中任何引用本身接口IMPLEMENTS类的类的语句都将设置循环依赖问题。例如:

个人.cs

'Class Person
Option explicit

Public Sub SaySomething()
   Dim B as Boy            '<--- here we cause the problem!
End sub 

男孩.cs

'Class Boy
Option explicit

Implements Person

Private Sub Person_SaySomething()
   Debug.Print "Hello"
End sub

所以我希望你能看到 Boy.cs 实现 Person.cs,其中包含一个 Boy.cs,它实现了一个包含 Boy.cs 的 Person.cs .... VBA 在这一点上发疯了:)

有点遗憾的是,VB 编辑器没有提供比“无效数据格式”错误或“访问文件时出错。网络连接可能已丢失”更有用的错误消息。这让用户感到困惑!

解决方案是从接口类的源代码中删除这些语句。如果这被证明很难做到,因为您实际上在接口类中编写了很多业务逻辑,那么一个有用的方法是将业务逻辑移到单独的类中。只需自己执行此操作即可解决接口的编译问题并让您的代码再次运行。以我自己的经验,正是出于这个原因,我故意尝试从接口类中删除任何业务逻辑,以确保不会发生这种错误,并且接口类变得非常简单——只是一个方法签名的列表。如果有我没有的常见业务逻辑' 不想在每个将实现我的接口的类中复制,然后我创建一个额外的类来保存这个公共业务逻辑并确保接口需要这个类存在。例如:

iMusicalInstrument.cs

'iMusicalInstrument interface
Option Explicit
Property Get Common() as csMusicalInstrumentCommon
End Property

csMusicalInstrumentCommon.cs

'MusicalInstrumentCommon class
Option Explicit
' add any methods you want to be available to all implementers of the interface.
Property Get GUID() as string        '<-- just an example, could be any method
     GUID = 'function to create a GUID
End Property

csTrumpet.cs

' csTrumpet class
Option Explicit
Implements iMusicalInstrument
Private mCommon As csMusicalInstrumentCommon
Private Sub Class_Initialize()
    Set mCommon = New csMusicalInstrumentCommon
End Sub
Private Sub Class_Terminate()
    Set mCommon = Nothing
End Sub
Private Property Get iMusicalInstrument_Common() As csMusicalInstrumentCommon
    Set iMusicalInstrument_Common = mCommon
End Property

用法

Public Sub Test()
    Dim Trumpet As New csTrumpet
    Dim iTrumpet As iMusicalInstrument
    Set iTrumpet = Trumpet
    Debug.Print iTrumpet.Common.GUID
End Sub

:)

于 2020-02-27T19:41:24.970 回答
1

VBA 不支持类多态。

我认为您误解了关键字的用途Implements

当您希望一个类实现一个接口时使用它- 而不是另一个类(好吧,至少不是字面意思,因为 VBA 中的接口是另一个类模块对象)

请参阅此答案以更好地理解VBA 中的关键字Implements

有关 VBA 多态性的信息,另请参阅此内容

于 2013-10-29T16:34:57.013 回答