SSブログ
Programming ブログトップ
前の10件 | -

Entity Framework - 3 [Programming]

私の場合、Entity Framework に興味があって手を出したわけではなく、単に LINQ to SQL に代わりに使える環境が欲しかったので勉強を始めてみたというだけのものだった。LINQ to SQL が SQL Server に特化したアプローチだったのに対して、Entity Framework が他のデータベースにも対応可能になるということで、乗り換えが促されている感じがする。ただし、現時点では OLE DB や ODBC には対応していないために、コストをかけずに(これが大事) LINQ を体験するには SQL Server を導入する以外に選択肢がない。なので、なんのための Entity Framework なのか今一スッキリしないところもある。既存のデータベースにアクセスしたいならコードファーストなんてアプローチはとりあえず「暇があったら見てみてもいい」程度の進化でしかないしねぇ。

Entity Framework に対応した LINQ to Entities も利用可能ではあるけれど、LINQ to SQL に対し削除されてしまった機能があるのがつらい。今の段階(とりあえず、利用可能な環境を広めたい?)では SQL Server 固有の機能を盛り込むのは難しいのかもしれないけど、完全な互換性を保っていないというのは、かなりの減点対象になると思う。

Sub Main()
    Console.OutputEncoding = System.Text.Encoding.GetEncoding(932)

    Using ex = New 駅データDataContext()
        Dim query =
            From s In ex.Stations
            Where s.Line.line_name Like "*京浜東北線*"
            Order By s.station_cd
            Select New With {s.station_name}
        For Each item In query
            Console.WriteLine(String.Format("{0}", item.station_name))
        Next
    End Using
End Sub

SQL Server (LINQ to SQL) を利用している限りは(DataContext の構築は LINQ to SQL 固有のアプローチですが)普通に利用可能な構文だけど、このまま LINQ to Entities で利用しようとすると、実行時にエラーが発生してしまう。ワイルドカードがの構文が異なるとかいう理由ではなく、Like 演算子が利用できないという理由らしいので、ビルド時にエラーしてくれれば多少は許せるのだけれど、、、。


Entity Framework - 2 [Programming]

プログラムでデータベースを扱うようになると、ほぼ確実にお目にかかるのが Join。

Sub Main()
    Console.OutputEncoding = System.Text.Encoding.GetEncoding(932)

    Console.WriteLine("vb01")
    Using table As New DataTable()
        Using con As New SqlClient.SqlConnection(My.Settings.駅データ)
            Using cmd As New SqlClient.SqlCommand(
                "SELECT s.station_name " &
                "FROM station AS s " &
                "JOIN line AS l ON s.line_cd = l.line_cd " &
                "WHERE l.line_name = @name " &
                "ORDER BY s.station_cd", con
            )
                cmd.Parameters.Add(New SqlClient.SqlParameter("@name", "JR京浜東北線"))
                Using adp As New SqlClient.SqlDataAdapter(cmd)
                    adp.Fill(table)
                End Using
            End Using
        End Using
        For Each item In table.Rows
            Console.WriteLine(String.Format("{0}", item("station_name")))
        Next
    End Using
End Sub

型付けされていない Data Set を使用しているので若干ながくなっているけど、動作は雰囲気で読み取れると思う。これを LINQ を使って書き直すとこうなる。


Sub Main()
    Console.OutputEncoding = System.Text.Encoding.GetEncoding(932)

    Console.WriteLine("vb03")
    Using ex = New 駅データEntities()
        Dim query =
            From s In ex.Stations
            Join l In ex.Lines On s.line_cd Equals l.line_cd
            Where l.line_name = "JR京浜東北線"
            Order By s.station_cd
            Select New With {s.station_name}
        For Each item In query
            Console.WriteLine(String.Format("{0}", item.station_name))
        Next
    End Using
End Sub

これでも一応は動作するのだけれど、もう少し手を入れられる。

Sub Main()
    Console.OutputEncoding = System.Text.Encoding.GetEncoding(932)

    Console.WriteLine("vb03")
    Using ex = New 駅データEntities()
        Dim query =
            From s In ex.Stations
            Where s.Line.line_name = "JR京浜東北線"
            Order By s.station_cd
            Select New With {s.station_name}
        For Each item In query
            Console.WriteLine(String.Format("{0}", item.station_name))
        Next
    End Using
End Sub

LINQ を利用すると Join 句を消し去ることができることがある。代わりに登場するのがコレクションを介して参照先のテーブルにアクセスするコード。完全に同じ動作になるわけではないけど、SQL が生理的に馴染まないというプログラマにはお勧めの構文になる。

ソースコードからは読み取れないのだけど、データベースから ADO.NET Entity Data Model を作成することでこのようなカラクリが埋め込まれる。

Entity Framework - 1 [Programming]

.NET Framework 上で動作させるアプリケーションでデータベースを扱うなら、大抵は ADO.NET を利用することになる。ただ、ADO.NET にもバリエーションがあり、微妙に異なる構文を覚えていかないとならない。


ADO.NET の古典的な利用方法はコレ。


Sub Main()
    Console.OutputEncoding = Text.Encoding.GetEncoding(932)

    Using table As New 駅データ.StationDataTable()
        Using adp As New 駅データTableAdapters.StationTableAdapter()
            adp.FillByLineCode(table, 11103)
        End Using
        For Each item In table
            Console.WriteLine(String.Format("{0}:{1}", item.station_cd, item.station_name))
        Next
    End Using
End Sub

アダプタ(StationTableAdapter)やらデータセット(StationDataTable)を別に定義することになるので、ソースコード自体は結構すっきりしている。


型付けされていない Data Set を使用するならこうなる。


Sub Main()
    Console.OutputEncoding = System.Text.Encoding.GetEncoding(932)

    Using table As New DataTable()
        Using con As New SqlClient.SqlConnection(My.Settings.駅データ)
            Using cmd As New SqlClient.SqlCommand("SELECT * FROM station WHERE line_cd=@code ORDER BY station_cd", con)
                cmd.Parameters.Add(New SqlClient.SqlParameter("@code", 11103))
                Using adp As New SqlClient.SqlDataAdapter(cmd)
                    adp.Fill(table)
                End Using
            End Using
        End Using
        For Each item In table.Rows
            Console.WriteLine(String.Format("{0}:{1}", item("station_cd"), item("station_name")))
        Next
    End Using
End Sub

Visual Basic では Using を使用するたびにネストが増えてしまうので、異様に深いネストが気になることがある。状況によっては SqlConnection や SqlCommand を使用しないことも可能だけれど、パラメータクエリを利用しようとするとどうしても長くなってしまう。


最後は Entity Framework。LINQ to Entity が利用できるのが最大の特徴。


Sub Main()
    Console.OutputEncoding = System.Text.Encoding.GetEncoding(932)

    Using ex = New 駅データEntities()
        Dim query = From x In ex.Stations Where x.line_cd = 11103 Order By x.station_cd
        For Each item In query
            Console.WriteLine(String.Format("{0}:{1}", item.station_cd, item.station_name))
        Next
    End Using
End Sub

 SQL が完全に姿を消すので個人的に一番気に入ってはいるのだけれど、既存のシステムのバージョンアップのような場合にはなかなか適用するのが難しい。


Windows 6 - Raster Operation [Programming]

Windows API にはイメージのブロック転送の機能が備わっている。ビットマップとブラシを使用する PatBlt、ビットマップ 2 対とブラシを使用する BitBlt、ビットマップ 2 対とブラシ、マスク用のビットマップを使用する MaskBlt 等が存在している。また、イメージの拡縮や変形をサポートする亜種もある。

ブロック転送の際にラスターオペレーションと呼ばれる演算を施すことが可能で、簡単なイメージの合成等ができるようになっている。上述の PatBlt に使用されるラスターオペレーションは 16 種の操作が利用できる 2 次ラスターオペレーション(Binary Raster Operation)、これ以外のものは 256 種類の操作が利用できる 3 次ラスターオペレーション(Ternary Raster Operation)と呼ばれる。

MaskBlt のラスターオペレーションは 4 次ではなく、3 次ラスターオペレーションを 2 種類使用する。

3 次ラスターオペレーションでは 256 種類の操作が可能とは書いたけど、すべてが実用的な操作かと言うと、そんなこともない。多く使われるのは単純なコピーを表す SRCCOPY とかベタ塗りの WHITENESS や BLACKNESS といった単純なものだろう。実用的なものの多くは SRCCOPY のようなシンボルが割り当てられているのだけれど、 一部の仕様が変わった影響なのかシンボルが割り当てられていないコードを使用することもある。

以下のようなイメージがあり、これを合成することを考える。左から順番にブラシに割り当てるパターン(背景と同化してしまって解りづらいけど、左半分が黒、右半分が白のパターンです)、転送元ビットマップに割り当てる花の絵、転送先ビットマップに割り当てるボタンの絵となっている。

pattern.pngsource.pngdestination.png 


ラスターオペレーションを使用するには真理値表を読み解く必要がある、、、というか、その場で書けるようにする事が肝心。以下、3 次ラスターオペレーション コード(ROP Code)を求める方法。


  1. とりあえず表から作る。手書きでもいいし、Excel を使ってもいい。タイトル行には左から P(Pattern の P) , S(Source の S) , D(Destination の D) , ROP と書き込んでいく。2 行目以降は P , S , D の下に 000 , 001 , 010 というように 2 進数の各桁を書き込んでいく。3 桁しかないので 8 通りの行データが出来上がれば正解。
  2. パターン 黒
    パターンが黒(P が 0)の部分(表の上半分)は転送先ビットマップの値を残すものとする。そこで、ROP 列には D 列の値を書き込んでやる。
  3. パターン 白
    パターンが白(P が 1)の部分(表の下半分)は転送元ビットマップの値を転送するようにする。ROP 列には S 列の値を書き込むようにする。
  4. 出来上がり
    出来上がった ROP 列の値を下から読み上げたものはオペレーションインデックスと呼ばれる。今回の場合は 1 1 0 0 1 0 1 0 となっているはず。

オペレーションインデックスをそのまま BitBlt 等の ROP Code として与えてやれればいいのだけど、実はひと手間が必要。オペレーションインデックスから ROP Code を求めてやらないといけない。一覧はココにあるので必要なら参照してください。

真理値表の値だけで結果が想像できる人も多くはないと思うので、結果のイメージを張っておきます。 

ROP.png

 


Windows - 5 SetWindowsHookEx [Programming]

Win32 API のなかで、怪しそうな仕様の筆頭って言ったらやっぱりコレ。知らなければ知らないで済むだろうけど、一度知ってしまうと全てコレで賄えちゃうんじゃないかって思うほど。Windows Hook と呼ばれるこの機能を要約すると、「スレッドに投げられたメッセージを、そっと抜き取る」盗聴器のようなものということになる。

Windows Hook の形態は 2 つあり、自分自身に仕掛けるもの(スレッドフック)と、システム全体に仕掛けるもの(グローバルフック)に分類される。 特定のスレッドに仕掛けるものがあったら便利そうだとも思うのだけど、残念ながらその選択肢は認められていない。

Windows Hook を利用するためには、知らなければならないものがいくつかある。 ひとつはコールバック関数。Windows のプログラミンをしていると結構出てくるのでご存知の方も多いとは思うけど、Windows Hook でも利用することになる。このコールバック関数で抜き取ったメッセージに対する処理を記述する。もうひとつは、DLL の作成方法。前述のスレッドフックの場合には不要なのだけど、グローバルフックを利用したいなら必須のスキルになる。できれば共有セクションのことも知っておくと便利に使えるようになる。DLL に上述のコールバック関数を仕込んで、稼働中のスレッドのメッセージキューに仕掛けるという事を行う。

今回は、簡単なスレッドフックの使い方。メッセージボックスの出現位置を変更してみる。元ネタはこちら。Visual Basic のコードを C++ で書き換えてみた。

    static HHOOK Hook = ::SetWindowsHookEx(
WH_CBT,
[](int nCode, WPARAM wParam, LPARAM lParam)->LRESULT {
if (nCode==HCBT_ACTIVATE) {
CRect desktop, rect;
::SystemParametersInfo(SPI_GETWORKAREA, 0, &desktop, 0);
::GetWindowRect((HWND)wParam, &rect);
::SetWindowPos(
(HWND)wParam,
NULL,
(desktop.Width()-rect.Width())/2,
(desktop.Height()-rect.Height())/2,
0,
0,
SWP_NOZORDER|SWP_NOSIZE
);
::UnhookWindowsHookEx(Hook);
}
return ::CallNextHookEx(Hook, nCode, wParam, lParam);
},
NULL,
::GetCurrentThreadId()
);
::MessageBox(NULL, _T("Information"), _T("テスト"), MB_OK|MB_ICONINFORMATION);

最近の C++ ではラムダが使えるようになっているので、コールバック関数の部分に使ってみた。このコード片をコマンドハンドラにでも仕込んでやれば、メッセージボックスの出現位置を変更することができる。ココの表示エリアの制限があるので縦長になってしまっているけど、実際の処理は見た目ほど長くはない。


Windows - 4 Double Buffering [Programming]

どんなプラットフォームであっても、凝った絵を描こうと思ったらプラットフォーム特有のお作法を覚える必要がある。直感的にわかりやすいのはフレームバッファに直接イメージを書き込んでいくような初期の PC にあったような方式だろうけど、これだとテキストを描くとか背景を抜くといった簡単な機能でさえ自分でロジックを記述しなければならないので意外にスキルを求められる。その対極にあるのが 3D のレンダリング。フレームバッファ自体を操作することはほぼ不可能で、与えられた描画機能だけを用いて画面を作り上げていく。画面上に現れる建物や人といったオブジェクトを作成するのにもそれなりのツールやテクニックが必要となるので上記とは別の意味で敷居が高かったりする。

Windows の場合、マルチタスク OS の環境下で動作するアプリケーションは、デバイス(ハードウェア)を抽象化する GDI(Graphics Device Interface) と呼ばれるサブシステムの提供する機能を用いて描画を実行することになる。直接デバイスを扱わないというのは、利点であるとともに欠点となってしまうこともある。ゲームのようにどうしてもデバイスの機能を扱いたいという要望に対しては、Direct X と呼ばれるものを利用していくというのが、当時の Windows の流儀のひとつだった。

今回の話は、フォームやダイアログに既成のコントロールに描画を任せてしまう場合には殆ど関係のない話。 

GDI を操作するための最初の関門は、いくつかの用語の意味を覚えるところにあった。その筆頭はデバイスコンテキスト、、、公式の文書はこれ。機械翻訳すると「デバイスコンテキストは、グラフィック・オブジェクトとそれらに関連する属性、出力に影響するグラフィックモードを定義する構造です。」な感じになるのだけれど、、、訳わからん。

意訳というか、ほぼ誤訳に近い感じになってしまうのだけど、「デバイスに依存しない描画機能の集合体」というのがその正体。「直線を引く」とか「図形を塗りつぶす」とか「文字列を書く」といった、描画機能を提供してくれているのがデバイスコンテキストになる。

次が GDI オブジェクト。GDI オブジェクトは、デバイスコンテキストに与えて、描画機能の特性を変更するためのオブジェクト。具体的には、「ペンの太さや色(Pen)」 とか「塗りつぶすパターン(Brush)」とか「文字フォント(Font)」といったものを表すオブジェクトのこと。ビットマップ(Bitmap) も GDI オブジェクトのひとつで、扱いとしてはフレームバッファに近いものだけど、実際は似非な存在。

2つめの関門は、自分の認識と GDI の実装とのギャップの大きさにある。GDI を使用した描画には「自分のタイミングで描画する事ができない」 という特性がある。具体的には、WM_PAINT メッセージに応答する形で描画を実行するという手続きを踏む。そして、ただ待っているだけでは WM_PAINT メッセージはやってこないので、再描画が必要となった領域を無効化することでメッセージを誘発するという手続きも必要になる。

実際の描画はこれらの要件(制約?)の下で実行して行くことになるのだけれど、これだけの情報で実践しても殆どの場合ガッカリするのがオチだったりする。Web でも「描画はできるようになったけど、チラツキが酷い。」とか「チラツキを抑えるためのテクニックを教えて」といった書き込みを結構見かける。

解ってしまえば別に難しいことでもないのだけど、教えてくれるひとが近くにいないと、なかなかゴールにたどり着けないかもしれない。

GDI を使った描画をする際、ダブルバッファリングの手法を用いる事がほぼ必須になる。オフスクリーンレンダリングなどとも呼ばれるこの手法、もともとはモニタの表示期間に描画を実行することを避けることで画面のチラツキを回避しようとするためのもので、直接 V-Sync 信号を扱う初期のパソコンや安価なゲーム機のプログラミングでは常套手段だった。

ハードウェアを直接操作することができない GDI を用いた描画では、ダブルバッファリングによって完全に問題が解決するわけではない。まあ、程度の問題ということになってしまうのだけど、動きのあるゲームのようなものでない限り、問題ないレベルに仕上がる手法ということになる。 

GDI がうまいこと処理していてくれていればアプリケーションレベルで対処する必要もないのだけれど、 やってくれないなら自分でやるしかない。そして、定型的な手順になるので何度か実践しているうちに慣れるのも早いだろうし、採用したことによる効果もほぼ問題ないレベルになる。

ダブルバッファリングを用いた描画の手順は以下の通りになる。

 

  1. WM_PAINT をメッセージを受けたら描画を開始する。Win32 API を利用する場合は BeginPaint の発行、MFC を利用する場合は CPaintDC の構築を行い デバイスコンテキストを取得する。
  2.  1. で作成したデバイスコンテキストのコピーを作成する。同様にビットマップを作成して、コピーに選択する。
  3. 2. で作成したデバイスコンテキストで描画を実行する。
  4. コピーに作った描画イメージを、1. で取得したデバイスコンテキストに転送する。多くの場合、BitBlt によるイメージの転送になる。
  5. 描画の終了。Win32 API の場合は、EndPaint を呼び出す。MFC なら、スコープの消滅とともに適当に処理してくれるので、なにも考えなくていい。

 

ダブルバッファリングを使用しない場合と比べると、2. および 4. の手順が追加になる。チラツキの根本的な回避というわけではなく、要は 3. の処理時間よりも 4. の処理時間の方が圧倒的に短くなるため、結果としてチラツキが目立ちにくくなるだけというものだったりする。

 


Windows - 3 Agenda [Programming]

巨大なライブラリやフレームワークに依存しないプログラミングというのも結構楽しいかもしれない。ひと昔以上も前の記憶の断片と、殆ど更新されることのない古い公式ドキュメントを頼りに、効率を無視した作業を進めている。

そんな中、Windows のネイティブアプリケーション特有のノウハウが必要となることを想定している。具体的に挙げると「Double Buffering」、「Layered Window」、「Dynamic Subclassing/Owner Draw」、「Windows Hook」、「Raster Operation」、「Multi Threading」といったものが該当する。

一応、記憶の断片が残っているのでなんとかなるような気もするけど、細かいところは完全に忘却の彼方だったりもするので、英文ドキュメントと戦わなければならないかもしれない。以前参照していたドキュメントが姿を消していることも多く、意外に苦労しそうな予感もある。

とりあえず見た目から整えようということで始めた作業は、 ダイアログベースのアプリケーションを基に、非矩形のウィンドウを表示すること。Layered Window を適用することで背景を抜くことはできるのだけど、透明な背景越しに下のウィンドウに触れられるように小細工をすることにした。秒間 2 ~ 3 コマ程度の簡単なアニメーションをするイメージを使用するつもりなので、常時ウィンドウの形状を変化させることにも対応したい。さらに言えば、GPU のスペックに依存せず、CPU 負荷を極力抑えることを考慮する。

目標としてはリモートデスクトップ越しの操作(私の使用する環境がほぼコレ)であってもちらつかず、動作がスムーズになるようにしたい。また、クライアント PC、サーバー PC (ともに Windows マシン)に関わらず動作できることが条件。

 


Windows - 2 Shell Lightweight Utility [Programming]

久しぶりに Windows 向けのプログラミングをして感じたのは、「結構変わったなぁ」ということ。Windows 7 以降のプラットフォーム向けに作ったのは殆どすべてが .NET ramework 上のものだったので、Win32 API を直接弄ることがなく、情報を集めていなかったのが原因のひとつだと思う。

Windows の環境には Shell Lightweight Utility という名の、ユーティリティ関数群が提供されている。これはパスやレジストリのような Windows 環境独自のリソースを扱うためのもの。

Windows の仕様に合わせた操作を提供してくれているので、適当に手書きしたコードを書くよりは絶対に安全だ、、、と思って現役時代は多用していた。

久しぶりにこのページ(https://msdn.microsoft.com/en-us/library/windows/desktop/bb773559(v=vs.85).aspx)を見たら、とんでもないことになっていた。

全てではないのだけれど、「誤って使用すると、バッファオーバーランが発生することがあります。」 という注意書きがある。

Visual Studio 2015 に含まれているヘッダーでは、以前の関数たちには deprecated とマークされている。 使用頻度の高そうなものはほぼ全滅しているようなので、必要なら差し替えた方がいいのかもしれない。

ただ、リプレイスされたバージョンのものは Windows 8/Windows Server 2012 以降のプラットフォームにしか対応していない模様。Windows Vista や Windows 7 に対応することができない。


Windows - 1 [Programming]

以前は、Windows ネイティブの開発が主だったのだけれど、最近は .NET Framework や Web アプリケーションのような(直接)Windows に関わらないような業務が増えてきている。開発環境としては Eclipse を使うようにもなってきたんだけど、やっぱり Visual Studio が馴染む感じがある。最近の Visual Studio は .NET Framework 以外の環境にも対応するようになってきているので、提供される全ての機能を把握するのは難しい(無理?)というのが率直な意見だったりもする。

休日に何の気もなしに Visual Studio を弄っていたら、 MFC(Microsoft Foundation Classes) のアプリケーションのサポートが続いていることが確認できた。MFC を現役で扱っていたのが Windows XP 全盛の頃(西暦 2000 年前後)なので相当の年月が経過しているのを実感した。

実業務とはちょっと離れ、あくまでも趣味のプログラミングということでネイティブな Windows プログラムを作ってみようと思う。素材はデスクトップに置くマスコット。ただの癒しではなく、ちょっとしたユーティリティとしての機能を盛り込んだものにするつもり。

以前、Windows XP 環境で使用する個人用のユーティリティとして作ったアプリケーションのリメイク版だったりする。当時の実行ファイルは現環境(Windows 10)でも動作可能であることは確認できているのだけれど、多くの外部ライブラリに依存してしまっている関係で再ビルドができなくなってしまっていて機能追加ができない。そこで、外部の依存関係を排除し作り直してみようと思う。

久しぶりに MFC アプリケーションのスケルトンを見た感じは、「懐かしい」の一言。.NET Framework のようなライブラリに比べると設計が古い感じは否めないし、提供される機能は少ない。多くのことを自分で構築しなければならないので、作業効率を考えたら今更感が先行してしまう。

現役だった頃は把握していたつもりのノウハウや制限に関して、年月の経過とともに忘却の彼方となってしまったことも含め、新しいモノに触れたような感触が楽しかったりする。

仕事から帰った後の趣味のプログラミング、サンデープログラマをしばらく楽しんでみようと思う。 


IDisposable [Programming]

.NET Framework の環境で開発をしていると結構な割合で出てくるこのインターフェース。公式な文書以外にも Tips として情報が公開されていることからも分かる通り、結構ややっこしいインターフェースだったりする。IDisposable インターフェースの定義そのものは下記の通り。

Namespace System
'
' 概要:
' アンマネージ リソースを解放するためのメカニズムを提供します。

Public Interface IDisposable
'
' 概要:
' アンマネージ リソースの解放およびリセットに関連付けられているアプリケーション定義のタスクを実行します。
Sub Dispose()
End Interface
End Namespace

IDisposable が解りづらい最大の要因は、これがインターフェースだということにある。インターフェースは見かけを指定することはできるけど、実装を強制することができないから。単に IDisposable インターフェースの要件を満たす実装をするだけならコレでもいい。

Class Test
Implements IDisposable

Public Sub Dispose() Implements IDisposable.Dispose
End Sub
End Class 

ただ、コレではなんの意味もないということは考えないでも解るだろう。IDisposable の使用目的(アンマネージ リソースを解放するためのメカニズムを提供します)に合わせた実装にするには多少手を加える必要があるのだけれど、ここでは先人の知恵を拝借するのが間違いない。Visual Studio の吐くコードはこんな感じになる。

Class Test
Implements IDisposable

Private disposedValue As Boolean ' 重複する呼び出しを検出するには

' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
' TODO: マネージ状態を破棄します (マネージ オブジェクト)。
End If

' TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下の Finalize() をオーバーライドします。
' TODO: 大きなフィールドを null に設定します。
End If
disposedValue = True
End Sub

' TODO: 上の Dispose(disposing As Boolean) にアンマネージ リソースを解放するコードが含まれる場合にのみ Finalize() をオーバーライドします。
'Protected Overrides Sub Finalize()
' ' このコードを変更しないでください。クリーンアップ コードを上の Dispose(disposing As Boolean) に記述します。
' Dispose(False)
' MyBase.Finalize()
'End Sub

' このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
Public Sub Dispose() Implements IDisposable.Dispose
' このコードを変更しないでください。クリーンアップ コードを上の Dispose(disposing As Boolean) に記述します。
Dispose(True)
' TODO: 上の Finalize() がオーバーライドされている場合は、次の行のコメントを解除してください。
' GC.SuppressFinalize(Me)
End Sub
End Class

Visual Basic では、IDisposable インターフェースを実装を定義(Implements IDisposable と記述し Enter キーを押下)した瞬間に必要なフィールド及びメソッドがすべて展開されるので、あとは TODO に必要な処理を追加してやればいい。コメントに今一つ意味不明な単語があるけど、とりあえず横に置いておいて、多くの場合ハイライトした部分だけが必須で、それ以外はほぼ不要ということで問題ないはず。

.NET Framework においては、ガベージコレクションの対象とならないオブジェクトをアンマネージド リソースと呼ぶ。メモリ以外のオブジェクトを指すと考えて問題ない。ファイルを扱う際のファイルハンドルやデータベースのコネクションなどがこれに含まれる。

注意しなければならないのは、上記のソースコードに含まれる、マネージ オブジェクトやアンマネージ オブジェクトという単語はコレとは少し違うということ。

例えば、System.IO.FileStream というクラスがある。このクラスはファイル ハンドルをカプセル化し、ファイルに対する各種のオペレーションを提供するもの。System.IO.FileStream の実装としては、OS の提供するファイルハンドルを管理する必要があるので、直接これを操作しているものと想像できる。上述の通り、ファイル ハンドルはアンマネージド リソースなのだけれど、アンマネージド リソースを管理する System.IO.FileStream というクラス(のオブジェクト)はマネージ オブジェクトという扱いになる。

なので、アンマネージ オブジェクトを意識することはあまり(絶対ではない)ないと考えられる。

さて、ここで上記の Test の派生クラスを定義する場合のことを考える。そこでも IDisposable の機能を定義したいとしたらどうしようか?この時に基本クラスの実装には手を付けないのが前提。

解答だけを示すならこうなる。

  • 派生クラスでは IDisposable インターフェースの再実装をしない。
  • 派生クラスで Dispose(disposing As Boolean) メソッドをオーバーライドする。
     処理は上記と一緒だけれど、以下の変更を施す必要がある。
    • Overridable を Overrides に変更する。
    • メソッドの最後で基本クラスの Dispose(disposing As Boolean) メソッドを呼び出す。
  • disposedValue フィールドは、基本クラスで定義されているものを流用しちゃいけない。
     もともと Private になっているので問題ないだろうけど、これを書き換えて無理やりアクセスしないこと。

要は、既に実装されている Dispose( ) メソッドから、独自の実装を施した Dispose(disposing As Boolean) を呼び出してもらえるようにするだけ。

これだけなら、単に仮想メソッドによる機能の追加になるのだけれど、よくよく見ると大きな問題が隠れている。

IDisposable インターフェースによって仕様が決められているのは、Dispose( ) メソッドだけであって、独自に定義し、派生クラスでオーバーライドした Dispose(disposing As Boolean) に関する仕様はどこにも明示されていない。

今回は基本クラスも派生クラスも自作しているので、Dispose(disposing As Boolean) の仕様はわかるけれど、前提が変わってしまった場合には上記の実装は結構危ういものになってしまことがわかる。Visual Studio のような開発環境を使用していると、メソッドのシグネチャ等の情報も提供してくれるけれど、実装については正確にわからない可能性もある。

上記は Microsoft が定めるガイドラインに沿った記述であり、Microsoft 謹製のツールが吐くコードなので問題になることは多くはないと思うけど、基本クラスを他者が作成している場合等も考えると絶対安全とも言えないところがある。

今更ながらではあるけど、今回のサンプルコードは Visual Basic のものを採用している。C# を使っても同様のコードを記述することも可能だし、Visual Studio がスニペットの挿入をサポートしているのでその機能を利用することも可能。ただ、その時にいくつかの選択肢が提示される。「Dispose パターンを使って(明示的に)インターフェースを実装します」を選択してやらないとなんの機能も実装していない Dispose( ) メソッドが構築されてしまう。Visual Basic ではなんの選択肢も提示されずにガイドライン通りのコードが生成される。紛れがなくていいのか、なんでもできるのがいいのか、好みの別れるところであるとは思うけど、、、。

 


前の10件 | - Programming ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。