在活动(未保存)Excel数据和C#对象之间建立接口的最快方式

在活动(未保存)Excel数据和C#对象之间建立接口的最快方式,c#,excel,automation,vsto,com-interop,C#,Excel,Automation,Vsto,Com Interop,我想知道在打开的Excel工作簿和c#对象之间读写数据的最快方法是什么。背景是我想开发一个c#应用程序,它使用Excel中的数据,并使用Excel中保存的数据 业务逻辑将驻留在c#应用程序中,但数据将驻留在Excel工作簿中。用户将使用Excel并单击Excel工作簿上的按钮(或执行类似操作)以启动c#应用程序。然后,c#应用程序将从excel工作簿中读取数据,处理数据,然后将数据写回excel工作簿。 可能有许多数据块需要读取并写回excel工作簿,但通常它们的大小相对较小,例如10行20列。

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

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

我知道使用VSTO做这件事相对容易,但我想知道什么是最快的(但仍然是健壮和优雅的)解决方案,并了解速度。我不介意这个解决方案推荐使用第三方产品或者使用C++。 显而易见的解决方案是使用VSTO或interop,但我不知道与我当前用于读取数据的VBA相比,性能如何,或者是否有其他解决方案

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

谢谢。

我已经使用VBA代码(宏)来收集和压缩数据,并在一次调用C#中获取这些数据,反之亦然。这可能是最有效的方法

使用C#,您将始终需要使用一些编组。使用VSTO或COM互操作,底层通信层(编组开销)是相同的

在VBA(Visual Basic For Application)中,可以直接处理Excel中的对象。因此,访问这些数据的速度将始终更快

但是。。。。一旦你有了C#中的数据,对这些数据的操作就会快得多

如果使用VB6或C++,则还将通过COM接口,也将面临跨进程编组。


因此,您正在寻找一种最小化跨进程调用和编组的方法。

Excel数据的最快接口是C API。有许多产品使用此界面将.NET链接到Excel

我喜欢的两种产品是Excel DNA(免费开源)和Addin Express(这是一种商业产品,具有C API和COM接口)。

如果C#应用程序是一个独立的应用程序,然后,您将总是有跨进程封送,这将压倒任何优化,可以通过切换语言,如C语言到C++。在这种情况下,坚持使用你最喜欢的语言,听起来像是C

但是,如果您愿意制作一个在Excel中运行的外接程序,那么您的操作将避免跨进程调用,并且运行速度将提高约50倍

如果你在Excel中运行作为一个外接程序,那么VBA是最快的选项之一,但它仍然涉及COM,因此使用XLL插件的C++调用将是最快的。但是VBA在调用Excel对象模型方面仍然相当快。然而,就实际计算速度而言,VBA作为pcode运行,而不是完全编译的代码,因此执行速度比本机代码慢2-3倍。这听起来很糟糕,但这并不是因为典型的Excel加载项或应用程序所花费的绝大多数执行时间都涉及到对Excel对象模型的调用,因此VBA与完全编译的COM加载项相比,比如说使用本机编译的VB6.0,只会慢5-15%,这是不明显的

VB6.0是一种编译的COM方法,对于与Excel无关的调用,其运行速度比VBA快2-3倍,但目前VB6.0已经有12年的历史,不会以64位模式运行,比如说安装Office 2010,它可以安装为运行32位或64位。目前64位Excel的使用量很小,但会随着使用量的增加而增加,因此出于这个原因,我会避免使用VB6.0

C#,如果以Excel外接程序的形式在进程中运行,则执行对Excel对象模型的调用的速度将与VBA一样快,而执行非Excel调用的速度将比VBA快2-3倍——如果以非剪切方式运行。然而,微软推荐的方法是运行完全填充,例如,通过使用。通过填充,Excel可以保护您的代码不受影响(如果有错误),并且您的代码完全可以保护您的代码不受其他可能导致问题的第三方加载项的影响。但是,缺点是,填充解决方案在单独的AppDomain中运行,这需要跨AppDomain封送,这会导致执行速度降低约40倍,这在许多上下文中都非常明显

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


ExelDDNA是一个免费的开源项目,允许使用C代码,然后将代码转换成使用C++代码的XLL插件。也就是说,Excel分析你的C代码,并为你创建所需的C++代码。我自己没有用过,但我对这个过程很熟悉,给人留下了深刻的印象。ExcelDna从使用它的人那里得到了很好的评价。[编辑:根据下面Govert的评论,注意以下更正:“嗨,Mike-我想添加一个小的更正,以澄清Excel Dna实现:所有托管到Excel的粘合都在运行时使用反射-t从托管程序集工作

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);
}
var myArray = (object[,])range.Value2;
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;
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);
}
}