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

わんくま同盟

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

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

ツリー一括表示

メモリリークに関して /kiku (19/07/02(Tue) 17:06) #91509
Re[1]: メモリリークに関して /PANG2 (19/07/02(Tue) 17:53) #91511
│└ Re[2]: メモリリークに関して /kiku (19/07/03(Wed) 09:06) #91516
Re[1]: メモリリークに関して /WebSurfer (19/07/02(Tue) 18:32) #91513
│└ Re[2]: メモリリークに関して /kiku (19/07/03(Wed) 09:10) #91517
│  └ Re[3]: メモリリークに関して /WebSurfer (19/07/03(Wed) 11:43) #91519
│    ├ Re[4]: メモリリークに関して /kiku (19/07/03(Wed) 13:26) #91521
│    │└ Re[5]: メモリリークに関して /WebSurfer (19/07/03(Wed) 14:40) #91522
│    │  └ Re[6]: メモリリークに関して /kiku (19/07/03(Wed) 17:09) #91526
│    └ Re[4]: メモリリークに関して /魔界の仮面弁士 (19/07/03(Wed) 17:07) #91525
│      └ Re[5]: メモリリークに関して /kiku (19/07/03(Wed) 17:43) #91528
│        └ Re[6]: メモリリークに関して /魔界の仮面弁士 (19/07/03(Wed) 19:37) #91530
│          ├ Re[7]: メモリリークに関して /魔界の仮面弁士 (19/07/04(Thu) 09:13) #91534
│          │└ Re[8]: メモリリークに関して /kiku (19/07/04(Thu) 09:33) #91536
│          └ Re[7]: メモリリークに関して /kiku (19/07/04(Thu) 09:08) #91533
│            └ Re[8]: メモリリークに関して /魔界の仮面弁士 (19/07/04(Thu) 09:59) #91537
│              └ Re[9]: メモリリークに関して /kiku (19/07/04(Thu) 10:46) #91538
│                └ Re[10]: メモリリークに関して /魔界の仮面弁士 (19/07/04(Thu) 12:11) #91539
│                  └ Re[11]: メモリリークに関して /kiku (19/07/04(Thu) 13:13) #91540
│                    └ Re[12]: メモリリークに関して /魔界の仮面弁士 (19/07/04(Thu) 15:18) #91541
│                      ├ Re[13]: メモリリークに関して /魔界の仮面弁士 (19/07/04(Thu) 15:26) #91543
│                      │└ Re[14]: メモリリークに関して /kiku (19/07/04(Thu) 16:12) #91545
│                      │  └ Re[15]: メモリリークに関して /kiku (19/07/04(Thu) 16:14) #91546
│                      │    └ Re[16]: メモリリークに関して /kiku (19/07/08(Mon) 11:36) #91569
│                      │      ├ Re[17]: メモリリークに関して /kiku (19/07/08(Mon) 11:36) #91570 解決済み
│                      │      └ Re[17]: メモリリークに関して /魔界の仮面弁士 (19/07/08(Mon) 14:19) #91571 解決済み
│                      │        └ Re[18]: メモリリークに関して /kiku (19/07/09(Tue) 10:26) #91572 解決済み
│                      │          └ Re[19]: メモリリークに関して /shu (19/07/10(Wed) 15:55) #91585 解決済み
│                      │            ├ Re[20]: メモリリークに関して /魔界の仮面弁士 (19/07/10(Wed) 16:37) #91586 解決済み
│                      │            │└ Re[21]: メモリリークに関して /kiku (19/07/11(Thu) 08:49) #91588 解決済み
│                      │            └ Re[20]: メモリリークに関して /kiku (19/07/11(Thu) 08:43) #91587 解決済み
│                      └ Re[13]: メモリリークに関して /kiku (19/07/04(Thu) 16:08) #91544
Re[1]: メモリリークに関して /魔界の仮面弁士 (19/07/02(Tue) 20:22) #91515
  └ Re[2]: メモリリークに関して /kiku (19/07/03(Wed) 09:14) #91518
    ├ Re[3]: メモリリークに関して /魔界の仮面弁士 (19/07/03(Wed) 13:17) #91520
    │└ Re[4]: メモリリークに関して /kiku (19/07/03(Wed) 16:50) #91523
    │  ├ Re[5]: メモリリークに関して /Hongliang (19/07/03(Wed) 17:04) #91524
    │  │└ Re[6]: メモリリークに関して /kiku (19/07/03(Wed) 17:20) #91527
    │  └ Re[5]: メモリリークに関して /PANG2 (19/07/03(Wed) 23:35) #91531
    │    └ Re[6]: メモリリークに関して /kiku (19/07/04(Thu) 09:23) #91535
    └ Re[3]: メモリリークに関して /魔界の仮面弁士 (19/07/03(Wed) 19:14) #91529
      └ Re[4]: メモリリークに関して /kiku (19/07/04(Thu) 09:00) #91532


親記事 / ▼[ 91511 ] ▼[ 91513 ] ▼[ 91515 ]
■91509 / 親階層)  メモリリークに関して
□投稿者/ kiku (98回)-(2019/07/02(Tue) 17:06:54)

分類:[C#] 

下記環境にて下記ソース(全体の抜粋)で、フォームのnewとdisposeを繰り返すと、
メモリ使用量がどんどん増加し、OS全体が不安定になる現象が発生しています。
下記ソースの★の部分を追加すると、メモリ使用量の増加が抑えられ、
OSが不安定になることはなくなりました。
※通常はGCで回収されると思っているのですが、
 その発生を確認することができなかった。
※通常はControls.Addしているのでフォーム解放時にdisposeされる認識

そこで質問です。

質問1
 本当にフォントやラベルが解放されないことを確認する方法はないでしょうか?

質問2
 解放されないことが確認できたとして、
 ★の部分の対処は適切でしょうか? 

●環境
 .NETCompactFramework3.5
 WindowsCE6.0
 C#
 VS2008

●ソース
    partial class Test
    {
        /// <summary>
        /// 必要なデザイナ変数です。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// 使用中のリソースをすべてクリーンアップします。
        /// </summary>
        /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
                this.lbl_serialnumber.Font.Dispose();//★対策追加
                this.lbl_serialnumber.Dispose();//★対策追加
            }
            base.Dispose(disposing);
        }

        #region Windows フォーム デザイナで生成されたコード

        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();//★対策追加
            this.lbl_serialnumber = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // lbl_serialnumber
            // 
            this.lbl_serialnumber.BackColor = System.Drawing.Color.White;
            this.lbl_serialnumber.Font = new System.Drawing.Font("MS ゴシック", 16F, System.Drawing.FontStyle.Bold);
            this.lbl_serialnumber.Location = new System.Drawing.Point(5, 115);
            this.lbl_serialnumber.Name = "lbl_serialnumber";
            this.lbl_serialnumber.Size = new System.Drawing.Size(310, 20);
            this.lbl_serialnumber.Text = "lbl_serialnumber";
            // 
            // Test
            // 
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit;
            this.ClientSize = new System.Drawing.Size(640, 480);
            this.ControlBox = false;
            this.Controls.Add(this.lbl_serialnumber);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "Test";
            this.Text = "Test";
            this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
            this.ResumeLayout(false);
        }

        #endregion

        private System.Windows.Forms.Label lbl_serialnumber;
    }

[ □ Tree ] 返信 編集キー/

▲[ 91509 ] / ▼[ 91516 ]
■91511 / 1階層)  Re[1]: メモリリークに関して
□投稿者/ PANG2 (308回)-(2019/07/02(Tue) 17:53:27)
No91509 (kiku さん) に返信
> 質問1
>  本当にフォントやラベルが解放されないことを確認する方法はないでしょうか?

WeakReferenceを List<WeakReference> に貯蔵して終了時に確認する。

http://pro.art55.jp/?eid=1109086
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91511 ] / 返信無し
■91516 / 2階層)  Re[2]: メモリリークに関して
□投稿者/ kiku (99回)-(2019/07/03(Wed) 09:06:41)
No91511 (PANG2 さん) に返信
> ■No91509 (kiku さん) に返信
>>質問1
>> 本当にフォントやラベルが解放されないことを確認する方法はないでしょうか?
>
> WeakReferenceを List<WeakReference> に貯蔵して終了時に確認する。
>
> http://pro.art55.jp/?eid=1109086

弱い参照を保持することが出来るんですね。
ご案内頂きありがとうございます。
試してみます。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91509 ] / ▼[ 91517 ]
■91513 / 1階層)  Re[1]: メモリリークに関して
□投稿者/ WebSurfer (1852回)-(2019/07/02(Tue) 18:32:00)
No91509 (kiku さん) に返信

質問に対する直接の回答にはなってないですが・・・

> 質問1
>  本当にフォントやラベルが解放されないことを確認する方法はないでしょうか?

System.Drawing.Font クラスなどアンマネージドリソースを保持しているクラスは、IDisposable
インターフェイスを継承して Dispose パターンを使った実装がされていて、Dispose メソッドで
アンマネージドリソースを開放できるようになっているはずです。

アンマネージドリソースは GC では解放されないので、確認するまでもなく Dispose しなければ
解放されてないはずです。

なので、IDisposable インターフェイス を継承して Dispose メソッドを実装しているクラスは全
て、そのオブジェクトが使用されなくなった時点で Dispose メソッドを呼び出すべきということ
のようです。

詳しくは、自分のブログで恐縮ですか、以下の記事を見てください。

Dispose パターン
http://surferonwww.info/BlogEngine/post/2019/05/31/dispose-pattern.aspx


> 質問2
>  解放されないことが確認できたとして、
>  ★の部分の対処は適切でしょうか? 

上で紹介した記事に書いてある通り「Dispose パターン」というものがあります。Visual Studio
で雛形を自動生成してくれますので、それを使うのがよさそうです。

自分で作るカスタムクラスの場合、マネージドリソースしか保持しない場合は Dispose パターン
の実装は不要です。必要なのはアンマネージドリソースを保持する場合のみですが、それには以下
のケースがあると思います。

(1) Dispose パターンを実装した .NET のクラスのインスタンスを保持している。

(2) クラス内でアンマネージドリソースを取得し、それを保持している。

プログラマが Dispose するのを忘れた場合でも GC は働きます。GC ではアンマネージドリソース
は開放できませんが、その際ファイナライザが呼び出されます。なので、Dispose パターンによっ
て、最悪でもファイナライザでアンマネージドリソースを開放できるようにします。

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91513 ] / ▼[ 91519 ]
■91517 / 2階層)  Re[2]: メモリリークに関して
□投稿者/ kiku (100回)-(2019/07/03(Wed) 09:10:40)
No91513 (WebSurfer さん) に返信
> ■No91509 (kiku さん) に返信
>
> 質問に対する直接の回答にはなってないですが・・・

下記1と2を混同していたため、混乱させてしまいました。
通常下記2でVusualStudiooの自動生成されたソース内の
Control.Addされるため、Control.Dispose内で自動的に
マネージドリソースもdisposeされる認識でした。
下記1は関係ないですね。

1.
>※通常はGCで回収されると思っているのですが、
> その発生を確認することができなかった。

2.
>※通常はControls.Addしているのでフォーム解放時にdisposeされる認識

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91517 ] / ▼[ 91521 ] ▼[ 91525 ]
■91519 / 3階層)  Re[3]: メモリリークに関して
□投稿者/ WebSurfer (1853回)-(2019/07/03(Wed) 11:43:19)
No91517 (kiku さん) に返信

自信度は高くなく間違っているかもしれませんので、以下のレスはそのつもりで見てください。

> 通常下記2でVusualStudiooの自動生成されたソース内の
> Control.Addされるため、Control.Dispose内で自動的に
> マネージドリソースもdisposeされる認識でした。

Form コントロールの場合、Dispose(), Dispose(bool) の実装は Control toha違うようです。
下のリンクをクリックして画像を見てください。

http://surferonwww.info/BlogEngine/image.axd?picture=2019%2f7%2fFormDispose0.jpg

VS2008 のヘルプの画像ですが、このあたりはたぶん最新版でも同じはずです。Form に実装され
ている Dispose() と Dispose(boolean) は上の画像の一番上と一番下のものです。

一番下の Form.Dispose (Boolean) メソッドには CE がサポートしているというアイコンがない
のに注目してください。

CE はどうしているかは不明ですが、魔界の仮面弁士さんのレスにある Control.Dispose(bool)
メソッド(画像の上から 3 番目のもの)を使っているのではなかろうかと思われます。

そうであれば、Dispose パターンが実装されていれば、Dispose メソッドを呼び出せば上の画像の
ヘルプの Control.Dispose(Boolean) メソッドの解説に書いてあったように、

"Control とその子コントロールが使用しているアンマネージ リソースを解放します。オプションで、
マネージ リソースも解放します。"

・・・となると思われます。


CE は関係なさそうですが、ご参考までに上の画像のヘルプの Form.Dispose メソッド (Boolean)
の解説の抜粋も書いておきますね。

*** Quote ***
Form で使用されていたリソース (メモリを除く) を解放します。

このメソッドは、パブリック メソッド Dispose と Finalize メソッドによって呼び出されます。
Dispose は、disposing パラメータに true を設定して、プロテクト メソッド Dispose(Boolean)
を呼び出します。Finalize は、disposing に false を設定して、Dispose を呼び出します。

disposing パラメータが true の場合、このメソッドは、この Form から参照されるすべての
マネージ オブジェクトが保持しているリソースをすべて解放します。このメソッドは、参照
される各オブジェクトの Dispose メソッドを呼び出します。

Dispose は、フォームが Show メソッドを使用して表示した場合に自動的に呼び出されます。
ShowDialog などの別のメソッドを使用する場合、またはフォームがまったく表示されない場合は、
アプリケーション内で Dispose を明示的に呼び出す必要があります。
*** Unqoute ***

ちなみに、CE 版でない通常の Windows Forms アプリが Form.Dispose (Boolean) メソッドを
使っていることは間違いなさそうです。

http://surferonwww.info/BlogEngine/image.axd?picture=2019%2f7%2fFormDispose1.jpg

質問者さんの CE 版の場合はどうなってるでしょう?
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91519 ] / ▼[ 91522 ]
■91521 / 4階層)  Re[4]: メモリリークに関して
□投稿者/ kiku (102回)-(2019/07/03(Wed) 13:26:36)
No91519 (WebSurfer さん) に返信
> 質問者さんの CE 版の場合はどうなってるでしょう?

回答になっているかわかりませんが、
Formは下記のようにusingにて囲っているため、
必ずフォームのDipposeが呼ばれる認識でいます。

            using (var form = new Test())
            {
                form.ShowDialog();
            }

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91521 ] / ▼[ 91526 ]
■91522 / 5階層)  Re[5]: メモリリークに関して
□投稿者/ WebSurfer (1854回)-(2019/07/03(Wed) 14:40:36)
No91521 (kiku さん) に返信

>>質問者さんの CE 版の場合はどうなってるでしょう?

その意味は、上の No91519 に張ったリンク先の画像(下に再掲)で base.Dispose(disposing; にカーソ
ルを当てて見ると、自分の環境では Form.Disposing(bool) となっていますが、質問者さんの CE 環境で
はどうなってますかという意味です。

http://surferonwww.info/BlogEngine/image.axd?picture=2019%2f7%2fFormDispose1.jpg

CE では Form.Disposing(bool) はサポートされてないそうなので。


> Formは下記のようにusingにて囲っているため、
> 必ずフォームのDipposeが呼ばれる認識でいます。
> 
>             using (var form = new Test())
>             {
>                 form.ShowDialog();
>             }
> 

Dispose は using を抜けるときに呼ばれると自分も思います。でも、No91519 で書きました、

> Dispose は、フォームが Show メソッドを使用して表示した場合に自動的に呼び出されます。
> ShowDialog などの別のメソッドを使用する場合、またはフォームがまったく表示されない場合は、
> アプリケーション内で Dispose を明示的に呼び出す必要があります。

の「ShowDialog などの別のメソッドを使用する場合」が気になります。気にしすぎだろうとは思
いますが。


[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91522 ] / 返信無し
■91526 / 6階層)  Re[6]: メモリリークに関して
□投稿者/ kiku (104回)-(2019/07/03(Wed) 17:09:11)
No91522 (WebSurfer さん) に返信
> その意味は、上の No91519 に張ったリンク先の画像(下に再掲)で base.Dispose(disposing; にカーソ
> ルを当てて見ると、自分の環境では Form.Disposing(bool) となっていますが、質問者さんの CE 環境で
> はどうなってますかという意味です。
> http://surferonwww.info/BlogEngine/image.axd?picture=2019%2f7%2fFormDispose1.jpg
> CE では Form.Disposing(bool) はサポートされてないそうなので。

画像が貼れなく手打ちになってしまったため、スペルミスがあるかもしれませんが、
下記の説明が出ていました。
Releases the unmanaged resources used by the System.Windows.Forms.Control and its child controls and optionally releases the managed resources.


> の「ShowDialog などの別のメソッドを使用する場合」が気になります。気にしすぎだろうとは思
> いますが。

なるほど、Formのdisposeが呼ばれていない可能性があるということですね。
考えに入れることにします。
disposeが呼ばれたがどうかの確認がやっぱり必要になってきそうです。
これが今うまくできずに困っています。

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91519 ] / ▼[ 91528 ]
■91525 / 4階層)  Re[4]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2215回)-(2019/07/03(Wed) 17:07:14)
No91519 (WebSurfer さん) に返信
> Form コントロールの場合、Dispose(), Dispose(bool) の実装は Control toha違うようです。
> 下のリンクをクリックして画像を見てください。
> http://surferonwww.info/BlogEngine/image.axd?picture=2019%2f7%2fFormDispose0.jpg

Dispose の実装は、Label と Form で差異が無さそうに見えます。
少なくとも .NET Compact Framework においては。


手元の VS2008 環境で、Form1 の継承関係をたどってみました。
CE 機が無いので、エミュレーターでの動作ですけれども。


 × は Dispose(boolean) が実装されていないクラス
 ※ は Dispose(boolean) が派生元にあるが、実装しなおしてないクラス
 ☆ は Sub Overrides Dispose(boolean) として実装しなおされているクラス
 ★ は Sub Overridable Dispose(boolean) として最初に実装されているクラス


.NET Compact Framework の場合

× Object
└× MarshalByRefObject
 └★ Component
  └☆ Control
   └※ ScrollableControl
    └※ ContainerControl
     └※ Form
      └☆ Form1


.NET Framework の場合

× Object
└× MarshalByRefObject
 └★ Component
  └☆ Control
   └☆ ScrollableControl
    └☆ ContainerControl
     └☆ Form
      └☆ Form1



Imports System.Reflection

Public Class Form1
  Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    ListBox1.DataSource = New Type(-1) {}
    ListBox1.DisplayMember = "Name"
    ListBox2.DataSource = New Type(-1) {}
    ListBox2.DisplayMember = "Name"
    ComboBox1.DataSource = New Type() { _
      GetType(Form1), _
      GetType(Form), _
      GetType(Label), _
      GetType(Panel), _
      GetType(Control) _
    }
    ComboBox1.DisplayMember = "FullName"
    TextBox1.Text = "Dispose"
    CheckBox1.Checked = True
  End Sub

  Private Sub Analyze(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged, CheckBox1.CheckStateChanged, TextBox1.TextChanged
    Analyze()
  End Sub

  Private Sub Analyze()
    Dim selType As Type = DirectCast(ComboBox1.SelectedItem, Type)
    Dim methodName As String = TextBox1.Text
    Dim declaredOnly As Boolean = CheckBox1.Checked
    CheckBox1.Enabled = Not String.IsNullOrEmpty(methodName)

    'Sub Dispose() の実装
    ListBox1.DataSource = EnumrateMethodImpl(selType, declaredOnly, methodName)

    'Sub Dispose(Boolean) の実装
    ListBox2.DataSource = EnumrateMethodImpl(selType, declaredOnly, methodName, GetType(Boolean))
  End Sub

  Public Shared Function EnumrateMethodImpl(ByVal t As Type, ByVal declaredOnly As Boolean, ByVal methodName As String, ByVal ParamArray params As Type()) As Type()
    Dim bf As BindingFlags = BindingFlags.NonPublic Or BindingFlags.Public Or BindingFlags.Instance
    If declaredOnly Then
      bf = bf Or BindingFlags.DeclaredOnly
    End If
    Dim targetType As Type = t
    Dim types As New List(Of Type)()
    Dim bt As Type = targetType
    Do
      If String.IsNullOrEmpty(methodName) Then
        types.Add(bt)
      ElseIf bt.GetMethod(methodName, bf, Type.DefaultBinder, params, Nothing) IsNot Nothing Then
        types.Add(bt)
      End If
      bt = bt.BaseType
    Loop Until bt Is Nothing
    Dim result As Type() = types.ToArray()
    Array.Reverse(result)
    Return result
  End Function
End Class
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91525 ] / ▼[ 91530 ]
■91528 / 5階層)  Re[5]: メモリリークに関して
□投稿者/ kiku (106回)-(2019/07/03(Wed) 17:43:52)
No91525 (魔界の仮面弁士 さん) に返信
> ■No91519 (WebSurfer さん) に返信
>>Form コントロールの場合、Dispose(), Dispose(bool) の実装は Control toha違うようです。
>>下のリンクをクリックして画像を見てください。
>>http://surferonwww.info/BlogEngine/image.axd?picture=2019%2f7%2fFormDispose0.jpg
> Dispose の実装は、Label と Form で差異が無さそうに見えます。
> 少なくとも .NET Compact Framework においては。

わざわざ検証いただきありがとうございます。

> 手元の VS2008 環境で、Form1 の継承関係をたどってみました。
> CE 機が無いので、エミュレーターでの動作ですけれども。

貼って頂いたソースを実機で動かすことはできましたが
その結果をどのように掲示板に報告するのが良いのか
ソースの内容を理解できていないため、
わかりませんでした。
継承関係を同じように報告すべきだとは思うのですが、
ごめんなさい。

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91528 ] / ▼[ 91534 ] ▼[ 91533 ]
■91530 / 6階層)  Re[6]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2217回)-(2019/07/03(Wed) 19:37:39)
No91528 (kiku さん) に返信
> その結果をどのように掲示板に報告するのが良いのか
> ソースの内容を理解できていないため、
> わかりませんでした。

とりあえず、Compact か否かで結果が異なるという程度の理解でも可ということで。
Fx と CFx で結果が異なることを確認しておきたかっただけです。
(これも逆アセンブル扱いになるのだろうか…?)


.NET Framework の場合、Reference Source を見れば、
Form.Dispose(bool) が実装されていて、
その中で、ContainerControl.Dispose(bool) が呼ばれていることがわかります。
そしてそれが、Control.Dispose(bool) を呼び出していることも。
https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Form.cs,3793


一方 .NET Comapct Framwork の場合、ソースコードは提供されていませんが、
No91525 の実験結果から、Form.Dispose(bool) は特に存在しておらず、
直接 Control.Dispose(bool) がそのまま呼ばれていることが確認できました。



No91529 (魔界の仮面弁士) に追記
> あと、経験則的なところで言えば、.NET Compact Framework 環境の場合、
> オブジェクトの使用後は、参照変数に Nothing 代入を積極的に行った方が良いみたいです。

Nothing の代入有無で、本当に解放状況が変化するかどうかを確認してみました。


端末を再起動して、空きメモリが十分に確保されている状態において、
Button1 でモードレス フォームを表示させた後、
Button2 でそれを Close させています。

すると、Close 直後の ★の行の Nothing 代入がコメントアウトされていた場合、
Form2 および LabelEx が直ちに Dispose されないケースが確認できました。

しかし Nothing 代入するようにした場合は、Button2 の時点ですぐに Dispose されるようになりました。


Partial Public Class Form1
  Inherits Form

  Private f As Form2 = Nothing

  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    f = New Form2()
    f.Show()
  End Sub

  Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    If f IsNot Nothing AndAlso Not f.IsDisposed Then
      f.Close()
      'f.Dispose()

      'f = Nothing  '★
    End If
  End Sub
End Class

Public NotInheritable Class LabelEx
  Inherits Label
  Public ReadOnly Id As Long
  Public Sub New()
    Id = Now.Ticks
    Trace.WriteLine(String.Format("==> {0} 生成 {1}", MyClass.GetType().Name, Id))
  End Sub
  Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    MyBase.Dispose(disposing)
    Trace.WriteLine(String.Format("<== {0} 破棄 {1} ({2})", MyClass.GetType().Name, Id, disposing))
  End Sub
End Class

Partial Public Class Form2
  Inherits Form
  Public ReadOnly Id As Long
  Public Sub New()
    Id = Now.Ticks
    InitializeComponent()
    Trace.WriteLine(String.Format("==> {0} 生成 {1}", MyClass.GetType().Name, Id))
  End Sub
  Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
    MyBase.OnLoad(e)
    Controls.Add(New LabelEx() With {.Text = .Id.ToString()})
    SendToBack()
  End Sub
  Private Sub Form2_Disposed(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Disposed
    Trace.WriteLine(String.Format("<== {0} 破棄 {1}", MyClass.GetType().Name, Id))
  End Sub
  Protected Overrides Sub OnDoubleClick(ByVal e As System.EventArgs)
    MyBase.OnDoubleClick(e)
    Close()
  End Sub
End Class



ただ、何度も実験を繰り返すと空きメモリが減ってくるようで、
その場合はどうやら GC が発動するらしく、Nothing 代入せずとも
Dispose される結果になるようです。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91530 ] / ▼[ 91536 ]
■91534 / 7階層)  Re[7]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2218回)-(2019/07/04(Thu) 09:13:04)
No91530 (魔界の仮面弁士) に追記
>>あと、経験則的なところで言えば、.NET Compact Framework 環境の場合、
>>オブジェクトの使用後は、参照変数に Nothing 代入を積極的に行った方が良いみたいです。
> Nothing の代入有無で、本当に解放状況が変化するかどうかを確認してみました。


すみません!
C# の質問に、うっかり VB で回答してたことに、今朝になって気がつきました。

# .NET 対応前、CE アプリを eVB3 で行ってきた期間が長すぎて、
# CE 環境だと反射的に VB で書く癖が…。orz


> 端末を再起動して、空きメモリが十分に確保されている状態において、
> Button1 でモードレス フォームを表示させた後、
> Button2 でそれを Close させています。
> すると、Close 直後の ★の行の Nothing 代入がコメントアウトされていた場合、
> Form2 および LabelEx が直ちに Dispose されないケースが確認できました。

上記の「Close 後、直ちに Dispose されていない」現象ですが、
改めて C# (CFx 3.5 SP1) でも実験してみたところ、上記の状況を再現させられませんでした。
Close されたら、null を代入せずとも即座に Dispose されているようにみえる…。


改めて、昨日の VB コードでも再検証してみましたが、
昨日と比べて問題の再現率が大幅に低下していました。
狙って再現させることは難しそうです。


public partial class Form1 : Form
{
  private Form2 f = null;
  public Form1()
  {
    InitializeComponent();
    DoubleClick += delegate { Close(); };
  }

  /// <summary>新規に Form2 を表示するコード</summary>
  private void button1_Click(object sender, EventArgs e)
  {
    f = new Form2();
    f.Show();
  }

  /// <summary>直前に表示された Form2 を閉じるコード</summary>
  private void button2_Click(object sender, EventArgs e)
  {
    if (f != null /* && !f.IsDisposed */)
    {
      f.Close();
      // f = null; // ★
    }
  }
}

// -=-=-=-=-=-=-=-=-=-=-=-=-

public partial class Form2 : Form
{
  private readonly Guid Id = Guid.Empty;
  public Form2()
  {
    Id = Guid.NewGuid();
    Trace.WriteLine(String.Format("==> {0} 生成 {1}", GetType().Name, Id));
    InitializeComponent();
    Controls.Add(new LabelEx());
    Text = Id.ToString();
    DoubleClick += delegate { Close(); };
    // Disposed += delegate { Trace.WriteLine(string.Format("<== {0} 破棄 {1}", GetType().Name, Id)); };
  }
  public sealed class LabelEx : Label
  {
    private readonly Guid Id;
    public LabelEx()
    {
      Id = Guid.NewGuid();
      Trace.WriteLine(String.Format("==> {0} 生成 {1}", GetType().Name, Id));
      Text = Id.ToString();
      //Disposed += delegate { Trace.WriteLine(string.Format("<== {0} 破棄 {1}", GetType().Name, Id)); };
    }
    protected override void Dispose(bool disposing)
    {
      Trace.WriteLine(string.Format("<== {0} 破棄 {1} ({2})", GetType().Name, Id, disposing));
      base.Dispose(disposing);
    }
  }
  protected override void Dispose(bool disposing)
  {
    Trace.WriteLine(string.Format("<== {0} 破棄 {1} ({2})", GetType().Name, Id, disposing));
    if (disposing && (components != null))
    {
      components.Dispose();
    }
    base.Dispose(disposing);
  }
}
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91534 ] / 返信無し
■91536 / 8階層)  Re[8]: メモリリークに関して
□投稿者/ kiku (110回)-(2019/07/04(Thu) 09:33:19)
No91534 (魔界の仮面弁士 さん) に返信
> すみません!
> C# の質問に、うっかり VB で回答してたことに、今朝になって気がつきました。

ぜんぜん大丈夫です。
VBでもC#でもそれほど大差ないですし。

> 上記の「Close 後、直ちに Dispose されていない」現象ですが、
> 改めて C# (CFx 3.5 SP1) でも実験してみたところ、上記の状況を再現させられませんでした。
> Close されたら、null を代入せずとも即座に Dispose されているようにみえる…。

なるほど、nullを代入せずとも良さそうということですね。
ご紹介頂いたnullを設定して効果があったという記事もありますし、
念のため、nullを代入してみるのも良いかも思いました。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91530 ] / ▼[ 91537 ]
■91533 / 7階層)  Re[7]: メモリリークに関して
□投稿者/ kiku (108回)-(2019/07/04(Thu) 09:08:37)
No91530 (魔界の仮面弁士 さん) に返信
> 一方 .NET Comapct Framwork の場合、ソースコードは提供されていませんが、
> No91525 の実験結果から、Form.Dispose(bool) は特に存在しておらず、
> 直接 Control.Dispose(bool) がそのまま呼ばれていることが確認できました。

だとすると、
子コントロールは明示的にDisposeしなくても良く、
子コントロールが保持するFontなどのアンマネージリソースのみをDisposeすれば良いのでしょうか?

> Nothing の代入有無で、本当に解放状況が変化するかどうかを確認してみました。
> 端末を再起動して、空きメモリが十分に確保されている状態において、
> Button1 でモードレス フォームを表示させた後、
> Button2 でそれを Close させています。
> すると、Close 直後の ★の行の Nothing 代入がコメントアウトされていた場合、
> Form2 および LabelEx が直ちに Dispose されないケースが確認できました。
> しかし Nothing 代入するようにした場合は、Button2 の時点ですぐに Dispose されるようになりました。
> ただ、何度も実験を繰り返すと空きメモリが減ってくるようで、
> その場合はどうやら GC が発動するらしく、Nothing 代入せずとも
> Dispose される結果になるようです。

そうすると、子コントロールは明示的にDisposeを行って、
子コントロールにnull(今回C#なため)を設定し、
子コントロールが保持するアンマネージリソースに対しても明示的にDisposeを行い、
その参照もnullに設定するのが良さそうと理解しました。

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91533 ] / ▼[ 91538 ]
■91537 / 8階層)  Re[8]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2219回)-(2019/07/04(Thu) 09:59:49)
No91533 (kiku さん) に返信
> 子コントロールは明示的にDisposeしなくても良く、
> 子コントロールが保持するFontなどのアンマネージリソースのみをDisposeすれば良いのでしょうか?

.NET Framework の場合、Control.Font プロパティは Ambient なので、手出しすべきではありません。
一つの Font インスタンスが、複数のコントロールで扱われる可能性があるためです。


.NET Compact Framework の場合は、正直分かりません。
各種処理が Microsoft.AGL.Forms 名前空間経由で、DllImport されてしまっていて、
処理内容を追跡できそうにありません。
Microsoft 有償サポートに問い合わせようにも、ライフサイクルが既に終了してしまっていますし。


以下、Font プロパティが返すインスタンスに関する実験コード。

var sb = new StringBuilder();
var f1 = textBox1.Font;
var f2 = f1;
textBox1.Font = f1;
var f3 = textBox1.Font;

sb.AppendLine("--等価判定の実装状況--");
sb.AppendLine(string.Format("f1==f2         :{0}", f1 == f2));
sb.AppendLine(string.Format("f1.Eq(f2)      :{0}", f1.Equals(f2)));
sb.AppendLine(string.Format("RefEq(f1, f2)  :{0}", ReferenceEquals(f1, f2)));
sb.AppendLine("--Fontプロパティ--");
sb.AppendLine(string.Format("f2==f3         :{0}", f2 == f3));
sb.AppendLine(string.Format("f2.Eq(f3)      :{0}", f2.Equals(f3)));
sb.AppendLine(string.Format("RefEq(f2, f3)  :{0}", ReferenceEquals(f2, f3)));

textBox1.Text = sb.ToString();
MessageBox.Show(sb.ToString());

====

【.NET Compact Framework 3.5 SP1】
--等価判定の実装状況--
f1==f2         :True
f1.Eq(f2)      :True
RefEq(f1, f2)  :True
--Fontプロパティ--
f2==f3         :False
f2.Eq(f3)      :True
RefEq(f2, f3)  :False


【.NET Framework 3.5 SP1】
--等価判定の実装状況--
f1==f2         :True
f1.Eq(f2)      :True
RefEq(f1, f2)  :True
--Fontプロパティ--
f2==f3         :True
f2.Eq(f3)      :True
RefEq(f2, f3)  :True

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91537 ] / ▼[ 91539 ]
■91538 / 9階層)  Re[9]: メモリリークに関して
□投稿者/ kiku (111回)-(2019/07/04(Thu) 10:46:45)
No91537 (魔界の仮面弁士 さん) に返信
> .NET Framework の場合、Control.Font プロパティは Ambient なので、手出しすべきではありません。
> 一つの Font インスタンスが、複数のコントロールで扱われる可能性があるためです。
> .NET Compact Framework の場合は、正直分かりません。
> 各種処理が Microsoft.AGL.Forms 名前空間経由で、DllImport されてしまっていて、
> 処理内容を追跡できそうにありません。
> Microsoft 有償サポートに問い合わせようにも、ライフサイクルが既に終了してしまっていますし。

AmbientとはフォームのFontを子コントロールが継承する機能であると理解しました。
よって、フォームのFontと子コントロールのFontは同じインスタンスである場合がある。
このような状況において、子コントロールのFontをDisposeしてしまうと、
フォームのFontがどうなっちゃうの?ってことであると理解しました。

フォームAのFontと、フォームBのFontが共用されることがなければ
フォームAのDisposeのタイミングで、フォームAの子コントールのFontを
Disposeするには、問題ないと考えても良いのでしょうか?
※よくわからないと記述があるので、わからないという回答かと思いますが(泣き)


> 以下、Font プロパティが返すインスタンスに関する実験コード。

この実験の結果をどのように理解していいか
正直に言いますと良くわかりませんでした。
FALSEになるってことは、textBox1.Font = f1;を実行したときに
f1が設定されるのではなく、フォームのFontから継承される機能が働き、
別のインスタンスになるのではという理解で現在はいます。

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91538 ] / ▼[ 91540 ]
■91539 / 10階層)  Re[10]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2220回)-(2019/07/04(Thu) 12:11:33)
No91538 (kiku さん) に返信
> AmbientとはフォームのFontを子コントロールが継承する機能であると理解しました。
> よって、フォームのFontと子コントロールのFontは同じインスタンスである場合がある。

アンビエントの具体例として:

.NET Framework の場合、
デザイン画面で Form に Label を貼って、
その後で Form の Font を変更すると、
Label の Font も連動して変更されます。

このとき、デザイナのプロパティ グリッドを見てみると、
Form の Font プロパティは変更されて太字表示されていますが、
Label の Font は細字表示のままとなります。

Label の Font を意図的に変更すれば、Label の Font プロパティも
太字で表示されますが、この状態だと、Form の Font に連動しなくなります。


ところが、.NET Compact Framework の場合、上記の動作が実装されていませんので、
Compact Framework の実装がアンビエントになっている可能性は低いと予想しています。


> この実験の結果をどのように理解していいか
> 正直に言いますと良くわかりませんでした。

.NET Comapct Framework の Font プロパティの getter は、
アクセスするたびに、新しい Font インスタンスを
生成する実装になっている可能性が高いことが伺えます。

その根拠は、
 var f1 = textBox1.Font;
 var f2 = textBox1.Font;
において、f1 と f2 が別のインスタンスとなったという点からです。
(同じインスタンスを返す条件が存在する可能性を否定することは出来ませんが)


また、No91525 のコードで「get_Font」メソッドの実装状況を調べたところ、
Font プロパティは Control クラスで宣言されているのみで、
継承先(Label や Form や ContainerControl 等)でオーバーライドされていないこともわかります。


という事はすなわち、最初の No91509 にて記述されていた、

>> this.lbl_serialnumber.Font.Dispose();//★対策追加

というコードが無価値であるように思うのです。

Font プロパティにアクセスするたびに新しい Font が生成され、
それがすぐに破棄されるだけなのではないかと。
(メモリ負荷等は見ていないので、アンマネージリソースの管理状況までは分かりませんが)


では、そもそも破棄した Font を使おうとしたらどうなるのか?

var f1 = new Font(FontFamily.GenericMonospace, 12, FontStyle.Regular);
var f2 = new Font(FontFamily.GenericMonospace, 12, FontStyle.Regular);
f2.Dispose();
button1.Font = f1;
button2.Font = f2;
button1.Font.Dispose();



てっきり ObjectDisposedException が発生するのかと思いきや、そうでは無いようです。

.NET Compact Framework の場合は、button1 だけが新しいフォントになります。
button2 の方は以前のフォントのままレンダリングされていました。

一方 .NET Framework においては、button1 / button2 両方とも
新しいフォントでレンダリングされました。エラーになることもなく。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91539 ] / ▼[ 91541 ]
■91540 / 11階層)  Re[11]: メモリリークに関して
□投稿者/ kiku (112回)-(2019/07/04(Thu) 13:13:14)
No91539 (魔界の仮面弁士 さん) に返信
> ところが、.NET Compact Framework の場合、上記の動作が実装されていませんので、
> Compact Framework の実装がアンビエントになっている可能性は低いと予想しています。

ありがとうございます。
アンビエントに関して良く理解できました。

> .NET Comapct Framework の Font プロパティの getter は、
> アクセスするたびに、新しい Font インスタンスを
> 生成する実装になっている可能性が高いことが伺えます。

解説ありがとうございます。
確かにgetterで新しいインスタンスを返すのであるならば
意味ないと思います。

textBox1.Font = f1;
上記のsetterで新しいインスタンスが生成されるという
ことは考えられないでしょうか?
この考えでもつじつまがあうように思いました。



[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91540 ] / ▼[ 91543 ] ▼[ 91544 ]
■91541 / 12階層)  Re[12]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2221回)-(2019/07/04(Thu) 15:18:05)
2019/07/04(Thu) 15:29:28 編集(投稿者)

No91540 (kiku さん) に返信
> textBox1.Font = f1;
> 上記のsetterで新しいインスタンスが生成されるという
> ことは考えられないでしょうか?

仮に setter でインスタンスが生成されていたとしても、
それと別に、getter で毎回生成されていることは間違いないかと。


using (var txt = new TextBox())
{
 // CE 環境においては "違う" らしい
 MessageBox.Show(ReferenceEquals(txt.Font, txt.Font) ? "同じ" : "違う");
}
using (var txt = new TextBox { Font = null })
{
 // "not null" と表示される
 MessageBox.Show((txt.Font == null) ? "null" : "not null");
}



No91539 (魔界の仮面弁士) に追記
> var f1 = new Font(FontFamily.GenericMonospace, 12, FontStyle.Regular);
> var f2 = new Font(FontFamily.GenericMonospace, 12, FontStyle.Regular);
> f2.Dispose();
> button1.Font = f1;
> button2.Font = f2;
> button1.Font.Dispose();
>
> .NET Compact Framework の場合は、button1 だけが新しいフォントになります。
> button2 の方は以前のフォントのままレンダリングされていました。

違ってました。

デザイン時に別のフォントを指定してから実行してみると、
button2 のフォントが変化する様子が確認できたので、
button2 が「以前のフォントのまま」で変化しないという推察は誤りですね。

Control.Font プロパティに「Dispose 済みの Font」や「null」が渡された場合、
『既定のフォントがセットされる』という動作になっているようです。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91541 ] / ▼[ 91545 ]
■91543 / 13階層)  Re[13]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2223回)-(2019/07/04(Thu) 15:26:21)
No91541 (魔界の仮面弁士) に追記
> Control.Font プロパティに「Dispose 済みの Font」や「null」が渡された場合、
> 『既定のフォントがセットされる』という動作になっているようです。


そもそも Font プロパティで内部管理されているものは、
Font オブジェクトのインスタンスそのものではなく、
それが指し示すアンマネージなハンドルっぽい?


// Compact Framework の Font オブジェクトの内部ハンドルを得る
static IntPtr GetHandle(Font f)
{
  BindingFlags bf = BindingFlags.NonPublic | BindingFlags.Instance;
  FieldInfo field = typeof(Font).GetField("m_htx", bf);
  if (field == null || f == null)
  {
    return IntPtr.Zero;
  }
  else
  {
    return (IntPtr)field.GetValue(f);
  }
}


private void button1_Click(object sender, EventArgs e)
{
  using (var f1 = new Font(FontFamily.GenericMonospace, 12, FontStyle.Regular))
  using (var f2 = new Font(FontFamily.GenericMonospace, 12, FontStyle.Regular))
  {
    // 別のインスタンスだけど、同じハンドルが得られている。
    // String でいうところの 文字列インターンプールのようなものだろうか?
    var h1 = GetHandle(f1);
    var h2 = GetHandle(f2);
    MessageBox.Show((h1 == h2) ? "同じ" : "違う");

    // 一方のインスタンスを破棄してみる
    f1.Dispose();

    // 破棄した方からは IntPtr.Zero が返される。
    var h3 = GetHandle(f1);
    var h4 = GetHandle(f2);
    MessageBox.Show((h3 == h4) ? "同じ" : "違う");
  }
}





[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91543 ] / ▼[ 91546 ]
■91545 / 14階層)  Re[14]: メモリリークに関して
□投稿者/ kiku (114回)-(2019/07/04(Thu) 16:12:40)
No91543 (魔界の仮面弁士 さん) に返信
> ■No91541 (魔界の仮面弁士) に追記
>>Control.Font プロパティに「Dispose 済みの Font」や「null」が渡された場合、
>>『既定のフォントがセットされる』という動作になっているようです。
> そもそも Font プロパティで内部管理されているものは、
> Font オブジェクトのインスタンスそのものではなく、
> それが指し示すアンマネージなハンドルっぽい?

なるほど。
こんなに深く考えたことがなかったので
勉強になります。

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91545 ] / ▼[ 91569 ]
■91546 / 15階層)  Re[15]: メモリリークに関して
□投稿者/ kiku (115回)-(2019/07/04(Thu) 16:14:33)
ここまでの情報や実験結果を用いてどのように対処していくのか
方向性を出したいと思っています。
少し考えさせてください。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91546 ] / ▼[ 91570 ] ▼[ 91571 ]
■91569 / 16階層)  Re[16]: メモリリークに関して
□投稿者/ kiku (116回)-(2019/07/08(Mon) 11:36:04)
No91546 (kiku さん) に返信
> ここまでの情報や実験結果を用いてどのように対処していくのか
> 方向性を出したいと思っています。
> 少し考えさせてください。

下記のような対応にて、様子を見ることになりました。
Font.Dispose()については、意味がないことを説明したのですが、
ご理解得ることはできなかったため、実装しています。
実装しても弊害が発生することはないと思いますので。
結果が出るまでに、時間がかかると思いますので
ここで解決済みとします。

    partial class Test
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            CustomDispose.Dispose(this.Controls);//★対策追加
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.lbl_serialnumber = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // lbl_serialnumber
            // 
            this.lbl_serialnumber.BackColor = System.Drawing.Color.White;
            this.lbl_serialnumber.Font = new System.Drawing.Font("MS ゴシック", 16F, System.Drawing.FontStyle.Bold);
            this.lbl_serialnumber.Location = new System.Drawing.Point(5, 115);
            this.lbl_serialnumber.Name = "lbl_serialnumber";
            this.lbl_serialnumber.Size = new System.Drawing.Size(310, 20);
            this.lbl_serialnumber.Text = "lbl_serialnumber";
            // 
            // Test
            // 
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit;
            this.ClientSize = new System.Drawing.Size(640, 480);
            this.ControlBox = false;
            this.Controls.Add(this.lbl_serialnumber);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "Test";
            this.Text = "Test";
            this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
            this.ResumeLayout(false);
        }

        private System.Windows.Forms.Label lbl_serialnumber;
    }

    //★ここ以降対策追加
    public class CustomDispose
    {
        static public void Dispose(Control.ControlCollection Controls)
        {
            foreach (var c in Controls)
            {
                Logger.Info(c.GetType().ToString());
                switch (c.GetType().FullName)
                {
                    case "System.Windows.Forms.Label":
                        ((System.Windows.Forms.Label)c).Font.Dispose();
                        ((System.Windows.Forms.Label)c).Dispose();
                        break;
                    //ここにTextBoxなどの他のコントロールも多数存在する。
                    //ここにカスタムコントロールも多数存在する。
                    default:
                        //●解放漏れの可能性あり●
                        break;
                }
            }
        }
    }


[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91569 ] / 返信無し
■91570 / 17階層)  Re[17]: メモリリークに関して
□投稿者/ kiku (117回)-(2019/07/08(Mon) 11:36:37)
解決済み
解決済み
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91569 ] / ▼[ 91572 ]
■91571 / 17階層)  Re[17]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2226回)-(2019/07/08(Mon) 14:19:34)
No91569 (kiku さん) に返信
> foreach (var c in Controls)
> {
>     Logger.Info(c.GetType().ToString());
>     switch (c.GetType().FullName)
>     {
>         case "System.Windows.Forms.Label":
>             ((System.Windows.Forms.Label)c).Font.Dispose();
>             ((System.Windows.Forms.Label)c).Dispose();

型ではなく型名でチェックしている点が気になりますが、それはさておき。


Form 上に Panel や TabControl 等を使用していた場合、this.Controls だけではなく
その配下のコントロールも再帰的に処理する必要があるのではないでしょうか。
(this.Controls[0].Controls 的な意味で)

https://stackoverflow.com/questions/2209854/find-all-child-controls-of-specific-type-using-enumerable-oftypet-or-linq



> protected override void Dispose(bool disposing)
> {
>     CustomDispose.Dispose(this.Controls);//★対策追加
>     if (disposing && (components != null))
>     {
>         components.Dispose();
>     }
>     base.Dispose(disposing);
> }

マネージリソースを破棄するのは、disposing == true の時だけなのでは?

# 一応、解決済みマークはつけたままにしておきます。

解決済み
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91571 ] / ▼[ 91585 ]
■91572 / 18階層)  Re[18]: メモリリークに関して
□投稿者/ kiku (118回)-(2019/07/09(Tue) 10:26:26)
No91571 (魔界の仮面弁士 さん) に返信
>>foreach (var c in Controls)
>>{
>> Logger.Info(c.GetType().ToString());
>> switch (c.GetType().FullName)
>> {
>> case "System.Windows.Forms.Label":
>> ((System.Windows.Forms.Label)c).Font.Dispose();
>> ((System.Windows.Forms.Label)c).Dispose();
>
> 型ではなく型名でチェックしている点が気になりますが、それはさておき。

switch (c.GetType())
{
case typeof(System.Windows.Forms.Label):
((System.Windows.Forms.Label)c).Font.Dispose();
((System.Windows.Forms.Label)c).Dispose();

こんな感じにしてみたのですが、ビルドできなかったので、
if文の羅列になってしまいます。
好みの問題なのですが、switch文の方が見た目がきれいなので
こちらにしてみました。
swich文でも書き方によってうまくできればよいのですが。

> Form 上に Panel や TabControl 等を使用していた場合、this.Controls だけではなく
> その配下のコントロールも再帰的に処理する必要があるのではないでしょうか。
> (this.Controls[0].Controls 的な意味で)

今回のアプリでは全画面にどんなコントロールが利用されているのかを
把握しています。
コンテナとなるようなコントロールは無いことを確認しているため、
実装していません。

> マネージリソースを破棄するのは、disposing == true の時だけなのでは?

確かにそうですね。
コメントありがとうございます。
対応させて頂きます。
解決済み
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91572 ] / ▼[ 91586 ] ▼[ 91587 ]
■91585 / 19階層)  Re[19]: メモリリークに関して
□投稿者/ shu (1185回)-(2019/07/10(Wed) 15:55:13)
2019/07/10(Wed) 16:18:04 編集(投稿者)

No91572 (kiku さん) に返信

>
> switch (c.GetType())
> {
> case typeof(System.Windows.Forms.Label):
> ((System.Windows.Forms.Label)c).Font.Dispose();
> ((System.Windows.Forms.Label)c).Dispose();
>
> こんな感じにしてみたのですが、ビルドできなかったので、
> if文の羅列になってしまいます。
> 好みの問題なのですが、switch文の方が見た目がきれいなので
> こちらにしてみました。
> swich文でも書き方によってうまくできればよいのですが。
>

C#はswitchでこの要件を満足するのは厳しいと認識しております。
一応次のようにすれば可能

int a = c is Label ? 1 :
c is Button ? 2 :
-1;

switch (a)
・・・





解決済み
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91585 ] / ▼[ 91588 ]
■91586 / 20階層)  Re[20]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2232回)-(2019/07/10(Wed) 16:37:37)
No91585 (shu さん) に返信
> C#はswitchでこの要件を満足するのは厳しいと認識しております。
> 一応次のようにすれば可能

C# の言語仕様的には一応、C# 7.0 以降で
「型による分岐」がサポートされるようになっていたりします。

今回は VS2008 (C# 3.0) なので使えないですが…。


foreach (Control c in Controls )
{
 switch (c)
 {
  case System.Windows.Forms.Label _:
  case System.Windows.Forms.TextBox _:
   c.Font.Dispose();
   c.Dispose();
   break;
 }
}

// ----------------

foreach (Control c in Controls )
{
 switch (c)
 {
  case System.Windows.Forms.Label lbl:
   lbl.Font.Dispose();
   lbl.Dispose();
   break;
  case System.Windows.Forms.PictureBox pic:
   pic.Image?.Dispose();
   pic.BackgroundImage?.Dispose();
   pic.Dispose();
   break;
 }
}

ただし、型による分岐はジャンプ命令に変換されないので、
内部的には if 分岐と変わらなくなりますね。
解決済み
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91586 ] / 返信無し
■91588 / 21階層)  Re[21]: メモリリークに関して
□投稿者/ kiku (121回)-(2019/07/11(Thu) 08:49:57)
No91586 (魔界の仮面弁士 さん) に返信
> foreach (Control c in Controls )
> {
>  switch (c)
>  {
>   case System.Windows.Forms.Label _:
>   case System.Windows.Forms.TextBox _:
>    c.Font.Dispose();
>    c.Dispose();
>    break;
>  }
> }

こんな風にかければ良かったんですが、
CompactFramework3.5なので><;
解決済み
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91585 ] / 返信無し
■91587 / 20階層)  Re[20]: メモリリークに関して
□投稿者/ kiku (120回)-(2019/07/11(Thu) 08:43:01)
No91585 (shu さん) に返信
> C#はswitchでこの要件を満足するのは厳しいと認識しております。
> 一応次のようにすれば可能
> int a = c is Label ? 1 :
> c is Button ? 2 :
> -1;
> switch (a)
> ・・・

まさかレスが付くとは。
検討頂きありがとうございます。
解決済み
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91541 ] / 返信無し
■91544 / 13階層)  Re[13]: メモリリークに関して
□投稿者/ kiku (113回)-(2019/07/04(Thu) 16:08:32)
No91541 (魔界の仮面弁士 さん) に返信
> 2019/07/04(Thu) 15:29:28 編集(投稿者)
> 仮に setter でインスタンスが生成されていたとしても、
> それと別に、getter で毎回生成されていることは間違いないかと。

こちらでも動作させてみました。
確かにgetterで間違いないですね。
勝手にDisposeさせないための仕様なのかわかりませんが、
なんかしっくりこないですね。

> Control.Font プロパティに「Dispose 済みの Font」や「null」が渡された場合、
> 『既定のフォントがセットされる』という動作になっているようです。

ありがとうございます。
Fontは常に有効なインスタンスを保持しているような動作になると
理解しました。

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91509 ] / ▼[ 91518 ]
■91515 / 1階層)  Re[1]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2213回)-(2019/07/02(Tue) 20:22:22)
No91509 (kiku さん) に返信
> ※通常はControls.Addしているのでフォーム解放時にdisposeされる認識

.NET Framework の場合、Control.Dispose(bool) メソッド内で

 For i = 0 To controlCollection.Count - 1
  Dim control As Control = controlCollection(i)
  control.Parent = Nothing
  control.Dispose()
 Next

に相当する処理が行われていますね。
https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,6040


ところが .NET Compact Framework の実装については、上記に相当する処理を見つけることができませんでした。
(一応 Dispose 時には、自身のハンドルを AGL.DLL の 序数2 (Destroy API) に渡しているようですが…)
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91515 ] / ▼[ 91520 ] ▼[ 91529 ]
■91518 / 2階層)  Re[2]: メモリリークに関して
□投稿者/ kiku (101回)-(2019/07/03(Wed) 09:14:41)
No91515 (魔界の仮面弁士 さん) に返信
> ■No91509 (kiku さん) に返信
>>※通常はControls.Addしているのでフォーム解放時にdisposeされる認識
>
> .NET Framework の場合、Control.Dispose(bool) メソッド内で
>
>  For i = 0 To controlCollection.Count - 1
>   Dim control As Control = controlCollection(i)
>   control.Parent = Nothing
>   control.Dispose()
>  Next
>
> に相当する処理が行われていますね。
> https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,6040
>
>
> ところが .NET Compact Framework の実装については、上記に相当する処理を見つけることができませんでした。
> (一応 Dispose 時には、自身のハンドルを AGL.DLL の 序数2 (Destroy API) に渡しているようですが…)

回答ありがとうございます。
なんと、これが直接的な原因みたいですね。
.NET Compact FrameworkのControl.Disposeのソースは、
.NET Frameworkのように閲覧可能なのでしょうか?
もし可能ならURL教えて頂けませんか?
自分の目でも確認したいです。

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91518 ] / ▼[ 91523 ]
■91520 / 3階層)  Re[3]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2214回)-(2019/07/03(Wed) 13:17:52)
2019/07/03(Wed) 13:23:45 編集(投稿者)

No91509 (kiku さん) に返信
> ※通常はGCで回収されると思っているのですが、

.NET Compact Framework の GC は、コンパクションありのマーク・アンド・スイープ GC ですが、
.NET Framework の GC は 世代別 GC 実装であり、そもそも管理方法からして別物だそうな。

https://blogs.msdn.microsoft.com/stevenpr/2004/07/26/an-overview-of-the-net-compact-framework-garbage-collector/
https://blogs.msdn.microsoft.com/stevenpr/2005/12/14/the-design-of-the-net-compact-framework-clr-part-iii-gc-heap-management/

https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals

https://ja.wikipedia.org/wiki/%E3%82%AC%E3%83%99%E3%83%BC%E3%82%B8%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3
https://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%BC%E3%82%AF%E3%83%BB%E3%82%A2%E3%83%B3%E3%83%89%E3%83%BB%E3%82%B9%E3%82%A4%E3%83%BC%E3%83%97
https://ja.wikipedia.org/wiki/%E4%B8%96%E4%BB%A3%E5%88%A5%E3%82%AC%E3%83%99%E3%83%BC%E3%82%B8%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3


追記でこちらも。
なお、下記の「コンカレント GC」は、docs.microsoft.com では
「同時実行ガベージ コレクション」と訳されているようです。
http://www.stprec.co.jp/ceblog/2012/05/14/




No91518 (kiku さん) に返信
> .NET Compact FrameworkのControl.Disposeのソースは、
> .NET Frameworkのように閲覧可能なのでしょうか?

いいえ。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91520 ] / ▼[ 91524 ] ▼[ 91531 ]
■91523 / 4階層)  Re[4]: メモリリークに関して
□投稿者/ kiku (103回)-(2019/07/03(Wed) 16:50:44)
No91520 (魔界の仮面弁士 さん) に返信
> 2019/07/03(Wed) 13:23:45 編集(投稿者)
>
> ■No91509 (kiku さん) に返信
>>※通常はGCで回収されると思っているのですが、
>
> .NET Compact Framework の GC は、コンパクションありのマーク・アンド・スイープ GC ですが、
> .NET Framework の GC は 世代別 GC 実装であり、そもそも管理方法からして別物だそうな。
> 追記でこちらも。
> なお、下記の「コンカレント GC」は、docs.microsoft.com では
> 「同時実行ガベージ コレクション」と訳されているようです。
> http://www.stprec.co.jp/ceblog/2012/05/14/

ありがとうございます。
助かります。

> ■No91518 (kiku さん) に返信
>>.NET Compact FrameworkのControl.Disposeのソースは、
>>.NET Frameworkのように閲覧可能なのでしょうか?
>
> いいえ。

残念。
実機で確認するしかなさそうです。



Labelであるlbl_serialnumberのDisposeを実行した場合、
lbl_serialnumber.FontのDisposeが実行されるのかどうかの
確認ができていない状況です。
PANG2さんからご紹介頂いたWeakReferenceを使ってみたのですが、
うまく判断できないように見えました。
Fontを継承したクラスを作ってDisposeが呼ばれることを
検証しようとしたのですが、Fontは継承禁止のクラスのようで
この方法での確認ができませんでした。

困りました。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91523 ] / ▼[ 91527 ]
■91524 / 5階層)  Re[5]: メモリリークに関して
□投稿者/ Hongliang (835回)-(2019/07/03(Wed) 17:04:10)
2019/07/03(Wed) 17:04:43 編集(投稿者)

> Labelであるlbl_serialnumberのDisposeを実行した場合、
> lbl_serialnumber.FontのDisposeが実行されるのかどうかの
> 確認ができていない状況です。

それはされないと断言していいでしょう。
特にControlのFontなど、共有することが多いので、あるコントロールが不要になったからと言って他のコントロールにとって不要かどうかは、そのコントロール自身は知る術がありません。
そのため勝手にDisposeはしない(できない)、と考えられます。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91524 ] / 返信無し
■91527 / 6階層)  Re[6]: メモリリークに関して
□投稿者/ kiku (105回)-(2019/07/03(Wed) 17:20:05)
No91524 (Hongliang さん) に返信
> 2019/07/03(Wed) 17:04:43 編集(投稿者)
>
>>Labelであるlbl_serialnumberのDisposeを実行した場合、
>>lbl_serialnumber.FontのDisposeが実行されるのかどうかの
>>確認ができていない状況です。
>
> それはされないと断言していいでしょう。
> 特にControlのFontなど、共有することが多いので、あるコントロールが不要になったからと言って他のコントロールにとって不要かどうかは、そのコントロール自身は知る術がありません。
> そのため勝手にDisposeはしない(できない)、と考えられます。

なるほど、確かにそうですね。
納得しました。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91523 ] / ▼[ 91535 ]
■91531 / 5階層)  Re[5]: メモリリークに関して
□投稿者/ PANG2 (309回)-(2019/07/03(Wed) 23:35:41)
No91523 (kiku さん) に返信
> PANG2さんからご紹介頂いたWeakReferenceを使ってみたのですが、
> うまく判断できないように見えました。

WindowsとCEで差異はありますか?

public static class WeakReferenceList
{
private class pair
{
public string name;
public WeakReference wRef;
}
private static readonly List<pair> list = new List<pair>();

public static void Add(Control c)
{
list.Add(new pair() {name = c.Name, wRef = new WeakReference(c) });
}

public static string dump()
{
var sw = new System.IO.StringWriter();
foreach (pair c in list) {
sw.Write(c.name);
if (c.wRef.IsAlive) {
sw.Write("(a)");
if (((Control)c.wRef.Target).IsDisposed)
sw.Write("(d)");
}
sw.WriteLine("");
}
return sw.ToString();
}
}

private void Form1_Load(object sender, EventArgs e)
{
WeakReferenceList.Add(this);
WeakReferenceList.Add(label1);
WeakReferenceList.Add(textBox1);
}

static void Main()
{
Application.Run(new Form1());

MessageBox.Show(WeakReferenceList.dump());
GC.Collect();
MessageBox.Show(WeakReferenceList.dump());
}

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91531 ] / 返信無し
■91535 / 6階層)  Re[6]: メモリリークに関して
□投稿者/ kiku (109回)-(2019/07/04(Thu) 09:23:39)
No91531 (PANG2 さん) に返信
> ■No91523 (kiku さん) に返信
>>PANG2さんからご紹介頂いたWeakReferenceを使ってみたのですが、
>>うまく判断できないように見えました。
>
> WindowsとCEで差異はありますか?

デバック実行にて実行してみました。
差異はありませんでした。

1回目のメッセージボックス
 From1(a)(d)
 label1(a)(d)
 textBox1(a)(d)

2回目のメッセージボックス
 From1
 label1
 textBox1

[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91518 ] / ▼[ 91532 ]
■91529 / 3階層)  Re[3]: メモリリークに関して
□投稿者/ 魔界の仮面弁士 (2216回)-(2019/07/03(Wed) 19:14:23)
No91518 (kiku さん) に返信
>> For i = 0 To controlCollection.Count - 1
>>  Dim control As Control = controlCollection(i)
>>  control.Parent = Nothing
>>  control.Dispose()
>> Next
>> .NET Compact Framework の実装については、上記に相当する処理を見つけることができませんでした。
> なんと、これが直接的な原因みたいですね。

Control.Dispose が上記を実装していれば、
Form1 が Dispose される時に、Controls が再帰的に列挙され、
Panel や Label といった子(あるいは子孫)コントロールも
連動して解放されるはずなのですけれどね。


とはいえ、GC 管理の仕組みがそもそも異なるので、
.NET Framework 版との単純な比較はできません。
(ソースコードは非公開ですし、逆アセンブルはライセンス上禁じられている…)

実際 CE においても、親 Form が Dispose されれば、その配下のコントロール群も
Dispose されることを確認できました。
とはいえ、子コントロールの Dispose が確実に呼ばれているのか、
たまたま GC されただけなのかまでは分かりません。

一応、Dispose(False)時は、ApplicationThreadContext のファイナライズキューへの
登録っぽい処理が行われると聞いたことがありますが、詳しいことは分からないです。


CE 環境は Application.OpenForms でキャッシュされることが無いとは言え、
 (1) VB 特有の「My.Forms.フォーム名」プロパティでキャッシュされることはある
 (2) Form 右上の×を押しても Minimize されるだけ(Close/Dispose されるとは限らない)
ということで、Dispose が呼ばれにくい状況に陥りやすそうではありますね。



あと、経験則的なところで言えば、.NET Compact Framework 環境の場合、
オブジェクトの使用後は、参照変数に Nothing 代入を積極的に行った方が良いみたいです。


[宇宙仮面の研究室] - [Compact Framework のメモリ管理]
https://uchukamen.wordpress.com/2007/01/23/compact-framework-%e3%81%ae%e3%83%a1%e3%83%a2%e3%83%aa%e7%ae%a1%e7%90%86/

[SWK623] - [[.NET] WindowsMobile上のC#アプリのメモリリークを解決せよ!]
https://swk623.com/2009/03/16/net-windowsmobile%E4%B8%8A%E3%81%AEc%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%AF%E3%82%92%E8%A7%A3%E6%B1%BA%E3%81%9B%E3%82%88%EF%BC%81/

[スラド] - [#1411305]
https://srad.jp/comment/1411305


基本的には、変数のスコープを抜ければ GC 対象にはなりそうですが、
Module / Shared 等で保持されたインスタンスや、
メインフォームのフィールド変数がもつ参照の場合は、
変数の寿命が長くなるので、自分で管理するしか無さそう。
[ 親 91509 / □ Tree ] 返信 編集キー/

▲[ 91529 ] / 返信無し
■91532 / 4階層)  Re[4]: メモリリークに関して
□投稿者/ kiku (107回)-(2019/07/04(Thu) 09:00:50)
No91529 (魔界の仮面弁士 さん) に返信
> 実際 CE においても、親 Form が Dispose されれば、その配下のコントロール群も
> Dispose されることを確認できました。
> とはいえ、子コントロールの Dispose が確実に呼ばれているのか、
> たまたま GC されただけなのかまでは分かりません。

子コントロールをすべて明示的にDisposeするように対応したいと思います。

> あと、経験則的なところで言えば、.NET Compact Framework 環境の場合、
> オブジェクトの使用後は、参照変数に Nothing 代入を積極的に行った方が良いみたいです。

引用されているURLも一通り読んでみました。
子コントロールにnullを設定するように対応したいと思います。
[ 親 91509 / □ Tree ] 返信 編集キー/


管理者用

- Child Tree -