19

何らかの理由で、フィールドにはオーバーヘッドが関連付けられていると常に想定してきました。これは、フィールドが初期化されているreadonlyかどうかを追跡する CLR と考えられていました。readonlyここでのオーバーヘッドは、状態を追跡するための余分なメモリ使用量と、値を割り当てるときのチェックです。

おそらく、フィールドがコンストラクター内またはフィールド宣言自体内でのみ初期化できることを知らなかったためreadonly、実行時チェックなしでは、さまざまな方法で複数回割り当てられていないことを保証できないため、これを想定しましたメソッド。しかし、今ではこれを知っています。C# コンパイラで簡単に静的にチェックできますよね? そうですか。

もう1つの理由は、の使用がパフォーマンスに「わずかな」影響を与えることを読んだことがありますreadonlyが、彼らはこの主張に関与したことがなく、この件に関する情報を見つけることができないため、私の質問です. 実行時チェック以外にパフォーマンスにどのような影響があるかはわかりません。

3 番目の理由は、それreadonlyがコンパイルされた IL に として保存されていることを確認したinitonlyことです。したがって、この情報が IL に含まれる理由はreadonly、フィールドが外部に割り当てられないという C# コンパイラによる保証にすぎない場合はどうなるでしょうか。コンストラクタまたは宣言?

一方、readonly intCLR が例外をスローすることなく、スルー リフレクションの値を設定できることがわかりました。これreadonlyは、ランタイム チェックの場合は不可能です。

だから私の推測は次のとおりです。「読み取り専用」はコンパイル時の機能にすぎません。誰でもこれを確認/拒否できますか? もしそうなら、この情報が IL に含まれる理由は何ですか?

4

4 に答える 4

16

アクセス修飾子と同じ視点から見る必要があります。アクセス修飾子は IL に存在しますが、それは本当にランタイム チェックですか? (1) コンパイル時にプライベート フィールドを直接割り当てることはできません。(2) リフレクションを使用してそれらを割り当てることができます。これまでのところ、 readonlyのようなランタイム チェックはないようです。

しかし、アクセス修飾子を調べてみましょう。以下をせよ:

  1. public class C で Assembly A.dll を作成する
  2. A.dll を参照するアセンブリ B.exe を作成します。B.exe はクラス C を使用します。
  3. 2 つのアセンブリをビルドします。B.exe を実行すると問題なく動作します。
  4. A.dll を再構築しますが、クラス C を内部に設定します。B.exe のディレクトリにある A.dll を置き換えます。

現在、B.exe を実行するとランタイム例外がスローされます。

アクセス修飾子は IL にも存在しますよね? それで、彼らの目的は何ですか?その目的は、.Net アセンブリを参照する他のアセンブリが、コンパイル時と実行時の両方で、アクセスが許可されているものとアクセスが許可されていないものを知る必要があることです。

Readonly は IL にも同様の目的があるようです。特定の型のフィールドに書き込むことができるかどうかを他のアセンブリに伝えます。ただし、readonly には、上記のサンプルでアクセス修飾子が示すのと同じ実行時チェックがないようです。readonly はコンパイル時のチェックであり、実行時には発生しないようです。ここでパフォーマンスのサンプルを見てみましょう:読み取り専用パフォーマンスと const

繰り返しますが、これは IL が役に立たないという意味ではありません。IL は、コンパイル時エラーが最初に発生することを確認します。ビルドするときは、コードに対してビルドするのではなく、アセンブリに対してビルドすることを忘れないでください。

于 2009-05-27T01:06:26.953 に答える
7

標準のインスタンス変数を使用している場合、readonly は通常の変数とほぼ同じように動作します。追加された IL はコンパイル時のチェックになりますが、実行時にはほとんど無視されます。

静的な読み取り専用メンバーを使用している場合、状況は少し異なります...

静的読み取り専用メンバーは静的コンストラクター中に設定されるため、JIT は値が存在することを「認識」します。余分なメモリはありません.readonlyは他のメソッドがこれを設定するのを防ぐだけですが、それはコンパイル時のチェックです.

JIT は、このメンバーが決して変更できないことを認識しているため、実行時に「ハードコード」されるため、最終的な効果は const 値を持つのと同じです。違いは、JIT コンパイラーが読み取り専用の値を所定の位置に配線するために余分な作業を行う必要があるため、JIT 時間自体に時間がかかることです。(ただし、これは非常に高速になります。)

これについては、Marcus Hegee によるExpert C++/CLIにかなり適切な説明があります。

于 2009-05-27T01:11:06.990 に答える
4

他の回答でまだ言及されていない重要なポイントの 1 つは、読み取り専用フィールドにアクセスしたとき、またはプロパティにアクセスしたときに、データのコピーを使用して要求が満たされることです。問題のデータが 4 ~ 8 バイトを超えるデータを持つ値型である場合、この余分なコピーのコストが非常に大きくなることがあります。構造体が 16 バイトから 17 バイトに大きくなるとコストが大幅に増加しますが、多くのアプリケーションでは構造体が頻繁にコピーされなければ、クラスよりもかなり大きくても高速であることに注意してください。たとえば、3 次元空間で三角形の頂点を表す型を持つと想定されている場合。簡単な実装は、3 つの構造体を含む構造体になります。floatポイントごとに; おそらく合計 36 バイトです。ポイントと各ポイント内の座標が変更可能なパブリック フィールドであるsomeTriangle.P1.X場合、頂点 1 の Y 座標以外のデータをコピーする必要なく、すばやく簡単にアクセスできます。一方、P1プロパティまたはreadonlyフィールドの場合、コンパイラはP1一時構造にコピーしてXから、そこから読み取る必要があります。

于 2012-04-23T17:45:23.877 に答える
3

readonly がコンパイル時にのみ有効である場合でも、データをアセンブリ (つまり IL) に格納する必要があります。CLR は共通 言語ランタイムです。ある言語で記述されたクラスは、他の言語で使用および拡張できます。

CLR のすべてのコンパイラは、他のすべての言語を読み取ってコンパイルする方法を知っているわけではないため、readonlyフィールドのセマンティクスを維持するために、そのデータをアセンブリに格納して、他の言語のコンパイラがそれを尊重できるようにする必要があります。

もちろん、フィールドがマークされているという事実readonlyは、JIT が最適化 (値のインライン使用など) などの他のことを実行できることを意味します。リフレクションを使用してフィールドの値を変更したという事実に関係なく、initonly対応するコンストラクター (フィールドの型に応じてインスタンスまたは静的) の外側にあるフィールドは、検証不能なアセンブリになります。

于 2009-05-27T01:12:08.937 に答える