Подтвердить что ты не робот

Самый быстрый способ взаимодействия между живыми (несохраненными) данными Excel и объектами С#

Я хочу знать, что самый быстрый способ - читать и записывать данные в открытую книгу Excel и открывать ее с помощью объектов С#. Фон заключается в том, что я хочу разработать приложение С#, которое используется в Excel, и использует данные, хранящиеся в excel.

Бизнес-логика будет находиться в приложении С#, но данные будут находиться в книге Excel. Пользователь будет использовать Excel и щелкнет кнопкой (или сделает что-то подобное) в книге excel, чтобы инициировать приложение С#. Приложение С# затем считывает данные из рабочей книги Excel, обрабатывает данные, а затем записывает данные в книгу Excel.
Могут быть многочисленные блоки данных, которые должны быть считаны и записаны в книгу Excel, но они обычно будут иметь относительно небольшой размер, например 10 строк и 20 столбцов. Иногда может потребоваться обработка большого списка данных порядка 50 000 строк и 40 столбцов.

Я знаю, что это относительно легко сделать, используя VSTO, но я хочу знать, что самое быстрое (но все же надежное и элегантное) решение и получить представление о скорости. Я не против, если решение рекомендует использовать сторонние продукты или использует С++.

Очевидным решением является использование VSTO или interop, но я не знаю, что такое производительность по сравнению с VBA, которую я сейчас использую для чтения в данных, или если есть какие-либо другие решения.

Это было опубликовано на обмене экспертов, в котором говорилось, что VSTO был значительно медленнее, чем VBA, но это было пару лет назад, и я не знаю, улучшилась ли производительность.

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

Спасибо.

4b9b3361

Ответ 1

Если приложение С# является автономным приложением, то всегда будет задействован кросс-процесс, который будет перегружать любые оптимизации, которые вы можете сделать, переключая языки, например, с С# на С++. Придерживайтесь своего наиболее предпочтительного языка в этой ситуации, который звучит как С#.

Если вы хотите сделать надстройку, которая работает в Excel, то ваши действия позволят избежать межпроцессных вызовов и работать примерно на 50 раз быстрее.

Если вы работаете в Excel в качестве надстройки, VBA является одним из самых быстрых параметров, но он все еще связан с COM, поэтому вызовы С++ с использованием надстройки XLL будут самыми быстрыми. Но VBA все еще довольно быстр в плане вызовов объектной модели Excel. Тем не менее, что касается фактической скорости вычислений, VBA работает как pcode, а не как полностью скомпилированный код, и поэтому выполняется примерно на 2-3 раза медленнее, чем собственный код. Это звучит очень плохо, но дело не в том, что подавляющее большинство времени выполнения, взятое с типичной надстройкой или приложением Excel, связано с вызовами объектной модели Excel, поэтому VBA против полностью скомпилированного надстройки COM, скажем, используя изначально составленный VB 6.0, будет примерно на 5-15% медленнее, что не заметно.

VB 6.0 - это скомпилированный COM-подход и выполняется на 2-3 раза быстрее, чем VBA для вызовов, не связанных с Excel, но VB 6.0 составляет около 12 лет на данный момент и не будет работать в режиме 64 бит, скажем, если установка Office 2010, который можно установить для запуска 32-битного или 64-битного. Использование 64-битного Excel в настоящее время крошечное, но будет расти в использовании, и поэтому я бы избегал VB 6.0 по этой причине.

С#, если в процессе работы в качестве надстройки Excel будут выполняться вызовы объектной модели Excel так же быстро, как VBA, и выполнять вызовы не-Excel на 2-3 раза быстрее, чем VBA, - если они выполняются без ошибок. Однако подход, рекомендованный Microsoft, заключается в том, чтобы полностью выполнить подгонку, например, с помощью COM Shim Wizard. Благодаря тому, что Excel защищен от вашего кода (если он неисправен), и ваш код полностью защищен от других сторонних надстроек, которые в противном случае могли бы вызвать проблемы. Тем не менее, нижняя сторона этого заключается в том, что исправленное решение работает в пределах отдельного AppDomain, для чего требуется кросс-админ-марширование, которое приводит к штрафу за скорость выполнения около 40 раз, что очень заметно во многих контекстах.

Надстройки с использованием Visual Studio Tools for Office (VSTO) автоматически загружаются в прокладку и выполняются в пределах отдельного AppDomain. Этого нельзя избежать, если использовать VSTO. Таким образом, вызовы в объектную модель Excel также будут приводить к ухудшению скорости выполнения примерно 40 раз. VSTO - великолепная система для создания очень богатых надстроек Excel, но скорость выполнения - это ее слабость для таких приложений, как ваша.

ExcelDna - это бесплатный проект с открытым исходным кодом, который позволяет вам использовать код С#, который затем преобразуется для вас в надстройку XLL, которая использует код С++. То есть ExcelDna анализирует ваш код на С# и создает для вас необходимый код С++. Я не использовал его сам, но я знаком с процессом, и он очень впечатляет. ExcelDna получает очень хорошие отзывы от тех, кто ее использует. [Изменить: Обратите внимание на следующую коррекцию в соответствии с комментариями Govert ниже: "Привет, Майк. Я хочу добавить небольшую поправку, чтобы прояснить реализацию Excel-Dna: весь склейка с управляемым в Excel работает во время выполнения из вашей управляемой сборки с использованием отражения - там не является дополнительным этапом предварительной компиляции или генерации кода на С++. Кроме того, несмотря на то, что Excel-Dna использует .NET, не требуется никакого взаимодействия с COM при разговоре с Excel - в качестве .xll собственный интерфейс может использоваться непосредственно из .NET. (хотя вы также можете использовать COM, если хотите). Это позволяет использовать высокопроизводительные UDF и макросы". - Говерт]

Вы также можете посмотреть надстройку Express. Это не бесплатно, но это позволит вам вводить код на С#, и хотя он затягивает ваше решение в отдельном приложении AppDomain, я считаю, что его скорость исполнения невыполнима. Если я правильно понимаю скорость выполнения, то я не уверен, как это работает надстройкой Express, но он может использовать что-то, называемое FastPath AppDomain marshaling. Однако не цитируйте меня ни по одному из них, поскольку я не очень хорошо знаком с Add-in Express. Вы должны проверить это и провести собственное исследование. [Редактировать: читайте ответ Чарльза Уильямса, похоже, что Add-in Express разрешает доступ к COM и C API. И Говерт утверждает, что ДНК Excel также позволяет использовать как COM, так и быстрый доступ к C API. Поэтому вы, вероятно, захотите проверить оба варианта и сравнить их с ExcelDna.]

Мой совет - изучить надстройку Express и ExcelDna. Оба подхода позволят вам использовать код С#, который вам кажется наиболее знакомым.

Другой основной проблемой является то, как вы делаете свои звонки. Например, 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 для считывания массива значений из диапазона за один шаг. Выполнение этого, а затем циклическое перемещение значений внутри массива значительно быстрее, чем циклическое изменение значений внутри ячеек диапазона.

Ответ 2

Я возьму это как вызов и постараюсь, что самый быстрый способ перетасовать ваши данные между Excel и С# - использовать Excel-Dna - http://exceldna.codeplex.com. (Отказ от ответственности: я разрабатываю Excel-Dna, но это все еще правда...)

Поскольку он использует собственный интерфейс .xll, он пропускает все служебные издержки интеграции COM, которые у вас были бы с VSTO или другим подходом на основе COM. С помощью Excel-Dna вы можете создать макрос, который подключается к кнопке меню или ленты, которая считывает диапазон, обрабатывает его и записывает обратно в диапазон в Excel. Все с использованием собственного интерфейса Excel из С# - не COM-объект в поле зрения.

Я сделал небольшую тестовую функцию, которая принимает текущий выбор в массив, разбивает каждое число в массиве и записывает результат в Лист 2, начиная с ячейки A1. Вам просто нужно добавить (бесплатную) рабочую среду Excel-Dna, которую вы можете скачать из http://exceldna.codeplex.com.

Я читаю в С#, обрабатываю и записываю обратно в 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);
}
}

Ответ 3

Далее, Майк Розенблюм комментирует использование массивов, я хотел бы добавить, что я использовал самый подход (массивы VSTO +), и когда я его измерил, фактическая скорость чтения была в миллисекундах. Просто не забудьте отключить обработку событий и обновление экрана до чтения/записи и не забудьте снова включить после завершения операции.

Используя С#, вы можете создавать массивы на 1 основе точно так же, как и сам VBA Excel. Это очень полезно, особенно потому, что даже в VSTO, когда вы извлекаете массив из объекта Excel.Range, массив основан на 1, поэтому сохранение ориентированных на Excel массивов на основе 1 помогает избежать необходимости всегда проверять, массив основан на базе одного или на основе нуля. (Если позиция столбца в массиве имеет значение для вас, иметь дело с 0-based и 1-based массивами может быть реальной болью).

Обычно чтение Excel.Range в массив будет выглядеть примерно так:

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


Моя вариация Mike Rosenblum array-write использует массив на основе 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;

Ответ 4

Самый быстрый интерфейс для данных Excel - это API C. Существует ряд продуктов, которые связывают .NET с Excel, используя этот интерфейс.

2 продуктов Мне нравится, что это Excel DNA (который является свободным и открытым исходным кодом) и Addin Express (который является коммерческим продуктом и имеет как C API, так и COM-интерфейс).

Ответ 5

Во-первых, ваше решение не может быть Excel UDF (пользовательская функция). В наших руководствах мы приводим следующее определение: "Excel UDF используются для создания пользовательских функций в Excel для конечного пользователя, чтобы использовать их в формулах". Я не против, если вы предложите лучшее определение:)

Это определение показывает, что UDF не может добавить кнопку в пользовательский интерфейс (я знаю, что XLL могут изменять интерфейс CommandBar) или перехватывать сочетания клавиш, а также события Excel.

То есть, ExcelDNA выходит за рамки, потому что она предназначена для разработки надстроек XLL. То же самое относится к функциональности надстройки Excel, ориентированной на Excel, поскольку она позволяет разрабатывать надстройки XLL и надстройки Excel Automation.

Поскольку вам нужно обрабатывать события Excel, ваше решение может быть автономным приложением, но есть очевидные ограничения такого подхода. Единственный реальный способ - создать надстройку COM; он позволяет обрабатывать события Excel и добавлять пользовательские элементы в интерфейс Excel. У вас есть три возможности:

  • VSTO
  • Надстройка Express (функция надстройки COM)
  • Общая надстройка (см. соответствующий элемент в диалоговом окне "Новый проект" в VS)

Если говорить о разработке надстройки Excel COM, эти три средства предоставляют различные функции: визуальные дизайнеры, shimming и т.д. Но я не думаю, что они отличаются скоростью доступа к объектной модели Excel. Скажем, я не знаю (и не могу себе представить), почему получение COM-объекта из AppDomain по умолчанию должно отличаться от получения того же COM-объекта из другого AppDomain. BTW, вы можете проверить, влияет ли регулировка скорости на скорость работы, создав общую надстройку, а затем с помощью мастера COM Shim Wizard, чтобы подделать ее.

Скорость II. Как я писал вам вчера: "Лучший способ ускорить чтение и запись в ряд ячеек - создать переменную типа Excel.Range, относящуюся к этому диапазону, а затем прочитать/записать массив из/в свойство Value от переменной". Но, вопреки тому, что говорит Франческо, я не отношу это к VSTO; это особенность объектной модели Excel.

Скорость III. Самые быстрые Excel UDF написаны на родном С++, а не на любом языке .NET. Я не сравнивал скорость надстройки XLL, созданной ExcelDNA и Add-in Express; Я не думаю, что вы найдете здесь существенную разницу.

Подводя итог. Я убежден, что вы ошибаетесь: надстройки COM, основанные на надстройке Express, VSTO или общей надстройке, должны читать и записывать ячейки Excel с одинаковой скоростью. Я буду рад (искренне), если кто-то опровергнет это утверждение.

Теперь по твоим другим вопросам. VSTO не позволяет разрабатывать надстройку COM, поддерживающую Office 2000-2010. Он требует трех разных кодовых баз и по крайней мере двух версий Visual Studio для полностью поддержки Office 2003-2010; вам нужно иметь сильные нервы и часть удачи для развертывания надстройки на основе VSTO для Excel 2003. С помощью Add-in Express вы создаете надстройку COM для всех версий Office с единой кодовой базой; Add-in Express предоставляет вам проект настройки, который готов для установки надстройки в Excel 2000-2010 (32-разрядная и 64-разрядная); Кроме того, развертывание ClickOnce также находится на борту.

VSTO превосходит надстройку Express в одной области: позволяет создавать так называемые надстройки уровня документа. Представьте себе книгу или шаблон с некоторым кодом .NET за ним; Однако я не удивлюсь, если развертывание таких вещей будет кошмаром.

В событиях Excel. Все события Excel перечислены в MSDN, например, см. События Excel 2007

Относительно Беларуси (GMT + 2),

Андрей Смолин Руководитель группы надстроек Add-in

Ответ 6

Я использовал код VBA (макрос) для сбора и уплотнения данных и получения этих данных одним вызовом на С# и наоборот. Это, вероятно, будет наиболее эффективным подходом.

Используя С#, вам всегда нужно будет использовать некоторую сортировку. Используя VSTO или COM Interop, уровень подстилающей связи (сортировка служебных данных) одинаков.

В VBA (Visual Basic для приложения) вы работаете непосредственно с объектами в Excel. Таким образом, доступ к этим данным будет всегда быстрее.

Но... Как только у вас есть данные на С#, манипулирование этими данными может быть намного быстрее.

Если вы используете VB6 или С++, вы также проходите через COM-интерфейс, и вы также столкнетесь с перекрестным процессом сортировки.

Итак, вы ищете способ минимизации перекрестных вызовов процессов и сортировки.