我陷入了 VBA 困境,在与错误 91 相关的其他问题中找不到好的答案。我想创建一个对象并在该对象内存储变量和数组。我尝试了一种像在 js 中那样的方法:
\nDim block As Object\n...\nSet block = Nothing\nblock.Name = "Unbekannter Pr\xc3\xbcfblock"\nblock.Awf = "Unbekannter Anwendungsfall"\nblock.Conditions = Array()\nblock.Checks = Array()\nRun Code Online (Sandbox Code Playgroud)\n我使用“Set block = Nothing”,因为我将在循环中多次使用它。
\n但我得到的只是错误 91 - 未设置对象变量
\n我如何设置对象?\n我真的必须在 vba 中声明所有内容吗?\n不是有一个“停止用声明通知烦我”开关吗?;-)
\n非常感谢大家的详细解答!
\n按照建议,我创建了一个“块”类,也创建了一个“条件”和“检查”类。块例如:
\nOption Explicit\n\nPublic name As String\nPublic awf As String\nPublic conditions As Collection\nPublic checks As Collection\nRun Code Online (Sandbox Code Playgroud)\n然后我在我的代码中使用它,如下所示:
\nDim bl As Block\nDim co As Condition\nDim ce As Check\n\nSet bl = New Block\nbl.name = ws.Range("B" & i).value\nbl.awf = ws.Range("B" & i).value\n\nSet co = New Condition\nco.attr = ws.Range("B" & i).value\nco.value = ws.Range("C" & i).value\nbl.conditions.Add co\nRun Code Online (Sandbox Code Playgroud)\n
VBA 不是 Javascript;而是 对象及其成员无法内联创建,它们需要类定义。
当您对对象进行成员调用时,您通过名称引用它,每当该名称引用空引用 ( Nothing) 时,您都会收到错误 91。
要修复此问题,您需要确保每个成员调用都是针对有效的对象引用进行的。使用Set关键字,您可以分配这样的引用,并且要创建对象的新实例,您可以使用关键字,New后跟定义您想要新实例的类型的类的名称:
Dim Block As Object
Block.Something = 42 ' Error 91
Set Block = New SomeClass ' set reference
Block.Something = 42 ' OK
Run Code Online (Sandbox Code Playgroud)
请注意,因为对象已声明As Object,所以每个成员调用都是后期绑定的(在运行时解析);如果该成员不存在(或者存在拼写错误),您将在运行时收到错误 438。
您可以通过使用更具体的数据类型进行声明,通过早期绑定将此错误移至编译时:
Dim Block As SomeClass
Run Code Online (Sandbox Code Playgroud)
因为 SomeClass 的成员在编译时是已知的,所以当您键入成员调用时,IDE 现在将为您提供成员完成列表,并且拼写错误在编译时将不再有效:力争保持在早期-尽可能绑定领域!注意:(As Variant无论是否明确)也同样是后期绑定。
因此,我们添加一个新的类模块并调用它SomeClass,然后添加几个公共字段:
Option Explicit
Public Name As String
Public Case As String
Public Condition As Variant
Public Check As Variant
Run Code Online (Sandbox Code Playgroud)
现在您可以创建并使用该类的新实例,并将其实例添加到集合中以便稍后处理(注意:您不能使用 UDT/ 来执行此操作Type)。
VBIDE 设置有一个烦人的选项(“自动语法检查”,IIRC),只要当前行出现编译错误,它就会立即弹出消息框;取消选中它(无效行将显示为红色,没有消息框),但请选中“需要变量声明”设置:它将添加Option Explicit到每个模块,这将使您避免许多容易避免的运行时错误,将它们移至编译时。
在 JS 中,您可以即时向对象添加属性(连同值)。这在 VBA(以及大多数其他语言)中是不可能的。
\n您的声明Dim block As Object正在定义一个应该指向Object 的变量。但它还没有指向任何东西,默认情况下它是用 初始化的Nothing,从字面上看,什么也没有,既没有属性也没有方法,它只是一个占位符,表示“我还没有指向任何东西”。此外,Object无法实例化。
在 VBA 中,您可以将某些内容分配给对象变量Set(这与大多数其他语言不同)。如果你想创建一个新对象,你可以使用关键字New。
然而,在这样做之前,您需要知道您需要什么类型的对象(哪个类)。这可以是现有的类,例如 Excel 中的工作表或范围,也可以是自己定义的类(通过在代码中创建新的类模块)。无论如何,您需要定义该类的属性和方法。考虑最简单的类模块Class1(当然你应该考虑一个更有用的名称):
Option Explicit\nPublic name as String\nPublic case as String\nRun Code Online (Sandbox Code Playgroud)\n(我不会开始谈论私有成员和getter和setter)。
\n然后你写
\nDim block As Class1\nSet block = New Class1\nblock.name = "Unbekannter Pr\xc3\xbcfblock"\nRun Code Online (Sandbox Code Playgroud)\n(但这block.data = "Hello world"是不可能的,因为data它不是您的类的成员。)\n此尝试的一大优点是,编译器可以在您启动代码(调试->编译)之前,例如在您输错属性名称时向您显示。在 JS 中,您将得到运行时错误或动态新属性 - 很难找到令人讨厌的错误。
如果您稍后需要一个新的(空)对象,只需使用创建一个新对象即可New。如果旧对象没有在其他地方引用,VBA 运行时将负责释放内存(因此无需编写Set block = Nothing)。
如果您确实事先不知道属性(例如,当您读取 XML 或 JSON 文件或 Excel 工作表中的键值列表时...),您可以考虑使用 aCollection或Dictionary. SO 和其他地方有很多例子。
一句评论block.Condition = Array()。VBA 中的数组并不是真正动态的,您无法在运行时轻松添加或删除条目。在 VBA 中,有静态和动态数组:
Dim a(1 to 10) as String ' Static array with 10 members (at compile time)\nDim b() as String ' Dynamic array.\nRun Code Online (Sandbox Code Playgroud)\n但是,对于动态成员,您需要在写入内容之前定义需要多少个成员,您可以使用ReDim该语句。如果您可以提前计算成员数量,则很有用:
Redim b(1 to maxNames) ' Dynamic array, now with maxNames members (whatever maxNames is)\nRun Code Online (Sandbox Code Playgroud)\n您可以使用 更改动态数组的数组大小Redim Preserve,但这应该是一个例外(糟糕的编程风格,性能不佳)。如果没有Preserve,您将得到一个新数组,但以前的数据会丢失。
Redim Preserve b(1 to maxNames+10) ' Now with 10 more members.\nRun Code Online (Sandbox Code Playgroud)\n如果您确实不知道成员的数量并且它在运行时可能经常变化,那么 aCollection或 aDictionary可能是更好的选择。请注意,例如字典本身可以作为值,这允许定义树结构。