Disposeまたはデストラクタから仮想メソッドを呼び出しても大丈夫ですか?
-
02-07-2019 - |
質問
参照が見つかりませんが、デストラクタ内の仮想(ポリモーフィック)メソッドまたはIDisposableのDispose()メソッドを呼び出すのは良い考えではなかったことを読んだことを覚えています。
これは真実ですか?そうであれば、誰かがその理由を説明できますか?
解決
ファイナライザー/ Dispose
から仮想メソッドを呼び出すことは、同じ理由でコンストラクタで行うのは安全ではありません。仮想メソッドが適切に実行するために必要な状態を派生クラスがまだクリーンアップしていないことを確認することは不可能です。
標準のディスポーザブルパターンと、仮想メソッド virtual Dispose(bool disposing)
の使用に混乱している人がいます。これにより、 any 処分中の仮想メソッド。次のコードを検討してください。
class C : IDisposable {
private IDisposable.Dispose() {
this.Dispose(true);
}
protected virtual Dispose(bool disposing) {
this.DoSomething();
}
protected virtual void DoSomething() { }
}
class D : C {
IDisposable X;
protected override Dispose(bool disposing) {
X.Dispose();
base.Dispose(disposing);
}
protected override void DoSomething() {
X.Whatever();
}
}
d
と呼ばれるタイプ D
のオブジェクトを破棄して廃棄するとどうなりますか:
- 一部のコード呼び出し
((IDisposable)d).Dispose()
-
C.IDisposable.Dispose()
は仮想メソッドD.Dispose(bool)
を呼び出します
-
D.Dispose(bool)
はD.X
を破棄します
-
D.Dispose(bool)
はC.Dispose(bool)
を静的に呼び出します(呼び出しのターゲットは atコンパイル時) -
C.Dispose(bool)
は仮想メソッドD.DoSomething()
を呼び出します
-
D.DoSomething
は、すでに破棄されているD.X
でメソッド - ?
D.X.Whatever()
を呼び出します
今、このコードを実行するほとんどの人はそれを修正するために一つのことをします-自分のオブジェクトをクリーンアップする前に base.Dispose(dispose)
呼び出しを移動します。そして、はい、それは動作します。しかし、あなたが本当に C
を開発し、 D
を書くことを割り当てられた会社のUltra-Junior DeveloperであるProgrammer Xを信頼して、エラーが検出されたか、または base.Dispose(disposing)
呼び出しが正しい場所にありますか?
Disposeから仮想メソッドを呼び出すコードを決して書かないでください決して、その仮想メソッドの 文書 >要件 C
の下で派生したクラスで定義された状態を使用しないこと。
他のヒント
仮想メソッドは、コンストラクタとデストラクタの両方で推奨されていません。
理由は何よりも実用的です。仮想メソッドは、オーバーライドによって選択された任意の方法でオーバーライドできます。また、構築中のオブジェクトの初期化など、保証する必要がありますランダムなヌルと無効な状態を持つオブジェクトを使用します。
仮想メソッドの呼び出しに対する推奨事項はないと思います。覚えている禁止事項は、ファイナライザで管理対象オブジェクトを参照することに対するルールかもしれません。
Dispose()の実装方法に関する.Netドキュメントで定義されている標準パターンがあります。パターンは非常に適切に設計されているため、厳密に従う必要があります。
要点は次のとおりです。Dispose()は、仮想メソッドDispose(bool)を呼び出す非仮想メソッドです。ブールパラメータは、メソッドがDispose()から呼び出されているか(true)オブジェクトのデストラクタから呼び出されているか(false)を示します。継承の各レベルで、クリーンアップを処理するためにDispose(bool)メソッドを実装する必要があります。
Dispose(bool)に値falseが渡されると、ファイナライザーがdisposeメソッドを呼び出したことを示します。この状況では、管理されていないオブジェクトのクリーンアップのみを試行する必要があります(特定のまれな状況を除きます)。これは、ガベージコレクターがfinalizeメソッドを呼び出したばかりであるため、現在のオブジェクトにファイナライズの準備ができている必要があるためです。したがって、それが参照するオブジェクトはすべて、最終読み取り用としてマークされている可能性があり、シーケンスは非決定的であるため、最終化がすでに行われている可能性があります。
.NetドキュメントでDispose()パターンを検索し、正確に追跡することを強くお勧めします。奇妙で難しいバグから保護される可能性が高いからです!
Jonの回答を拡張するには、仮想メソッドを呼び出す代わりに、そのレベルでリソースを処理する必要がある場合、サブクラスの破棄またはデストラクタをオーバーライドする必要があります。
とはいえ、「ルール」があるとは思いません。ここでの行動に関して。しかし、一般的な考えは、リソースクリーンアップを実装のそのレベルのそのインスタンスのみに分離することです。