C# と VB.NET の質問掲示板

わんくま同盟

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

■103064 / 1階層)  検索を早く
□投稿者/ 魔界の仮面弁士 (3761回)-(2024/04/15(Mon) 14:40:04)
2024/04/15(Mon) 15:18:10 編集(投稿者)

No103060 (よし さん) に返信
> using Excel = Microsoft.Office.Interop.Excel;
現在の方法は、実行環境に Excel をインストールしておき、
それをプログラムから操作する方法ですよね。

高速参照が目的ならば、Excel を COM オートメーションで操作するのではなく、
xlsx ファイル自体を直接読み込めるタイプのライブラリを利用することをお奨めします。
たとえば、速度に定評があるところで ExcelDataReader とか。

https://techblog.gracetory.co.jp/entry/2020/11/24/110000
https://docs.sakai-sc.co.jp/article/programing/closedxml-vs-epplus.html
https://ja.stackoverflow.com/questions/72835/%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%82%B7%E3%83%BC%E3%83%88%E3%82%92%E5%90%AB%E3%82%80excel%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E9%AB%98%E9%80%9F%E3%81%A7%E9%96%8B%E3%81%8F%E6%96%B9%E6%B3%95


使用するライブラリを今更変えられないのであれば、コードを見直してみましょう。


Excel VBA が ThisWorkbook を操作する場合、自分自身の操作(インプロセス)なので比較的高速に処理できるのですが、
Excel を C# などから操作するような場合は、アウトプロセス COM サーバーとの通信となるため、
メソッドやプロパティの読み書きのパフォーマンスが、VBA の時よりも幾分遅くなります。

※たとえ Excel VBA であっても、自身以外の Application インスタンスへの操作はパフォーマンスが劣化します。


とはいえ、どの操作が特に遅くなりえるのかは、実際の環境でないと分からないため、
ボトルネックの調査はご自身で行ってみてください。
VBA で処理しても遅い操作は、C# ならもっと遅くなるわけですし。

またボトルネック調査の際には、その処理が何回繰り返されるのかも重要です。

たとえば仮に、ある操作の呼び出しが、VBA の実装だと 0.0003 秒で終わる処理があったとして、
同じ処理を C# から呼び出すと、0.01 秒かかってしまうとしましょう。この処理を実行するための
アクセス回数が 10 回なら、 0.003 秒 VS 0.1 秒なので、体感的にはどちらも一瞬で終わりますが、
アクセス回数が 1000 回なら、0.3 秒 VS 10.0 秒なので、体感する速度差に大きく差が付きます。



> if (usedRange.Cells[i, "Y"].Value2 != null && !string.IsNullOrEmpty(usedRange.Cells[i, "Y"].Value2.ToString()))

たとえばこの一行だけで、最大 8 回も Excel との通信が行われる可能性があります。

 1. Range オブジェクト (usedRange:変数)
 2. Range オブジェクト (.Cells: 引数無し property の getter)
 3. Range オブジェクト ([i, "Y"]: 引数あり property の getter/インデクサ)
 4. Variant(Object) (.Value2 引数無し property の getter)
 5. Range オブジェクト (usedRange 変数)
 6. Range オブジェクト (.Cells: 引数無し property の getter)
 7. Range オブジェクト ([i, "Y"]: 引数あり property の getter/インデクサ)
 8. Variant(Object) (.Value2 引数無し property の getter)


「Excel を外部から操作する場合は、繰り返し回数を極力減らすべき」というのが、
.NET が登場する前から言われてきたノウハウだったりします。

下記は、Excel 97〜2000 (VB5〜VB6 頃) の古い資料ですが、参考までに。
(既に公開が終了しているので、Internet Archive から拾いだしています)

KB414107: [XL2000] オートメーションでセルの値の取得やコピーを繰り返すと応答しない
https://web.archive.org/web/20140126120748/http%3A%2F%2Fsupport.microsoft.com/kb/414107/ja


ということで、Excel を外部操作する場合、Excel との通信回数を減らすことで高速化できる可能性があります。

たとえば、セルを一つ一つ読み取るのではなく、Excel の Find / FindNext メソッドを併用して捜索したり、
あるいは複数の範囲を一括で読み取って配列として読み書きするといった手もあります。


たとえば
> Excel.Range usedRange = excelWorksheet.UsedRange;
の後で、
 int rowCount = usedRange.Rows.Count;
 if (usedRange.Cells[i, "Y"].Value2 != null && !string.IsNullOrEmpty(usedRange.Cells[i, "Y"].Value2.ToString()))
のように処理するのではなく、複数セル範囲のまま
 rangeValues = usedRange.Value;
のように丸ごと受け取って、それを (Excel を介さず C# 側だけで)配列データとして扱ってみます。

UsedRange.Value の値は、通常は object[,] な二次元配列を返しますが、
使用しているセルが一つ以下だと、戻り値が配列では無く、null や string や double になりえるため、
この場合の rangeValues 変数は、一旦 dynamic 型 または object 型で受けておく方が良いでしょう。


 // ここは Excel との通信が発生する
 dynamic rangeValues = usedRange.Value; // セル範囲内の値を一括して受け取る

 // ここから下は C# 側だけで完結しているため、Excel との通信はおこなわれない
 var array = rangeValues as object[,];
 if ( array != null ) { // 配列として受け取れた場合
  // 受けとった配列のインデックスが 0 始まりになるか 1 始まりになるかは、
  // 環境によって変わることがあるので、念のために GetLowerBound を使った方が良い
  int rowLower = array.GetLowerBound(0);
  int rowUpper = array.GetUpperBound(0);
  int colLower = array.GetLowerBound(1);
  int colUpper = array.GetUpperBound(1);

  for (int rowIndex = rowLower ; rowIndex <= rowUpper; rowIndex++ ) {
   for (int colIndex = colLower ; colIndex <= colUpper; colIndex++ ) {
    object data = array[rowIndex, colIndex];
    Debug.WriteLine("[{0},{1}]={2}", rowIndex, colIndex, data);
   }
  }
 }


なお、セル範囲が余りに巨大な場合、メモリの確保(と解放)のコストが高くなるため、
その場合は、複数の配列範囲にブロック分割して処理するといった手もあります。


> excelWorkbook = excelApp.Workbooks.Open(fileInfo.FullName);
> Excel.Worksheet excelWorksheet = excelWorkbook.Sheets[1];
そういった操作は、COM オブジェクトの解放漏れを引き起こします。

excelApp.Workbooks が返す Excel.Workbook オブジェクトや、
excelWorkbook.Sheets が返す Excel.Sheets オブジェクトといった
ひとつひとつの COM オブジェクトを変数に受け取るように書き換えるべきかと。

https://support.microsoft.com/ja-jp/topic/96068fdb-7a84-ecf0-3b91-282fae81a618
https://hanatyan.sakura.ne.jp/dotnet/Excel08.htm
https://hanatyan.sakura.ne.jp/dotnet/Excel09.htm
編集キー/

前の記事(元になった記事) 次の記事(この記事の返信)
←検索を早く /よし →Re[2]: 検索を早く /furu
 
上記関連ツリー

検索を早く / よし (24/04/15(Mon) 10:40) #103060
Re[1]: 検索を早く / WebSurfer (24/04/15(Mon) 12:43) #103061
│└ Re[2]: 検索を早く / ぶなっぷ (24/04/15(Mon) 13:31) #103062
│  └ Re[3]: 検索を早く / ぶなっぷ (24/04/15(Mon) 13:36) #103063
検索を早く / 魔界の仮面弁士 (24/04/15(Mon) 14:40) #103064 ←Now
  └ Re[2]: 検索を早く / furu (24/04/16(Tue) 11:28) #103065
    └ Re[3]: 検索を早く / よし (24/04/19(Fri) 17:11) #103084 解決済み

上記ツリーを一括表示 / 上記ツリーをトピック表示
 
上記の記事へ返信