私は物事が自然にクリーンアップされるのが本当に好きです...それで、私のためにすべてのクリーンアップを行ういくつかのラッパークラスを作成しました! これらは、さらに下に文書化されています。
終了コードは非常に読みやすく、アクセスしやすいものです。Close()
ワークブックとアプリケーションの後に実行されている Excel のファントム インスタンスをまだ見つけていませんQuit()
(プロセスの途中でアプリをデバッグして閉じる場所以外に)。
function void OpenCopyClose() {
var excel = new ExcelApplication();
var workbook1 = excel.OpenWorkbook("C:\Temp\file1.xslx", readOnly: true);
var readOnlysheet = workbook1.Worksheet("sheet1");
var workbook2 = excel.OpenWorkbook("C:\Temp\file2.xslx");
var writeSheet = workbook.Worksheet("sheet1");
// do all the excel manipulation
// read from the first workbook, write to the second workbook.
var a1 = workbook1.Cells[1, 1];
workbook2.Cells[1, 1] = a1
// explicit clean-up
workbook1.Close(false);
workbook2 .Close(true);
excel.Quit();
}
注:Close()
とのQuit()
呼び出しはスキップできますが、Excel ドキュメントに書き込む場合は、少なくともSave()
. オブジェクトがスコープ外に出る (メソッドが戻る) と、クラス ファイナライザーが自動的に開始され、クリーンアップが実行されます。Worksheet COM オブジェクトからの COM オブジェクトへの参照は、変数のスコープに注意している限り、自動的に管理およびクリーンアップされます。たとえば、COM オブジェクトへの参照を格納する場合にのみ、変数を現在のスコープに対してローカルに保持します。必要に応じて、必要な値を POCO に簡単にコピーしたり、以下で説明するように追加のラッパー クラスを作成したりできます。
DisposableComObject
これらすべてを管理するために、任意の COM オブジェクトのラッパーとして機能するクラス を作成しました。インターフェイスを実装しIDisposable
、嫌いな人のためのファイナライザーも含まれていますusing
。
このDispose()
メソッドは、プロパティを呼び出してから nullMarshal.ReleaseComObject(ComObject)
に設定します。ComObjectRef
ComObjectRef
プライベートプロパティが nullの場合、オブジェクトは破棄された状態です。
プロパティが破棄されたComObject
後にアクセスされると、ComObjectAccessedAfterDisposeException
例外がスローされます。
Dispose()
メソッドは手動で呼び出すことができます。また、using
ブロックの終了時、およびusing var
その変数のスコープの終了時に、ファイナライザーによって呼び出されます。
、、、およびの最上位クラスはMicrosoft.Office.Interop.Excel
、それぞれが のサブクラスである独自のラッパー クラスを取得します。Application
Workbook
Worksheet
DisposableComObject
コードは次のとおりです。
/// <summary>
/// References to COM objects must be explicitly released when done.
/// Failure to do so can result in odd behavior and processes remaining running after the application has stopped.
/// This class helps to automate the process of disposing the references to COM objects.
/// </summary>
public abstract class DisposableComObject : IDisposable
{
public class ComObjectAccessedAfterDisposeException : Exception
{
public ComObjectAccessedAfterDisposeException() : base("COM object has been accessed after being disposed") { }
}
/// <summary>The actual COM object</summary>
private object ComObjectRef { get; set; }
/// <summary>The COM object to be used by subclasses</summary>
/// <exception cref="ComObjectAccessedAfterDisposeException">When the COM object has been disposed</exception>
protected object ComObject => ComObjectRef ?? throw new ComObjectAccessedAfterDisposeException();
public DisposableComObject(object comObject) => ComObjectRef = comObject;
/// <summary>
/// True, if the COM object has been disposed.
/// </summary>
protected bool IsDisposed() => ComObjectRef is null;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // in case a subclass implements a finalizer
}
/// <summary>
/// This method releases the COM object and removes the reference.
/// This allows the garbage collector to clean up any remaining instance.
/// </summary>
/// <param name="disposing">Set to true</param>
protected virtual void Dispose(bool disposing)
{
if (!disposing || IsDisposed()) return;
Marshal.ReleaseComObject(ComObject);
ComObjectRef = null;
}
~DisposableComObject()
{
Dispose(true);
}
}
使い方を少し簡単にする便利な汎用サブクラスもあります。
public abstract class DisposableComObject<T> : DisposableComObject
{
protected new T ComObject => (T)base.ComObject;
public DisposableComObject(T comObject) : base(comObject) { }
}
最後にDisposableComObject<T>
、Excel 相互運用クラスのラッパー クラスを作成するために使用できます。
サブクラスには、新しい Excel アプリケーション インスタンスへのExcelApplication
参照があり、ワークブックを開くために使用されます。
OpenWorkbook()
ExcelWorkbook
DisposableComObject のサブクラスでもある を返します。
Dispose()
Dispose()
基本メソッド を呼び出す前に Excel アプリケーションを終了するようにオーバーライドされています。Quit()
のエイリアスですDispose()
。
public class ExcelApplication : DisposableComObject<Application>
{
public class OpenWorkbookActionCancelledException : Exception
{
public string Filename { get; }
public OpenWorkbookActionCancelledException(string filename, COMException ex) : base($"The workbook open action was cancelled. {ex.Message}", ex) => Filename = filename;
}
/// <summary>The actual Application from Interop.Excel</summary>
Application App => ComObject;
public ExcelApplication() : base(new Application()) { }
/// <summary>Open a workbook.</summary>
public ExcelWorkbook OpenWorkbook(string filename, bool readOnly = false, string password = null, string writeResPassword = null)
{
try
{
var workbook = App.Workbooks.Open(Filename: filename, UpdateLinks: (XlUpdateLinks)0, ReadOnly: readOnly, Password: password, WriteResPassword: writeResPassword, );
return new ExcelWorkbook(workbook);
}
catch (COMException ex)
{
// If the workbook is already open and the request mode is not read-only, the user will be presented
// with a prompt from the Excel application asking if the workbook should be opened in read-only mode.
// This exception is raised when when the user clicks the Cancel button in that prompt.
throw new OpenWorkbookActionCancelledException(filename, ex);
}
}
/// <summary>Quit the running application.</summary>
public void Quit() => Dispose(true);
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (!disposing || IsDisposed()) return;
App.Quit();
base.Dispose(disposing);
}
}
ExcelWorkbook
もサブクラス化DisposableComObject<Workbook>
し、ワークシートを開くために使用されます。
Worksheet()
メソッドは、ご想像のExcelWorksheet
とおり、 のサブクラスでもある を返しますDisposableComObject<Workbook>
。
Dispose()
メソッドはオーバーライドされ、ベースを呼び出す前に最初にワークシートが閉じられますDispose()
。
注: を反復処理するために使用するいくつかの拡張メソッドを追加しましたWorkbook.Worksheets
。コンパイル エラーが発生する場合は、これが原因です。最後に拡張メソッドを追加します。
public class ExcelWorkbook : DisposableComObject<Workbook>
{
public class WorksheetNotFoundException : Exception
{
public WorksheetNotFoundException(string message) : base(message) { }
}
/// <summary>The actual Workbook from Interop.Excel</summary>
Workbook Workbook => ComObject;
/// <summary>The worksheets within the workbook</summary>
public IEnumerable<ExcelWorksheet> Worksheets => worksheets ?? (worksheets = Workbook.Worksheets.AsEnumerable<Worksheet>().Select(w => new ExcelWorksheet(w)).ToList());
private IEnumerable<ExcelWorksheet> worksheets;
public ExcelWorkbook(Workbook workbook) : base(workbook) { }
/// <summary>
/// Get the worksheet matching the <paramref name="sheetName"/>
/// </summary>
/// <param name="sheetName">The name of the Worksheet</param>
public ExcelWorksheet Worksheet(string sheetName) => Worksheet(s => s.Name == sheetName, () => $"Worksheet not found: {sheetName}");
/// <summary>
/// Get the worksheet matching the <paramref name="predicate"/>
/// </summary>
/// <param name="predicate">A function to test each Worksheet for a macth</param>
public ExcelWorksheet Worksheet(Func<ExcelWorksheet, bool> predicate, Func<string> errorMessageAction) => Worksheets.FirstOrDefault(predicate) ?? throw new WorksheetNotFoundException(errorMessageAction.Invoke());
/// <summary>
/// Returns true of the workbook is read-only
/// </summary>
public bool IsReadOnly() => Workbook.ReadOnly;
/// <summary>
/// Save changes made to the workbook
/// </summary>
public void Save()
{
Workbook.Save();
}
/// <summary>
/// Close the workbook and optionally save changes
/// </summary>
/// <param name="saveChanges">True is save before close</param>
public void Close(bool saveChanges)
{
if (saveChanges) Save();
Dispose(true);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (!disposing || IsDisposed()) return;
Workbook.Close();
base.Dispose(disposing);
}
}
最後に、ExcelWorksheet
.
UsedRows()
Microsoft.Office.Interop.Excel.Range
ラップされていないオブジェクトの列挙型を返すだけです。オブジェクトのプロパティからアクセスされる COM オブジェクトMicrosoft.Office.Interop.Excel.Worksheet
がApplication
、 、Workbook
、およびWorksheet
. これらはすべて、自動的にクリーンアップされるようです。ほとんどの場合、Ranges を反復処理して値を取得または設定するだけだったので、特定のユース ケースは利用可能な機能ほど高度ではありません。
Dispose()
この場合、ワークシートに対して特別なアクションを実行する必要がないため、オーバーライドはありません。
public class ExcelWorksheet : DisposableComObject<Worksheet>
{
/// <summary>The actual Worksheet from Interop.Excel</summary>
Worksheet Worksheet => ComObject;
/// <summary>The worksheet name</summary>
public string Name => Worksheet.Name;
// <summary>The worksheets cells (Unwrapped COM object)</summary>
public Range Cells => Worksheet.Cells;
public ExcelWorksheet(Worksheet worksheet) : base(worksheet) { }
/// <inheritdoc cref="WorksheetExtensions.UsedRows(Worksheet)"/>
public IEnumerable<Range> UsedRows() => Worksheet.UsedRows().ToList();
}
さらに多くのラッパー クラスを追加することができます。必要に応じてメソッドを追加ExcelWorksheet
し、ラッパー クラスで COM オブジェクトを返すだけです。ExcelApplication.OpenWorkbook()
と を介してワークブックをラップするときに行ったことをコピーするだけExcelWorkbook.WorkSheets
です。
いくつかの便利な拡張メソッド:
public static class EnumeratorExtensions
{
/// <summary>
/// Converts the <paramref name="enumerator"/> to an IEnumerable of type <typeparamref name="T"/>
/// </summary>
public static IEnumerable<T> AsEnumerable<T>(this IEnumerable enumerator)
{
return enumerator.GetEnumerator().AsEnumerable<T>();
}
/// <summary>
/// Converts the <paramref name="enumerator"/> to an IEnumerable of type <typeparamref name="T"/>
/// </summary>
public static IEnumerable<T> AsEnumerable<T>(this IEnumerator enumerator)
{
while (enumerator.MoveNext()) yield return (T)enumerator.Current;
}
/// <summary>
/// Converts the <paramref name="enumerator"/> to an IEnumerable of type <typeparamref name="T"/>
/// </summary>
public static IEnumerable<T> AsEnumerable<T>(this IEnumerator<T> enumerator)
{
while (enumerator.MoveNext()) yield return enumerator.Current;
}
}
public static class WorksheetExtensions
{
/// <summary>
/// Returns the rows within the used range of this <paramref name="worksheet"/>
/// </summary>
/// <param name="worksheet">The worksheet</param>
public static IEnumerable<Range> UsedRows(this Worksheet worksheet) =>
worksheet.UsedRange.Rows.AsEnumerable<Range>();
}