Было опубликовано много хороших ответов, но я хотел бы добавить еще один.
Не все числа могут быть представлены с помощью float / double. Например, будет представлено число «0,2» как «0.200000003» в одинарной точности в стандарте по плавающей точке IEEE754.
Модель для хранения действительных чисел под капотом представляет собой число с плавающей запятой в качестве
Хотя вы можете легко ввести 0.2
, FLT_RADIX
и DBL_RADIX
равно 2; не 10 для компьютера с FPU, который использует «Стандарт IEEE для двоичной арифметики с плавающей запятой (ISO / IEEE Std 754-1985)».
. Точно так же трудно точно представлять такие числа. Даже если вы укажете эту переменную явно без какого-либо промежуточного вычисления.
Простое правило: избегайте использования выражений с двумя точками, таких как:
var workbook = excel.Workbooks.Open(/*params*/)
... потому что таким образом вы создаете объекты RCW не только для workbook
, но для Workbooks
, и вы тоже должны его отпустить (что невозможно, если ссылка на объект не поддерживается).
Итак, правильный путь будет:
var workbooks = excel.Workbooks;
var workbook = workbooks.Open(/*params*/)
//business logic here
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excel);
В вашем коде у вас есть:
excel.Workbooks.Open(...)
excel.Workbooks
создает COM-объект. Затем вы вызываете функцию Open
из этого COM-объекта. Однако вы не освобождаете COM-объект, когда вы закончили.
Это обычная проблема при работе с COM-объектами. В принципе, в вашем выражении никогда не должно быть более одной точки, потому что вам нужно будет очистить COM-объекты, когда вы закончите.
Тема просто слишком велика, чтобы полностью исследовать ответ, но Я думаю, вы найдете статью Джейка Гиннивана по этому вопросу чрезвычайно полезной: VSTO и COM Interop
Если вы устали от всех вызовов ReleaseComObject, вы можете найти этот вопрос полезным: Как правильно очистить объект взаимодействия Excel в C #, редакция 2012 года
В случае отчаяния. Не используйте этот подход, если вы не понимаете, что он делает :
foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
{
proc.Kill();
}
Мне пришлось это сделать, потому что, хотя я закрыл каждый объект COM в своем коде, я все еще У него был упрямый процесс Excel.exe. Конечно, это не лучшее решение.
Как указано в других ответах, использование двух точек создаст скрытые ссылки, которые нельзя закрыть Marshal.FinalReleaseComObject
. Я просто хотел поделиться своим решением, которое устраняет необходимость запоминать Marshal.FinalReleaseComObject
- это очень легко пропустить, и боль, чтобы найти виновника.
Я использую общий класс оболочки IDisposable, который можно использовать на любом COM-объекте. Он работает как шарм, и он держит все красивым и чистым. Я могу даже повторно использовать частные поля (например, this.worksheet
). Он также автоматически освобождает объект, когда что-то выдает ошибку из-за характера IDisposable (метод Dispose работает как finally
).
using Microsoft.Office.Interop.Excel;
public class ExcelService
{
private _Worksheet worksheet;
private class ComObject<TType> : IDisposable
{
public TType Instance { get; set; }
public ComObject(TType instance)
{
this.Instance = instance;
}
public void Dispose()
{
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(this.Instance);
}
}
public void CreateExcelFile(string fullFilePath)
{
using (var comApplication = new ComObject<Application>(new Application()))
{
var excelInstance = comApplication.Instance;
excelInstance.Visible = false;
excelInstance.DisplayAlerts = false;
try
{
using (var workbooks = new ComObject<Workbooks>(excelInstance.Workbooks))
using (var workbook = new ComObject<_Workbook>(workbooks.Instance.Add()))
using (var comSheets = new ComObject<Sheets>(workbook.Instance.Sheets))
{
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet1"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "Action";
this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
}
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet2"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "Status";
this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
}
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet3"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "ItemPrices";
this.worksheet.Activate();
using (var comRange = new ComObject<Range>(this.worksheet.Range["A4"]))
using (var comWindow = new ComObject<Window>(excelInstance.ActiveWindow))
{
comRange.Instance.Select();
comWindow.Instance.FreezePanes = true;
}
}
if (this.fullFilePath != null)
{
var currentWorkbook = (workbook.Instance as _Workbook);
currentWorkbook.SaveAs(this.fullFilePath, XlFileFormat.xlWorkbookNormal);
currentWorkbook.Close(false);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
throw;
}
finally
{
// Close Excel instance
excelInstance.Quit();
}
}
}
}
В качестве альтернативы вы можете убить процесс Excel, как описано здесь здесь .
Сначала импортируйте функцию SendMessage:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
Затем отправьте WM_CLOSE сообщение в главное окно:
SendMessage((IntPtr)excel.Hwnd, 0x10, IntPtr.Zero, IntPtr.Zero);
У меня была такая же проблема, мы можем решить проблему без какого-либо убийства, мы всегда забываем закрыть интерфейсы, которые мы использовали в классе Microsoft.Office.Interop.Excel, так что вот фрагмент кода и следуйте структуре и образом очистились объекты, также следите за интерфейсом «Листы» в коде, это главный виновник, который мы часто закрываем приложением, рабочей книгой, книгами, диапазоном, листом, но мы забываем или неосознанно не выпускаем объект «Листы» или используемый интерфейс, поэтому вот код:
Microsoft.Office.Interop.Excel.Application app = null;
Microsoft.Office.Interop.Excel.Workbooks books = null;
Workbook book = null;
Sheets sheets = null;
Worksheet sheet = null;
Range range = null;
try
{
app = new Microsoft.Office.Interop.Excel.Application();
books = app.Workbooks;
book = books.Add();
sheets = book.Sheets;
sheet = sheets.Add();
range = sheet.Range["A1"];
range.Value = "Lorem Ipsum";
book.SaveAs(@"C:\Temp\ExcelBook" + DateTime.Now.Millisecond + ".xlsx");
book.Close();
app.Quit();
}
finally
{
if (range != null) Marshal.ReleaseComObject(range);
if (sheet != null) Marshal.ReleaseComObject(sheet);
if (sheets != null) Marshal.ReleaseComObject(sheets);
if (book != null) Marshal.ReleaseComObject(book);
if (books != null) Marshal.ReleaseComObject(books);
if (app != null) Marshal.ReleaseComObject(app);
}
Вот фрагмент кода, который я написал, потому что у меня была такая же проблема, как и вы. В принципе, вам нужно закрыть книгу, выйти из приложения, а затем освободить ВСЕ ваши COM-объекты (а не только объект приложения Excel). Наконец, вызовите сборщик мусора для хорошей меры.
/// <summary>
/// Disposes the current <see cref="ExcelGraph" /> object and cleans up any resources.
/// </summary>
public void Dispose()
{
// Cleanup
xWorkbook.Close(false);
xApp.Quit();
// Manual disposal because of COM
while (Marshal.ReleaseComObject(xApp) != 0) { }
while (Marshal.ReleaseComObject(xWorkbook) != 0) { }
while (Marshal.ReleaseComObject(xWorksheets) != 0) { }
while (Marshal.ReleaseComObject(xWorksheet) != 0) { }
while (Marshal.ReleaseComObject(xCharts) != 0) { }
while (Marshal.ReleaseComObject(xMyChart) != 0) { }
while (Marshal.ReleaseComObject(xGraph) != 0) { }
while (Marshal.ReleaseComObject(xSeriesColl) != 0) { }
while (Marshal.ReleaseComObject(xSeries) != 0) { }
xApp = null;
xWorkbook = null;
xWorksheets = null;
xWorksheet = null;
xCharts = null;
xMyChart = null;
xGraph = null;
xSeriesColl = null;
xSeries = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
Правила - никогда больше не используйте одну точку
- одну точку
var range = ((Range)xlWorksheet.Cells[rowIndex, setColumn]);
var hyperLinks = range.Hyperlinks;
hyperLinks.Add(range, data);
- Две или более точки
(Range)xlWorksheet.Cells[rowIndex, setColumn]).Hyperlinks.Add(range, data);
- - Пример
using Microsoft.Office.Interop.Excel;
Application xls = null;
Workbooks workBooks = null;
Workbook workBook = null;
Sheets sheets = null;
Worksheet workSheet1 = null;
Worksheet workSheet2 = null;
workBooks = xls.Workbooks;
workBook = workBooks.Open(workSpaceFile);
sheets = workBook.Worksheets;
workSheet1 = (Worksheet)sheets[1];
// removing from Memory
if (xls != null)
{
foreach (Microsoft.Office.Interop.Excel.Worksheet sheet in sheets)
{
ReleaseObject(sheet);
}
ReleaseObject(sheets);
workBook.Close();
ReleaseObject(workBook);
ReleaseObject(workBooks);
xls.Application.Quit(); // THIS IS WHAT IS CAUSES EXCEL TO CLOSE
xls.Quit();
ReleaseObject(xls);
sheets = null;
workBook = null;
workBooks = null;
xls = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
Сложно избавиться от всех ссылок, поскольку вам нужно угадать, если такие вызовы:
var workbook = excel.Workbooks.Open("")
Создает экземпляр Workbooks
, на который вы не ссылаетесь.
Даже такие ссылки, как:
targetRange.Columns.AutoFit()
Создают экземпляр .Columns()
, если вы не знаете и не освобождены должным образом.
В итоге я написал класс, содержащий список ссылок на объекты, которые могли бы распоряжаться всеми объектами в обратном порядке.
Класс имеет список объектов и Add()
функций для всего, что вы ссылаетесь при использовании Excel interop, который возвращает сам объект:
public List<Object> _interopObjectList = new List<Object>();
public Excel.Application add(Excel.Application obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Range add(Excel.Range obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Workbook add(Excel.Workbook obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Worksheet add(Excel.Worksheet obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Worksheets add(Excel.Worksheets obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Sheets add(Excel.Sheets obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Workbooks add(Excel.Workbooks obj)
{
_interopObjectList.Add(obj);
return obj;
}
Затем, чтобы отменить регистрацию объектов, я использовал следующий код:
//Release all registered interop objects in reverse order
public void unregister()
{
//Loop object list in reverse order and release Office object
for (int i=_interopObjectList.Count-1; i>=0 ; i -= 1)
{ ReleaseComObject(_interopObjectList[i]); }
//Clear object list
_interopObjectList.Clear();
}
/// <summary>
/// Release a com interop object
/// </summary>
/// <param name="obj"></param>
public static void ReleaseComObject(object obj)
{
if (obj != null && InteropServices.Marshal.IsComObject(obj))
try
{
InteropServices.Marshal.FinalReleaseComObject(obj);
}
catch { }
finally
{
obj = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
Тогда принцип состоит в том, чтобы создать класс и зафиксировать ссылки следующим образом:
//Create helper class
xlsHandler xlObj = new xlsHandler();
..
//Sample - Capture reference to excel application
Excel.Application _excelApp = xlObj.add(new Excel.Application());
..
//Sample - Call .Autofit() on a cell range and capture reference to .Columns()
xlObj.add(_targetCell.Columns).AutoFit();
..
//Release all objects collected by helper class
xlObj.unregister();
Not возможно, код большой красоты, но может вдохновить на что-то полезное.