среда, 20 мая 2009 г.

Работа с Excel из C#.

Столкнулся с необходимостью поработать с файлами Excel из C#. Задачи простые - считать данные из книги Excel, создать новую книгу Excel, внести в нее данные. Предполагается, что Excel на компьютере имеется, но какой версии, не известно. Файлы по размерам маленькие, скорость работы не критична.

Существует несколько вариантов работы с Excel из C#: автоматизация Excel, подключение через OleDB/ODBC, дополнительные библиотеки (Aspose Excel), работа через XML, через Open XML и т.п.

Наиболее простой вариант - воспользоваться автоматизацией Excel. Да, скорость работы - не блеск. Зато удобно использовать, код пишется быстро, объемы кода не велики. Из .NET автоматизация подключается парой кликов мыши - добавил в References сборку Microsoft.Office.Interop.Excel - и работай с привычными com-объектами Application,Workbook, Worksheet и т.п.

Проблема одна - сборки "Microsoft.Office.Interop.Excel" для каждой версии Excel разные. У меня установлен office 2003 и, соответственно, interop-сборка версии 11. А что делать, если мне нужно разработать приложение, которое может работать с Excel 97? Или с любыми версиями Excel? Для того, чтобы считать или записать пару значений в ячейки листа Excel сложного api не требуется - любой Excel сгодится. А мне приходится привязываться к конкретной версии.

Можно воспользоваться поздним связыванием. Но если его использовать "в лоб", то ни о никаком удобстве работы речи уже не идет - код станет сложным, а вызовы методов - косвенными, нетипизированными. Ошибок будет - вагон.

Выход нашелся. На СodeProject обнаружил замечательную статью "SafeCOMWrapper - Managed Disposable Strongly Typed safe wrapper to late bound COM". В ней изложена элегантная методика использования позднего связывания, устраняющая все проблемы: связывание становится поздним (привязки к конкретной версии Excel нет), для объектов автоматизации автоматически реализуется шаблон IDisposable (отработанные объекты уничтожаются автоматически), все вызовы методов явные.

Идея реализации, вкратце, следующая. Для каждого COM-объекта автоматизации вы прописываете отдельный интерфейс. В интерфейс включаете все методы и свойства, которые вам необходимо использовать у этого COM-объекта. Обратите внимание - только те, которые необходимо использовать, а вовсе не все, реализуемые COM-объектом.

Каждый COM-объект автоматизации "заворачивается" в класс COMWrapper, унаследованный от RealProxy. RealProxy - это стандартный класс, позволяющий организовать перехват вызовов методов. При создании COM-объекта вы создаете экземпляр COMWrapper и указываете требуемый вам интерфейс. Среда динамически генерирует прокси-объект, реализующий этот интерфейс. С этим прокси-объектом вы в дальнейшем и работаете как с объектом автоматизации. Вызов любого метода прокси-объетка перехватывается и транслируется в вызов метода Invoke класса RealProxy, перекрытый в классе COMWrapper. Здесь его можно обработать как душе угодно. В реализации по умолчанию, вызовы свойств транслируются в вызовы соответствующих методов get_ и set_, создаваемых .NET, возвращаемые объекты автоматизации автоматически заворачиваются в COMWrapper и т.п.

В оригинально статье приведен пример использования данной методики для Microsoft.Outlook. Я, на базе приведенных исходных кодов, адаптировал методику для работы с Microsoft.Excel. Естественно, реализовав интерфейсы только для тех объектов автоматизации, которые потребовались мне для работы. Дополнить интерфейсы недостающими методами или добавить интерфейсы для других объектов автоматизации не составляет труда. Достаточно посмотреть через Object Browser метаданные сборки "Microsoft.Office.Interop.Excel" и скопировать оттуда необходимые объявления методов и интерфейсов (с минимальной адаптацией).

Пример кода для работы с Excel:
using (Application app = ExcelApplication.Create()) {
String s = app.Version;
app.Visible = true; // make excel visible
using (Workbook wb = app.Workbooks.Add(Type.Missing)) {
wb.Title = "new workbook";
using (Worksheets worksheets = wb.Sheets) {
using (Worksheet ws = worksheets[1]) {
//try to assign some values to some cells
using (Range cells = ws.Cells) {
for (int i = 1; i < 10; ++i) {
using (Range r = cells[i, i]) {
r.Value = i * i;
}
}
}
}
}
wb.Saved = true;
wb.Close();
}
app.Quit();
}
Оригинальные исходные коды лежат на codeproject вместе со статьей. Исходные коды, адаптированные для Microsoft Excel, можно взять здесь.Update. Практика использования выявила небольшую проблему - забыть вызвать Dispose у оберток объектов Excel очень легко. После чего Excel висит в памяти до тех пор, пока не закроешь приложение (или до следующей сборки мусора). Пример:
using (Workbook wb = app.Workbooks.Add(Type.Missing)) {
using (Worksheet ws = wb.Sheets[1]) 
/*Здесь потерян Dispose у объекта Worksheets, который создается при 
вызове wb.Sheets*/
{ .... }}
вместо
using (Workbook wb = app.Workbooks.Add(Type.Missing)) {
using (Worksheets worksheets = wb.Sheets) {
using (Worksheet ws = worksheets[1]) {
.... }}

Просто надо быть внимательнее.

понедельник, 4 мая 2009 г.

Ссылки: доклад Авантюриста, переписка Стругацкого с Ходорковским

Стенограмма доклада Авантюриста о глобальном экономическом кризисе на "Большом кофе-брейке для бизнеса", в г. Томске, 27 марта 2009 года. Вот здесь обработанные выдержки из доклада - уже более официальный источник. P.s.Похоже расшифровка выступления авантюриста изначально взята отсюда.

Во что вкладывать деньги? В профессию и в детей...

Переписка Бориса Стругацкого и Михаила Ходорковского в Новой газете:
письмо 1,
письмо 2,
письмо 3.