VBA传两个参数报错
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VBA传两个参数报错相关的知识,希望对你有一定的参考价值。
参考技术A 首先打开excle软件,进入VBA的编程界面,创建一个子程序test1,在VBA编程函数中怎样传递参数。然后在子程序中定义两个变量num1和num2,在VBA编程函数中怎样传递参数。,紧接着对这两个变量赋值。在VBA编程函数中怎样传递参数。调用sumup函数,并传入num1和num2这两个参数,并将返回的结果输出来。在VBA编程函数中怎样传递参数。把num1和num2这两个变量的值打印出来,看看这两个值被传入另外一个函数内部之后,值有没有改变。在VBA编程函数中怎样传递参数。创建函数sumup,在sumup函数里面有两个参数,在参数前面用了ByVal,表示参数是按照值传递的方式进行,默认是按照地址进行传递的。
将参数传递给VBA中的构造函数
【中文标题】将参数传递给VBA中的构造函数【英文标题】:Pass arguments to Constructor in VBA 【发布时间】:2013-02-19 20:56:24 【问题描述】:您如何构造直接将参数传递给您自己的类的对象?
类似这样的:
Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)
无法做到这一点很烦人,最终你会得到肮脏的解决方案来解决这个问题。
【问题讨论】:
codereview.stackexchange.com/questions/67825/… 为了不变性,可以使用类中的私有初始化和工厂:Private VBA Class Initializer called from Factory 跟进上述评论。 GitHub 上的 repo 现在支持 Private Class 初始化程序。方法称为RedirectInstance
,需要从私有函数调用。结合类工厂,实现了不变性。
【参考方案1】:
这是我最近使用的一个小技巧,效果很好。想和那些经常用VBA打架的人分享一下。
1.- 在您的每个自定义类中实现一个公共启动子例程。我在所有课程中都将其称为 InitiateProperties。此方法必须接受您要发送给构造函数的参数。
2.- 创建一个名为 factory 的模块,并创建一个公共函数,其中包含单词“Create”加上与类相同的名称,以及与构造函数所需的相同的传入参数。此函数必须实例化您的类,并调用第 (1) 点中解释的启动子例程,并传递接收到的参数。最终返回了实例化和初始化的方法。
例子:
假设我们有自定义类 Employee。和前面的例子一样,is 必须用 name 和 age 实例化。
这是 InitiateProperties 方法。 m_name 和 m_age 是我们要设置的私有属性。
Public Sub InitiateProperties(name as String, age as Integer)
m_name = name
m_age = age
End Sub
现在在工厂模块中:
Public Function CreateEmployee(name as String, age as Integer) as Employee
Dim employee_obj As Employee
Set employee_obj = new Employee
employee_obj.InitiateProperties name:=name, age:=age
set CreateEmployee = employee_obj
End Function
最后当你想实例化一个员工时
Dim this_employee as Employee
Set this_employee = factory.CreateEmployee(name:="Johnny", age:=89)
当你有几个类时特别有用。只需在模块工厂中为每个模块放置一个函数并通过调用 factory.CreateClassA(arguments)、factory.CreateClassB(other_arguments) 等进行实例化。
编辑
正如 stenci 指出的那样,您可以通过避免在构造函数中创建局部变量来使用更简洁的语法来做同样的事情。例如 CreateEmployee 函数可以这样写:
Public Function CreateEmployee(name as String, age as Integer) as Employee
Set CreateEmployee = new Employee
CreateEmployee.InitiateProperties name:=name, age:=age
End Function
哪个更好。
【讨论】:
不错的解决方案!虽然我可能会将其重命名为factory.CreateEmployee
以减少歧义......
工厂模块相对于每个类中的 Construct 方法有什么好处。所以你会打电话给Set employee_obj = New Employee
,然后是employee_obj.Construct "Johnny", 89
,构造的东西发生在课堂上。只是好奇。
嗨,我看到了一些好处。它们可能有些重叠。首先,您以标准方式使用构造函数,就像在任何普通的 OOP 语言中一样,这提高了清晰度。然后,每次实例化一个对象时,保存该行来初始化对象,这样可以减少编写量,然后不能忘记初始化对象,最后在过程中减少一个概念,从而降低复杂性。
您可以使用私有变量对其进行跟踪。你可以定义Class_Initialize
,然后在那里定义一个变量m_initialized = false
。当您输入InitiateProperties
时,检查m_initialized
,如果它为假,则继续,最后将其设置为真。如果为真,则引发错误或不执行任何操作。如果再次调用 InitiateProperties 方法,它将为 true,并且对象的状态不会改变。
很抱歉把它从死里唤醒,但最后一段是错误的,那是不是“更好”的代码。将函数的返回机制(分配给标识符)视为声明的局部变量是误导和混淆充其量,而不是“更好”(看起来像递归调用,不它?)。如果您想要更简洁的语法,请利用我的回答中所示的模块属性。作为奖励,您失去了 Single-Responsibility-Principle-takes-a-beating “工厂包”模块,该模块负责创建几乎所有事物及其母亲的实例,并且不可避免地会在任何体面的大小中变得一团糟项目。【参考方案2】:
我使用一个Factory
模块,每个类包含一个(或多个)构造函数,它调用每个类的Init
成员。
例如Point
类:
Class Point
Private X, Y
Sub Init(X, Y)
Me.X = X
Me.Y = Y
End Sub
一个Line
类
Class Line
Private P1, P2
Sub Init(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
If P1 Is Nothing Then
Set Me.P1 = NewPoint(X1, Y1)
Set Me.P2 = NewPoint(X2, Y2)
Else
Set Me.P1 = P1
Set Me.P2 = P2
End If
End Sub
还有一个Factory
模块:
Module Factory
Function NewPoint(X, Y)
Set NewPoint = New Point
NewPoint.Init X, Y
End Function
Function NewLine(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
Set NewLine = New Line
NewLine.Init P1, P2, X1, Y1, X2, Y2
End Function
Function NewLinePt(P1, P2)
Set NewLinePt = New Line
NewLinePt.Init P1:=P1, P2:=P2
End Function
Function NewLineXY(X1, Y1, X2, Y2)
Set NewLineXY = New Line
NewLineXY.Init X1:=X1, Y1:=Y1, X2:=X2, Y2:=Y2
End Function
这种方法的一个很好的方面是可以很容易地在表达式中使用工厂函数。例如,可以执行以下操作:
D = Distance(NewPoint(10, 10), NewPoint(20, 20)
或:
D = NewPoint(10, 10).Distance(NewPoint(20, 20))
它很干净:工厂做的很少,它在所有对象中始终如一地做,只是创建和一个Init
调用每个creator。
而且它是相当面向对象的:Init
函数是在对象内部定义的。
编辑
我忘了补充一点,这允许我创建静态方法。例如,我可以做类似的事情(在使参数可选之后):
NewLine.DeleteAllLinesShorterThan 10
不幸的是,每次都会创建一个新的对象实例,因此任何静态变量在执行后都会丢失。此伪静态方法中使用的行集合和任何其他静态变量必须在模块中定义。
【讨论】:
这比选择的答案更干净。 我上次玩VBA已经很久了,但是... 1:你如何从Factory
的子例程中获取构造对象? Sub
的定义没有返回值。 2:即使我遗漏了这一点,您的 Factory
所做的事情与我的几乎相同:创建一个对象(我分两步完成,您的语法显然更短),调用 Init
/InitiateProperties
方法,在我的例子中,显式返回。
@ikaros45 他们应该是Function
,而不是Sub
,我编辑了帖子,谢谢。是的,它和你的一样,只是随着类的数量和每个类的“构造函数”数量的增加,它的组织方式更容易管理(在我看来)。
是的,组织完全一样,但我同意你的方式更简洁。这意味着相同,但您为每个构造函数节省了两行代码,这很好。如果你不介意,我会用你的语法更新我的代码。【参考方案3】:
另一种方法
假设你创建了一个类 clsBitcoinPublicKey
在类模块中创建一个额外的子例程,它的行为就像你希望真正的构造函数表现一样。下面我将其命名为 ConstructorAdjunct。
Public Sub ConstructorAdjunct(ByVal ...)
...
End Sub
From the calling module, you use an additional statement
Dim loPublicKey AS clsBitcoinPublicKey
Set loPublicKey = New clsBitcoinPublicKey
Call loPublicKey.ConstructorAdjunct(...)
唯一的惩罚是额外的调用,但好处是可以将所有内容保留在类模块中,调试变得更容易。
【讨论】:
除非我忽略了某些东西,否则这就像每次实例化任何对象时手动调用我的“InitiateProperties”,这是我一开始想避免的。【参考方案4】:当您导出一个类模块并在记事本中打开文件时,您会注意到在顶部附近有一堆隐藏的属性(VBE 不显示它们,并且不公开调整其中大部分的功能任何一个)。其中之一是VB_PredeclaredId
:
Attribute VB_PredeclaredId = False
将其设置为True
,保存并将模块重新导入到您的 VBA 项目中。
带有PredeclaredId
的类有一个您可以免费获得的“全局实例” - 与UserForm
模块完全一样(导出用户表单,您会看到它的 predeclaredId 属性设置为 true)。
很多人只是乐于使用预先声明的实例来存储状态。这是错误的 - 这就像将实例状态存储在静态类中!
相反,您可以利用该默认实例来实现您的工厂方法:
[Employee
类]
'@PredeclaredId
Option Explicit
Private Type TEmployee
Name As String
Age As Integer
End Type
Private this As TEmployee
Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As Employee
With New Employee
.Name = emplName
.Age = emplAge
Set Create = .Self 'returns the newly created instance
End With
End Function
Public Property Get Self() As Employee
Set Self = Me
End Property
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
Public Property Get Age() As String
Age = this.Age
End Property
Public Property Let Age(ByVal value As String)
this.Age = value
End Property
有了它,你可以这样做:
Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)
Employee.Create
正在处理默认实例,即它被视为类型的成员,并且仅从默认实例调用。
问题是,这也是完全合法的:
Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)
这很糟糕,因为现在你有一个令人困惑的 API。您可以使用 '@Description
annotations / VB_Description
属性来记录使用情况,但如果没有 Rubberduck,编辑器中将无法在呼叫站点向您显示该信息。
此外,Property Let
成员是可访问的,因此您的 Employee
实例是可变的:
empl.Name = "Jane" ' Johnny no more!
诀窍是让你的类实现一个接口,它只公开需要公开的内容:
[IEmployee
类]
Option Explicit
Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property
现在你让Employee
实现 IEmployee
- 最后的类可能看起来像这样:
[Employee
类]
'@PredeclaredId
Option Explicit
Implements IEmployee
Private Type TEmployee
Name As String
Age As Integer
End Type
Private this As TEmployee
Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As IEmployee
With New Employee
.Name = emplName
.Age = emplAge
Set Create = .Self 'returns the newly created instance
End With
End Function
Public Property Get Self() As IEmployee
Set Self = Me
End Property
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
Public Property Get Age() As String
Age = this.Age
End Property
Public Property Let Age(ByVal value As String)
this.Age = value
End Property
Private Property Get IEmployee_Name() As String
IEmployee_Name = Name
End Property
Private Property Get IEmployee_Age() As Integer
IEmployee_Age = Age
End Property
注意Create
方法现在返回接口,而接口不公开Property Let
成员?现在调用代码可以是这样的:
Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)
由于客户端代码是针对接口编写的,empl
暴露的唯一成员是IEmployee
接口定义的成员,这意味着它看不到Create
方法,也看不到Self
getter,也不是任何Property Let
mutators:因此,其余代码可以使用“抽象”IEmployee
接口,而不是使用“具体”Employee
类,并享受不可变的多态对象。
【讨论】:
注意:immutable 并不是真正可以实现的;该实例可以访问其字段,并且可以很好地改变它们的值。但这比将Property Let
暴露给外界(或者更糟的是,公共领域!)要好。
调用代码不会是 Dim empl as Employee
,因为 Employee Class
实现 IEmployee
否则运行时错误与您编写它的方式有关
@Jose Dim empl As IEmployee
工作精确因为类Implements IEmployee
。
为什么我得到Variable not Defined
for Employee.Create
?
@Matheiu Guindon - 并不是要发送垃圾邮件,但我在将近 3 个月后重新访问这篇文章。从那以后,我一直在 OOP 上一遍又一遍地阅读你的橡皮鸭博客,现在这个答案对我来说完全有意义。我不敢相信我在上面的 cmets 中提出的问题。【参考方案5】:
使用技巧
Attribute VB_PredeclaredId = True
我发现了另一种更紧凑的方式:
Option Explicit
Option Base 0
Option Compare Binary
Private v_cBox As ComboBox
'
' Class creaor
Public Function New_(ByRef cBox As ComboBox) As ComboBoxExt_c
If Me Is ComboBoxExt_c Then
Set New_ = New ComboBoxExt_c
Call New_.New_(cBox)
Else
Set v_cBox = cBox
End If
End Function
如您所见,调用 New_ 构造函数来创建和设置类的私有成员(如 init),唯一的问题是,如果在非静态实例上调用,它将重新初始化私有成员。但这可以通过设置标志来避免。
【讨论】:
【参考方案6】:为什么不这样:
-
在类模块 »myClass« 中使用
Public Sub Init(myArguments)
而不是 Private Sub Class_Initialize()
实例化:
Dim myInstance As New myClass: myInstance.Init myArguments
【讨论】:
请用英文写下你的答案,Stack Overflow is an English site.以上是关于VBA传两个参数报错的主要内容,如果未能解决你的问题,请参考以下文章