2008-01-22
LINQ to Excel を作ってみる その2
対象とするExcelのバージョンはOffice 2003。事前にTlbimp.exeを使ってExcel.exeからRCWを作っておく。アセンブリ名はExcel.Interop、名前空間もExcel.Interopとしておく。
まずはシートを列挙する機能から作る。
クラス構造はだいたい↓こんな感じ
XlsWorkbookクラスをルートとして、ワークシートを表すXlsWorksheetクラスとそれを管理するXlsWorksheetsクラスからなる。
XlsWorksheetsクラスにはXlsWorkbookのプロパティからアクセスすることができる。
まずはワークブックに対する操作を行うためのXlsWorkbookクラス。
XlsWorkbook.cs
using System; using System.IO; using System.Runtime.InteropServices; using System.Diagnostics; using Excel.Interop; namespace Excel.Linq { /// <summary> /// Excelワークブックに対する操作を提供するクラス /// </summary> public class XlsWorkbook { /// <summary> /// 省略可能引数に渡す値 /// </summary> private static readonly object None = Type.Missing; private Application xlsApp; private Workbook workbook; /// <summary> /// 指定したファイル名のExcel ファイルを読み込みます。 /// </summary> /// <param name="fileName">ファイル名</param> public XlsWorkbook(string fileName) { xlsApp = new ApplicationClass(); workbook = xlsApp.Workbooks.Open(Path.GetFullPath(fileName), None, true, None, None, None, None, None, None, None, None, None, None, None, None ); } ~XlsWorkbook() { Dispose(); } protected virtual void Dispose(bool disposing) { if(!disposing) return; if(workbook != null) { workbook.Close(false, None, None); Marshal.ReleaseComObject(workbook); workbook = null; } if(xlsApp != null) { xlsApp.Quit(); Marshal.ReleaseComObject(xlsApp); xlsApp = null; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }
とりあえず、ここではコンストラクタで指定したファイル名のワークブックを開くだけ。生のWorkbookオブジェクトをフィールドとして保持しておき、Disposeパターンを使ってUnmanaged リソースを破棄するようにしておく。
次はワークシートに対する操作を行うXlsWorksheetクラス。
XlsWorksheet.cs
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Diagnostics; using Excel.Interop; namespace Excel.Linq { /// <summary> /// Excelワークシートに対する操作を提供するクラス /// </summary> public class XlsWorksheet : IDisposable { private Worksheet worksheet; /// <summary> /// ワークシート名を取得、設定します。 /// </summary> public string Name { get { return worksheet.Name; } set { worksheet.Name = value; } } /// <summary> /// 生のWorksheetオブジェクトを設定するコンストラクタ /// </summary> /// <param name="worksheet">Worksheetオブジェクト</param> protected internal XlsWorksheet(Worksheet worksheet) { this.worksheet = worksheet; } ~XlsWorksheet() { Dispose(); } protected virtual void Dispose(bool disposing) { if(!disposing) return; if(worksheet != null) { Marshal.ReleaseComObject(worksheet); worksheet = null; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }
生のWorksheetオブジェクトをフィールドとして保持し、シート名を返すプロパティを持つ。
XlsWorksheetオブジェクトを列挙するXlsWorksheetsクラス。
XlsWorksheets.cs
using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Diagnostics; using Excel.Interop; namespace Excel.Linq { /// <summary> /// Excelワークシートのコレクションに対する操作を提供するクラス /// </summary> public class XlsWorksheets : IEnumerable<XlsWorksheet>, IDisposable { private Sheets worksheets; /// <summary> /// 生のWorksheetオブジェクトを設定するコンストラクタ /// </summary> /// <param name="worksheets">Worksheetオブジェクト</param> protected internal XlsWorksheets(Sheets worksheets) { this.worksheets = worksheets; } ~XlsWorksheets() { Dispose(); } public IEnumerator<XlsWorksheet> GetEnumerator() { foreach(Worksheet sheet in worksheets) yield return new XlsWorksheet(sheet); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } protected virtual void Dispose(bool disposing) { if(!disposing) return; if(worksheets != null) { Marshal.ReleaseComObject(worksheets); worksheets = null; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }
IEnumerable<XlsWorksheet>インターフェースを実装し、GetEnumeratorメソッドでXlsWorksheetオブジェクトを列挙する。
このクラスをXlsWorkbookのプロパティとして追加しておく。コンストラクタにもXlsWorksheetsオブジェクトをインスタンス化するコードを追加しておく。
XlsWorkbook.cs
private XlsWorksheets worksheets; /// <summary> /// ワークシートのコレクションを取得します。 /// </summary> public XlsWorksheets Worksheets { get { return worksheets; } } public XlsWorkbook(string fileName) { xlsApp = new ApplicationClass(); workbook = xlsApp.Workbooks.Open(Path.GetFullPath(fileName), None, true, None, None, None, None, None, None, None, None, None, None, None, None ); // ↓追加 worksheets = new XlsWorksheets(workbook.Sheets); }
この時点でも以下のようなクエリは成立する。
using(XlsWorkbook book = new XlsWorkbook("100.xls")) { var sheets = from s in book.Worksheets where s.Contains("hoge") select s; foreach(var sheet in sheets) Console.WriteLine(sheet.Name); }
でも、これではXlsWorksheetsのGetEnumeratorメソッドで全シートがXlsWorksheetオブジェクトにラップされて列挙されるため、無駄にオブジェクトがインスタンス化されている。
そうではなくて、XlsWorksheetにラップして列挙する前の段階でwhere演算子に指定された条件でフィルタリングした結果のみをXlsWorksheetでラップして列挙してあげたい。これができればLINQの仕組みが少しは見えてくるだろう。
そのためには、まずXlsWorksheetsクラスをIQueryable<XlsWorksheet>インターフェースを実装するように変更する。
XlsWorksheets.cs
using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Diagnostics; using Excel.Interop; namespace Excel.Linq { /// <summary> /// Excelワークシートのコレクションに対する操作を提供するクラス /// </summary> public class XlsWorksheets : IQueryable<XlsWorksheet>, IDisposable { private Sheets worksheets; /// <summary> /// 生のWorksheetオブジェクトを設定するコンストラクタ /// </summary> /// <param name="worksheets">Worksheetオブジェクト</param> protected internal XlsWorksheets(Sheets worksheets) { this.worksheets = worksheets; } ~XlsWorksheets() { Dispose(); } public IEnumerator<XlsWorksheet> GetEnumerator() { foreach(Worksheet sheet in worksheets) yield return new XlsWorksheet(sheet); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } // IQueryableの実装 Type IQueryable.ElementType { get { return typeof(XlsWorksheet); } } Expression IQueryable.Expression { get { return null; } } IQueryProvider IQueryable.Provider { get { return null; } } protected virtual void Dispose(bool disposing) { if(!disposing) return; if(worksheets != null) { Marshal.ReleaseComObject(worksheets); worksheets = null; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }
IQueryable<T>は、
- Type ElementType { get; }
- Expression Expression { get; }
- IQueryProvider Provider { get; }
の三つのプロパティを実装する必要がある。
とりあえず何を返せばいいのかわからないので、ElementTypeでXlsWorksheetの型を他の二つではnullを返しておく。
ここで一度先程のコードを実行してみるとNullReferenceExceptionが投げられた。
デバッガで追ってみると、まずProviderプロパティが呼び出され、その後にExpressionプロパティが呼び出された。これらがnullのために発生した事はわかるが役目がよくわからない。
どのように実装すればいいのかわからないので、System.Data.LinqアセンブリをReflector.NETで逆アセンブルして調べる。
目次
- 8 http://www.google.co.jp/search?sourceid=navclient&hl=ja&ie=UTF-8&rls=GGLG,GGLG:2005-35,GGLG:ja&q=GC.SuppressFinalize
- 5 http://d.hatena.ne.jp/NyaRuRu/
- 4 http://d.hatena.ne.jp/NyaRuRu/20080120/p1
- 4 http://d.hatena.ne.jp/keyword/C#
- 3 http://reader.m.livedoor.com/reader/item?sss=64392a55af041145&sub=7296006&id=45494720
- 3 http://www.google.com/search?hl=ja&lr=lang_ja&ie=UTF-8&oe=UTF-8&q=JQuery+ASP.NET&num=50
- 3 http://www.google.com/search?num=50&hl=ja&q=C#+リモーティング+サーバ+クライアント&btnG=検索&lr=lang_ja
- 2 http://d.hatena.ne.jp/PoohKid/20071226/ipc
- 2 http://search.yahoo.co.jp/search?fr=slv1-tbtop&p=C# PowerShell
- 2 http://search.yahoo.co.jp/search?p=C#+PowerShell&search.x=1&fr=top_ga1&tid=top_ga1&ei=UTF-8