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

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

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

Re[4]: OracleCommandについてDispose


(過去ログ 145 を表示中)

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

■84927 / inTopicNo.1)  OracleCommandについてDispose
  
□投稿者/ yapii (4回)-(2017/08/24(Thu) 10:55:12)

分類:[.NET 全般] 

マイクロソフトの下記サイトで、以下のようなコードの記載がありました。
https://msdn.microsoft.com/ja-jp/library/system.data.oracleclient.oraclecommand(v=vs.110).aspx

OracleCommandについてDisposeをしていないのですが、
OracleCommandはDispose不要なのでしょうか?
不要であればその理由をご教授ください。

またreaderもcloseはしていますが、disposeはしていません。
readerもcloseされていれば、disposeは不要でしょうか?


Public Sub ReadMyData(ByVal connectionString As String)
    Dim queryString As String = "SELECT EmpNo, DeptNo FROM Scott.Emp"
    Using connection As New OracleConnection(connectionString)
        Dim command As New OracleCommand(queryString, connection)
        connection.Open()
        Dim reader As OracleDataReader = command.ExecuteReader()
        Try
            While reader.Read()
                Console.WriteLine(reader.GetInt32(0) & ", " _
                   & reader.GetInt32(1))
            End While
        Finally
            ' always call Close when done reading.
            reader.Close()
        End Try
    End Using
End Sub

OracleCommandのDisposeを呼んでおいたほうが良い場合、
複数のOracleCommandを使用すると可読性が悪くなります。
書き方のコツがあればご教授ください。

Public Sub ReadMyData2(ByVal connectionString As String, flg As Boolean)
    Dim cmd1 As OracleCommand = Nothing
    Dim cmd2 As OracleCommand = Nothing
    …cmd3, cmd4
    Try
        'コマンド1
        Dim queryString1 As String = "SELECT * FROM Table1 WHERE Field1 = :Field1 And Field2 = :Field2"
        cmd1 = New OracleCommand(queryString1)
        cmd1.Parameters.Add("Field1", 12345)
        cmd1.Parameters.Add("Field2", "12345")

        'コマンド2 ※条件により使用しない※
        If flg Then
            Dim queryString2 As String = "SELECT * FROM Table2 WHERE Field1 = :Field1 And Field2 = :Field2"
            cmd2 = New OracleCommand(queryString1)
            cmd2.CommandText = queryString2
            cmd2.Parameters.Add("Field1", 99999)
            cmd2.Parameters.Add("Field2", "99999")
        End If



        Using connection As New OracleConnection(connectionString)
            connection.Open()
            Try

                'コマンド1実行
                Using reader As OracleDataReader = cmd1.ExecuteReader()
                    While reader.Read()
                        Console.WriteLine(reader.GetInt32(0) & ", " & reader.GetInt32(1))
                    End While
                    reader.Close()
                End Using 'reader


                'コマンド2実行 ※条件により実行しない※
                If cmd2 IsNot Nothing Then
                    Using reader As OracleDataReader = cmd2.ExecuteReader()
                        While reader.Read()
                            Console.WriteLine(reader.GetInt32(0) & ", " & reader.GetInt32(1))
                        End While
                        reader.Close()
                    End Using 'reader
                End If

            Finally
                connection.Close()
            End Try

        End Using 'connection

    Finally
        If cmd1 IsNot Nothing Then cmd1.Dispose()
        If cmd2 IsNot Nothing Then cmd2.Dispose()
    End Try
End Sub

引用返信 編集キー/
■84929 / inTopicNo.2)  Re[1]: OracleCommandについてDispose
□投稿者/ WebSurfer (1285回)-(2017/08/24(Thu) 12:01:16)
No84927 (yapii さん) に返信

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

OracleCommand が継承している DbCommand は IDisposable インターフェイス
を継承しているので、オブジェクトが使用されなくなった時点で Dispose メ
ソッドを呼び出すのが基本だと思います。

ただし、実質的に何かの効果があるかと言うと、MSDN ライブラリには "マネー
ジリソースのみを使用する型は、ガベージコレクターによって自動的にクリアさ
れるため、このような型で Dispose メソッドを実装しても、パフォーマンス上
の利点はありません" と書いてあったりします。

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

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

ではどうするかですが、ソースコードは変更される可能性がある、将来ネイティ
ブリソースが含まれる可能性はゼロではない(ゼロに近いとは思いますが)・・・
・・・ということを考えるべきで、やはり Dispose は呼ぶべきと思います。

詳しくは以下の記事を見てください。

SqlCommand の Dispose は呼ぶべきか?
http://surferonwww.info/BlogEngine/post/2013/04/23/whether-to-call-dispose-method-for-sqlcommand-object.aspx

可読性の件については、上の記事のサンプルコードにあるように、using 句を使
ってはいかがですか?
引用返信 編集キー/
■84936 / inTopicNo.3)  Re[1]: OracleCommandについてDispose
□投稿者/ 魔界の仮面弁士 (1393回)-(2017/08/24(Thu) 14:13:29)
No84927 (yapii さん) に返信
> マイクロソフトの下記サイトで、以下のようなコードの記載がありました。
> https://msdn.microsoft.com/ja-jp/library/system.data.oracleclient.oraclecommand(v=vs.110).aspx

Microsoft 製の System.Data.OracleClient は .NET 4.0 では非推奨機能となっていますので、
Oracle 純正ののもの、あるいはサードパーティ製品(DataDirect とか dotConnect)での
接続に切り替えることをお勧めします。

廃止(Obsolete)となったことで、内部実装については、今後仕様変更されることも無さそうですが、
そもそも廃止されてしまえば呼び出すことすらできないわけで。


> OracleCommandについてDisposeをしていないのですが、
ちなみに DataSet や DataTable も IDisposable です。


まぁ、良く分からなければ Dispose しておいた方が無難とは思いますが、
OracleCommand が IDisposable なのは、このクラスが
「System.ComponentModel.Component クラス」を継承しているからに過ぎません。

Component クラスは IDisposable インターフェイスを実装していますので、
その派生クラスにも Dipose メソッドが存在することになります。


> OracleCommandはDispose不要なのでしょうか?

Dispose されていないサンプルコードがあったとき、
「Dispose し忘れた」のか
「Dispose してもしなくても一緒」だったのか
「Dispose すべきではないので呼ばなかった」のかは
なかなか分かりにくいですね。


で、OracleCommand について言えば、私は一応呼ぶようにしていますが、
オラクルのリソースという点について言えば、呼ばなくても問題ありません。


> 不要であればその理由をご教授ください。

Component クラスの IDisposable.Dispose 実装というのは、内部処理的には
 Public Sub Dispose()
  Me.Dispose(True)
  GC.SuppressFinalize(Me)
 End Sub
になっています。処分処理の本体は Me.Dispose(True) の部分です。
https://referencesource.microsoft.com/#System/compmod/system/componentmodel/Component.cs,118

一方、Dipose されていない(GC.SuppressFinalize されていない)場合は、ファイナライザーにて
 Sub Finalize()
  Me.Dispose(False)
 End Sub
に相当する処理が行われ、このタイミングで処分されます。こちらは GC 任せのパターンですね。
https://referencesource.microsoft.com/#System/compmod/system/componentmodel/Component.cs,34

いずれにせよ、Component 継承クラスにおける IDisposable.Dispose の実装というのは
引数付きの Dispose メソッド――具体的には:
 Component クラス自身の Protected Overridable Sub Dispose(disposing As Boolean)
もしくは
 Component 継承クラスの Protected Overrides Sub Dispose(disposing As Boolean)
のいずれかで行われる仕組みです。

そして、Component.Dispose(Boolean) で実装されている処理は、
Disposed イベントを RemoveHandler したり、
Me.Site?.Container?.Remove(Me) を呼び出しているのみです。
しかも Dipose(True) で呼ばれたとき限定です。Dispose(False) の場合は何もしていません。
https://referencesource.microsoft.com/#System/compmod/system/componentmodel/Component.cs,180

でもって、Component を継承したクラスが廃棄すべき追加リソースを保有する場合には、
そのクラスに Sub Dispose(Boolean) メソッドをオーバーライドしておき、
その中に、追加リソースの廃棄処理を実装することになっています。

それらを踏まえた上で、改めて Component を継承した OracleCommand について見てみると、
そもそもこのクラスでは Component.Dispose(Boolean) が「オーバーライド」されておらず、
単に、Component.Dispose(Boolean) の実装がそのまま呼ばれるに過ぎません。

そのため、このクラスを Dispose したからといって、
Oracle のためのリソースが解放されるわけではないということです。


下記は、Dispose(Boolean) メソッドのオーバーライド状況を調べるための実験コードです。

Imports System.Reflection
Module Module1
 Sub Main()
  Dim types As Type() = { _
   GetType(System.Data.OracleClient.OracleCommand), _
   GetType(System.Data.OracleClient.OracleConnection), _
   GetType(System.Data.OracleClient.OracleDataReader) _
  }
  Dim bf As BindingFlags = BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic
  For Each t As Type In types
   Dim mi = t.GetMethod("Dispose", bf, Nothing, New Type() {GetType(Boolean)}, Nothing)
   Console.Write(mi.ReflectedType.FullName)
   Console.WriteLine(" - " & mi.ToString())
   Console.WriteLine("を宣言しているクラスは:" & mi.DeclaringType.FullName)
   Console.WriteLine()
  Next
 End Sub
End Module

【実行結果】
System.Data.OracleClient.OracleCommand - Void Dispose(Boolean)
を宣言しているクラスは:System.ComponentModel.Component

System.Data.OracleClient.OracleConnection - Void Dispose(Boolean)
を宣言しているクラスは:System.Data.OracleClient.OracleConnection

System.Data.OracleClient.OracleDataReader - Void Dispose(Boolean)
を宣言しているクラスは:System.Data.Common.DbDataReader



> またreaderもcloseはしていますが、disposeはしていません。
> readerもcloseされていれば、disposeは不要でしょうか?

丁寧に処分するなら、Close した上で Dispose でしょうけれども、
OracleDataReader の Dispose と Close はほぼ等価ですね。

上記実験結果からも分かるように、OracleDataReader.Dispose(Boolean) を実装しているのは
DbDataReader.Dispose(Boolean) メソッドだそうです。
しかしこのメソッドが行っている処理とは Me.Close() を呼び出すこと『だけ』です。
https://referencesource.microsoft.com/#System.Data/System/Data/Common/DbDataReader.cs,70

ゆえに、OracleDataReader については Dispose ≒ Close と言えるでしょう。

なお、OracleConnection の方はコネクションプールの管理があるため、
こちらの Dipose では、Close 以外の処理も行われています。
引用返信 編集キー/
■84937 / inTopicNo.4)  Re[1]: OracleCommandについてDispose
□投稿者/ furu (118回)-(2017/08/24(Thu) 14:22:42)
No84927 (yapii さん) に返信
Dispose(Using)の使用と不使用があり
一貫性が取れてなくて、ひどいですね。

Disposeメソッドを実装しているものはDisposeすべきだと思います。
そのクラスがアンマネージリソースを使っているかどうか
すぐに判断できるものでないし、将来使われるかもしれないです。

ただ、そうは言ってもDisposeをサボっている時もあります。
印刷処理でSystem.Drawing.FontをNewしていますが
まったくDisposeしていません。

可読性が悪くなる問題ですが、以下のどちらかで書いています。

 ・無駄でも全部usingしてしまう
 ・使用する時(ifブロックの中)にusingする

引用返信 編集キー/
■84943 / inTopicNo.5)  Re[2]: OracleCommandについてDispose
□投稿者/ 魔界の仮面弁士 (1395回)-(2017/08/24(Thu) 15:49:59)
No84937 (furu さん) に返信
> Disposeメソッドを実装しているものはDisposeすべきだと思います。

System.Data.OracleClient 名前空間に限定しない一般的な話として見れば、
基本的にはその通りだと思います。

ただしそれには、『使い終わったときに』という条件が付きます。
直ちに Dispose すべきなのは、『そのオブジェクトがもう使われない事が確実な場合』だけです。



たとえば MemoryStream に対して「BinaryWriter」で書き込んで、
その MemoryStream の Position を戻してから「BinaryReader」で
読みなおすような処理があったとします。

この場合、書き込みが終わったからといって、
BinaryWriter を直ちに Dispose するわけにはいきません。

対象となる MemoryStream まで閉じられてしまうため、
その後の BinaryReader の処理が行えなくなってしまうためです。


> 印刷処理でSystem.Drawing.FontをNewしていますが
> まったくDisposeしていません。
PrintDocument_PrintPage などで New Font した場合は、
Dispose した方が良いと思います。使われなくなるタイミングが明確だからです。

PrintPage のたびに New → Dispose するかわりに、
Load 時に New して、FormClosed 等で Dispose するのも手でしょう。


一方、画面上で使われる Font の場合は、『他の場所でも使われているのか』を
判断することは難しいので、私は Dipose しないことが多いです。


たとえば Form 上のコントロールだと、Font プロパティがアンビエントになっているため、
初期状態では、TextBox1.Font と、TextBox1.Parent.Font には
同じインスタンスがセットされていたりします。


そのほか、
 Dim f As New Font("MS Mincho", 36)
 TextBox1.Font = f
 TextBox2.Font = f
のように、ひとつのインスタンスが複数のコントロールに
セットされるケースもありますので、これも話を難しくています。

上記の場合、TextBox1.Font と TextBox2.Font と f は、
すべて同一のインスタンスを参照することになります……通常は。

しかし新しい Font インスタンスを代入したとしても、その前にセットされていた Font が
同じ内容だった場合―― oldFont.Equals(newFont) が True となるような場合――には、
代入操作そのものが無視される可能性があります。

そのため上記の代入処理の直後でも、これらが別のインスタンスになっている
可能性は否定できません。

このような特性があるため、特定の Font インスタンスが、他の場所で
未使用であることを保証するのは難しかったりします。Image などもそうですね。
引用返信 編集キー/
■84946 / inTopicNo.6)  Re[3]: OracleCommandについてDispose
□投稿者/ なちゃ (213回)-(2017/08/24(Thu) 16:07:48)
一貫性や将来的な保証を考えてすべてDisposeすべきという考え方自体はそれはそれで理解できますが、はっきり言って可読性の低下やメンテナンス性の低下のデメリットに比べて、将来的にDispose必要なように書き換えられる可能性などほぼ皆無といって良いので、現実的にはデメリットが大きいです。
必要かどうか判断できない場合は実行する方が無難ではありますが。
引用返信 編集キー/
■84948 / inTopicNo.7)  Re[4]: OracleCommandについてDispose
□投稿者/ なちゃ (214回)-(2017/08/24(Thu) 16:28:45)
例えば上でも触れられていますが、データセットなどは明確に値セマンティクスのオブジェクトであり、これにDisposeが必要になるようなことはあり得ません。
IDisposableだからといってこれを呼び出すのは明確に不要です。
シリアライズやデシリアライズも起こるので、そもそも完全に寿命を管理することは不可能です。
つまり最初からそもそも一貫性はとれてないわけです。
まあこれはやや設計が良くなかったとは感じます。、
引用返信 編集キー/


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

このトピックに書きこむ

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

管理者用

- Child Tree -