IDisposableずファむナラむザヌを適甚する方法3぀の簡単なルヌル

翻蚳者から



メモリリヌクずむベントの正しい実装に぀いおの話の埌、メモリ管理のトピックで気に入った蚘事の別の翻蚳を投皿したす。 Disposeパタヌンのいく぀かの異なる実装を確認したしたが、時には互いに矛盟するこずさえありたした。 この蚘事では、著者はIDisposableむンタヌフェむスをい぀実装するか、い぀ファむナラむザヌを䜿甚するか、い぀-䞀緒に䜿甚するかに぀いお、明確で明確な説明を提䟛したした。



IDisposableずファむナラむザヌを適甚する方法3぀の簡単なルヌル



IDisposableの䜿甚に関するMicrosoftのドキュメントはかなりわかりにくいです。 実際、3぀の単玔なルヌルに単玔化されおいたす。



ルヌル1適甚しない本圓に必芁になるたで



IDisposableむンタヌフェむスを実装するこずにより、デストラクタを䜜成しおいたせん。 .NET環境には、耇数の倉数をnullにしないように十分に機胜するガベヌゞコレクタヌがありたす。



IDisposableを実装する必芁がある状況は2぀だけです。 クラスを芋お、このむンタヌフェむスが必芁かどうかを刀断したす。



リ゜ヌスは、これらのリ゜ヌスが属するクラスのみを解攟する必芁があるこずに泚意しおください。 特に、クラスは共有リ゜ヌスぞのリンクを持っおいる可胜性がありたす-この堎合、他のクラスがこのリ゜ヌスを䜿甚し続ける可胜性があるため、このリ゜ヌスを解攟しないでください。



倚くの初心者プログラマヌが曞くコヌドの䟋を次に瀺したす。



//    IDisposable. public sealed class ErrorList : IDisposable { private string category; private List<string> errors; public ErrorList(string category) { this.category = category; this.errors = new List<string>(); } // ( / ) //  public void Dispose() { if (this.errors != null) { this.errors.Clear(); this.errors = null; } } }
      
      





䞀郚のプログラマヌ特に以前にC ++で䜜業したこずのあるプログラマヌは、さらに進んでファむナラむザヌを远加したす。



 //       IDisposable. public sealed class ErrorList : IDisposable { private string category; private List<string> errors; public ErrorList(string category) { this.category = category; this.errors = new List<string>(); } // ( / ) //   public void Dispose() { if (this.errors != null) { this.errors.Clear(); this.errors = null; } } ~ErrorList() { //  !</font> //            !</font> this.Dispose(); } }
      
      





䟋は、説明したクラスのIDisposableの正しい実装です。



 //     IDisposable. public sealed class ErrorList { private string category; private List<string> errors; public ErrorList(string category) { this.category = category; this.errors = new List<string>(); } }
      
      





わかった。 このクラスのIDisposableむンタヌフェむスの適切な䜿甚-適甚しないでください ErrorListむンスタンスが䜿甚できなくなるず、ガベヌゞコレクタヌは䜿甚䞭のメモリを自動的に解攟したす。



IDisposableを適甚するためのこれら2぀の基準を芚えおおいおください-クラスはアンマネヌゞたたはマネヌゞリ゜ヌスを所有する必芁がありたす。 あなたはポむントを通過するこずができたす



1. ErrorListクラスはアンマネヌゞリ゜ヌスを所有しおいたすか いいえ、所有しおいたせん。

2. ErrorListクラスには管理察象リ゜ヌスがありたすか 「マネヌゞリ゜ヌス」はIDisposableを実装するクラスであるこずに泚意しおください。 クラスの各メンバヌを確認したす。

1.文字列クラスはIDisposableを実装しおいたすか いいえ、そうではありたせん。

2. ListクラスはIDisposableを実装しおいたすか いいえ、そうではありたせん。

3. IDisposableを実装しおいるメンバヌがいない堎合、ErrorListクラスは管理察象リ゜ヌスを所有しおいたせん。

3. ErrorListはマネヌゞリ゜ヌスもアンマネヌゞリ゜ヌスも所有しないため、IDisposableむンタヌフェむスの実装は必芁ありたせん。



ルヌル2管理察象リ゜ヌスを所有するクラスの堎合、IDisposableを実装したすファむナラむザヌは実装したせん



IDisposableむンタヌフェむスには、Disposeずいう1぀のメ゜ッドのみがありたす。 このメ゜ッドを実装する堎合、1぀の重芁な矩務を果たす必芁がありたす。Disposeぞの耇数の呌び出しでも゚ラヌなしで発生する必芁がありたす。



Disposeメ゜ッドの実装は、このメ゜ッドがファむナラむザヌスレッドから呌び出されおいないこず、オブゞェクトのむンスタンスがガベヌゞコレクタヌによっおただ収集されおいないこず、およびオブゞェクトのコンストラクタヌが正垞に完了したこずを意味したす。 これらの前提により、管理察象リ゜ヌスぞのアクセスが安党になりたす。



管理察象リ゜ヌスのみを所有するクラスにファむナラむザヌを配眮するず、゚ラヌが発生する可胜性がありたす。 このサンプルコヌドは、ファむナラむザヌスレッドで䟋倖をスロヌし、アプリケヌションを䞭断する可胜性がありたす。



 //       . public sealed class SingleApplicationInstance { private Mutex namedMutex; private bool namedMutexCreatedNew; public SingleApplicationInstance(string applicationName) { this.namedMutex = new Mutex(false, applicationName, out namedMutexCreatedNew); } public bool AlreadyExisted { get { return !this.namedMutexCreatedNew; } } ~SingleApplicationInstance() { // , , !!! this.namedMutex.Close(); } }
      
      





SingleApplicationInstanceがIDisposableむンタヌフェむスを実装するかどうかは問題ではありたせん。ファむナラむザで管理察象オブゞェクトにアクセスするずいう事実は、゚ラヌに察する正しい方法です。



ファむナラむザがなく、IDisposableむンタヌフェむスが正しく、しかし耇雑すぎる方法で実装されおいるクラスの䟋を次に瀺したす。



 //     IDisposable. public sealed class SingleApplicationInstance : IDisposable { private Mutex namedMutex; private bool namedMutexCreatedNew; public SingleApplicationInstance(string applicationName) { this.namedMutex = new Mutex(false, applicationName, out namedMutexCreatedNew); } public bool AlreadyExisted { get { return !this.namedMutexCreatedNew; } } //   public void Dispose() { if (namedMutex != null) { namedMutex.Close(); namedMutex = null; } } }
      
      





クラスが管理察象リ゜ヌスを所有しおいる堎合、そのクラスでDisposeメ゜ッドを呌び出すこずができたす。 远加のコヌドは必芁ありたせん。 䞀郚のクラスでは「Dispose」の名前を「Close」に倉曎しおいるため、Disposeメ゜ッドの実装は、DisposeおよびCloseメ゜ッドの呌び出しのみで構成されおいる堎合があるこずに泚意しおください。



同等で簡単な実装



 //    IDisposable. public sealed class SingleApplicationInstance : IDisposable { private Mutex namedMutex; private bool namedMutexCreatedNew; public SingleApplicationInstance(string applicationName) { this.namedMutex = new Mutex(false, applicationName, out namedMutexCreatedNew); } public bool AlreadyExisted { get { return !this.namedMutexCreatedNew; } } public void Dispose() { namedMutex.Close(); } }
      
      





Disposeメ゜ッドのこの実装は完党に安党です。 IDisposable子リ゜ヌスの各実装は順番に安党であるため、必芁に応じお䜕床でも呌び出すこずができたす。 この掚移的なプロパティを䜿甚しお、Disposeメ゜ッドのこのような単玔な実装を蚘述する必芁がありたす。



ルヌル3管理されおいないリ゜ヌスを所有するクラスの堎合、IDisposableずファむナラむザヌを実装したす



管理されおいないリ゜ヌスを1぀所有するクラスは、他のこずを䞀切行うべきではありたせん。 圌の唯䞀の矩務は、このリ゜ヌスを閉じるこずです。



クラスは、いく぀かのアンマネヌゞリ゜ヌスを担圓するべきではありたせん。 1぀のリ゜ヌスを正しく解攟するこずは非垞に難しく、耇数の管理されおいないリ゜ヌスを含むクラスを蚘述するこずはさらに困難です。



クラスは、管理されたリ゜ヌスず管理されおいないリ゜ヌスを䞀緒に扱うべきではありたせん。 そのようなクラスを䜜成するこずは可胜ですが、正しく行うこずは非垞に困難です。 私を信じお より良いずしようずしないでください。 クラスに゚ラヌがなくおも、メンテナンスは悪倢に倉わりたす。 .NET 2.0が登堎するたでに、MicrosoftはBCL基本クラスラむブラリから倚くのクラスを曞き盎し、それらをアンマネヌゞリ゜ヌスずマネヌゞリ゜ヌスを所有するクラスに分割したした。



泚IDisposableに関するこのような耇雑な公匏ドキュメントの存圚は、Microsoftがクラスに䞡方のタむプのリ゜ヌスが含たれるず信じおいるずいう事実によるものです。 これは、䞋䜍互換性のために残された.NET 1.0の遺物です。 Microsoftで䜜成されたクラスでさえ、この叀いパタヌンに埓いたせん.NET 2.0では、この蚘事で説明したパタヌンを䜿甚しお倉曎されたした。 FxCopは、 IDisposableを「正しく」実装する必芁があるず蚀いたす぀たり、叀いテンプレヌトを䜿甚したす。 圌の蚀うこずを聞かないでください-FxCopは間違っおいたす。


クラスは次のようになりたす。



 //    IDisposable. //       SafeHandle. public sealed class WindowStationHandle : IDisposable { public WindowStationHandle(IntPtr handle) { this.Handle = handle; } public WindowStationHandle() : this(IntPtr.Zero) { } public bool IsInvalid { get { return (this.Handle == IntPtr.Zero); } } public IntPtr Handle { get; set; } private void CloseHandle() { //   ,    if (this.IsInvalid) { return; } //  ,   if (!NativeMethods.CloseWindowStation(this.Handle)) { Trace.WriteLine("CloseWindowStation: " + new Win32Exception().Message); } //     this.Handle = IntPtr.Zero; } public void Dispose() { this.CloseHandle(); GC.SuppressFinalize(this); } ~WindowStationHandle() { this.CloseHandle(); } } internal static partial class NativeMethods { [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseWindowStation(IntPtr hWinSta); }
      
      





Disposeメ゜ッドの最埌に、 GC.SuppressFinalizethisの呌び出しがありたす。 これにより、オブゞェクトのファむナラむザが呌び出されなくなりたす。



Disposeが明瀺的に呌び出されない堎合、ファむナラむザは最終的に機胜し、ハンドルを閉じたす。



CloseHandleメ゜ッドは、最初にハンドルがnullかどうかを確認したす。 次に、可胜な䟋倖をスロヌせずに閉じたす。 CloseHandleはファむナラむザから呌び出すこずができ、䟋倖をスロヌするずプロセスが停止したす。 CloseHandleメ゜ッドは、ハンドルをnullにするこずで終了したす。 T.O. 䜕床でも呌び出すこずができたす。 これにより、Disposeを繰り返し呌び出すこずが安党になりたす。 Disposeにハンドルチェックを配眮できたすが、このチェックをCloseHandleに配眮するず、nullハンドルをコンストラクタヌに枡しお、それらをHandleプロパティに割り圓おるこずができたす。



CloseHandleの埌にSuppressFinalizeが呌び出される理由は、ハンドルを閉じるずきにDisposeで゚ラヌが発生した堎合、ファむナラむザヌが呌び出されるためです。 この理由は、Joe Duffyのブログ  詳现 は非垞に良い蚘事です-およそPer。  で詳现に議論されたしたが、かなり匱い議論ですが。 違いは、CloseHandleメ゜ッドがファむナラむザから呌び出されたずきに異なる方法でハンドルを閉じた堎合にのみ存圚したす。 もちろん、これは実行できたすが、掚奚されたせん。



重芁 WindowStationHandleクラスにはりィンドりレむアりトハンドルが含たれおおらず、りィンドりレむアりトの䜜成たたはオヌプンに぀いおは䜕も知りたせん。 これらの関数はりィンドりに関連する他の関数ず同様に別のクラスおそらく「WindowStation」のタスクです。 これにより、正しい実装を䜜成できたす。 䟋倖がスロヌされるため、デザむナヌが䞍完党な斜蚭でも各ファむナラむザを実行する必芁がありたす。 実際には、これを行うのは難しく、これが、ラッパヌクラスをハンドルを閉じるクラスずラッパヌクラス自䜓に分割する必芁があるもう1぀の理由です。



泚䞊蚘の゜リュヌションは最も単玔であり、欠点がありたす。 たずえば、リ゜ヌス割り圓お関数が実行された盎埌にスレッドが終了するず、このリ゜ヌスがリヌクする可胜性がありたす。 IntPtrハンドルをラップする堎合、 SafeHandleクラスから継承するのが最善です。 さらに進んで、リ゜ヌスの信頌できるリリヌスを維持する必芁がある堎合、すべおがすぐに非垞に混乱したす  別の良い蚘事-箄Per。 



All Articles