实时(未保存的)Excel数据和C#对象之间的最快接口

我想知道什么是从一个打开的Excel工作簿读取和写入数据到c#对象的最快方式。 背景是我想开发从Excel使用的ac#应用程序,并使用Excel中保存的数据。

业务逻辑将驻留在C#应用程序中,但数据将驻留在Excel工作簿中。 用户将使用Excel,并在Excel工作簿上点击一个button(或者做类似的事情)来启动C#应用程序。 然后,C#应用程序将从Excel工作簿中读取数据,处理数据,然后将数据写回到Excel工作簿。
可能有许多数据块需要被读取并写回到Excel工作簿,但通常是相对较小的尺寸,例如10行和20列。 有时可能需要处理大量的数据列表,大约50,000行和40列。

我知道用VSTO做这个比较容易,但是我想知道什么是最快的(但仍然健壮和优雅)的解决scheme,并得到速度的想法。 我不介意解决scheme是否推荐使用第三方产品或使用C ++。

显而易见的解决scheme是使用VSTO或interop,但我不知道性能是什么样的,而我目前用来读取数据的VBA,或者是否有其他解决scheme。

这是张贴在专家交stream,说VSTO是比VBA慢得多,但那是几年前,我不知道是否performance有所改善。

http://www.experts-exchange.com/Microsoft/Development/VSTO/Q_23635459.html

谢谢。

如果C#应用程序是一个独立的应用程序,那么您将始终使用跨进程封送处理,从而通过将语言从C#转换为C ++来压倒优化。 坚持你最喜欢的语言在这种情况下,这听起来像是C#。

如果你愿意 Excel中创build一个插件,那么你的操作将避免跨进程的调用并且运行速度提高50倍。

如果您在Excel中作为加载项运行,则VBA是最快的选项之一,但它仍然涉及COM,因此使用XLL加载项的C ++调用将是最快的。 但是对于Excel对象模型的调用,VBA仍然是相当快的。 但是,对于实际的计算速度,VBA作为pcode运行,而不是完全编译的代码,因此执行速度比本地代码慢大约2-3倍。 这听起来很糟糕,但这并不是因为绝大多数使用典型的Excel插件或应用程序的执行时间都涉及到调​​用Excel对象模型,所以VBA与完全编译的COM加载项相比,本地编译的VB 6.0,只会慢5-15%,这是不明显的。

VB 6.0是一种编译的COM方法,运行速度比VBA快2-3倍,而非Excel相关的调用,但是VB 6.0在这一点上已经有12年的历史,并且不会以64位模式运行,比如安装Office 2010,可以安装运行32位或64位。 64位Excel的使用目前很less,但会增加使用,所以我会避免使用VB 6.0。

如果C#运行在Excel中,那么Excel加载项将以与VBA相同的速度执行对Excel对象模型的调用,并且执行非Excel调用的速度将比VBA快2-3倍 – 如果运行未调节的话。 但是,Microsoft推荐的方法是使用COM Shim Wizard来完全运行,例如。 通过被删除,Excel可以保护您的代码(如果有问题),并且您的代码完全免受可能会导致问题的其他第三方加载项的影响。 然而,这一方面的缺点是在单独的AppDomain中运行一个简化的解决scheme,这需要跨AppDomain编组,这导致执行速度大约是40倍,这在许多情况下是非常明显的。

使用Visual Studio Tools for Office(VSTO)的加载项会自动加载到一个Shim中,并在单独的AppDomain中执行。 如果使用VSTO,则无法避免这种情况。 因此,调用Excel对象模型也会导致执行速度降低大约40倍。 VSTO是制作非常丰富的Excel插件的华丽系统,但执行速度是其应用程序(如您的应用程序)的弱点。

ExcelDna是一个免费的开源项目,允许您使用C#代码,然后将其转换为使用C ++代码的XLL插件。 也就是说,ExcelDnaparsing您的C#代码并为您创build所需的C ++代码。 我自己并没有用过,但是我对这个过程很熟悉,而且非常令人印象深刻。 ExcelDna从使用它的人得到非常好的评价。 [编辑]请注意以下Govert的评论下面的修正:“嗨迈克 – 我想添加一个小的更正,以澄清Excel-Dna的实施:所有托pipe的Excel胶水在您的托pipe程序集在运行时使用reflection – 在那里没有额外的预编译步骤或者C ++代码生成,而且,即使Excel-Dna使用.NET,与Excel交谈时也不需要任何COM互操作 – 就像.xll一样,本地接口可以直接从.NET使用。 (尽pipe你也可以使用COM),这使得高性能的UDF和macros成为可能。 – Govert]

你也可能想看看加载项快车。 这不是免费的,但它可以让你在C#中编写代码,虽然它将你的解决scheme分散到一个单独的AppDomain中,但我相信它的执行速度非常出色。 如果我正确理解其执行速度,那么我不确定外接程序如何执行此操作,但它可能会利用FastPath AppDomain编组。 不过,不要在此引用任何内容,因为我对“加载项快捷版”不是很熟悉。 你应该检查一下,做你自己的研究。 [编辑:阅读Charles Williams的答案,看起来像加载项快速启用COM和C API访问。 Govert指出,Excel DNA也支持COM和fastrer C API访问。 所以你可能要检查两者,并将其与ExcelDna进行比较。]

我的build议是研究Add-in Express和ExcelDna。 这两种方法都可以让你用C#编写代码,这是你最熟悉的。

另一个主要问题是如何打电话。 例如,Excel在处理作为数组来回传递的整个范围的数据时速度非常快。 这比循环遍历单元格更有效率。 例如,下面的代码使用Excel.Range.set_Value访问器方法将一个10 x 10的值数组分配到一个10 x 10范围的单元格中:

void AssignArrayToRange() { // Create the array. object[,] myArray = new object[10, 10]; // Initialize the array. for (int i = 0; i < myArray.GetLength(0); i++) { for (int j = 0; j < myArray.GetLength(1); j++) { myArray[i, j] = i + j; } } // Create a Range of the correct size: int rows = myArray.GetLength(0); int columns = myArray.GetLength(1); Excel.Range range = myWorksheet.get_Range("A1", Type.Missing); range = range.get_Resize(rows, columns); // Assign the Array to the Range in one shot: range.set_Value(Type.Missing, myArray); } 

可以类似地使用Excel.Range.get_Value访问器方法在一个步骤中从一个范围读取一个值的数组。 这样做,然后遍历数组中的值比循环单独范围的单元格中的值要快得多。

我将把这作为一个挑战,并打赌最快的方式来洗牌你的数据之间的Excel和C#是使用Excel-Dna – http://exceldna.codeplex.com 。 (免责声明:我开发了Excel-Dna,但是它仍然是正确的…)

因为它使用了原生的.xll接口,所以它将跳过所有与VSTO或其他基于COM的加载项方法相关的COM集成开销。 使用Excel -Dna,您可以创build一个macros,该macros连接到一个菜单或function区button,该菜单或function区button用于读取范围,对其进行处理,然后将其写回Excel中的范围。 所有使用C#的本地Excel接口 – 不是一个COM对象。

我做了一个小testing函数,将当前的select放入一个数组中,对数组中的每个数字进行平方,然后将结果写入Sheet 2从单元格A1开始。 您只需要添加(免费)Excel-Dna运行时,您可以从http://exceldna.codeplex.com下载。;

我读入C#,在一秒钟内写入一百万个单元格的Excel。 这对你来说足够快吗?

我的function如下所示:

 using ExcelDna.Integration; public static class RangeTools { [ExcelCommand(MenuName="Range Tools", MenuText="Square Selection")] public static void SquareRange() { object[,] result; // Get a reference to the current selection ExcelReference selection = (ExcelReference)XlCall.Excel(XlCall.xlfSelection); // Get the value of the selection object selectionContent = selection.GetValue(); if (selectionContent is object[,]) { object[,] values = (object[,])selectionContent; int rows = values.GetLength(0); int cols = values.GetLength(1); result = new object[rows,cols]; // Process the values for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (values[i,j] is double) { double val = (double)values[i,j]; result[i,j] = val * val; } else { result[i,j] = values[i,j]; } } } } else if (selectionContent is double) { double value = (double)selectionContent; result = new object[,] {{value * value}}; } else { result = new object[,] {{"Selection was not a range or a number, but " + selectionContent.ToString()}}; } // Now create the target reference that will refer to Sheet 2, getting a reference that contains the SheetId first ExcelReference sheet2 = (ExcelReference)XlCall.Excel(XlCall.xlSheetId, "Sheet2"); // Throws exception if no Sheet2 exists // ... then creating the reference with the right size as new ExcelReference(RowFirst, RowLast, ColFirst, ColLast, SheetId) int resultRows = result.GetLength(0); int resultCols = result.GetLength(1); ExcelReference target = new ExcelReference(0, resultRows-1, 0, resultCols-1, sheet2.SheetId); // Finally setting the result into the target range. target.SetValue(result); } } 

除了Mike Rosenblum关于数组使用的评论之外,我想补充一点,我一直在使用这种方法(VSTO +数组),当我测量它时,实际的读取速度本身在几毫秒之内。 只要记住在读/写之前禁用事件处理和屏幕更新,并记住操作完成后重新启用。

使用C#,您可以创build与Excel VBA完全相同的基于1的数组。 这非常有用,特别是因为即使在VSTO中,当从Excel.Range对象中提取数组时,该数组也是基于1的,所以保持面向Excel的数组1可以帮助您避免需要始终检查数组是基于一个或基于零的。 (如果数组中的列位置对您有意义,则不得不处理基于0的数组和基于1的数组可能是一个真正的痛苦)。

通常将Excel.Range读入一个数组看起来像这样:

 var myArray = (object[,])range.Value2; 

Mike Rosenblum的数组写的变体使用了一个基于1的数组,如下所示:

 int[] lowerBounds = new int[]{ 1, 1 }; int[] lengths = new int[] { rowCount, columnCount }; var myArray = (object[,])Array.CreateInstance(typeof(object), lengths, lowerBounds); var dataRange = GetRangeFromMySources(); // this example is a bit too atomic; you probably want to disable // screen updates and events a bit higher up in the call stack... dataRange.Application.ScreenUpdating = false; dataRange.Application.EnableEvents = false; dataRange = dataRange.get_Resize(rowCount, columnCount); dataRange.set_Value(Excel.XlRangeValueDataType.xlRangeValueDefault, myArray); dataRange.Application.ScreenUpdating = true; dataRange.Application.EnableEvents = true; 

Excel数据最快的接口是C API。 有很多产品使用这个接口将.NET链接到Excel。

我喜欢这样做的2个产品是Excel DNA(这是免费和开源的)和Addin Express(这是一个商业产品,同时具有C API和COM接口)。

首先,您的解决scheme不能是Excel UDF(用户定义的函数)。 在我们的手册中,我们给出如下定义:“Excel UDF用于在Excel中构build自定义函数,供最终用户在公式中使用它们。 我不介意如果你提出一个更好的定义:)

该定义显示,UDF不能向UI添加button(我知道XLL可以修改CommandBar UI)或拦截键盘快捷键以及Excel事件。

也就是说,ExcelDNA超出了范围,因为它旨在开发XLL加载项。 这同样适用于Add-in Express的针对Excel的function,因为它允许开发XLL加载项和Excel自动化加载项。

因为您需要处理Excel事件,所以您的解决scheme可以是独立的应用程序,但这种方法存在明显的局限性。 唯一真正的方法是创build一个COM加载项; 它允许处理Excel事件并将自定义事件添加到Excel UI。 你有三种可能性:

  • VSTO
  • 加载项快速(COM加载项function)
  • 共享加载项(请参阅VS中“新build项目”对话框中的相应项目)

如果谈论开发一个Excel COM插件,上面提到的3个工具提供了不同的function:可视化devise者,匀场等。但是我不认为他们访问Excel对象模型的速度不同。 说,我不知道(也不能想象)为什么从默认AppDomain获取COM对象应该从另一个AppDomain获取相同的COM对象不同。 顺便说一句,您可以通过创build一个共享加载项,然后使用COM Shim向导来填充它来检查匀场是否影响操作的速度。

速度II。 正如我昨天写给你的:“加速读取和写入一系列单元格的最好方法是创build一个引用该范围的Excel.Rangetypes的variables,然后从/向Value属性读取/写入数组的variables“。 但是与弗朗西斯科所说的相反,我不认为这是VSTO; 这是Excel对象模型的一个function。

速度三。 最快的Excel UDF是用本地C ++编写的,而不是用任何.NET语言编写的。 我没有比较由ExcelDNA和Add-in Express生成的XLL插件的速度; 我不认为你会在这里发现任何实质性的差异。

总结一下。 我相信你错了:基于Add-in Express,VSTO或Shared Add-in的COM加载项应该以相同的速度读写Excel单元格。 如果有人反驳这个说法,我会很高兴(真诚的)。

现在你的其他问题。 VSTO不允许开发支持Office 2000-2010的COM加载项。 它需要三个不同的代码库和至less两个版本的Visual Studio才能完全支持Office 2003-2010; 您需要拥有强大的神经和部分好运来为Excel 2003部署基于VSTO的加载项。使用加载项快捷方式,可以为所有Office版本创build一个COM加载项,并使用一个代码库; Add-in Express为您提供了一个安装项目,可以在Excel 2000-2010(32位和64位)中安装您的加载项; ClickOnce部署也在船上。

VSTO可以在一个区域内快速添加插件:它允许创build所谓的文档级插件。 想象一个工作簿或模板,后面有一些.NET代码。 但是,如果部署这样的事情是一场噩梦,我不会感到惊讶。

在Excel事件上。 所有Excel事件都列在MSDN中,例如,请参阅Excel 2007事件

白俄罗斯(GMT + 2),

Andrei Smolinjoin快速组长

我已经使用VBA代码(macros)来收集和压缩数据,并获得这个数据在一个调用C#,反之亦然。 这可能是最高性能的方法。

使用C#,你将永远需要使用一些编组。 使用VSTO或COM Interop,下层通信层(编组开销)是相同的。

在VBA(Visual Basic For Application)中,您直接在Excel中的对象上工作。 所以访问这些数据总是会更快。

但是,一旦你有了C#中的数据,这个数据的操作可以快得多。

如果您使用的是VB6或C ++,则还需要通过COM接口,而且您还将面临跨进程编组。

所以你正在寻找一种方法来尽量减less跨进程调用和编组。