小编典典

在实时(未保存)Excel数据和C#对象之间进行交互的最快方法

c#

我想知道最快的方法是在打开的Excel工作簿和c#对象之间读写数据。背景是我要开发从Excel使用的ac#应用程序,并使用excel中保存的数据。

业务逻辑将驻留在c#应用程序中,而数据将驻留在Excel工作簿中。用户将使用Excel,并在excel工作簿上单击一个按钮(或执行类似操作)以启动c#应用程序。然后,C#应用程序将从Excel工作簿中读取数据,处理数据,然后将数据写回到Excel工作簿中。
可能需要读取并写回excel工作簿的数据块很多,但通常它们的大小相对较小,例如10行20列。有时可能需要处理大量数据,大约50,000行40列。

我知道使用VSTO相对容易做到这一点,但是我想知道最快(但仍然健壮且优雅)的解决方案是什么,并对速度有所了解。我不介意该解决方案建议使用第三方产品还是使用C
++。

显而易见的解决方案是使用VSTO或interop,但我不知道与我当前用于读取数据的VBA相比,性能如何,或者是否还有其他解决方案。

专家交流说,VSTO比VBA慢得多,但这是几年前的事,我不知道性能是否有所提高。

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

谢谢。


阅读 477

收藏
2020-05-19

共1个答案

小编典典

如果C#应用程序是独立的应用程序,那么您将始终涉及跨进程封送处理,这将使您无法将语言从C#切换为C
++,从而无法进行任何优化。在这种情况下,请坚持使用您最喜欢的语言,这听起来像是C#。

但是,如果您愿意制作一个 Excel 中运行的加载项,则您的操作将避免跨进程调用,并且运行速度快约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方法,与非Excel相关的调用相比,运行速度比VBA快2-3倍,但是此时VB
6.0已使用了12年左右,并且无法在64位模式下运行,例如,如果安装Office
2010,可以安装运行32位或64位。目前64位Excel的使用量很小,但使用量会增加,因此出于这个原因,我将避免使用VB 6.0。

C#如果以Excel加载项的形式在进程内运行,则对Excel对象模型的调用将以VBA一样快的速度执行,并且非Excel调用的执行速度比VBA快2-3倍-如果运行不带垫片。但是,Microsoft建议的方法是完全匀场运行,例如,通过使用COM
Shim向导
。通过填充,可以保护Excel免受代码(如果有错误)的侵害,并且可以完全保护您的代码不受其他第三方加载项的影响,否则可能会导致问题。但是,不利的一面是,匀场解决方案在单独的AppDomain中运行,这需要跨AppDomain封送处理,这会导致执行速度损失约40倍-在许多情况下这非常明显。

使用Visual Studio Tools for
Office(VSTO)的加载项将自动加载到填充程序中,并在单独的AppDomain中执行。如果使用VSTO,这是不可避免的。因此,对Excel对象模型的调用也将导致执行速度下降大约40倍。VSTO是用于生成非常丰富的Excel加载项的出色系统,但是执行速度是它对诸如您的应用程序的缺点。

ExcelDna是一个免费的开源项目,允许您使用C#代码,然后将其转换为使用C
代码的XLL加载项。也就是说,ExcelDna解析您的C#代码并为您创建所需的C
代码。我自己没有使用过它,但是我熟悉该过程,并且给人留下了深刻的印象。ExcelDna受到使用它的人的好评。
[编辑:请按照以下Govert的注释进行以下更正:“嗨,迈克-我想添加一个小更正以阐明Excel-
Dna的实现:所有托管到Excel胶水都可以在运行时使用反射在托管程序集中运行-不需要额外的预编译步骤或C ++代码生成;而且,即使Excel-
Dna使用.NET,在与Excel通话时也无需涉及任何COM互操作-
作为.xll,可以直接从.NET使用本机接口(尽管您也可以根据需要使用COM。)这使高性能的UDF和宏成为可能。” – Govert]

您可能还需要查看Add-in
Express。它不是免费的,但是它允许您使用C#进行编码,尽管它可以将解决方案填充到单独的AppDomain中,但我认为它的执行速度非常出色。如果我正确地理解了它的执行速度,那么我不确定Add-
in Express是如何做到这一点的,但是它可能正在利用称为FastPath AppDomain封送处理的优势。但是,由于我对Add-in
Express不太熟悉,所以请不要对我进行任何引用。不过,您应该检查一下并进行自己的研究。 [编辑:阅读Charles
Williams的答案,看来Add-in Express启用了COM和C API访问。 Govert指出,Excel DNA还支持COM和更快的C
API访问。因此,您可能希望同时检查两者并将它们与ExcelDna进行比较。]

我的建议是研究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访问器方法一步来读取范围中的值数组。这样做然后循环遍历数组中的值比循环遍历范围内单元格中的值要快得多。

2020-05-19