Экспорт списка списков C # в Excel

Есть ли прямой способ экспортировать список списков (т.е. List<List<T>>) в Excel 2003 с помощью C #?

Я разбираю большие текстовые файлы и экспортирую в Excel. Запись по одной ячейке за раз создает слишком много накладных расходов. Я решил использовать List<T>, чтобы не беспокоиться об указании количества строк или столбцов.

В настоящее время я дожидаюсь конца файла, а затем помещаю содержимое моего List<List<object>> в двумерный массив. Затем массив можно установить как значение объекта Excel.Range. Это работает, но похоже, что я могу взять свой список списков, не беспокоясь о количестве строк или столбцов, и просто выгружать его на лист из A1 куда угодно.

Вот фрагмент кода, который я хотел бы заменить или улучшить:

object oOpt = System.Reflection.Missing.Value; //for optional arguments
Excel.Application oXL = new Excel.Application();
Excel.Workbooks oWBs = oXL.Workbooks;
Excel._Workbook oWB = oWBs.Add(Excel.XlWBATemplate.xlWBATWorksheet);
Excel._Worksheet oSheet = (Excel._Worksheet)oWB.ActiveSheet;

int numberOfRows = outputRows.Count;
int numberOfColumns = int.MinValue;

//outputRows is a List<List<object>>
foreach (List<object> outputColumns in outputRows)
{
        if (numberOfColumns < outputColumns.Count)
        { numberOfColumns = outputColumns.Count; }
}

Excel.Range oRng = oSheet.get_Range("A1", oSheet.Cells[numberOfRows,numberOfColumns]);

object[,] outputArray = new object[numberOfRows,numberOfColumns];

for (int row = 0; row < numberOfRows; row++)
{
        for (int col = 0; col < outputRows[row].Count; col++)
        {
                outputArray[row, col] = outputRows[row][col];
        }
}

oRng.set_Value(oOpt, outputArray);

oXL.Visible = true;
oXL.UserControl = true;

Это работает, но я бы предпочел использовать список непосредственно в Excel, чем выполнять промежуточный шаг по созданию массива только для Excel. Любые идеи?


person Matt    schedule 29.06.2009    source источник


Ответы (4)


Стратегически вы делаете это правильно. Как говорит Джо, гораздо быстрее выполнять присвоение значений ячейкам, передавая весь массив значений за один раз, а не просматривая ячейки одну за другой.

Excel основан на COM и поэтому работает с Excel через взаимодействие .NET. К сожалению, при взаимодействии не используются универсальные шаблоны, поэтому вы не можете передать ему список ‹T› или что-то подобное. Двумерный массив - единственный выход.

Тем не менее, есть несколько способов очистить код, чтобы сделать его более управляемым. Вот некоторые мысли:

(1) Если вы используете .NET 3.0, вы можете использовать LINQ для сокращения кода:

int numberOfColumns = int.MinValue;

foreach (List<object> outputColumns in outputRows)
{
        if (numberOfColumns < outputColumns.Count)
        { numberOfColumns = outputColumns.Count; }
}

в одну строку:

int numberOfColumns = outputRows.Max(list => list.Count);

(2) Не используйте интерфейсы _Worksheet или _Workbook. Используйте вместо этого Worksheet или Workbook. См. Обсуждение здесь: Взаимодействие с Excel: _Worksheet или Worksheet?.

(3) Рассмотрите возможность использования метода Range.Resize, который в C # обозначается как Range.get_Resize. Однако это подбрасывание - мне действительно нравится, как вы устанавливаете размер диапазона. Но я подумал, что это то, о чем вы, возможно, захотите узнать. Например, ваша строка здесь:

Excel.Range oRng = oSheet.get_Range("A1", oSheet.Cells[numberOfRows,numberOfColumns]);

Может быть изменено на:

Excel.Range oRng = 
    oSheet.get_Range("A1", Type.Missing)
        .get_Resize(numberOfRows, numberOfColumns);

(4) Вам не нужно устанавливать Application.UserControl на true. Достаточно сделать Excel видимым для пользователя. Свойство UserControl не делает то, что вы думаете. (См. Файлы справки здесь). Пользователь может управлять Excel или нет, вам следует использовать защиту рабочего листа или можно установить Application.Interactive = false, если вы хотите заблокировать своих пользователей. (Редко бывает хорошей идеей.) Но если вы хотите разрешить пользователю использовать Excel, достаточно просто сделать его видимым.

В целом, имея это в виду, я думаю, что ваш код может выглядеть примерно так:

object oOpt = System.Reflection.Missing.Value; //for optional arguments
Excel.Application oXL = new Excel.Application();
Excel.Workbooks oWBs = oXL.Workbooks;
Excel.Workbook oWB = oWBs.Add(Excel.XlWBATemplate.xlWBATWorksheet);
Excel.Worksheet oSheet = (Excel.Worksheet)oWB.ActiveSheet;

//outputRows is a List<List<object>>
int numberOfRows = outputRows.Count;
int numberOfColumns = outputRows.Max(list => list.Count);

Excel.Range oRng = 
    oSheet.get_Range("A1", oOpt)
        .get_Resize(numberOfRows, numberOfColumns);

object[,] outputArray = new object[numberOfRows, numberOfColumns];

for (int row = 0; row < numberOfRows; row++)
{
    for (int col = 0; col < outputRows[row].Count; col++)
    {
        outputArray[row, col] = outputRows[row][col];
    }
}

oRng.set_Value(oOpt, outputArray);

oXL.Visible = true;

Надеюсь это поможет...

Майк

person Mike Rosenblum    schedule 29.06.2009
comment
Спасибо! Хотя мы не на .NET 3.0, я ценю все ваши разъяснения. - person Matt; 15.07.2009
comment
Нет проблем, рад, что помог. :-) Только пункт №1 имел отношение к .NET 3.0, все остальные применимы в любой .NET Framework. LINQ - основная причина рассмотреть возможность перехода на 3.0. Это не революционизирует весь ваш код, но в некоторых областях LINQ - невероятно приятная функция. - person Mike Rosenblum; 16.07.2009
comment
Спасибо! Это сработало отлично, однозначно положительный отзыв! Однако одна маленькая деталь, которая может помочь; Мне пришлось добавить в этот код предложение oXL.Quit;, потому что в противном случае процессы продолжали работать, и в определенный момент я начал получать ошибки. - person Soph; 12.01.2012
comment
Хотел бы я проголосовать за этот ответ сто раз :) - person ; 12.03.2014

Намного быстрее передать двумерный массив в Excel, чем обновлять ячейки по одной.

Создайте двумерный массив объектов со значениями из вашего списка списков, измените размер диапазона Excel на размеры вашего массива, а затем вызовите range.set_Value, передав двумерный массив.

person Joe    schedule 29.06.2009
comment
Это то, что я делаю сейчас; но казалось, что если бы я собирался использовать List<T> вместо массива в первую очередь, мог бы существовать другой способ получить данные в Excel, не загружая их все сначала в промежуточный массив. - person Matt; 30.06.2009

List<"classname"> getreport = cs.getcompletionreport();

var getreported = getreport .Select(c => new { demographic = c.rName);

надеюсь, что это поможет

где cs.getcompletionreport() - файл эталонного класса - это бизнес-уровень для приложения.

person Rizwan Patel    schedule 05.11.2012

Для будущих искателей: это правда, что если вы хотите скопировать список, чтобы преуспеть, вам нужно вставить object[,] (2-мерный). На всякий случай, если у кого-то есть одномерный список, вы можете запустить только один for loop со вторым размером 1:

List<DateTime> listDate; //I'm filling it with data from datagridview
...
object[,] outDate = new object[listDate.Count, 1];
for(int row = 0; row < listDate.Count; row++)
{
    outDate[row, 0] = listDate[row];
}
person Stana Macala    schedule 16.11.2018