Самый быстрый способ взаимодействия между живыми (несохраненными) данными 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
Спасибо.
Ответы
Ответ 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-интерфейс, и вы также столкнетесь с перекрестным процессом сортировки.
Итак, вы ищете способ минимизации перекрестных вызовов процессов и сортировки.