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

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

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

Re[3]: IDisposableの実装について


(過去ログ 130 を表示中)

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

■77096 / inTopicNo.1)  IDisposableの実装について
  
□投稿者/ 日曜プログラマー (1回)-(2015/09/10(Thu) 23:44:50)

分類:[VB.NET/VB2005 以降] 

2015/09/11(Fri) 00:08:11 編集(投稿者)
2015/09/11(Fri) 00:05:29 編集(投稿者)

いつもお世話になっております。

IDisposableの使用方法について質問です。

例えば、コンボボックスの更新時にBeginupdate・Endupdateを使用する際に
Endupdateが必ず実行されるようにTry-Finallyで実装することがあると思います。
でも、わざわざTry-Finallyを書くよりもusingで実装できたほうが楽だと考え、下記のようなサンプルクラスを作成しました。
ただIDisposableはリソースを開放するためのインターフェイスだと理解していますが、
今回のサンプルや追々実装したい内容ではリソースの開放は行いません。

これは例で、他にもDB上のデータのロック・アンロックなどDispose内でクエリを実行するようなのも実装したいと考えています。

変な質問ですが、この実装はアリですか?ナシですか?
理由やそういう結論に達する考え方も教えてほしいです。
また、他に楽に書ける方法などあればご教授いただきたいです。

よろしくお願いいたします。

'サンプルクラス
Public Class Sample
Implements IDisposable

Private _target As ComboBox
Public Sub New(ByVal target As ComboBox)
Me._target = target
End Sub

Public Sub Update()
Me._target.BeginUpdate()
End Sub

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

Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
End If
If Me._target IsNot Nothing Then
Me._target.EndUpdate()
End If

End If
disposedValue = True
End Sub

Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
End Sub
End Class
引用返信 編集キー/
■77097 / inTopicNo.2)  Re[1]: IDisposableの実装について
□投稿者/ 魔界の仮面弁士 (489回)-(2015/09/11(Fri) 01:54:45)
2015/09/11(Fri) 10:37:13 編集(投稿者)
2015/09/11(Fri) 10:22:29 編集(投稿者)
2015/09/11(Fri) 10:06:44 編集(投稿者)

No77096 (日曜プログラマー さん) に返信
> ただIDisposableはリソースを開放するためのインターフェイスだと理解していますが、
> 今回のサンプルや追々実装したい内容ではリソースの開放は行いません。

このインターフェイスの説明は、こう書かれていますね。

.NET 1.1、2.0、3.0:
『割り当てられたアンマネージ リソースを解放するメソッドを定義します。』

.NET 3.5、4
『割り当てられたリソースを解放するメソッドを定義します。』
『このインターフェイスは主に、アンマネージ リソースを解放するために使用します。』

.NET 4.5、4.6
『アンマネージ リソースを解放するためのメカニズムを提供します。』
『このインターフェイスは主に、アンマネージ リソースを解放するために使用します。』


リソースの解放(≠開放)のために使われる事が多いですが、
実際には何も解放しないというクラス実装になっている例もあります。

また、今回やりたいことは、データベース処理における
TransactionScope クラスのような物なのだと思いますし、
手順的には問題無いものだと思います。


ただ、Dispose という語からは「処分する」動作がイメージされますので、
それが不自然に感じないようにする工夫は必要かと思います。


たとえば StreamReader/StreamWriter の場合、Dispose された場合には、
内包される BaseStream も巻き込んで処分されるようになっていますよね。

ゆえに Reader/Writer の場合には、IDisposable でありながらも
意図的に Dispose しない場合も多々あるわけで。


それを考えると、利用者側の立場からすれば
 Using New Sample(ComboBox1)
が行われたあと、Dispsoe 時に ComboBox を巻き込んで処分されるのでは
無いだろうかという不安さ・不自然さを感じてしまいました。


もちろん、クラスの実装者側からすれば、ComboBox が Dispose されないことは
明確なのですが、それがクラスの利用者側に伝わるかは別の問題ですよね。

クラス名に拘るとか、ドキュメント/XML コメントなどで明記ししておくなどの
気遣いは必要かも知れません。

――そのための具体例となると、あまり良い例が思いつかないのですが、
クラス名に何らかの統一ルールを決めておくのは如何でしょうか。

たとえば TransactionScope を真似て、
ComboBoxUpdateScope クラスとか、DataLockScope(Of T) クラスなど、
何某 Scope といった命名規約で統一しておくとか。



それと「Using ブロック」を使う前提だという事は、その呼び出し処理の
開始と完了は、単一のメソッド内で完了するわけですよね。

たとえば、Button1_Click にて BeginUpdate して
Timer1_Tick にて EndUpdate される…といった、
複数のメソッドにまたがる処理では無い、と。


だとすれば、利用者側で Using を強制するよりは、Azulean さんが
書かれたように、Using 内で行わせようとしていた処理そのものを、
AddressOf なりラムダ式なりで渡す方が良いかもしれません。

ajax のメソッドチェーンや、.NET の Task クラスなども、
処理内容を匿名メソッド等で渡せるようになっていますね。
引用返信 編集キー/
■77098 / inTopicNo.3)  Re[1]: IDisposableの実装について
□投稿者/ Azulean (518回)-(2015/09/11(Fri) 07:14:45)
2015/09/11(Fri) 07:39:16 編集(投稿者)
No77096 (日曜プログラマー さん) に返信
> ただIDisposableはリソースを開放するためのインターフェイスだと理解していますが、
> 今回のサンプルや追々実装したい内容ではリソースの開放は行いません。

本来の意図・理想を重視するとその通りです。
それに反することに違和感を覚える方もいらっしゃるので、そのチーム次第かと思います。

> 変な質問ですが、この実装はアリですか?ナシですか?
> 理由やそういう結論に達する考え方も教えてほしいです。
> また、他に楽に書ける方法などあればご教授いただきたいです。

私は「あり」だと考えています。
try-finally を重ねていくとインデントが深くなる、実装ミスのリスクが増えるので、かんたんに書ける using スコープを使うテクニックの方を推しています。

実際、Rx(Reactive Extentions) には「Dispose が呼ばれたら任意の Action を実行する」オブジェクトを作ることができる、Disposable.Create というメソッドもあるので、
そういった文化もあると考えています。
https://msdn.microsoft.com/en-us/library/system.reactive.disposables.disposable.create



あとは、BeginUpdate, EndUpdate, Try-Finally を書いたユーティリティメソッドを用意しておいて、そいつに更新用のアクションを渡すことでしょうか。
C# で申し訳ないですが、イメージとして。

void InUpdateAction(ComboBox combo, Action action)
{
  combo.BeginUpdate();
  try
  {
    action();
  }
  finally
  {
    combo.EndUpdate();
  }
}

// 例
InUpdateAction(_comboTest, () => { _comboTest.Items.Clear(); /* BeginUpdate と EndUpdate の間にやりたい処理を詰める */ });

引用返信 編集キー/
■77099 / inTopicNo.4)  Re[1]: IDisposableの実装について
□投稿者/ 魔界の仮面弁士 (490回)-(2015/09/11(Fri) 10:43:04)
2015/09/11(Fri) 14:01:36 編集(投稿者)

No77096 (日曜プログラマー さん) に返信
> Public Class Sample
>  Implements IDisposable
>  Public Sub New(ByVal target As ComboBox)

試しに ListBox 版を作って実験してみました。

<削除>ListBox2 は高速化したけど、ListBox3 / ListBox4 は高速化しなかった…。</削除>


<追記>
→ごめんなさい! 私の勘違いでした。

BeginUpdate が、コンストラクタの中で
  Me._target = target
  Me._target.BeginUpdate()
と同時に呼ばれているのだと思い違いをしていましたが、
提示頂いたコードでは、Update メソッドを明示的に呼ばないと
BeginUpdate されない実装になっていたのですね。
</追記>




 For n = 1 To 10000
  ListBox1.Items.Add(n)
 Next


 ListBox2.BeginUpdate()
 For n = 1 To 10000
  ListBox2.Items.Add(n)
 Next
 ListBox2.EndUpdate()


 Using x As New Sample(ListBox3)
  x.Update()  '追記:これを呼ばないと BeginUpdate されない
  For n = 1 To 10000
   ListBox3.Items.Add(n)
  Next
 End Using


 Using x = ListBox4.CreateUpdateBlock() 'Sample を Return する匿名メソッド
  x.Update()  '追記:これを呼ばないと BeginUpdate されない
  For n = 1 To 10000
   ListBox4.Items.Add(n)
  Next
 End Using
引用返信 編集キー/
■77100 / inTopicNo.5)  Re[2]: IDisposableの実装について
□投稿者/ ぶなっぷ (50回)-(2015/09/11(Fri) 12:49:48)
個人的には、
C++時代はデストラクタを何でも後始末メソッドとして使ってました。

当時の私はデストラクタこそ、C++の最大の武器だと思っていました。
Listの派生クラスを作って、そのデストラクタで全要素を削除する。

とにかく全てのクラスはnewしたらそのリストにAddする。
で、リストの寿命をその要素が必要な期間に合わせておく。
で、疑似ガベージコレクタとか言ってました(笑)

なので、その延長で考えるなら、IDisposableで「何でも後始末」は賛成
と言いたいところですが、問題はC++のデストラクタは完全自動実行です
が、IDisposableはusingしなきゃ実行されないこと。

人はusingはリソースの後始末だと思ってるので、
「リソースの後始末がなきゃよばなくていいんじゃね?」
と思われてしまったら、呼んでもらえないと言うこと。

そののあたりのジレンマとどう闘うかですね(^^;)

引用返信 編集キー/
■77104 / inTopicNo.6)  Re[1]: IDisposableの実装について
□投稿者/ WebSurfer (652回)-(2015/09/12(Sat) 12:28:57)
No77096 (日曜プログラマー さん) に返信

全くの個人的意見で質問者さんには当てはまらないかもしれませんが・・・

> この実装はアリですか?ナシですか?

「ナシ」だと思います。理由は、ルールを作ってドキュメント化し、組
織的にシステマチックに強制的に実施しないと忘れるからです。

質問者さんのハンドル名からの想像ですが、個人でプログラムを作って
おられるのではないかと思います。

そうであれば「ルールを作ってドキュメント化し・・・」というところ
はどうなんでしょう?

自分は趣味のサンデープログラマーなんですが、いろいろと忘れてしま
って、昔作ったプログラムを修正するような場合に苦労しています。ま
して、特別なことをすると、保守不能になるかもしれません。(笑)

質問者さんの場合は問題ないということでしたら失礼しました。



以下、質問とは直接関係ないことですが・・・

Dispose は不要と言っているわけではないです。

逆に、IDisposable を継承するクラスは Dispose を呼ぶべきというのが
基本だと思っています。

以下の MSDN ライブラリによると、"マネージリソースのみを使用する型は、
ガベージコレクターによって自動的にクリアされるため、このような型で
Dispose メソッドを実装しても、パフォーマンス上の利点はありません。"
とのことです。(.NET 4.6 / 4.5 の記事にはその説明はありませんが同じ
ことかと思います)

Dispose メソッドの実装
https://msdn.microsoft.com/ja-jp/library/vstudio/fs2xkftw(v=vs.100).aspx

しかし、Dispose メソッドには、メモリ開放の機能以外に、GC.SuppressFinalize
メソッドを実装することにより、冗長なファイナライザーの呼び出しを防ぐこ
とができるそうです。上に紹介した「Dispose メソッドの実装」にもそのこと
が書いてあります。

クラスによってはコンストラクタに GC.SuppressFinalize メソッドが実装さ
れており、冗長な Finalize メソッドの呼び出しを防ぐという意味では
Dispose メソッドを呼ぶ必要はないものもあります。

ただし、コンストラクタでの GC.SuppressFinalize の実装は MSDN ライブラ
リなどにはドキュメント化されてない(ソースコードを見ないと分からない)、
ソースコードは変更される可能性がある、将来ネイティブリソースが含まれる
可能性はゼロではない・・・ということを考えると、やはりDispose は呼んだ
方がよさそうです。

引用返信 編集キー/
■77105 / inTopicNo.7)  Re[2]: IDisposableの実装について
□投稿者/ 日曜プログラマー (2回)-(2015/09/12(Sat) 13:55:38)
ご返信ありがとうございます。

皆様のご意見、大変参考になりました。

確かに決まりきった実装以外を匿名メソッドにしておけば
プログラマーは内部の実装だけを気にしておけばよいですね。

毎度Try-Finallyを記述するのが面倒だったので、
今回のご返信内容を参考にして解消できそうです。

ありがとうございました。
解決済み
引用返信 編集キー/
■77115 / inTopicNo.8)  Re[3]: IDisposableの実装について
□投稿者/ Jitta (160回)-(2015/09/14(Mon) 20:28:14)
No77100 (ぶなっぷ さん) に返信

> なので、その延長で考えるなら、IDisposableで「何でも後始末」は賛成
> と言いたいところですが、問題はC++のデストラクタは完全自動実行です
> が、IDisposableはusingしなきゃ実行されないこと。
>
> 人はusingはリソースの後始末だと思ってるので、
> 「リソースの後始末がなきゃよばなくていいんじゃね?」
> と思われてしまったら、呼んでもらえないと言うこと。
>
> そののあたりのジレンマとどう闘うかですね(^^;)

「後始末が必要なクラスである」ことを通知するのが、IDisposable インターフェイスを実装することかと。
つまり、「リソースの後始末がなきゃ呼ばなくてもいいんじゃね?」ではなく、
「リソースの後始末が必要だから IDisposable を実装する」
「IDisposable を実装しているから、インスタンスが必要なくなったら Dispose する」
じゃないかと。

 Windows Form の Control クラスは、インスタンス化しただけではリソースの確保を行っていません。
表示して初めてリソースの確保を行い、Dispose が必要になります。
Form クラスは、Show/Close の組み合わせなら Dispose も行います。しかし、ShowDialog では Dispose を呼ばなければなりません。
そんなことは関係なく、「IDisposable を実装しているから、必要なくなったら Dispose する」。
解決済み
引用返信 編集キー/
■77116 / inTopicNo.9)  Re[3]: IDisposableの実装について
□投稿者/ なちゃ (80回)-(2015/09/15(Tue) 03:09:34)
昔と違って標準ライブラリなどでも平気でリソース解放ではなくいわゆるコンテキストやスコープ生成的な目的でIDisposableを使うようになっているので、目的が明確になっていれば使えばいいと思いますよ。
クラスのドキュメントコメントに明記しとけば忘れる心配はありません。
※もちろんきちんと実装する前提ですが

個人的には例えばMarshalByValueComponentなんてのはDispose呼ぶ必要なんてないと思いますけどね。
これにネイティブリソースが追加されたりDispose呼ばないとリソース解放漏れが発生するように実装が変えられるなんてありえないので。
例えばDataSetとかなんてそうです。
勝手にシリアライズされて管理外に飛んでったりするものを、確実にDisposeするなんて不可能ですし無意味なのは自明なので。
解決済み
引用返信 編集キー/


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

このトピックに書きこむ

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

管理者用

- Child Tree -