使用数组的Excel VBA编程:传递它们还是不传递它们?

问题:我想知道哪个是Excel 2003 VBA中处理数组的最佳解决scheme

背景:我在Excel 2003中有一个超过5000行的macros。 在过去两年中,我已经创build了新的function,作为新的程序,这有助于分割代码,debugging,更改或添加到该function。 缺点是我在多个过程中使用了很多相同的基本信息,这要求我将它加载到多次存在细微差异的数组中。 我现在遇到了运行时间长的问题,所以我现在可以做一个完整的重写。
该文件用于获取多个制造stream程项目(最多4个不同的设置,总共多达10个不同的stream程,每个stream程最多1000步骤),其中信息是stream程特定的,子stream程专用于分组/sorting目的和数据(如移动,库存,CT,…)
然后,将数据粘贴到多张用于pipe理stream程的工作表上,利用要查阅的数据表,图表和单元格格式来表示stream程能力/历史logging。
stream程在Excel文件中,制造数据用7个不同的OO4O Oracle SQL读取读入,一些重复使用多次

arrays是:
arrrFlow(1到1000,1到4)作为loggingtypes与4个string
arrrSubFlow(1到1000,1到10)作为loggingtypes,包含4个string,2个整数和1个单
arrrData(1到1000,1到10)作为loggingtypes,有1个string,4个整数,12个长整数和1个单
arriSort(1到1000,1到4)为整数(用作指针数组,以分组顺序对子stream,子stream和数据进行sorting,同时按步骤顺序保留原始数组)

可能性:
1)将macros重写为一个大的过程,将数据加载到过程中定义的主数组中
临:在过程中的大小,而不是在模块中的公共variables,并没有通过。
Con:用一个大型程序而不是多个小型程序debugging更困难。

2)保持macros多个程序,但通过arrays
临:更容易debugging与多个较小的程序的代码。
Con:通过数组(昂贵?)

3)使用多个程序保持macros,但在模块中数组是公共调光的variables
临:更容易debugging与多个较小的程序的代码。
Con:公共arrays(昂贵?)

那么,社区的判决是什么? 有谁知道使用公共数组vsarrays的花费? 这些任何一个的成本是否值得让我的程序集中在一个function上?

更新:
我以离散级别(每步多个)加载库存数据,在总级别上移动数据(每个步骤一个),以及在总体级别上移动库存的开始。 我将库存数据一步一步放在工作状态类别(运行,等待,…)中,我已经在工作表上创build了目标数据。

我有一个stream程图,按types显示工作stream程,目前3个产品有类似但不完全相同的stream程,2个产品是不同的stream程,类似但又不相同。 我已经将不同stream程中的每一组步骤分配给一个组和一个子组。

我将这些数据放置在多个工作表中,一些在步骤顺序中,一些在组/子组顺序中。 我也需要集团和产品,集团/子集团和产品,线路和产品的一部分,以及产品总结的数据。

我使用loggingtypes,所以我实际上有一个可读的三维数组,arrSubFlow(1,1).strStep(第一个设备的第一步的步骤名称),arrData(10,5).lngYest(第十步的昨天的移动第五个设备)。

我的主要优化将在每一次从头创build10页的部分。 通过合并单元格,边界,标题,…这是一个非常耗时的过程。 我将添加一个将我的数据与页面进行比较以查看是否需要更改的部分,如果是,则只需重新创build它,我将清除数据的每个部分,并只写入更改到表单的数据。 这将是巨大的,根据我的时间logging数据。 但是,每当我更新代码时,我总是尝试改进代码的其他方面。 我看到数据加载到一个结构(数组,logging集,集合),既是一点点的优化,但更重要的是数据完整性,所以我没有机会以不同的方式加载不同的表。

我现在看到远离arrays的主要问题是:
*已经投入很大,但这不是一个足以改变的理由
*不知道是否有太多的成本通过他们,因为它会由ByRef
*我使用sortingfunction来创build一个sorting的“指针”数组,使我可以按步骤顺序离开数组,同时按组/子组顺序轻松引用它。

由于我总是试图为现在和未来做我的代码,我不反对将数组更新为RecordSet或Collections,而不仅仅是为了改变它们学习一些很酷的东西。 我的arrays工作,从我的研究,他们增加了几秒的运行时间,而不是2分钟的报告大量的。 所以,如果另一个结构比loggingtypes的二维数组更容易更新,那么请让我知道,但是有没有人知道把数组传递给一个过程的代价,假设你没有进行ByVal传递?

你已经提供了很多细节,但是如果没有看到一些代码,就很难理解到底发生了什么。 在你的问题中,我可以确定至less有4个重要的主题:制造业,数据访问,VBA和编码最佳实践。 我很难确切地告诉你要问什么,因为你的问题范围很大。 无论哪种方式,我感谢您尝试在VBA中编写更好的代码。

我很难理解你打算如何处理数组。 你说:

缺点是我在多个过程中使用了很多相同的基本信息,这要求我将它加载到多次存在细微差异的数组中。

我不确定你在这里的意思。 您是否正在使用数组来表示从数据库中检索的一行数据? 如果是这样,你可以考虑使用类模块,而不是通常的“macros”模块。 这些将允许您使用完整的对象而不是值的数组(或引用,视情况而定)。 类需要更多的工作来设置和使用,但是它们使你的代码更容易处理,并且将极大地帮助你分割你的代码。

正如用户Emtucifor已经指出的那样,可能存在诸如ADO Recordset对象(可能需要安装访问权限……不确定)的对象,这些对象可以帮助很大。 或者你可以创build自己的。

下面是一个很好的例子,说明如何使用课程可以帮助你。 虽然这个例子很长,但它会告诉你如何面向对象编程的一些原则真的可以帮助你清理你的代码。

在VBA编辑器中,转到Insert > Class Module 。 在“属性”窗口(默认情况下,屏幕左下angular)中,将模块的名称更改为WorkLogItem 。 将下面的代码添加到类中:

 Option Explicit Private pTaskID As Long Private pPersonName As String Private pHoursWorked As Double Public Property Get TaskID() As Long TaskID = pTaskID End Property Public Property Let TaskID(lTaskID As Long) pTaskID = lTaskID End Property Public Property Get PersonName() As String PersonName = pPersonName End Property Public Property Let PersonName(lPersonName As String) pPersonName = lPersonName End Property Public Property Get HoursWorked() As Double HoursWorked = pHoursWorked End Property Public Property Let HoursWorked(lHoursWorked As Double) pHoursWorked = lHoursWorked End Property 

上面的代码会给我们一个强types的对象,它是我们正在使用的数据的特定对象。 使用multidimensional array存储数据时,代码如下所示: arr(1,1)是ID, arr(1,2)是PersonName,而arr(1,3)是HoursWorked。 使用这种语法,很难知道是什么。 让我们假设你仍然将你的对象加载到一个数组中,而是使用我们上面创build的WorkLogItem 。 这个名字,你可以用arr(1).PersonName来获取这个人的名字。 这使得你的代码更容易阅读。

让我们继续移动这个例子。 不是将数组存储在数组中,我们将尝试使用一个collection

接下来,添加一个新的类模块并将其称为ProcessWorkLog 。 把下面的代码放在那里:

 Option Explicit Private pWorkLogItems As Collection Public Property Get WorkLogItems() As Collection Set WorkLogItems = pWorkLogItems End Property Public Property Set WorkLogItems(lWorkLogItem As Collection) Set pWorkLogItems = lWorkLogItem End Property Function GetHoursWorked(strPersonName As String) As Double On Error GoTo Handle_Errors Dim wli As WorkLogItem Dim doubleTotal As Double doubleTotal = 0 For Each wli In WorkLogItems If strPersonName = wli.PersonName Then doubleTotal = doubleTotal + wli.HoursWorked End If Next wli Exit_Here: GetHoursWorked = doubleTotal Exit Function Handle_Errors: 'You will probably want to catch the error that will ' 'occur if WorkLogItems has not been set ' Resume Exit_Here End Function 

上面的类将被用于与WorkLogItem集合“做某事”。 最初,我们将其设置为计算工作的总小时数。 我们来testing我们编写的代码。 创build一个新的模块(这次不是类模块;只是一个“常规”模块)。 将以下代码粘贴到模块中:

 Option Explicit Function PopulateArray() As Collection Dim clnWlis As Collection Dim wli As WorkLogItem 'Put some data in the collection' Set clnWlis = New Collection Set wli = New WorkLogItem wli.TaskID = 1 wli.PersonName = "Fred" wli.HoursWorked = 4.5 clnWlis.Add wli Set wli = New WorkLogItem wli.TaskID = 2 wli.PersonName = "Sally" wli.HoursWorked = 3 clnWlis.Add wli Set wli = New WorkLogItem wli.TaskID = 3 wli.PersonName = "Fred" wli.HoursWorked = 2.5 clnWlis.Add wli Set PopulateArray = clnWlis End Function Sub TestGetHoursWorked() Dim pwl As ProcessWorkLog Dim arrWli() As WorkLogItem Set pwl = New ProcessWorkLog Set pwl.WorkLogItems = PopulateArray() Debug.Print pwl.GetHoursWorked("Fred") End Sub 

在上面的代码中, PopulateArray()只是创build一个WorkLogItem集合。 在你真实的代码中,你可以创build类来parsing你的Excel工作表或数据对象来填充一个集合或一个数组。

TestGetHoursWorked()代码简单地演示了如何使用这些类。 您注意到ProcessWorkLog被实例化为一个对象。 实例化后, WorkLogItem的集合成为pwl对象的一部分。 您注意到这行Set pwl.WorkLogItems = PopulateArray() 。 接下来,我们简单地调用我们编写的作用于集合WorkLogItems

为什么这有帮助?

假设你的数据改变了,你想添加一个新的方法。 假设你的WorkLogItem现在包含一个WorkLogItem的字段,并且你想添加一个新的方法来计算它。

所有你需要做的是像这样添加一个属性到WorkLogItem

 Private pHoursOnBreak As Double Public Property Get HoursOnBreak() As Double HoursOnBreak = pHoursOnBreak End Property Public Property Let HoursOnBreak(lHoursOnBreak As Double) pHoursOnBreak = lHoursOnBreak End Property 

当然,你需要改变你的方法来填充你的集合(我使用的示例方法是PopulateArray() ,但是你可能应该有一个单独的类)。 然后,只需将您的新方法添加到ProcessWorkLog类中:

 Function GetHoursOnBreak(strPersonName As String) As Double 'Code to get hours on break End Function 

现在,如果我们想要更新我们的TestGetHoursWorked()方法来返回GetHoursOnBreak结果,我们只需要添加下面一行:

  Debug.Print pwl.GetHoursOnBreak("Fred") 

如果你传递了一组代表你的数据的值,你将不得不在你的代码中find你使用数组的所有地方,然后相应地更新它。 如果使用类(及其实例化对象),则可以更轻松地更新代码以处理更改。 另外,如果允许以多种方式使用类(也许一个函数只需要4个对象属性,而另一个函数需要6个),则它们仍然可以引用同一个对象。 这使您不必为不同types的函数使用多个数组。

为了进一步阅读,我强烈推荐获取VBA开发者手册第2版的副本。 这本书充满了很好的例子和最佳实践以及大量的示例代码。 如果你在一个严肃的项目上投入了大量的时间在VBA上,那么看看这本书是非常值得的。

这听起来也许Excel和arrays不是你正在做的工作的最佳工具。 如果你能解释一下你正在使用的数据types以及你正在做什么,这将有助于提供更好的答案。 尽可能详细地描述您对数据进行的操作types以及input和输出。

我将给出一些我认为能够帮助你的亮点,然后可以编辑我的答案,使其更加完整,因为我得到了你的答复,所以我有更多的时间去补充一点。

  • 有一个对象自然处理你正在使用的loggingtypes的对象,称为Recordset。 在VBA编辑器中,转到工具 – >参考,并添加Microsoft ActiveX数据对象2.X库(在您的机器上最高的一个)。 您可以声明ADODB.Recordsettypes的对象,然后执行Recordset.Fields.Append来添加字段,然后打开它,最后添加新的,设置字段值和.Update。 这是一个在程序中作为input或输出parameter passing的自然对象。 它具有自然的遍历和定位function(.Eof,.Bof,.AbsolutePosition,.MoveNext,.MoveFirst,.MovePrevious),并支持search和过滤(.Filter =“Field ='abc'”,.Find等)。

  • 我不推荐使用公共variables,尽pipe没有理解你在做什么,我不能真正在这里build议你。

  • 我也会避免一个大的程序。 代码应该分解成可重复使用的function单元,它们只做一件事,其名字本质上是自我logging的。

  • 如果要提高代码的性能,请在运行时随机点击ctrl-break并分解代码。 然后按Ctrl-L查看调用堆栈。 记下每次列表中的内容。 如果大部分时间出现任何项目,这是瓶颈,您应该花时间去优化它。 然而,我不build议试图优化你所拥有的东西,直到你做出一些更高层次的决定(比如是否要切换到logging集)。

我真的需要更多的信息来帮助你更好。

如果你有兴趣,我会处理一些演示代码,它将显示Recordset对象是多么有用。 用Recordset.GetRows或.GetString插入数据从Recordset到Excel范围是非常容易的(尽pipe可能需要一些数组转换,但这也不难)。

更新:如果你的目标是要加快你的过程,那么在做任何事情之前,我认为最好的做法是掌握最需要花费时间的知识。 请你按ctrl-break约10次,每次记下调用堆栈,然后告诉我调用堆栈中最常见的东西是什么?

在更新单元格格式的速度方面,这是我的经验:

  1. 合并是您可以做的最慢的操作。 尽量避免它,如果可能的话。 使用“跨越select中心”是一种select。 另一个不是合并,而是使用适当的尺寸,边框,单元格背景颜色以及closures整个工作簿的网格线的组合。

  2. 将边界或其他格式一次应用于可能最大的事情,而不是像许多细小的事物一样。 例如,如果大多数单元格都具有所有的边界,但有一些则不具有边界,则将所有边界应用于整个范围,并在循环过程中删除不需要的边界。 即使如此,尝试做整行和更大的范围。

  3. 保存模板文件的边框和格式已经应用。 比方说,你把一行放在一个特定部分的格式中。 在一个步骤中,将该行复制到该节所需的行数,例如20行,它们将具有相同的格式。 复制行比单独应用格式化要快得多。

另外,我不会自动去使用类。 虽然面向对象是伟大的,我自己做(哎呀,我只是build立了8类的东西来build模一个层次结构,所以我可以很容易地揭露它的一部分,当我需要他们),在实践中可以慢一些。 类中的一组简单公共variables比使用getter和setter更快。 用户定义的Type比类更快,但是你可能会遇到一些尝试在类中传递UDT的问题(它们必须在非公共模块中声明,即使这样他们也会出现问题)。

埃里克