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

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

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

Re[5]: COMの解放もれについて


(過去ログ 169 を表示中)

[トピック内 6 記事 (1 - 6 表示)]  << 0 >>

■97479 / inTopicNo.1)  COMの解放もれについて
  
□投稿者/ 初# (5回)-(2021/05/23(Sun) 18:34:06)

分類:[C#] 

      下記コードにてexcelがタスクマネージャーに残ってしまいます。
    どこで解放漏れが起きているのか調べる方法はありますか。



    //Excelオブジェクトの変数宣言
        Microsoft.Office.Interop.Excel.Application excel = null;
        Microsoft.Office.Interop.Excel.Workbooks books = null;
        Microsoft.Office.Interop.Excel.Workbook book = null;
        Microsoft.Office.Interop.Excel.Sheets sheets = null;
        Microsoft.Office.Interop.Excel.Worksheet sheet = null;
        Microsoft.Office.Interop.Excel.PageSetup page = null;

            try
            {
                excel = new Microsoft.Office.Interop.Excel.Application();
                excel.DisplayAlerts = false; // アラートを表示しない
                books = excel.Workbooks;

                
                filepath = dataGridView1.Rows[i].Cells["filepath"].Value.ToString();

                filename = dataGridView1.Rows[i].Cells["filename"].Value.ToString();

                
                footer = filename.Substring(0, filename.IndexOf("_"));
                footer = footer + Path.GetExtension(filepath);

                book = books.Open(filepath,
                                  Type.Missing, Type.Missing, Type.Missing,
                                  Type.Missing, Type.Missing, Type.Missing,
                                  Type.Missing, Type.Missing, Type.Missing,
                                  Type.Missing, Type.Missing, Type.Missing);

                
                sname = dataGridView1.Rows[i].Cells["sheetname"].Value.ToString();

                string[] ar1 = sname.Split(';');

                
                sheets = book.Worksheets[ar1];
                sheets.Select(Type.Missing);


                if (FooterCheckBox.Checked == true)
                {
                    foreach (string s in ar1)
                    {
                        sheet = sheets[s];
                        page = sheet.PageSetup;
                        page.LeftFooter = "&L" + footer;
                    }
                }
                //印刷処理
                sheets.PrintOutEx(Type.Missing, Type.Missing, Type.Missing,
                                  false, ptintername, Type.Missing, Type.Missing,
                                  Type.Missing, Type.Missing);

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                //Excelを閉じる
                book.Close(false, Type.Missing, Type.Missing);

                //COMオブジェクト解放
                FinalReleaseComObject(page);
                FinalReleaseComObject(sheet);
                FinalReleaseComObject(sheets);
                FinalReleaseComObject(book);
                FinalReleaseComObject(books);

                // Excelを終了する
                excel.Quit();

                FinalReleaseComObject(excel);

                GC.Collect();

            }
     

引用返信 編集キー/
■97480 / inTopicNo.2)  Re[1]: COMの解放もれについて
□投稿者/ 魔界の仮面弁士 (3102回)-(2021/05/24(Mon) 07:31:33)
No97479 (初# さん) に返信
> どこで解放漏れが起きているのか調べる方法はありますか。
COM オブジェクトのインスタンスを解放するのが目的なのに、
単に変数を FinalReleaseComObject しているだけに見えます。


> filepath = dataGridView1.Rows[i].Cells["filepath"].Value.ToString();
> filename = dataGridView1.Rows[i].Cells["filename"].Value.ToString();

filepath = dataGridView1["filepath", i].Value.ToString();
とします。


> book = books.Open(filepath,
>   Type.Missing, Type.Missing, Type.Missing,
>   Type.Missing, Type.Missing, Type.Missing,
>   Type.Missing, Type.Missing, Type.Missing,
>   Type.Missing, Type.Missing, Type.Missing);

どのバージョンの C# をお使いなのでしょうか?

C# 3.0 以下であればいざ知らず、C# 4.0 以降においては
System.Type.Missing や System.Reflection.Missing.Value を使う必要は無いはず。
https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments

book.Worksheets の戻り値が object 型となる時代の C# であれば
Type.Missing が使われますが、
book.Worksheets の戻り値が dyanamic 型となる時代の C# であれば
Type.Missing は使わず、単に引数を省略します。


> sheets = book.Worksheets[ar1];
> sheets.Select(Type.Missing);

複数シートを選択するにしても、

sheetsAll = book.Worksheets;
sheetsSel = sheetsAll[ar1];
sheetsSel.Select();

でしょう。


> if (FooterCheckBox.Checked == true)
Checked プロパティは bool 型を返しますので、
単に if (FooterCheckBox.Checked) で良いと思います。

bool を true/false と比較すること自体は間違いでは無いですが、
 if ( (FooterCheckBox.Checked == true) == true)
のようなコードと同じで、冗長的かと。



> foreach (string s in ar1)
> {
>  sheet = sheets[s];
>  page = sheet.PageSetup;
>  page.LeftFooter = "&L" + footer;
> }

Sheet1.PageSetup と Sheet2.PageShetup は異なるインスタンスなので、

foreach (string s in ar1)
{
 sheet = (Microsoft.Office.Interop.Excel.Worksheet)sheetsAll[s];
 page = sheet.PageSetup;
 page.LeftFooter = "&L" + footer;
 FinalReleaseComObject(page);
 FinalReleaseComObject(sheet);
}

のように、それぞれの COM オブジェクトごとに解放が必要です。


> GC.Collect();
GC を強制発動すると、「その時点で解放されなかったオブジェクト」は、
ジェネレーションが昇格して、むしろ解放されにくくなってしまいます。

強制的な GC 発動までが必要なら、

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

のように書きます。
ただ、COM 側の解放処理がきちんとなされていれば、
.NET 側の解放は、通常の GC 任せでもさほど問題にはならないでしょう。
引用返信 編集キー/
■97482 / inTopicNo.3)  Re[2]: COMの解放もれについて
□投稿者/ 初# (7回)-(2021/05/24(Mon) 21:21:48)
No97480 (魔界の仮面弁士 さん) に返信

> どのバージョンの C# をお使いなのでしょうか?
>
> C# 3.0 以下であればいざ知らず、C# 4.0 以降においては
> System.Type.Missing や System.Reflection.Missing.Value を使う必要は無いはず。
> https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments
>
> book.Worksheets の戻り値が object 型となる時代の C# であれば
> Type.Missing が使われますが、
> book.Worksheets の戻り値が dyanamic 型となる時代の C# であれば
> Type.Missing は使わず、単に引数を省略します。


バージョンは7.2でした。なので
book = books.Open(Filename:filepath);
sheetsSel.PrintOutEx(Preview:false, ActivePrinter:ptintername);
のように名前付き引数にて指定するようにいたしました。

ここでもうひとつ質問なのですが、
datagridviewにてあらかじめ設定した列に、別の処理で行を追加する際に
dataGridView1.Rows.Add(false, Type.Missing, strFileName, strFilePath, sname);
のような記述で追加しています。
列は左からcheckbox,button,textbox,textbox,textboxです。
このような場合にbutton列には特に引数を指定する必要がないのですが名前がわからず引数の指定の仕方がわかりません。
Type.Missingを消してみるとうまく行が追加されなくなりました。
このような場合はどのようにすればよろしいでしょうか。

> 複数シートを選択するにしても、
>
> sheetsAll = book.Worksheets;
> sheetsSel = sheetsAll[ar1];
> sheetsSel.Select();
>
> でしょう。

上記のように修正いたしました。
この場合はsheetsAllとsheetsSelでそれぞれ解放が必要になるのでしょうか?



>>if (FooterCheckBox.Checked == true)
> Checked プロパティは bool 型を返しますので、
> 単に if (FooterCheckBox.Checked) で良いと思います。
>
> bool を true/false と比較すること自体は間違いでは無いですが、
>  if ( (FooterCheckBox.Checked == true) == true)
> のようなコードと同じで、冗長的かと。

たしかに冗長でした。直します。


> Sheet1.PageSetup と Sheet2.PageShetup は異なるインスタンスなので、
>
> foreach (string s in ar1)
> {
>  sheet = (Microsoft.Office.Interop.Excel.Worksheet)sheetsAll[s];
>  page = sheet.PageSetup;
>  page.LeftFooter = "&L" + footer;
>  FinalReleaseComObject(page);
>  FinalReleaseComObject(sheet);
> }
>
> のように、それぞれの COM オブジェクトごとに解放が必要です。



上記のように修正いたしました。
このような場合にFinallyにて解放処理を行いたい場合、
tryのなかにまたtryを入れていくしかないのでしょうか、
またはforeachの処理を別の関数で行いその関数でFinallyを設けるのが良いのでしょうか。


> GC を強制発動すると、「その時点で解放されなかったオブジェクト」は、
> ジェネレーションが昇格して、むしろ解放されにくくなってしまいます。
>
> 強制的な GC 発動までが必要なら、
>
> GC.Collect();
> GC.WaitForPendingFinalizers();
> GC.Collect();
>
> のように書きます。
> ただ、COM 側の解放処理がきちんとなされていれば、
> .NET 側の解放は、通常の GC 任せでもさほど問題にはならないでしょう。

GC.Collect();は削除しました。


以上の内容に加え最後の解放処理を

book.Close(false);

//COMオブジェクト解放
FinalReleaseComObject(page);
FinalReleaseComObject(sheet);
FinalReleaseComObject(sheetsSel);
FinalReleaseComObject(sheetsAll);
FinalReleaseComObject(book);
FinalReleaseComObject(books);


// Excelを終了する
excel.Quit();

FinalReleaseComObject(excel);

のように変更したらタスクに残ることはありませんでした。
初心者であまり深くまで理解が追い付ていないところが多く
コード的におかしい箇所があるかとは思いますが、
丁寧に指摘していただき大変勉強になります。有難うございます。

引用返信 編集キー/
■97485 / inTopicNo.4)  Re[3]: COMの解放もれについて
□投稿者/ 魔界の仮面弁士 (3103回)-(2021/05/25(Tue) 10:41:40)
No97482 (初# さん) に返信
> //COMオブジェクト解放
> FinalReleaseComObject(page);
Marshal.ReleaseComObject や
Marshal.FinalReleaseComObject ではなく
FinalReleaseComObject と書いていますね。

これは自作のメソッドですか?


> book = books.Open(Filename:filepath);
Filename は最初の引数なので、引数位置指定だけの
 book = books.Open(filepath);
と書いても良いですね。
変数名で意味が分かるので、名前付き引数を使わずとも意図が分かりやすいです。


> sheetsSel.PrintOutEx(Preview:false, ActivePrinter:ptintername);
逆にこちらのメソッドの場合、名前付き引数を使った方が分かりやすいですね。
とはいえ指定すべき変数名は、ptintername (ぷてぃんたー) ではなく printername (ぷりんたー) ですよね。


で、こうした変数名は全て小文字で書くよりも、
後続の単語部分の頭文字を大文字にすることをお奨めします。

たとえば、メソッドの引数や、メソッド内のローカル変数名は camelCase 構文として、
 filePath
 printerName
のように書きます。先頭は小文字にすることが多いです。

そしてクラス名やメソッド名については、先頭も大文字にした PascalCase 構文にして
 Open
 FinalReleaseComObject
のような命名にするのが一般的です。



>>複数シートを選択するにしても、
>>sheetsAll = book.Worksheets;
>>sheetsSel = sheetsAll[ar1];
>>sheetsSel.Select();
>>でしょう。
> 上記のように修正いたしました。

シートを選択するなら Select メソッドですが、そもそもシートを選択する必要は無いはずです。

Select メソッドは使わずに
 sheetsSel = sheetsAll[ar1];
 sheetsSel.PrintOutEx(Preview:false, ActivePrinter:ptinterName);
だけで、ar1 で指定したシートだけを出力できます。

Excel の読み書きや印刷のために、Activate メソッド/Select メソッドや
Active何某プロパティ/Selection プロパティを使う必要は殆どありません。



> datagridviewにてあらかじめ設定した列に、別の処理で行を追加する際に
> dataGridView1.Rows.Add(false, Type.Missing, strFileName, strFilePath, sname);
> のような記述で追加しています。
> このような場合にbutton列には特に引数を指定する必要がないのですが名前がわからず引数の指定の仕方がわかりません。

省略したい場合には、Type.Missing ではなく DBNull.Value を指定しましょう。
または、「Button 列 に表示する文字列」を指定します。
これによって、それぞれの行のボタンのテキストが設定されます。

もし、ボタンごとにテキストを替える必要が無く、
全ての行でボタンのテキストが同じで良い場合には、
デザイン時に Button 列の「Text プロパティ」を設定しておいて、
Button 列の「UseColumnTextForButtonValue プロパティ」を true にします。



> この場合はsheetsAllとsheetsSelでそれぞれ解放が必要になるのでしょうか?
はい、そうです。また、Excel に限らず、すべての COM オブジェクトが対象です。
Excel.Applictation、InternetExplorer.Application、ADODB.Recordset 等々。


たとえば、VBA において
 ThisWorkbook.Worksheets("Sheet1").Cells(4, 2).Value = "TEST"
の 1 行で済むコードがあったとします。

C# からだと、これを
 book.sheets["Sheet1"].Cells[4, 2].Value = "TEST";
と書けそうに思えますが、これだと解放できません。

そのため、
 Excel.Sheets sheets = book.Worksheets;
 Excel.Worksheet sheet = (Excel.Worksheet)sheets["Sheet1"];
 Excel.Range cells = sheet.Cells;
 Excel.Range cell = cells[4, 2];
 cell.Value = "TEST";
 Marshal.ReleaseComObject(cell);
 Marshal.ReleaseComObject(cells);
 Marshal.ReleaseComObject(sheet);
 Marshal.ReleaseComObject(sheets);
のような記述が必要です。

もちろん、まだ使っている最中のオブジェクトを解放するのは NG です。


さらに言えば、COM オブジェクトを foreach で処理するのも避けてください。
 foreach(Excel.Worksheet sheet in sheets)
 {
  // :
  // :
  Marshal.ReleaseComObject(sheet);
 }
foreach を使って列挙すると、System.Runtime.InteropServices.CustomMarshalers.EnumeratorViewOfEnumVariant という
COM オブジェクトが内部的に生成されてしまい、それが解放漏れの要因になることがあるためです。

可能な場合は、かわりに for ループを使うようにします。
 for (int i = 1; i <= sheets.Count; i++)
 {
  Excel.Worksheet sheet = (Excel.Worksheet)Sheets[i];
  // :
  // :
  Marshal.ReleaseComObject(sheet);
 }


Excel の場合には for で代用できるはずですが、どうしても foreach しか使えない COM オブジェクトの場合は、
ICustomAdapter.GetUnderlyingObjectメソッドを使って解放する手法もあります。今回は不要ですが。

また、COM オブジェクトのメソッドが、COM オブジェクトを object または dynamic 型の引数で
受け渡す仕様になっている場合も注意が必要です。このようなメソッドを呼び出した場合、
引き渡した COM オブジェクトの参照カウントが意図せず増加してしまうことがあるためです。



> このような場合にFinallyにて解放処理を行いたい場合、
> tryのなかにまたtryを入れていくしかないのでしょうか、
> またはforeachの処理を別の関数で行いその関数でFinallyを設けるのが良いのでしょうか。

この掲示板の左上 [C# と VB.NET 入門] のリンクから、sitemap で
[サンプル]-[Visual C# .NET]-[プログラミング全般]-[COM オブジェクトの参照カウントを解放する]
を見ると、try ブロックを使った解放例が載っていますね。


こうした解放が面倒な場合には、メニューの [ツール]-[nuget パッケージ マネージャー] から
参照:「NetOffice」で検索し、一覧に出てくる「NetOffice.Excel」をプロジェクトにインストールしてみてください。
これにより、解放の手間が大幅に削減されます。

// using Excel = NetOffice.ExcelApi;

void Sample()
{
 using (Excel.Application app = new Excel.Application()) {
  Excel.Workbook book = app.Workbooks.Open(@"test.xlsx");
  Excel.Worksheet workSheet = (Excel.Worksheet)book.Worksheets[1];
  System.Diagnostics.Debug.WriteLine(workSheet.Cells[1, 1].Value);
  book.Close();
  app.Quit();
 }
}
https://github.com/NetOfficeFw/NetOffice
引用返信 編集キー/
■97489 / inTopicNo.5)  Re[4]: COMの解放もれについて
□投稿者/ 初# (9回)-(2021/05/25(Tue) 20:40:37)
No97485 (魔界の仮面弁士 さん) に返信
> ■No97482 (初# さん) に返信
>>//COMオブジェクト解放
>>FinalReleaseComObject(page);
> Marshal.ReleaseComObject や
> Marshal.FinalReleaseComObject ではなく
> FinalReleaseComObject と書いていますね。
>
> これは自作のメソッドですか?


private void FinalReleaseComObject(object o)
{
if (o != null) Marshal.FinalReleaseComObject(o);
}
どこかのサイトで見たものをコピーしただけですが、このようなメソッドで解放をしています。



>>book = books.Open(Filename:filepath);
> Filename は最初の引数なので、引数位置指定だけの
>  book = books.Open(filepath);
> と書いても良いですね。
> 変数名で意味が分かるので、名前付き引数を使わずとも意図が分かりやすいです。
>
>
>>sheetsSel.PrintOutEx(Preview:false, ActivePrinter:ptintername);
> 逆にこちらのメソッドの場合、名前付き引数を使った方が分かりやすいですね。
> とはいえ指定すべき変数名は、ptintername (ぷてぃんたー) ではなく printername (ぷりんたー) ですよね。
>
>
> で、こうした変数名は全て小文字で書くよりも、
> 後続の単語部分の頭文字を大文字にすることをお奨めします。
>
> たとえば、メソッドの引数や、メソッド内のローカル変数名は camelCase 構文として、
>  filePath
>  printerName
> のように書きます。先頭は小文字にすることが多いです。
>
> そしてクラス名やメソッド名については、先頭も大文字にした PascalCase 構文にして
>  Open
>  FinalReleaseComObject
> のような命名にするのが一般的です。

自分でも分かりやすいようになるべく名前付き引数を使うように心がけます。
ptintername は気づきませんでした。お恥ずかしい。
変数名も指摘されたとおりに統一していきます。



> シートを選択するなら Select メソッドですが、そもそもシートを選択する必要は無いはずです。
>
> Select メソッドは使わずに
>  sheetsSel = sheetsAll[ar1];
>  sheetsSel.PrintOutEx(Preview:false, ActivePrinter:ptinterName);
> だけで、ar1 で指定したシートだけを出力できます。
>
> Excel の読み書きや印刷のために、Activate メソッド/Select メソッドや
> Active何某プロパティ/Selection プロパティを使う必要は殆どありません。

Selectメソッドをコメントアウトしても正常に動作しました。



> 省略したい場合には、Type.Missing ではなく DBNull.Value を指定しましょう。
> または、「Button 列 に表示する文字列」を指定します。
> これによって、それぞれの行のボタンのテキストが設定されます。
>
> もし、ボタンごとにテキストを替える必要が無く、
> 全ての行でボタンのテキストが同じで良い場合には、
> デザイン時に Button 列の「Text プロパティ」を設定しておいて、
> Button 列の「UseColumnTextForButtonValue プロパティ」を true にします。


dataGridView1.Rows.Add(false, DBNull.Value, strFileName, strFilePath, sheetName);
このような記載に修正いたしました。
ボタンのテキストはすべて同じなので指摘されたとおりのコードで実装しております。


>>この場合はsheetsAllとsheetsSelでそれぞれ解放が必要になるのでしょうか?
> はい、そうです。また、Excel に限らず、すべての COM オブジェクトが対象です。
> Excel.Applictation、InternetExplorer.Application、ADODB.Recordset 等々。
>
>
> たとえば、VBA において
>  ThisWorkbook.Worksheets("Sheet1").Cells(4, 2).Value = "TEST"
> の 1 行で済むコードがあったとします。
>
> C# からだと、これを
>  book.sheets["Sheet1"].Cells[4, 2].Value = "TEST";
> と書けそうに思えますが、これだと解放できません。
>
> そのため、
>  Excel.Sheets sheets = book.Worksheets;
>  Excel.Worksheet sheet = (Excel.Worksheet)sheets["Sheet1"];
>  Excel.Range cells = sheet.Cells;
>  Excel.Range cell = cells[4, 2];
>  cell.Value = "TEST";
>  Marshal.ReleaseComObject(cell);
>  Marshal.ReleaseComObject(cells);
>  Marshal.ReleaseComObject(sheet);
>  Marshal.ReleaseComObject(sheets);
> のような記述が必要です。
>
> もちろん、まだ使っている最中のオブジェクトを解放するのは NG です。
>
>
> さらに言えば、COM オブジェクトを foreach で処理するのも避けてください。
>  foreach(Excel.Worksheet sheet in sheets)
>  {
>   // :
>   // :
>   Marshal.ReleaseComObject(sheet);
>  }
> foreach を使って列挙すると、System.Runtime.InteropServices.CustomMarshalers.EnumeratorViewOfEnumVariant という
> COM オブジェクトが内部的に生成されてしまい、それが解放漏れの要因になることがあるためです。
>
> 可能な場合は、かわりに for ループを使うようにします。
>  for (int i = 1; i <= sheets.Count; i++)
>  {
>   Excel.Worksheet sheet = (Excel.Worksheet)Sheets[i];
>   // :
>   // :
>   Marshal.ReleaseComObject(sheet);
>  }
>
>
> Excel の場合には for で代用できるはずですが、どうしても foreach しか使えない COM オブジェクトの場合は、
> ICustomAdapter.GetUnderlyingObjectメソッドを使って解放する手法もあります。今回は不要ですが。
>
> また、COM オブジェクトのメソッドが、COM オブジェクトを object または dynamic 型の引数で
> 受け渡す仕様になっている場合も注意が必要です。このようなメソッドを呼び出した場合、
> 引き渡した COM オブジェクトの参照カウントが意図せず増加してしまうことがあるためです。


COMオブジェクトの解放についてはまだまだ勉強が必要そうです。
.が2つ以上になると解放漏れが発生してしまうと調べたら記載があったので極力変数に入れるようにしています。
foreachでのCOM オブジェクト生成については初めて知りました。
for文を使用するようにいたします。




>>このような場合にFinallyにて解放処理を行いたい場合、
>>tryのなかにまたtryを入れていくしかないのでしょうか、
>>またはforeachの処理を別の関数で行いその関数でFinallyを設けるのが良いのでしょうか。
>
> この掲示板の左上 [C# と VB.NET 入門] のリンクから、sitemap で
> [サンプル]-[Visual C# .NET]-[プログラミング全般]-[COM オブジェクトの参照カウントを解放する]
> を見ると、try ブロックを使った解放例が載っていますね。
>
>
> こうした解放が面倒な場合には、メニューの [ツール]-[nuget パッケージ マネージャー] から
> 参照:「NetOffice」で検索し、一覧に出てくる「NetOffice.Excel」をプロジェクトにインストールしてみてください。
> これにより、解放の手間が大幅に削減されます。
>
> // using Excel = NetOffice.ExcelApi;
>
> void Sample()
> {
>  using (Excel.Application app = new Excel.Application()) {
>   Excel.Workbook book = app.Workbooks.Open(@"test.xlsx");
>   Excel.Worksheet workSheet = (Excel.Worksheet)book.Worksheets[1];
>   System.Diagnostics.Debug.WriteLine(workSheet.Cells[1, 1].Value);
>   book.Close();
>   app.Quit();
>  }
> }
> https://github.com/NetOfficeFw/NetOffice

一応印刷処理をする方法を調べてその結果Microsoft.Office.Interop.Excelにて仕方なく使用しています。
引用返信 編集キー/
■97490 / inTopicNo.6)  Re[5]: COMの解放もれについて
□投稿者/ 魔界の仮面弁士 (3106回)-(2021/05/26(Wed) 01:52:46)
No97489 (初# さん) に返信
> private void FinalReleaseComObject(object o)
> {
> if (o != null) Marshal.FinalReleaseComObject(o);
> }
> どこかのサイトで見たものをコピーしただけですが、このようなメソッドで解放をしています。

ReleaseComObject / FinalReleaseComObject は COM オブジェクト専用であることから、
 if (o != null && Marshal.IsComObject(o))
としておくと、より安全かと思います。

同じ COM オブジェクトでも、プライマリ相互運用機能アセンブリ(PIA) の刷新によって、
以前は COM オブジェクトして管理されていたものがマネージ化され扱いやすくなり、
一部のオブジェクトが ReleaseComObject の対象外となるケースがあったためです。
(例:ADODB ライブラリにおける Fields や Field オブジェクトなど)


> dataGridView1.Rows.Add(false, DBNull.Value, strFileName, strFilePath, sheetName);
> このような記載に修正いたしました。
> ボタンのテキストはすべて同じなので指摘されたとおりのコードで実装しております。

UseColumnTextForButtonValue を true にしている場合、ボタン列のセルごとの値は無視されるので、
ボタン列に何を設定しても構いません。DBNull.Value でも "" でも "dummy" でも OK です。


> .が2つ以上になると解放漏れが発生してしまうと調べたら記載があったので極力変数に入れるようにしています。

確かに COM オブジェクトを使っている場合は、複数の . がある行で解放漏れになりがちです。

とはいえ問題となるのは、あくまでも COM オブジェクトの場合だけあり、
マネージオブジェクトのメンバー呼び出しなら、2 つ以上の . があっても問題にはなりません。

名前空間や列挙型のための「.」は対象外なので、
 var align = Microsoft.Office.Interop.Excel.XlHAlign.xlHAlignCenter;
 int n = align.ToString().Length;
といったコードだと、. が複数連なっていても問題ありませんし、解放も不要です。

一方、分かりにくいのが先に述べた
 sheet = book.Worksheets["Sheet1"];

 range = sheet.Cells[1, 1];
のパターンのケースですね。
これらは 「.」が 1 つしか無いですが、 No97485 で示したように、
 sheets = book.Worksheets;
 sheet = sheets["Sheet1"];

 range1 = sheet.Cells;
 range2 = range1[1, 1];
のように書かねばなりません。


> foreachでのCOM オブジェクト生成については初めて知りました。
> for文を使用するようにいたします。
下記は foreach で COM の解放漏れが生じる事例と、その対処例(GetUnderlyingObjectメソッド)です。
Excel ではなく Visual Source Safe の事例なので、今回は直接関係ありませんが参考までに。
https://divakk.co.jp/aoyagi/csharp_tips_vssenum.html


>>参照:「NetOffice」で検索し、一覧に出てくる「NetOffice.Excel」をプロジェクトにインストールしてみてください。
>>これにより、解放の手間が大幅に削減されます。
>>// using Excel = NetOffice.ExcelApi;
> 一応印刷処理をする方法を調べてその結果Microsoft.Office.Interop.Excelにて仕方なく使用しています。

NetOffice.Excel は Microsoft.Office.Interop.Excel をカプセル化したものであり、
印刷処理もそのまま使えます。上位オブジェクトだけ解放すれば、下位のオブジェクトも
解放できるなどの工夫がされています。とはいえ既に Microsoft.Office.Interop.Excel での
解放漏れも解決しているわけですし、今から乗り換える必要性も無いですね。忘れてください。
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -