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

jw_*_*_pr 29 c# excel automation vsto com-interop

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

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

我知道使用VSTO说这是相对容易的,但我想知道最快(但仍然健壮和优雅)的解决方案是什么,并了解速度.我不介意解决方案是否建议使用第三方产品或使用C++.

显而易见的解决方案是使用VSTO或互操作,但我不知道与我目前用于读取数据的VBA或者是否有任何其他解决方案相比,性能是什么样的.

这是在专家交流中发布的,说VSTO比VBA慢得多,但那是几年前我不知道性能是否有所改善.

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

谢谢.

Gov*_*ert 40

我将把这作为一个挑战,并打赌在Excel和C#之间改变数据的最快方法是使用Excel-Dna - http://exceldna.codeplex.com.(免责声明:我开发了Excel-Dna.但它仍然是真的...)

因为它使用本机.xll接口,所以它会跳过您使用VSTO或其他基于COM的加载项方法所带来的所有COM集成开销.使用Excel-Dna,您可以创建一个连接到菜单或功能区按钮的宏,该按钮读取范围,处理它,并将其写回Excel中的范围.所有这些都使用C#的原生Excel界面 - 而不是COM对象.

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

我读了C#,处理并在一秒钟内写回一百万个单元格的Excel.这对你来说足够快吗?

我的功能看起来像这样:

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);
}
}
Run Code Online (Sandbox Code Playgroud)

  • +1(如果可以的话,我会投票给*10,我在一周前发现了Excel-DNA,看起来这正是我过去5年工作中缺少的软件) (4认同)

Mik*_*lum 38

如果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加载项在进程中运行,则会像VBA一样快地执行对Excel对象模型的调用,并且执行非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);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 嗨 Mike - 我想添加一个小的更正来阐明 Excel-Dna 实现:所有托管到 Excel 的胶水都在运行时使用反射从托管程序集工作 - 没有额外的预编译步骤或 C++ 代码生成。此外,即使 Excel-Dna 使用 .NET,在与 Excel 交谈时也不需要涉及任何 COM 互操作 - 作为 .xll,本机接口可以直接从 .NET 使用(尽管您也可以根据需要使用 COM)。这使得高性能 UDF 和宏成为可能。 (2认同)