.NETにRAIIがないのはなぜですか?
-
05-07-2019 - |
質問
主にC ++開発者であるため、Javaの RAII(Resource Acquisition Is Initialization) .NETは常に私を悩ませてきました。クリーンアップの責任がクラスライターからそのコンシューマーに移動するという事実( try finally
または.NETの using
コンストラクト)は著しく劣っているようです。
すべてのオブジェクトがヒープ上にあり、ガベージコレクターが本質的に決定論的な破壊をサポートしないため、JavaではRAIIがサポートされない理由がわかりますが、.NETでは値型( struct
)RAIIの(一見)完全な候補者がいます。スタック上に作成された値型には、適切に定義されたスコープがあり、C ++デストラクタセマンティクスを使用できます。ただし、CLRでは、値型にデストラクタを含めることは許可されていません。
ランダム検索では、値の型がボックス化であるという引数が1つ見つかりましたガベージコレクターの管轄下にあるため、その破壊は非決定論的になります。 この議論は十分に強くないと思います。RAIIの利点は、デストラクタを持つ値型をボックス化(またはクラスメンバーとして使用)できないと言うほど十分に大きいと思います。
簡単に言えば私の質問です:RAIIを.NETに導入するために値型を使用できない理由は他にありますか? (または、RAIIの明らかな利点に関する私の議論に欠陥があると思いますか?)
編集:最初の4つの答えがポイントを逃したため、質問を明確に表現していないはずです。 Finalize
とその非決定的な特性について知っています、 using
構造について知っていますが、これら2つのオプションはRAIIに劣ると感じています。 using
は、クラスのコンシューマーが覚えておく必要のあるもう1つのことです( using
ブロックに StreamReader
を入れるのを忘れた人は何人いますか?)。私の質問は言語設計に関する哲学的な質問です。なぜそれがそうであり、改善できるのでしょうか?
たとえば、汎用の決定論的に破壊可能な値型を使用すると、 using
および lock
キーワードを冗長にすることができます(ライブラリクラスで実現可能):
public struct Disposer<T> where T : IDisposable
{
T val;
public Disposer(T t) { val = t; }
public T Value { get { return val; } }
~Disposer() // Currently illegal
{
if (val != default(T))
val.Dispose();
}
}
私はかつて見た適切な引用符で終わることはできませんが、現在その起源を見つけることができません。
冷えた死んだ手が範囲外になったとき、あなたは私の決定論的な破壊を受けることができます。 -アノン
解決
より良いタイトルは、「C#/ VBにRAIIがない理由」です。 C ++ / CLI(Managed C ++であった中絶の進化)には、C ++とまったく同じ意味でRAIIがあります。すべては、他のCLI言語が使用するのと同じファイナライズパターンの単なる構文糖です(C ++ / CLIの管理対象オブジェクトのデストラクタは事実上ファイナライザーです)。
http://blogs.msdn.com /hsutter/archive/2004/07/31/203137.aspx
他のヒント
すばらしい質問と、私を大いに悩ませた質問。 RAIIの利点は非常に異なって認識されているようです。 .NETでの私の経験では、決定論的(または少なくとも信頼性の高い)リソースコレクションの欠如は大きな欠点の1つです。実際、.NETは、明示的な収集を必要とする可能性がある(ただしそうではない可能性がある)管理対象外のリソースを処理するために、アーキテクチャ全体を何度も使用することを私に強制しました。もちろん、これはアーキテクチャ全体をより難しくし、クライアントの注意をより中心的な側面から遠ざけるため、大きな欠点です。
ブライアン・ハリーは、こちらの理論的根拠について素晴らしい投稿をしています。
>抜粋:
確定的なファイナライズと値型(構造)はどうですか?
--------------構造体に関する多くの質問を見てきました デストラクタなど。これは価値があります コメント。いろいろあります 一部の言語がそうしない理由の問題 それらを持っている。
(1)構成-彼らはあなたに与えない 一般的な決定論的寿命 同じ種類の構成の場合 上記の理由。どれか 1つを含む非決定的クラス それまでデストラクタを呼び出さない とにかくGCによって確定されました。
(2)コンストラクターのコピー-1か所 本当にいいところは スタックに割り当てられたローカル。彼らは メソッドにスコープされ、すべてが すばらしいです。残念ながら、取得するために これは本当に機能するために、あなたもする必要があります コピーコンストラクタを追加して呼び出します インスタンスがコピーされるたび。 これは最もuくて最も C ++に関する複雑なこと。あなたは終わります コードを実行する 予期しない場所。それ 言語の問題の束を引き起こします。 一部の言語デザイナーは、 これに近づかないでください。
で構造体を作成したとしましょう デストラクタが、の束を追加しました 行動を起こすための制限 問題に直面して賢明 上記。制限は 次のようなもの:
(1)ローカルとしてのみ宣言できます 変数。
(2)渡すことしかできません by-ref
(3)割り当てることはできません。 フィールドにアクセスして呼び出すことができます それらのメソッド。
(4)ボックス化できません それら。
(5)使用する際の問題 リフレクション(レイトバインディング) 通常、ボクシングが含まれます。
さらに、 しかし、それは良いスタートです。
これらのことはどのような用途でしょうか?でしょうか 実際にファイルを作成するか できるデータベース接続クラス ローカル変数としてのみ使用されますか?私 誰も本当にそうだとは思わないでください。 代わりに行うことは、作成することです 汎用接続 自動破壊されたラッパーを作成します スコープ付きローカル変数として使用します。の 発信者はその後、彼らが何を選ぶでしょう 使用したかった。発信者が 決定とそれは完全ではありません オブジェクト自体にカプセル化されています。 あなたが何かを使用できることを考えると で提案が出てくるように いくつかのセクション。
.NETのRAIIの代替はusing-patternであり、慣れればほぼ同様に機能します。
それに最も近いのは、非常に限られたstackalloc演算子です。
検索すると似たようなスレッドがいくつかありますが、基本的に.NETでRAIIを実行するには、IDisposable型を実装して「using」を使用するだけです。確定的廃棄を取得するステートメント。そうすれば、同じイデオムの多くを実装し、少し言葉遣いだけで使用できます。
私見、VB.netとC#が必要とする大きなものは次のとおりです。
- a「使用中」」フィールドの宣言。これにより、コンパイラは、このようにタグ付けされたすべてのフィールドを破棄するコードを生成します。デフォルトの動作は、コンパイラがクラスをIDisposableを実装しないようにするか、多くの一般的なIDisposal実装パターンのいずれかのメイン廃棄ルーチンの開始前に廃棄ロジックを挿入するか、属性を使用してそれを指定することです。廃棄物は特定の名前のルーチンに入れる必要があります。
- デフォルトの動作(デフォルトの廃棄メソッドを呼び出す)またはカスタムの動作(特定の名前のメソッドを呼び出す)によって、コンストラクターやフィールド初期化子が例外をスローするオブジェクトを決定論的に破棄する手段。
- vb.netの場合、すべてのWithEventフィールドをヌルにする自動生成メソッド。
これらはすべて、vb.netではかなり良く、C#ではやや劣りますが、それらに対する一流のサポートは両方の言語を改善します。
finalize()メソッドを使用して、.netおよびjavaでRAIIの形式を実行できます。 finalize()オーバーロードは、クラスがGCによってクリーンアップされる前に呼び出されるため、クラスが絶対に保持してはならないリソース(ミューテックス、ソケット、ファイルハンドルなど)をクリーンアップするために使用できます。しかし、まだ決定論的ではありません。
.NETでは、IDisposableインターフェイスとusingキーワードを使用して、これらの一部を確定的に行うことができますが、これには制限があります(確定的な動作に必要な場合は構造を使用しますが、確定的なメモリの割り当て解除はなく、クラスで自動的に使用されませんなど) )。
そして、はい、RAIIのアイデアを.NETやその他のマネージ言語に導入する場所があると感じていますが、正確なメカニズムは無限に議論される可能性があります。私が見ることができる他の唯一の選択肢は、(メモリだけでなく)任意のリソースのクリーンアップを処理できるGCを導入することですが、リソースを決定的に解放する必要がある場合は問題が発生します。