Nullableと3値論理
.NETのNullable
nullと非nullの比較
int? value1 = null; int? value2 = 10;
だと
式 | 結果 |
value1 < value2 | false |
value1 <= value2 | false |
value1 == value2 | false |
value1 != value2 | true |
value1 > value2 | false |
value1 >= value2 | false |
nullとnullの比較
int? value1 = null; int? value2 = null;
だと
式 | 結果 |
value1 < value2 | false |
value1 <= value2 | false |
value1 == value2 | true |
value1 != value2 | false |
value1 > value2 | false |
value1 >= value2 | false |
考察
どうやら次のような感じか。
- 「==」や「!=」での比較は、SQLにおけるIS NULL的にちゃんと行われる。
- 「>」や「<」などによる大小の比較は、nullが混じった時点で常にfalseを返す。
Windowsフォームアプリケーションでリソースリークしないために(1)
最近になって初めて*1、本格的なWindowsフォームアプリケーションを開発した。その中でいくつかリソースリークしやすいポイントを見つけたので記録する。
書いてみるとかなりの分量になったので、分割することにした。本エントリはその第1回。
Windowsフォームアプリケーションとは
いきなり「Windowsフォームアプリケーション」という言葉を使ったが、.NET FrameworkのSystem.Windows.Forms名前空間以下に含まれる型を使って作成するデスクトップアプリケーションという意味で使った。以降もこの意味で使う。
さて、ここからが主題。
Image.Disposeを忘れない
Windowsフォームアプリケーションで画像を扱う場合、一般的にImageクラスを使う。このクラスはIDisposableインターフェイスを実装している。よってそのインスタンスを使い終わった時点で、Disposeしてやる必要がある。なお分かりやすい例としてImageクラスを挙げたが、System.Drawing名前空間のオブジェクトはほとんど(全て?)同様だ。
ここまでだと当たり前に思えるが、次の場合はどうだろう。
PictureBox.Imageも要Dispose
Windowsフォームアプリケーションで画面上に画像を表示する場合、一般的にPictureBoxコントロールを使う。リンク先(DOBON.NETさん)に重要なことがサラッと書かれている。「PictureBox.Imageプロパティを使って表示した画像を消去する」のサンプルコードだ。次に引用する。
if (PictureBox1.Image != null) { PictureBox1.Image.Dispose(); PictureBox1.Image = null; }
PictureBox.Imageプロパティにnullを設定する前に、Disposeしているのだ。これを忘れるとリソースリークに繋がる。
画像を消去する場合だとまだ良いが、画像を差し替える場合などには忘れがちではないだろうか。画像を差し替える場合、差し替える前のPictureBox.Imageプロパティ値をDisposeしなければならない。*2これを踏まえると、次のようなユーティリティメソッド*3が書ける。
public static void SwapImage(this PictureBox pictureBox, Image newImage) { if (pictureBox == null) { throw new ArgumentNullException("pictureBox"); } var oldImg = pictureBox.Image; pictureBox.Image = newImage; if (oldImg != null) { oldImg.Dispose(); } }
画像を消去する場合はnewImage引数にnullを指定する。というのは直感的でないので、次のようなメソッドも作成しておくと良いだろう。
public static void ClearImage(this PictureBox pictureBox) { SwapImage(pictureBox, null); }
DOBON.NETさんのコードとはDisposeのタイミングが異なることに注意する。私のコードではPictureBox.Imageプロパティの上書きより後に、元のImageオブジェクトをDisposeしている。この順序は画像の消去では特に重要ではないのだが、画像の差し替えの場合に非常に重要だ。この点について次に説明する。
PictureBox.Imageの古い値のDisposeは、新しい値の設定より後で
先述のサンプルコードのSwapImageメソッドでは、PictureBox.Imageプロパティの上書きより後に、元のImageオブジェクトをDisposeしていた。これには実は意味がある。Dispose→上書きの順だと、ある条件下で次のような例外が発生してしまうことがあるのだ。
[ArgumentException: 使用されたパラメータが有効ではありません。]
場所 System.Drawing.Image.get_Width()
場所 System.Drawing.Image.get_Size()
場所 System.Windows.Forms.PictureBox.ImageRectangleFromSizeMode(PictureBoxSizeMode mode)
場所 System.Windows.Forms.PictureBox.OnPaint(PaintEventArgs pe)
場所 System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer, Boolean disposeEventArgs)
場所 System.Windows.Forms.Control.WmPaint(Message& m)
場所 System.Windows.Forms.Control.WndProc(Message& m)
場所 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
場所 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
場所 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
ある条件とは何か。私が確認した範囲では、次の条件をともに満たした場合に再現した。
- Windows Server 2008である(Windows XPでは発生しない)
- 非メインスレッドからControl.InvokeメソッドまたはControl.BeginInvokeメソッドを通して*4呼び出される
これら以外にも条件があるかもしれないし、これらのうちに再現性に無関係な条件があるかもしれない。しかしここで重要なのは、Dispose→上書きの順だと例外が発生してしまう場合があるということだ。条件の正確さはそれほど重要ではない。Disposeは一般的に、参照している変数があるインスタンスに対して呼び出されるべきではないから、ここでもそれに従って上書き→Disposeとすべきだろう。
PictureBoxのDispose時は、PictureBox.Imageも一緒にDispose
PictureBox.DisposeメソッドはPictureBox.Imageプロパティ値のImageオブジェクトをDisposeしない。このことはリファレンスを見ても分からない。それどころかリファレンスを見ると、全く逆のことを想像してしまうだろう。*5次にリファレンスを引用する。
disposing パラメーターが true の場合、このメソッドは、この PictureBox から参照されるすべてのマネージ オブジェクトが保持しているリソースをすべて解放します。 このメソッドは、参照される各オブジェクトの Dispose() メソッドを呼び出します。
ところでPictureBox.DisposeがPictureBox.Imageプロパティ値をDisposeしないのは、自然なことだ。PictureBoxにはDisposeして良いかが分からないからだ。PictureBox.Imageプロパティ値のImageインスタンスは、PictureBoxのDispose後も使い続けられる可能性がある。
しかし逆に、PictureBox.Disposeの時点でPictureBox.Imageプロパティ値も不要になるなら、Disposeを忘れてはならない。普通のアプリケーションなら、大抵の場合これが該当するのではないか。そのような場合のために、次のようなクラスを作成しておくと良いだろう。PictureBoxと同時にImageオブジェクトもDisposeして良い場面では、単なるPictureBoxでなくこちらを使う。
public sealed class AutoPictureBox : PictureBox { protected override void Dispose(bool disposing) // (1) { try { if (IsDisposed) { return; } if (disposing) { // マネージドリソースを解放 this.ClearImage(); // (2) } // アンマネージドリソースを解放 } finally { base.Dispose(disposing); } } }
(2)のClearImageメソッドは「PictureBox.Imageも要Dispose」で作成したものだ。
(1)でDisposeメソッドをオーバーライドしているが、Disposeメソッドを正しく実装するのは「知らないとできない」。私はいつも、「アンマネージ リソースをクリーンアップするための Finalize および Dispose の実装」を参考にしている。
IronPythonでプログラミング可能なCONFIGファイルを実装する
はじめに
IronPythonを使えば、CONFIGファイルで自由にプログラミング可能なアプリケーションを、実装できる。本エントリでは、その方法を簡単に紹介する。
ASP.NETで忘れずにやっておくべきこと(2) セッションハイジャック対策
はじめに
本エントリは『ASP.NETで忘れずにやっておくべきこと』シリーズの第2回。「セッションハイジャック対策」について記す。
書評『Patterns of Enterprise Application Architecture』
Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))
- 作者: Martin Fowler
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2002/11/05
- メディア: ハードカバー
- 購入: 4人 クリック: 36回
- この商品を含むブログ (41件) を見る
ITアーキテクトのバイブルと言われるだけあって、素晴らしい。私にとってもバイブルとなった。
続きを読むDataTableの一部の列でのDistinct
DataTableの一部の列でDistinctするコードを書いたので、記録しておく。
続きを読む