66

主に C++ の開発者である私は、Java と .NET にRAII (Resource Acquisition Is Initialization)がないことにいつも悩まされてきました。クリーンアップの責任がクラスの作成者から消費者に移されたという事実 (try finallyまたは .NET のusingコンストラクトによって) は、著しく劣っているように思われます。

すべてのオブジェクトがヒープ上にあり、ガベージ コレクターが本質的に決定論的破壊をサポートしていないため、Java で RAII がサポートされていない理由がわかりましたが、値型 ( struct) の導入により .NET では (一見) RAIIの完璧な候補。スタック上に作成された値型には明確に定義されたスコープがあり、C++ デストラクター セマンティクスを使用できます。ただし、CLR では、値型がデストラクタを持つことは許可されていません。

私のランダムな検索では、値の型がボックス化されている場合、ガベージ コレクターの管轄下にあるため、その破棄が非決定的になるという 1 つの引数が見つかりました。この議論は十分に強力ではないと感じています。RAII の利点は、デストラクタを持つ値型をボックス化 (またはクラス メンバーとして使用) できないと言うのに十分大きいです。

簡単に言うと、私の質問は次のとおりです。RAII を .NET に導入するために値型を使用できない理由は他にありますか? (または、RAII の明らかな利点についての私の議論には誤りがあると思いますか?)

編集:最初の4つの回答が要点を逃したため、質問を明確に表現していなかったに違いありません。私はその非決定論的特性について知っており、構造について知っており、これら2つのオプションは RAII よりも劣っていると感じています。は、クラスの消費者が覚えておかなければならないもう 1 つのことです (ブロックに aを入れるのを忘れた人は何人いますか?)。私の質問は、言語設計に関する哲学的なものです。なぜそうなのか、改善できるのでしょうか?FinalizeusingusingStreamReaderusing

たとえば、一般的な決定論的に破壊可能な値型を使用すると、usingandlockキーワードを冗長にすることができます (ライブラリ クラスによって達成可能)。

    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();
        }
    }

私は、かつて見たが、現在その出所を見つけることができない適切な引用で終わらせざるを得ません。

私の冷たい死んだ手が範囲外になると、私の決定論的破壊を受け取ることができます. --アノン

4

7 に答える 7

15

より適切なタイトルは、「C#/VB に RAII がない理由」です。C++/CLI (Managed C++ だった中断の進化) には、C++ とまったく同じ意味で RAII があります。それはすべて、残りの CLI 言語が使用するのと同じファイナライズ パターンの単なる構文糖衣です (C++/CLI のマネージド オブジェクトのデストラクタは事実上ファイナライザです)。

http://blogs.msdn.com/hsutter/archive/2004/07/31/203137.aspxが好きかもしれません

于 2008-10-06T10:48:14.743 に答える
14

素晴らしい質問であり、私を大いに悩ませた質問です。RAII の利点は、非常に異なって認識されているようです。.NET に関する私の経験では、決定論的 (または少なくとも信頼できる) リソース コレクションの欠如は、主要な欠点の 1 つです。実際、.NET では、明示的な収集が必要な場合もあれば (そうでない場合もあります)、管理されていないリソースを処理するために、アーキテクチャ全体を採用することを何度か余儀なくされました。もちろん、これは大きな欠点です。なぜなら、全体的なアーキテクチャがより難しくなり、クライアントの注意がより中心的な側面から逸れてしまうからです。

于 2008-10-06T09:44:41.827 に答える
14

ブライアン・ハリーは、その理論的根拠について素晴らしい投稿をしています

ここに抜粋があります:

決定論的なファイナライズと値の型 (構造体) はどうですか?

-------------- デストラクタを持つ構造体などについて多くの質問を見てきました。これはコメントする価値があります。一部の言語にそれらがない理由については、さまざまな問題があります。

(1) 構成 - 上記と同じ種類の構成の理由により、一般的なケースでは決定論的な寿命は得られません。デストラクタを含む非決定論的クラスは、とにかくGCによってファイナライズされるまで、デストラクタを呼び出しません。

(2) コピー コンストラクター - 本当に便利な場所の 1 つは、スタックに割り当てられたローカルです。それらはメソッドにスコープされ、すべてが素晴らしいでしょう。残念ながら、これを実際に機能させるには、インスタンスがコピーされるたびにコピー コンストラクターを追加して呼び出す必要があります。これは、C++ で最も醜く、最も複雑なことの 1 つです。予期しない場所でコードが実行されることになります。それは言語の問題の束を引き起こします。一部の言語設計者は、これを避けることを選択しました。

デストラクタを使用して構造体を作成したが、上記の問題に直面したときにその動作を適切にするために多くの制限を追加したとします。制限は次のようになります。

(1) ローカル変数としてのみ宣言できます。

(2) 参照渡しのみ可能

(3)それらを割り当てることはできません。フィールドにアクセスしてメソッドを呼び出すことしかできません。

(4) それらをボックス化することはできません。

(5) リフレクション (遅延バインディング) を介してそれらを使用する際の問題。これには通常、ボクシングが含まれるためです。

もっとかもしれませんが、それは良いスタートです。

これらのものは何の役に立ちますか?ローカル変数としてのみ使用できるファイルまたはデータベース接続クラスを実際に作成しますか? 私は誰も本当にそうするとは思わない。代わりに、汎用接続を作成してから、スコープ付きローカル変数として使用する自動破棄ラッパーを作成します。発信者は、使用したいものを選択します。呼び出し元が決定を下し、オブジェクト自体に完全にカプセル化されていないことに注意してください。いくつかのセクションで出てくる提案のようなものを使用できることを考えると.

.NET の RAII に代わるものは using-pattern で、慣れればほとんど同じように機能します。

于 2008-10-06T09:57:40.820 に答える
1

検索すると同様のスレッドがいくつかありますが、基本的には、.NET で RAII を使用する場合は、単純に IDisposable 型を実装し、"using" ステートメントを使用して確定的な Disposal を取得するということです。そうすれば、同じイデオムの多くを実装して使用することができますが、これは少しだけ言葉の多い方法で行うことができます。

于 2008-10-06T09:32:45.970 に答える
1

それに最も近いのは、非常に限定された stackalloc 演算子です。

于 2008-10-06T09:27:57.073 に答える
1

私見、VB.net と C# が必要とする大きなものは次のとおりです。

  1. フィールドの「using」宣言。これにより、コンパイラは、タグ付けされたすべてのフィールドを破棄するコードを生成します。デフォルトの動作は、コンパイラがクラスに IDisposable を実装しない場合は実装させるか、いくつかの一般的な IDisposal 実装パターンのいずれかのメインの破棄ルーチンの開始前に破棄ロジックを挿入するか、属性を使用してそれを指定することです。廃棄物は、特定の名前のルーチンで実行する必要があります。
  2. コンストラクターやフィールド初期化子が例外をスローするオブジェクトを、デフォルトの動作 (デフォルトの破棄メソッドを呼び出す) またはカスタムの動作 (特定の名前のメソッドを呼び出す) のいずれかによって決定論的に破棄する手段。
  3. vb.net の場合、すべての WithEvent フィールドを null にする自動生成メソッド。

これらはすべて、vb.net ではかなりうまく処理できますが、C# では多少うまくいきませんが、それらの最高のサポートにより、両方の言語が改善されます。

于 2010-12-14T04:16:09.583 に答える
-3

finalize() メソッドを使用して、.net および Java で RAII の形式を実行できます。finalize() オーバーロードは、クラスが GC によってクリーンアップされる前に呼び出されるため、絶対にクラスによって保持されるべきではないリソース (ミューテックス、ソケット、ファイル ハンドルなど) をクリーンアップするために使用できます。ただし、まだ決定論的ではありません。

.NET では、IDisposable インターフェイスと using キーワードを使用して決定論的にこれの一部を実行できますが、これには制限があります (決定論的な動作に必要な使用時にコンストラクトを使用する、まだ決定論的なメモリ割り当てが解除されない、クラスで自動的に使用されないなど)。

確かに、RAII のアイデアを .NET やその他のマネージ言語に導入する余地はあると思いますが、正確なメカニズムについては果てしなく議論される可能性があります。私が見ることができる他の唯一の代替手段は、(メモリだけでなく) 任意のリソースのクリーンアップを処理できる GC を導入することですが、そのリソースを決定論的に解放する必要がある場合に問題が発生します。

于 2008-10-06T09:31:45.537 に答える