来自Parent的“TypeOf ... Is Child”会导致Excel文件损坏

时间:2013-10-29 16:17:56

标签: excel-vba vba excel

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

基本上,简化的场景是这样的:两个类模块,Parent和Child,其中Child实现Parent。 Parent中的某个位置是行TypeOf Me Is Child,其中Me可以是任何对象。

根据我的理解,当TypeOf...Is行编译为P代码(Debug>编译或调用方法)并保存到文件(.xlsm或.xlsb)时,它会导致文件不正常打开。代码运行正常,但是当文件被保存,关闭和重新打开时,它会在打开(或打开VBE)时发出错误,指出Invalid data formatError 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

2 个答案:

答案 0 :(得分:2)

IMPLEMENTS语句导致循环依赖

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

OP可能被误解为暗示"Parent"归咎于某种原因。但是,TypeOf语句并不特殊。 interface 类中所有引用本身是TypeOf .. Is interface 类的类的语句都将设置循环依赖问题。 例如:

Person.cs

IMPLEMENTS

Boy.cs

'Class Person
Option explicit

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

所以我希望您能看到Boy.cs实现了包含Boy.cs的Person.cs,而Boy.cs实现了包含Boy.cs的Person.cs .... VBA在这一点上很疯狂:)

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

解决方案是从接口类的源代码中删除这些语句。如果由于在接口类中实际编写了许多业务逻辑而被证明很难做到,那么一种有用的方法是将业务逻辑移到一个单独的类中。只需单独执行此操作即可解决Interface的编译问题,并使您的代码再次运行。 以我自己的经验,出于这个原因,我特意尝试从接口类中删除任何业务逻辑,以确保不会发生此类错误,并且接口类变得非常简单-仅列出方法签名。如果我不想在将实现接口的每个类中重复通用的业务逻辑,那么我将创建一个附加类来保存此通用业务逻辑,并确保接口要求该类存在。 例如:

iMusicalInstrument.cs

'Class Boy
Option explicit

Implements Person

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

csMusicalInstrumentCommon.cs

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

csTrumpet.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 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

:)

答案 1 :(得分:1)

VBA不支持类多态。

我认为你误解了关键字Implements的目的。

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

请参阅this answer for better understanding of the Implements keyword in VBA

有关VBA多态性的信息,请参阅this