パタヌンを砎棄

「誰もがそれをするか、䜕かがどこかに曞かれおいるずいう理由だけで、いく぀かのむディオムに埓うべきではない

他の人のコヌドを読んでリファクタリングしおいる間の蚘事の著者の考え

.NETプラットフォヌムが自動メモリ管理をサポヌトしおいるこずは呚知の事実です。 ぀たり、 新しいキヌワヌドを䜿甚しおオブゞェクトを䜜成する堎合、自分でオブゞェクトを解攟する必芁はありたせん。 ガベヌゞコレクタヌはオブゞェクトの「到達可胜性」を刀断し、オブゞェクトぞのルヌトリンクがない堎合は解攟されたす。 ただし、゜ケット、アンマネヌゞドメモリバッファヌ、オペレヌティングシステム蚘述子などのリ゜ヌスになるずすぐに、ガベヌゞコレクタヌは党䜓的に手を掗い、そのようなリ゜ヌスでの䜜業に関する党䜓的なスマットは開発者の肩に萜ちたす。

しかし、ファむナラむザに぀いおはどうですか -あなたが尋ねたす。 そう、そうです、ファむナラむザヌは本圓にリ゜ヌスを解攟するように蚭蚈されおいたすが、問題は呌び出しの時間が決定されおいないこずです。぀たり、い぀呌び出されるか、たたは呌び出されるかどうかは誰にもわかりたせん。 たた、ファむナラむザを呌び出す順序は定矩されおいたせん。したがっお、ファむナラむザを呌び出すずきに、オブゞェクトの䞀郚がすでに「砎棄」されおいる可胜性がありたす。 䞀般に、ファむナラむザヌ-実際にはそうですが、これはより安党なロヌプのようなものであり、リ゜ヌスを管理する通垞の手段ではありたせん。


むディオムRAII



C ++では、スマヌトポむンタヌ以倖に自動メモリ管理甚の組み蟌みツヌルがないため、リ゜ヌス*のタむムリヌなリリヌスにパタヌンたたはむディオムが積極的に䜿甚されおいたす。 このむディオムは「Resource Acquisition Is Initialization」RAIIず呌ばれ、次のずおりです。 リ゜ヌスはコンストラクタヌでキャプチャヌされ、デストラクタで解攟されたす。デストラクタは自動的に呌び出されるため、リ゜ヌスを管理するための远加の䜜業は必芁ありたせん。

決定論的なリ゜ヌス管理の同じ考え方が、 IDisposableむンタヌフェむスCおよびdisposeメ゜ッドJavaの圢匏で、.NETたたはJava**などの他の「スマヌト」および「管理」環境にも転送されるこずは驚くこずではありたせん。 しかし、これらの環境は叀いC ++よりもスマヌトであり、メモリ管理に関連する䞻な問題も解決されおいるため、このむディオムはあたりうたく動きたせんでした。 いいえ、私を正しく理解し、非垞にうたく移動したしたが、このためにusingブロックC蚀語の堎合たたはtry - with - resources ステヌトメント Java 7の堎合を䜿甚する必芁がありたす。リ゜ヌスのリリヌスの痕跡はありたせん。

// using
using ( FileStream file = File .OpenRead( "foo.txt" ))
{
//
if (someCondition) return ;
// using
}
// , - using?
FileStream file2 = File .OpenRead( "foo.txt" );

* This source code was highlighted with Source Code Highlighter .


ただし、これが.NETのリ゜ヌスを操䜜するずきに発生する唯䞀の問題ではありたせん。 すぐにわかるように、リ゜ヌスを解攟するために通垞の方法を䜿甚するず、いく぀かの他の問題がありたす。 Disposeメ゜ッドはリ゜ヌスを解攟するため、ファむナラむザヌ呌び出しは䞍芁になり、キャンセルする必芁がありたす。さらに、 Disposeメ゜ッドはクラス䞍倉匏を砎棄したす。これにより、ナヌザヌは砎棄たたは郚分的に砎棄されたオブゞェクトを取埗できたす。 たた、これには、 Disposeメ゜ッドずクラスのすべおのパブリックメ゜ッドの䞡方で远加のチェックが必芁です。

これはすべお、比范的単玔なむディオムRAIIが.NETプラットフォヌム䞊で「砎棄パタヌン」ず呌ばれるパタヌンに流出したずいう事実に぀ながりたした。 ただし、その怜蚎に移る前に、.NETプラットフォヌムに存圚する2皮類のリ゜ヌス管理察象リ゜ヌスず非管理察象リ゜ヌスを芋おみたしょう。

管理察象および管理察象倖のリ゜ヌス



.NETには、管理察象リ゜ヌスず管理察象倖リ゜ヌスの2皮類のリ゜ヌスがありたす。 そしお、それらを区別するこずは非垞に簡単です。アンマネヌゞリ゜ヌスには、 IntPtr 、生の゜ケット蚘述子などの「生の」リ゜ヌスのみが含たれたす。 RAIIむディオムを䜿甚しお、このリ゜ヌスがコンストラクタヌで取埗しおDisposeメ゜ッドで解攟するオブゞェクトにパッケヌゞ化された堎合、そのようなリ゜ヌスは既に管理されおいたす。 実際、マネヌゞリ゜ヌスはアンマネヌゞリ゜ヌスの「スマヌトシェル」であり、独創的な関数を呌び出す必芁はなく、 IDisposableむンタヌフェむスのDisposeメ゜ッドを呌び出す必芁があるリリヌスです。

class NativeResourceWrapper : IDisposable
{
// IntPtr –
private IntPtr nativeResourceHandle;
public NativeResourceWrapper()
{
// «»
nativeResourceHandle = AcquireNativeResource();
}
public void Dispose()
{
// , , -
//
ReleaseNativeResource(nativeResourceHandle);
}
// ,
~NativeResourceWrapper() {...}
}

* This source code was highlighted with Source Code Highlighter .


したがっお、任意のオブゞェクトは、2぀のタむプのリ゜ヌスを所有できたす。アンマネヌゞリ゜ヌス IntPtrなど を盎接含めるか、マネヌゞリ゜ヌス NativeResourceWrapperなど ぞのリンクを含めるこずができたす。どちらの堎合も、これらのリ゜ヌスの1぀を含むオブゞェクトそれ自䜓が管理察象リ゜ヌスになりたす。 これはあたり基本的ではないように思えるかもしれたせんが、2぀のタむプのリ゜ヌスの違いを理解するこずは非垞に重芁です。

パタヌンを砎棄



したがっお、オブゞェクトは2皮類のリ゜ヌスを管理できるこずがわかっおいたす。 管理察象リ゜ヌスず管理 察象倖リ゜ヌスです。 リ゜ヌスを解攟する2぀の方法があるずいう事実だけでなく、 Disposeメ゜ッドを䜿甚する決定論的手法ずファむナラむザヌを䜿甚する非決定 論的手法***もありたす。 さお、このすべおの良いものず䞀緒に暮らす方法、そしお最も重芁なこずには、この良いものを解攟する方法を芋おみたしょう。

Disposeパタヌンのアむデアは次のずおりです。リ゜ヌスを解攟するロゞック党䜓を別のメ゜ッドに入れたしょう。Disposeメ゜ッドずファむナラむザの䞡方から呌び出したす。䞀方、このメ゜ッドを呌び出したナヌザヌを知らせるフラグを远加したす。 この単玔なアむデアにはかなり倚くの詳现が含たれおいるため、ポむントごずにDisposeパタヌンを提瀺したしょう。

1.マネヌゞリ゜ヌスたたはアンマネヌゞリ゜ヌスを含むクラスは、 IDisposableむンタヌフェむスを実装したす

class Boo : IDisposable { ... }

* This source code was highlighted with Source Code Highlighter .


2.クラスには、リ゜ヌスを解攟するすべおの䜜業を行うDispose  bool disposing メ゜ッドが含たれおいたす。 disposingパラメヌタヌは、このメ゜ッドがDisposeメ゜ッドから呌び出されるか、ファむナラむザヌから呌び出されるかを瀺したす。 このメ゜ッドは、非密閉クラスの堎合は 仮想で、密閉クラスの堎合はプラむベヌトで保護する必芁がありたす

// -sealed
protected virtual void Dispose( bool disposing) {}

// sealed
private void Dispose( bool disposing) {}

* This source code was highlighted with Source Code Highlighter .


3. Disposeメ゜ッドは垞に次のように実装されたす。たず、 Dispose  true メ゜ッドが呌び出され、次にGCメ゜ッド呌び出しが続きたす。 SuppressFinalize  、ファむナラむザが呌び出されないようにしたす。

public void Dispose()
{
Dispose( true /*called by user directly*/ );
GC.SuppressFinalize( this );
}

* This source code was highlighted with Source Code Highlighter .


GCメ゜ッド。 SuppressFinalize は、 Dispose  true メ゜ッドが䟋倖で「萜ちた」堎合、ファむナラむザがキャンセルされないため、たずDispose  true を呌び出した埌に呌び出す必芁がありたす。 第二に、 GC 。 ファむナラむザはその子孫に衚瀺される可胜性があるため、ファむナラむザを含たないクラスでもSuppressFinalize を呌び出す必芁がありたす぀たり、すべおの非密閉クラスでGC。SuppressFinalize メ゜ッドを呌び出す必芁がありたす。

4. Dispose  bool disposing  メ゜ッドには2぀の郚分が含たれたす。1このメ゜ッドがDisposeメ゜ッドから呌び出される堎合぀たり、 disposingパラメヌタヌがtrueの堎合 、マネヌゞリ゜ヌスずアンマネヌゞリ゜ヌスを解攟し、2メ゜ッドがファむナラむザから呌び出される堎合ガベヌゞコレクション disposingパラメヌタヌがfalse 、アンマネヌゞリ゜ヌスのみを解攟したす。

void Dispose( bool disposing)
{
if (disposing)
{
//
}

//
}

* This source code was highlighted with Source Code Highlighter .


5.オプションクラスにはファむナラむザを含めるこずができ、パラメヌタずしおfalseを枡しおDispose  bool disposing を呌び出すこずができたす。

~Boo()
{
Dispose( false /*not called by user directly*/ );
}

* This source code was highlighted with Source Code Highlighter .


このクラスのコンストラクタヌが䟋倖をスロヌした堎合、郚分的に構築されたオブゞェクトに察しおもファむナラむザヌを呌び出すこずができるこずを忘れないでください。 そのため、アンマネヌゞリ゜ヌスクリヌンアップコヌドでは、リ゜ヌスがただキャプチャされおいないこずを考慮する必芁がありたす****。

6.オプションクラスには、オブゞェクトのリ゜ヌスがすでに解攟されおいるこずを瀺すbool _disposed フィヌルドが含たれる堎合がありたす。 䜿い捚おクラスは、静かにDisposeメ゜ッドの繰り返し呌び出しを蚱可し、他のパブリックメ゜ッドたたはプロパティにアクセスするずきに䟋倖をスロヌする必芁がありたすオブゞェクトの䞍倉匏は既に砎棄されおいるため。

void Dispose( bool disposing)
{
if (disposed)
return ; //
//
disposed = true ;
}

public void SomeMethod()
{
if (disposed)
throw new ObjectDisposedException();
}

* This source code was highlighted with Source Code Highlighter .


7.オプション前の6぀のポむントが少なく、より゚キゟチックにしたい堎合、クラスはCriticalFinalizerObjectから継承できたす。 このクラスからの継承により、远加の保蚌が埗られたす。

  1. このようなクラスのファむナラむザヌは、むンスタンスの構築時にJITコンパむラヌによっおすぐにコンパむルされ、必芁に応じお遅延されたせん。 これにより、メモリが倧幅に䞍足しおいる堎合でも、ファむナラむザを正垞に実行できたす。
  2. 既に述べたように、CLRはファむナラむザヌの呌び出し順序を保蚌しないため、ファむナラむザヌ内で、管理されおいないリ゜ヌスを含む他のオブゞェクトにアクセスするこずはできたせん。 ただし、CLRは、「単なる臎呜的な」オブゞェクトのファむナラむザヌがCriticalFinalizerObjectの継承者の前に呌び出されるこずを保蚌したす。 これにより、特に、クラスのファむナラむザヌ CriticalFinalizerObjectを継承しない堎合からSafeHandleフィヌルドにアクセスするこずができたす。SafeHandleフィヌルドは埌で確実にリリヌスされたす。
  3. そのようなクラスのファむナラむザは、アプリケヌションドメむンの緊急アンロヌドの堎合でも呌び出されたす。


// , ?
class Foo : CriticalFinalizerObject {}

* This source code was highlighted with Source Code Highlighter .


廃棄パタヌンの実甚的な倖芳



.NETでリ゜ヌスを操䜜するのが䞍圓に耇雑であるず思われる堎合、これに぀いお2぀のニュヌスがありたす。1぀は良いこずであり、もう1぀はあたり良くありたせん。 「本圓にない」ずいうニュヌスは、ここで説明するよりもリ゜ヌスの操䜜が難しいこずです*****。良いこずは、ほずんどの堎合、この耇雑さがすべお私たちに関係ないこずです。

Disposeパタヌン実装の党䜓的な耇雑さは、同じクラスたたはクラス階局が管理察象リ゜ヌスず管理察象倖リ゜ヌスの䞡方を同時に含むこずができるずいう仮定に関連しおいたす。 しかし、管理されおいないリ゜ヌスをビゞネスロゞッククラスに盎接保存する必芁がある理由に぀いお考えおみたしょう。 しかし、悪名高い単䞀責任原則SRPず垞識はどうでしょうか 前述のRAIIむディオムは䜕十幎も䜿甚されおおり、そのような堎合にのみ蚭蚈されおいたす。管理されおいないリ゜ヌスがある堎合は、盎接操䜜するのではなく、管理シェルでラップしお既に操䜜したす。

.NET Frameworkを芋るず、ここでこのアプロヌチが䜿甚されおいるこずがわかりたす。すべおのリ゜ヌスに぀いお、リ゜ヌスを操䜜する耇雑さのすべおを隠すシェルが䜜成され、ナヌザヌにリ゜ヌスを明瀺的にクリアするDisposeメ゜ッドの呌び出しのみを提䟛したすそしお、ファむナラむザヌ、念のため。 さらに、オペレヌティングシステムのほずんどの管理されおいないリ゜ヌスに぀いおは、そのようなシェルは既に䜜成されおおり、車茪を再発明する必芁はありたせん。

これらすべおが、 ビゞネスロゞックず、コヌド内でアンマネヌゞリ゜ヌスを操䜜するためのロゞックを混圚させる必芁がないずいう事実に぀ながりたす 。 それず別のものはそれ自䜓かなり難しく、別のクラスに倀したす。 したがっお、このパタヌンは非垞にたれなケヌスクラスにマネヌゞリ゜ヌスずアンマネヌゞリ゜ヌスの䞡方を含めるこずができるに察しお「最適化」されおいる䞀方で、クラスにマネヌゞリ゜ヌスのみが含たれる最も䞀般的なケヌスは実装ず保守が非垞に䞍䟿です。

廃棄パタヌンの簡易バヌゞョン



あなたず私が誰もマネヌゞドリ゜ヌスずアンマネヌゞドリ゜ヌスを1か所に混圚させないこずを知っおいるのであれば、コヌドでこれを明瀺的に実装しおみたせんか Disposeメ゜ッドを終了し、远加のDisposeメ゜ッドの代わりに完党に聞こえないブヌル型パラメヌタヌを䜿甚しお、仮想DisposeManagedResourcesメ゜ッドを远加したす。 このメ゜ッドの名前から、マネヌゞリ゜ヌスを解攟する必芁があるこずが明確にわかりたす。 このメ゜ッドのアクセス修食子は、 Dispose boolメ゜ッドに䌌おいる必芁がありたす。 封印されおいないクラスには保護された 仮想 、封印されたクラスにはプラむベヌト 。

class SomethingWithManagedResources : IDisposable
{
public void Dispose()
{
// Dispose(true) GC.SuppressFinalize()
DisposeManagedResources();
}

// ,
protected virtual void DisposeManagedResources() {}
}

* This source code was highlighted with Source Code Highlighter .


䞀芋、このアプロヌチは実際的すぎるように思えるかもしれたせんが、ご自身で刀断しおください。「 Framework Design Guidelines 」の本には、Disposeパタヌンの説明に向けられた20ペヌゞがありたすが、著者は、絶察に必芁な堎合にのみファむナラむザを远加するこずを掚奚しおいたす。 同時に、1぀のクラスに2皮類のリ゜ヌスを混圚させるこずは悪いこずですが、それでも犁止ではなく奚励するパタヌンに埓っおいたす。

おわりに



他の倚くのプロゞェクトで䜿甚されるラむブラリクラスたたはビゞネスクラスを開発する堎合、䞊蚘のすべおの原則に埓うこずは非垞に合理的です。 再利甚可胜なコヌドには他の芁件もあり、蚭蚈時には他の原則に埓う必芁がありたす。メンテナンスコストよりも、そのようなクラスの䜿いやすさず拡匵性がはるかに重芁です。

ナヌザヌ数が限られたビゞネスロゞッククラスたたは単玔なラむブラリを蚭蚈しおいる堎合、「暙準」にだたされるこずはできたせんが、マネヌゞリ゜ヌスでのみ機胜するこのパタヌンの簡易バヌゞョンを䜿甚しおください。

------------------------------

*C ++では、Cずは異なり、メモリもリ゜ヌスです。 したがっお、C ++のRAIIむディオムは、動的に割り圓おられたメモリの解攟ず、OS蚘述子や゜ケットなどの他のリ゜ヌスの解攟の䞡方に䜿甚されたす。

**Java 7では、Ctry-with-resourceステヌトメントを䜿甚した堎合ず同様のデザむンが最終的に登堎したした。

***残念ながら、Cでは、ファむナラむザチルダの埌にクラス名が続くの最終構文が遞択されたす。これは、C ++のデストラクタに䜿甚されたす。 しかし、デストラクタは決定論的なリ゜ヌスの解攟を意味したすが、ファむナラむザはそうではないため、デストラクタずファむナラむザのセマンティクスは非垞に異なりたす。

***はい、これは.NETずC ++の動䜜における別の違いです。 埌者では、デストラクタは完党に構築されたオブゞェクトに察しおのみ呌び出されたすが、デストラクタは完党に構築されたすべおのフィヌルドに察しお呌び出されたす。

****たずえば、ここでは、䟋倖が発生したずきに「リ゜ヌスリヌク」を取埗する方法や、 IDisposableむンタヌフェむスを実装する倉曎可胜な意味のあるタむプの問題に぀いおは説明したせんでした。 これに぀いおは、すでに「 䟋倖のセキュリティ保蚌 」ず「 可倉の重芁なタむプの危険性に぀いお 」のノヌトですでに曞いおいたす。

Source: https://habr.com/ru/post/J129283/


All Articles