質問者
Decimal.One って何のためにあるんですか?

質問
すべての返信
-
意味は同じだと思います。
Why does Decimal.MinusOne exist?
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1761032&SiteID=1に、パフォーマンスを考慮しているのではないか? という話が載っています。
例えば、String.Emptyと""の違いのようなものではないでしょうか? ""だとString型の空文字のインスタンスを生成することになるので、パフォーマンスが悪いそうです。 -
例えば、String.Emptyと""の違いのようなものではないでしょうか? ""だとString型の空文字のインスタンスを生成することになるので、パフォーマンスが悪いそうです。
いえ、文字列に関しては文字列インターンプールがあるので同じ文字列は同じインスタンスを共有します。コード ブロックstring s1 = "aiueo";
string s2 = "aiueo";パフォーマンスの面で言うと、むしろ String.Empty の方が悪いという気もします。IL だと
コード ブロックstring s1 = String.Empty;コード ブロックstring s2 = "";
本題ですが、Decimal は値型ですので、0m を使っても Decimal.Zero を使っても常に新しいオブジェクトを作っています。よって効率の違いは全くないかと思います。-1, 0, 1 のような値の場合
コード ブロックDecimal(Int32)
のコンストラクタが使用されて Decimal 型がつくられます。-1, 0, 1 のような整数の場合特別に短い IL オペコードでスタックに格納できます。それで -1, 0, 1は特別扱いしているのあかなぁと思ったんですが、特別に短い IL オペコードは -1~8 が対応していますので、Dicimal.Seven 等がないと合いませんね。
完全に想像に依りますが、プラットフォームの違いによる Decimal 演算の単位元の違い(違いなんてあるのか知りませんが)を考慮してるのかなと思ったり。
-
-
String.Emptyに関しては勘違いしていたかもしれません。どこかで読んだ気がして探してみたんですが、見つかりませんでした。すみません。考えてみれば、""と書いたところでオプティマイザがString.Emptyにしてしまえばいいような気がします。しかし、ILレベルでは違うんですよね。
あとは、JIT コンパイラがどう解釈するかなんでしょうね。闇の領域なので分かりませんが、同じ処理として扱うかもしれません。(SSCLI を見ればある程度分かるかもしれませんが)。
""はよく使うものなので、何か特別な仕掛けがあるようにも思えます。ldsfldは静的フィールドのプッシュであり、ldstrは文字列参照のプッシュですから、String.Emptyはインスタンスを生成しない分、有利ということはないのでしょうか?
文字列インターンプールの仕掛けがあるので、String.Empty も "" も同じインスタンスのはずです。どこかで String 型を使おうとする前(String はライブラリも使っているので、恐らく相当早い段階)に String.Empty は初期化されるので、"" を使う際は String.Empty が指しているインスタンスを使うと思います。そして、ldstr の方がどう考えても軽そうなので、物凄く厳密に言うと「"" を使う方が効率が良いのではないか」という妄想です^^;。
-
囚人 さんからの引用 文字列インターンプールの仕掛けがあるので、String.Empty も "" も同じインスタンスのはずです。どこかで String 型を使おうとする前(String はライブラリも使っているので、恐らく相当早い段階)に String.Empty は初期化されるので、"" を使う際は String.Empty が指しているインスタンスを使うと思います。
そして、ldstr の方がどう考えても軽そうなので、物凄く厳密に言うと「"" を使う方が効率が良いのではないか」という妄想です^^;。
なるほど、そういうことだったのですね。詳細についてはヘルスバーグさんに聞いてみたいところですね。
-
脱線してる話題に関してのみですが、
囚人 さんからの引用 いえ、文字列に関しては文字列インターンプールがあるので同じ文字列は同じインスタンスを共有します。これは「C# の文字列定数」に関することであれば真であり、文字列型全般に関しては誤りです。インターンされいてる文字列 X を参照する2つの文字列型のインスタンス a, b が同じインスタンスである必要はありません。また、インターンプールそのものは CLR の持つ機能で、多くの .NET 対応のプログラミング言語/環境では、文字列定数を初期値としてこのテーブルに登録しますが、すべての .NET 対応プログラミング言語/環境がそうなっていることを保障するものではないようです。このため、string.Empty は、プログラム言語等に依存しないで空文字列の参照を提供する手段として提供されている、という見方をすることもできます。Decimal 型の定数を表記できない .NET 対応プログラム言語も存在しうるでしょう。# 例えば、VB では日付時刻型の定数をソースコード上に記載する文法がありますが、C# にはありません。ちなみに、string.Empty のインスタンスはインターンプールを参照しておらず、ソースコード上に空文字列が "" という形で登場した場合には、string.Empty とは別個のインスタンスとして異なる string 型のインスタンスが生成されますので、文字列型のインスタンス生成の回数が増えるのは確実でしょう。また、インターンプールのエントリが1つ増加することで文字列型のインターンチェックにおいて検索する時間がわずかに増えることになりますので、インターンプールを検索するような文字列操作のパフォーマンスの低下が発生する可能性もあるかと思います。 -
コメントがある事に気付きませんで遅くなりました。
これは「C# の文字列定数」に関することであれば真であり、文字列型全般に関しては誤りです。
インターンされいてる文字列 X を参照する2つの文字列型のインスタンス a, b が同じインスタンスである必要はありません。
意味が分かりませんでした。「インターンされいてる文字列 X を参照する」なのに、「インスタンス a, b」は有りえなくないでしょうか。X を参照している時点でインスタンス a, b なんて存在しません。インスタンスは X のみです。
ちなみに、string.Empty のインスタンスはインターンプールを参照しておらず、ソースコード上に空文字列が "" という形で登場した場合には、string.Empty とは別個のインスタンスとして異なる string 型のインスタンスが生成されますので、文字列型のインスタンス生成の回数が増えるのは確実でしょう。また、インターンプールのエントリが1つ増加することで文字列型のインターンチェックにおいて検索する時間がわずかに増えることになりますので、インターンプールを検索するような文字列操作のパフォーマンスの低下が発生する可能性もあるかと思います。
"" に限らず、文字列定数は参照前に全てインターンプールに確保されるでしょう。明らかに必要である事が分かりますからね。String.IsIntern("");
String.IsIntern(String.Empty);
String.IsIntern("aiueo");
の戻り値は、最初に使う時点から null 参照ではありません。.NET Framework 1.1 では "" と String.Empty は同一ですが、.NET Framework 2.0 では "" と String.Empty は同一のような同一でないような挙動ですね。私が、String.Intern の仕様を正しく理解していないのかもしれませんが。
また、インターンプールそのものは CLR の持つ機能で、多くの .NET 対応のプログラミング言語/環境では、文字列定数を初期値としてこのテーブルに登録しますが、すべての .NET 対応プログラミング言語/環境がそうなっていることを保障するものではないようです。
プログラミング言語の違いが影響するでしょうか。プログラミング言語がどうであれ、IL 上の文字定数の扱いは同一では?
このため、string.Empty は、プログラム言語等に依存しないで空文字列の参照を提供する手段として提供されている、という見方をすることもできます。Decimal 型の定数を表記できない .NET 対応プログラム言語も存在しうるでしょう。
# 例えば、VB では日付時刻型の定数をソースコード上に記載する文法がありますが、C# にはありません。
多分逆で、プログラミング言語が「空文字としての表現」を String.Empty に合うようにインプリメントするんじゃないでしょうか。プログラミング言語にその表現がなければないで良いって話なだけで。
-
「インターンされいてる文字列 X を参照する」なのに、「インスタンス a, b」は有りえなくないでしょうか。X を参照している時点でインスタンス a, b なんて存在しません。インスタンスは X のみです。
文字列Xは特定の文字列インスタンスじゃなくてあるXという「文字列」の意味でしょう。
単にある文字列Xがインターンされていても、同じ文字列X(インスタンスじゃなくて)をあらわす
インターンされていない文字列インスタンスも存在できる、という話だと思いますよ。
このため、
String.IsIntern(String.Empty);
String.IsIntern("aiueo");
の戻り値は、最初に使う時点から null 参照ではありません。….NET Framework 2.0 では "" と String.Empty は同一のような同一でないような挙動ですね。
これらはその場で書かれている各文字列インスタンスがインターンされたものであることを表しているわけではありません。
実際にString.Emptyはインターンされていないようですね。
しかし文字列表現としての空文字列はインターンプールには存在しています。
※正確には、String.Emptyはインターンされた空文字列とは別のインスタンスを参照しています。
-
文字列Xは特定の文字列インスタンスじゃなくてあるXという「文字列」の意味でしょう。単にある文字列Xがインターンされていても、同じ文字列X(インスタンスじゃなくて)をあらわす
インターンされていない文字列インスタンスも存在できる、という話だと思いますよ。
このため、
String.IsIntern(String.Empty);
String.IsIntern("aiueo");
の戻り値は、最初に使う時点から null 参照ではありません。….NET Framework 2.0 では "" と String.Empty は同一のような同一でないような挙動ですね。
これらはその場で書かれている各文字列インスタンスがインターンされたものであることを表しているわけではありません。
実際にString.Emptyはインターンされていないようですね。
「このため」の係りが全く分からないのですが、
「これらはその場で書かれている各文字列インスタンスがインターンされたものであることを表しているわけではありません。」
の根拠はなんでしょうか?
インターンされたものであることを表しているのでなければ何を表しているものなのでしょうか? -
囚人 さんからの引用
「このため」の係りが全く分からないのですが、このためっていうのは、
インターンされた「文字列」と同じ「文字列」を表す、インターンされていないインスタンスというのがありうるため、
IsInternedの戻り値でnullじゃない値が返ってきていても、引数の文字列インスタンス自体がインターンされているとは限らない、
という流れのつもりで書きましたが、その前にIsInternedの動作について触れなかったので意味がわかりにくかったですかね。
※ていうかMSDNのIsInternedの説明は大いに誤解を招く表現になっているので。
ああそうそう、このことを書いたのは、
囚人 さんからの引用
"" に限らず、文字列定数は参照前に全てインターンプールに確保されるでしょう。明らかに必要である事が分かりますからね。String.IsIntern("");
String.IsIntern(String.Empty);
String.IsIntern("aiueo");
の戻り値は、最初に使う時点から null 参照ではありません。という記述から、String.IsInternedの戻り値がnullでないことを、「引数のインスタンス」がインターンされたものであることの根拠だといっているように思えたからです。
※String.IsInterned(String.Empty)がnullでないから、String.Emptyはインターンプールのインスタンスだ、と言っているように思えた。
囚人 さんからの引用
「これらはその場で書かれている各文字列インスタンスがインターンされたものであることを表しているわけではありません。」
の根拠はなんでしょうか?んーひょっとすると言いたかったことがうまく伝わっていないのかもしれませんが…とりあえず伝わっているものと仮定して…
ご自身で
囚人 さんからの引用
String.IsIntern("");
String.IsIntern(String.Empty);
String.IsIntern("aiueo");
の戻り値は、最初に使う時点から null 参照ではありません。.NET Framework 1.1 では "" と String.Empty は同一ですが、.NET Framework 2.0 では "" と String.Empty は同一のような同一でないような挙動ですね。
と書かれていたので単に自分で確かめられたんだと思ってて特に書きもしなかったんですが、例えば
string empty = string.Empty;
string literal = "";
string isEmptyInterned = string.IsInterned(empty);
string isLiteralInterned = string.IsInterned(literal);System.Diagnostics.Debug.WriteLine("empty = string.Empty");
System.Diagnostics.Debug.WriteLine("literal = \"\"");
System.Diagnostics.Debug.WriteLine("ReferenceEquals(empty, literal): " + object.ReferenceEquals(empty, literal));
System.Diagnostics.Debug.WriteLine("IsInterned(empty) != null: " + (isEmptyInterned != null));
System.Diagnostics.Debug.WriteLine("IsInterned(literal) != null: " + (isLiteralInterned != null));
System.Diagnostics.Debug.WriteLine("ReferenceEquals(empty, IsInterned(empty)): " + object.ReferenceEquals(empty, isEmptyInterned));
System.Diagnostics.Debug.WriteLine("ReferenceEquals(literal, IsInterned(literal)): " + object.ReferenceEquals(literal, isLiteralInterned));
System.Diagnostics.Debug.WriteLine("ReferenceEquals(IsInterned(empty), IsInterned(literal)): " + object.ReferenceEquals(isEmptyInterned, isLiteralInterned));こんなのを実行してみると、出力は例えばこうなります。
empty = string.Empty
literal = ""
ReferenceEquals(empty, literal): False
IsInterned(empty) != null: True
IsInterned(literal) != null: True
ReferenceEquals(empty, IsInterned(empty)): False
ReferenceEquals(literal, IsInterned(literal)): True
ReferenceEquals(IsInterned(empty), IsInterned(literal)): True囚人 さんからの引用
インターンされたものであることを表しているのでなければ何を表しているものなのでしょうか?同じ文字列表現がすでにインターンプールに存在している場合に、インターンプールのインスタンスを返すのであって、
引数のインスタンス自身がインターンされたものであるか、とは別です。
※引数のインスタンス自身がインターンされたものであれば、引数自身が返りますが。
こういう意味で「その場で書かれている各文字列インスタンスが」という書き方をしたんですが、その辺が伝わらなかったかもしれません。
-
囚人 さんからの引用 「インターンされいてる文字列 X を参照する」なのに、「インスタンス a, b」は有りえなくないでしょうか。X を参照している時点でインスタンス a, b なんて存在しません。インスタンスは X のみです。
すでに書かれているとおりで、「インスタンスが保持している内容」という意味で「文字列」と書いています。
# インターンテーブルの機能的な役割から容易に想像できるかと思ったんですが、わかりにくかったようで
囚人 さんからの引用 String.IsIntern("");
String.IsIntern(String.Empty);こちらも、すでに指摘されていますが、IsInterned は「文字列インスタンス」ではなく「文字列(内容)」がインターンテーブルに登録されているかどうかを調べるメソッドである、というところを理解されていないように見えます。(ついでに、インターンテーブルに登録されていれば、その文字列を保持する文字列型のインスタンスを得ることができる)囚人 さんからの引用 プログラミング言語の違いが影響するでしょうか。プログラミング言語がどうであれ、IL 上の文字定数の扱いは同一では?すいません、メタデータを経由しない文字列の生成ぐらい IL にありそうなもんだと思ったんですが、IL のリストみるかぎりそれっぽい機能はなさそうですね。# 複数のアセンブリのメタデータのマージは CLI がやってくれるとはいえ、非効率な場面がありそうだなぁ囚人 さんからの引用 プログラミング言語が「空文字としての表現」を String.Empty に合うようにインプリメントするんじゃないでしょうか。適当にメジャーな言語では、どれもメタデータを優先して string.Empty は知らない顔してるんですよね。まあ、例に 日付時間型 を出したように、文字列はあまりにも多くのプログラム言語でメジャーな要素なので、プログラム言語の利用者も、プログラム言語の実装者も、string.Empty って何なの?ってのは意識しにくいところなのではないか思います。逆に、DateTime や Decimal は定数表現が存在することを知らなかったり、思いつかなかったりする人はそれなりに居たりするので、イメージがつかみやすいかな、っと思いました。# C# を使っている人でも、ソースコード中に 4.5m とか出てきたら「4.5メートル?」と首をかしげる人は少なくは無いはず -
返事が遅くなりすみません。
なちゃ さんからの引用
インターンされた「文字列」と同じ「文字列」を表す、インターンされていないインスタンスというのがありうるため、IsInternedの戻り値でnullじゃない値が返ってきていても、引数の文字列インスタンス自体がインターンされているとは限らない、
という流れのつもりで書きましたが、その前にIsInternedの動作について触れなかったので意味がわかりにくかったですかね。
K.Takaoka さんからの引用
こちらも、すでに指摘されていますが、IsInterned は「文字列インスタンス」ではなく「文字列(内容)」がインターンテーブルに登録されているかどうかを調べるメソッドである、というところを理解されていないように見えます。(ついでに、インターンテーブルに登録されていれば、その文字列を保持する文字列型のインスタンスを得ることができる)String.IsInterned、String.Intern の引数に定数を渡しているから伝わりにくいのかな。
結局、「""をコードに書いたらその都度インスタンスが作られる」
って仰りたいって事でしょうか?
私はその都度作られるとは思っていませんが、なちゃんさんとTakaokaさんはその都度インスタンスが作られるという認識でなんですよね?
IsInterned、Intern は渡した「文字列の内容」がインターンされているかどうかを調べるメソッドですよね。
そして、文字列定数は CLR が初期化時にインターンテーブルに登録する。
例えば、
コード ブロックstring s1 = "aiueo";
string s2 = "aiueo";
string s3 = new StringBuilder("aiu").Append("eo").ToString();
上記の様に書いた場合、1、2、3行目は新しい文字列インスタンス作成していなく(アプリケーション初期化時に既に"aiueo"がインターンテーブルに登録されるため。初期化時に"aiueo"のインスタンスが作成される)、下記の結果は勿論全部 True です。
(もしかしたら、3行目は新しいインスタンスを作っているのかも。自信なし)コード ブロックConsole.WriteLine(String.IsInterned("aiueo") != null);
Console.WriteLine(String.IsInterned("aiueo") != null);
Console.WriteLine(String.IsInterned(new StringBuilder("aiu").Append("eo").ToString()) != null);
但し、1行目と2行目がなければ3行目は当然 False です。
なので、コード ブロックString.IsInterned("");って書いた時点で、"" は既にインターンテーブルに登録されているし、引数に新しいインスタンスを渡しているなんて事もありません。
-
どうも誤解があるようですが、
私はその都度作られるとは思っていませんが、なちゃんさんとTakaokaさんはその都度インスタンスが作られるという認識でなんですよね?
少なくとも私はそんなことは一言も言っていませんし、そのような認識ではありません。
私が言いたかったのは単に、String.IsInternedがnullを返さなくても、引数の文字列インスタンスがインターンされたインスタンスであるとは限らない、実際にString.Emptyはインターンされていない、ということと、
Takaokaさんがおっしゃっていた文字列Xがインターンプールにあるとき云々について、囚人さんがそんなことはないと書かれていたので、Takaokaさんが言いたかったのはこういうことだろうと補足しただけです。
"" に限らず、文字列定数は参照前に全てインターンプールに確保されるでしょう。明らかに必要である事が分かりますからね。
String.IsIntern("");
String.IsIntern(String.Empty);
String.IsIntern("aiueo");
の戻り値は、最初に使う時点から null 参照ではありません。String.IsIntern(String.Empty);
の戻り値が最初に使う時点から null 参照ではなかったからといって、String.Emptyがインターンされているということではないですよ、というだけのことです。そのインスタンスがインターンされているかを確認するには、戻り値が引数と同じインスタンスであることを確認する必要がある、という意味です。
-
追加です。
囚人 さんからの引用 IsInterned、Intern は渡した「文字列の内容」がインターンされているかどうかを調べるメソッドですよね。
そして、文字列定数は CLR が初期化時にインターンテーブルに登録する。
例えば、
コード ブロックstring s1 = "aiueo";
string s2 = "aiueo";
string s3 = new StringBuilder("aiu").Append("eo").ToString();
上記の様に書いた場合、1、2、3行目は新しい文字列インスタンス作成していなく(アプリケーション初期化時に既に"aiueo"がインターンテーブルに登録されるため。初期化時に"aiueo"のインスタンスが作成される)、下記の結果は勿論全部 True です。
(もしかしたら、3行目は新しいインスタンスを作っているのかも。自信なし)コード ブロックConsole.WriteLine(String.IsInterned("aiueo") != null);
Console.WriteLine(String.IsInterned("aiueo") != null);
Console.WriteLine(String.IsInterned(new StringBuilder("aiu").Append("eo").ToString()) != null);IsInternedの戻り値がnullでないことで何を示そうとされているのかが良く分からないんですが、3行目はインターンされていません。
戻り値が引数と同じインスタンスであることを確認しないと、引数のインスタンスがインターンされているかどうかは分かりません。
そんなことは分かっている、とおっしゃっているようなのですが、だったらなぜ再三戻り値がnullでない、という結果だけを示されるのかがよくわかりません。
--追記
これに関しては、単に定数で書かれた文字列がインターンプールに入っている、ということを確認したかっただけだから、ですかね。
私はそれは否定していないので(なので、それをわざわざ示されるとはあまり思っていなかったので)、ここは誤解しているかもしれません。
--追記ここまで
囚人 さんからの引用 なので、
コード ブロックString.IsInterned("");って書いた時点で、"" は既にインターンテーブルに登録されているし、引数に新しいインスタンスを渡しているなんて事もありません。
当然そうでしょうが、とりあえず私は別にそれを否定した覚えはありません。
しかし「そのことを確認するコードとしては」戻り値がnullかどうかだけでは不完全です。
-
そのインスタンスがインターンされているかを確認するには、戻り値が引数と同じインスタンスであることを確認する必要がある、という意味です。
「引数に渡したインスタンスが」という表現を再三しているので、「"" を使うたびにインスタンスを生成している」と考えていらっしゃると思ったのです。すみません。
「そのインスタンスが」と言ってますが、"" や String.Empty を IsInterned に渡しているのはあくまで参照ですよね。そして、インスタンスはインターンテーブルにあります。
なちゃ さんからの引用
String.IsIntern(String.Empty);
の戻り値が最初に使う時点から null 参照ではなかったからといって、String.Emptyがインターンされているということではないですよ、というだけのことです。
コード ブロックempty = string.Empty
literal = ""
ReferenceEquals(empty, literal): False
IsInterned(empty) != null: True
IsInterned(literal) != null: True
ReferenceEquals(empty, IsInterned(empty)): False -- (1)
ReferenceEquals(literal, IsInterned(literal)): True
ReferenceEquals(IsInterned(empty), IsInterned(literal)): True -- (2)
1.1 のときは、上記結果は全て True だったので、String.Empty と "" がインターンテーブルの同じ場所を使っていたんでしょうけど、2.0 はそれが別の場所を使うようになったというだけで(どうしてそうなったのか理由は分かりませんが)。
(1) が False になっているのは、IsInterned(empty) で "" の方を先に見つけたからとかでしょう。
ちなみに、"" を一度もコードに書かない以下のコードは、True になります。"" がインターンテーブルに登録されていないからです。
コード ブロックstring str1 = String.Empty;
string str2 = String.Intern(String.Empty);
Console.WriteLine(ReferenceEquals(str1, str2)); // Trueなので、String.Emptyはインターンされているし、"" はコードに書いていたら、アプリケーション初期化時にインターンされます。
-
IsInternedの戻り値がnullでないことで何を示そうとされているのかが良く分からないんですが、3行目はインターンされていません。[del]戻り値が null でない事で、3 行目は新しいインスタンスが作られない、という事を示しています。[/del]
じゃない。間違えました。
定数じゃないので、これを持ち出すと意味が分かりませんでしたね。すみません。
戻り値が引数と同じインスタンスであることを確認しないと、引数のインスタンスがインターンされているかどうかは分かりません。
戻り値と引数が同じインスタンスを確認するというか、そもそも、インターンされていたら「新しいインスタンス」が作られないです(定数は)。
なので、何故、戻り値が null かどうかを確認するだけで不完全なのか分かりません。勿論、引数で渡した参照のインスタンスと戻り値で得た参照のインスタンスが別(全く同じ文字列表現)である事はありえるでしょうが、引数渡した文字列がイターンされていないという事はないでしょう?String.Empty と "" のように。
-
ちょと確認ですが、どこまでが想像や推測でどこまでが事実ですか?
--追記
すみません、ちょっと書き方があれでしたが、
上記の、(1) の部分が False だから、String.Empty はインターンされていないと言いたいのでしょうけど、インターンテーブルに「String.Empty」と「""」の二つが別に登録されているだけです。
これがちょっと突拍子もないように思えたので。
ちょっと考えられないんですが、どっかに情報があったりしました?
--追記ここまで
コード ブロックusing System;
using System.Runtime.CompilerServices;
class Program
{
static void Main(string[] args)
{
string str1 = string.Empty;
string str2 = string.IsInterned(string.Empty);
string str3 = string.Intern(string.Empty);
string str4 = string.IsInterned(string.Empty);
string str5 = Literal();
string str6 = string.IsInterned(Literal());
Console.WriteLine(RuntimeHelpers.GetHashCode(str1));
Console.WriteLine(RuntimeHelpers.GetHashCode(str2));
Console.WriteLine(RuntimeHelpers.GetHashCode(str3));
Console.WriteLine(RuntimeHelpers.GetHashCode(str4));
Console.WriteLine(RuntimeHelpers.GetHashCode(str5));
Console.WriteLine(RuntimeHelpers.GetHashCode(str6));
}
[MethodImpl(MethodImplOptions.NoInlining)]
static string Literal()
{
return "";
}
}結果
9799115
0
9799115
9799115
9799115
9799115とりあえずこれを見ればおかしいことが分かるでしょう。
デバッガにアタッチせずに単独で実行すればこんな感じになるでしょう。
最初に""をロードするコードが実行される(もしくはJITコンパイルされる?)辺りまでは、
""のインターン化は実行されていませんし、string.IsInterned(string.Empty)はnullです。
string.Intern(string.Empty)によって、(まだインターンプールに""という文字列がないので)
string.Emptyインスタンスが、インターンプールに登録されます。
よって、string.IsInterned(string.Empty)はnullではなくインターンプールのインスタンスを返すようになり、
それは先ほど登録したstring.Emptyインスタンスです。
Literal()の呼び出しをもっと前に移動すれば、string.Emptyインスタンスより先に""がインターンプールに
登録されるため、string.IsInterned(string.Empty)はnullではなくstring.Emptyでもなくなります。
-
検証した結果を考えているだけなので、推測と言えば推測ですし、事実と言えば事実です。
逆に、なちゃさんの言っている事がはっきり書かれた出所を出してもらえますか?
結果9799115
0
9799115
9799115
9799115
9799115とりあえずこれを見ればおかしいことが分かるでしょう。
デバッガにアタッチせずに単独で実行すればこんな感じになるでしょう。
最初に""をロードするコードが実行される(もしくはJITコンパイルされる?)辺りまでは、
""のインターン化は実行されていませんし、string.IsInterned(string.Empty)はnullです。
あー、なるほど。大変失礼しました。
リテラルは初期化時にインターンされるというのをどっかで読んだ記憶があったのですが、初期化時ってのが、とりあえず呼び出されるまでなんですね。
String.Empty を使う限りは String.Empty はインターンされていなく、"" を使うと String.Empty はインターンされた方を使うんですね。
String.Empty がインターンされていないのは mscorlib.dll に StringFreezingAttribute がついているからだと。こっちを最初に示していただければすぐ納得できたのですが…。
じゃあ、私が最初に trapemiya さんに言ってた事は間違いで、「String.Empty を使う限りはパフォーマンスが良く、コード中に一度でも "" を使えば、以降 String.Empty も "" もインターンテーブルの空文字表現を使う」が正しいですね。
ちなみに、以下は GetHashCode の呼び出しのせでも String.Empty("")はインターン化されるようです。
string str1 = string.Empty;
Console.WriteLine(RuntimeHelpers.GetHashCode(str1));string str2 = string.IsInterned(string.Empty);
Console.WriteLine(RuntimeHelpers.GetHashCode(str2));string str3 = string.Intern(string.Empty);
Console.WriteLine(RuntimeHelpers.GetHashCode(str3));string str4 = string.IsInterned(string.Empty);
Console.WriteLine(RuntimeHelpers.GetHashCode(str4));string str5 = Literal();
Console.WriteLine(RuntimeHelpers.GetHashCode(str5));string str6 = string.IsInterned(Literal());
Console.WriteLine(RuntimeHelpers.GetHashCode(str6));結果
9799115
58225482
58225482
58225482
58225482
58225482 -
すみません、
>ちょと確認ですが、どこまでが想像や推測でどこまでが事実ですか?
この書き方はよろしくなかったと思っています。私も完全に事実のみ書いているわけではありません。
検証した結果を考えているだけなので、推測と言えば推測ですし、事実と言えば事実です。
逆に、なちゃさんの言っている事がはっきり書かれた出所を出してもらえますか?書籍(プログラミング .NET Framework)やら、MSDNの各記述、実験による検証結果、また一般的な文字列のインターン化という考え方や目的等々合わせた結果です。
そういう意味では、string.Internメソッドで、引数に渡した参照(文字列インスタンス)そのものがインターンプールに登録されているという部分だけはやや納得いきにくい部分ではあります。
バージョンの考慮事項に記述されている内容と、検証結果の動作から実際はこうなっている、と判断しています。まあこれは検証結果では明らかにそうなっているのでそうなのでしょう。
で、まあ事実か推測かというような書き方をしたことは悪かったと思っています。
ただ、こんなことを書いたのは、
「そのインスタンスが」と言ってますが、"" や String.Empty を IsInterned に渡しているのはあくまで参照ですよね。そして、インスタンスはインターンテーブルにあります。
や
インターンテーブルに「String.Empty」と「""」の二つが別に登録されているだけです。
や
上記の、(1) の部分が False だから、String.Empty はインターンされていないと言いたいのでしょうけど、インターンテーブルに「String.Empty」と「""」の二つが別に登録されているだけです。
1.1 のときは、上記結果は全て True だったので、String.Empty と "" がインターンテーブルの同じ場所を使っていたんでしょうけど、2.0 はそれが別の場所を使うようになったというだけで(どうしてそうなったのか理由は分かりませんが)。
(1) が False になっているのは、IsInterned(empty) で "" の方を先に見つけたからとかでしょう。
があまりに突拍子無く思えたからです。(私のこれまでの)常識的な感覚と、インターンの目的や効果、MSDNや書籍の記述などから考えてもこれはちょっとあり得ないだろうと思える内容だった(正直な感覚としては、検証結果などから見た合理的な推測の範囲とは考えられない)ので、
いったいこれはどこから出てきた話なのか、それ以外の部分もそもそもどの程度の確度(推測であった場合)で書かれているのか不安になったからです。
----
String.Empty がインターンされていないのは mscorlib.dll に StringFreezingAttribute がついているからだと。こっちを最初に示していただければすぐ納得できたのですが…。
いや、これは私は認識していませんでした。
ただ、各種情報と検証結果から総合して、String.Emptyは自動ではインターン化されていないのだと判断しただけです。
※他のリテラルである""などがロードされてインターン化される前に、明示的にstring.Intern(string.Empty)などとしない限り。
なるほどこういうことだったんですね。
じゃなく「String.Empty を普通に使う分には mscorlib.dll のネイティブイメージに固定化されたものを使い、"" はインターンテーブルを使う」です。
String.Emptyは読み取り専用フィールドですから、フィールドの初期化時に、通常は行われる自動的なインターン化やインターンプールの検索が行われない、ということですね。
※String.Emptyへのアクセス自体については、単にすでに初期化済みの参照を読むだけですね。
よって、事実上String.Emptyは、インターンプールとはまったく関与しない、ということですね。
なるほどです。
-
String.Empty はインターンされているはずだ(少なくとも .NET Framework 1.1 ではそのような挙動だったため)という前提の元に検証し、検証の手順次第ではそのように見えたため見誤っていました。大変失礼しました。
String.Emptyは読み取り専用フィールドですから、フィールドの初期化時に、通常は行われる自動的なインターン化やインターンプールの検索が行われない、ということですね。※String.Emptyへのアクセス自体については、単にすでに初期化済みの参照を読むだけですね。
よって、事実上String.Emptyは、インターンプールとはまったく関与しない、ということですね。
なるほどです。
StringFreezingAttribute の説明を読む限りでは、読み取り専用フィールドかどうかに関わらず、StringFreezingAttribute のついたアセンブリを Ngen した場合は、初期済みの固定化文字列を使用するっぽい記述ですね。
例えば以下のようなコードでも、mscorlib.dll 内ではインターンを使用しないで、ネイティブイメージのアドレスが直に設定されているという感じでしょうか。詳細な仕様が分からないので何とも言えませんけども。コード ブロックvoid F()
{
string s1 = "aiueo";
string s2 = "aiueo";
}大変勉強になりました。ありがとうございます。
-
GetHashCode()は,すぐに呼ばないといけないと思うので
コード ブロック-----C#-----
using System;
using System.Runtime.CompilerServices;
namespace MyEmptyStringTestCS
{
class Program
{
static void Main(string[] args)
{
string str0 = null;
Console.WriteLine(RuntimeHelpers.GetHashCode(str0));
string str1 = String.Empty;
Console.WriteLine(RuntimeHelpers.GetHashCode(str1));
string str2 = String.IsInterned(String.Empty);
Console.WriteLine(RuntimeHelpers.GetHashCode(str2));
Console.ReadLine();
}
}
}
-----VB-----
Imports System.Runtime.CompilerServices
Module Module1
Sub Main()
Dim str0 As String = Nothing
Console.WriteLine(RuntimeHelpers.GetHashCode(str0))
Dim str1 As String = String.Empty
Console.WriteLine(RuntimeHelpers.GetHashCode(str1))
Dim str2 As String = String.IsInterned(String.Empty)
Console.WriteLine(RuntimeHelpers.GetHashCode(str2))
Console.ReadLine()
End Sub
End Module
結果: どちらも
0
9799115
58225482String.Empty は,Intern されている
とフレームワーク側がそう考えているのは事実のようですが...。ヘルプに載ってる
コード ブロックstring str1 = String.Empty;
string str2 = String.Intern(String.Empty);
Console.WriteLine((object)str1 == (object)str2);ですが,
同じReleaseビルドでも,IDEから実行のとき(False)と,
単体で実行したとき(True)で結果が違ってきますね。また,
以下の str1 や str3 や str5 の GetHashCodeの意味するところですがコード ブロックusing System;
using System.Runtime.CompilerServices;
namespace MyEmptyStringTestCS
{
class Program
{
static void Main(string[] args)
{
string str0 = null;
Console.WriteLine(RuntimeHelpers.GetHashCode(str0));
string str1 = String.Empty;
Console.WriteLine(RuntimeHelpers.GetHashCode(str1));
string str2 = String.IsInterned(String.Empty);
Console.WriteLine(RuntimeHelpers.GetHashCode(str2));
string str3 = String.Empty;
Console.WriteLine(RuntimeHelpers.GetHashCode(str3));
string str4 = String.Intern(String.Empty);
Console.WriteLine(RuntimeHelpers.GetHashCode(str4));
string str5 = String.Empty;
Console.WriteLine(RuntimeHelpers.GetHashCode(str5));
Console.ReadLine();
}
}
}
は,
0
9799115
58225482
9799115
58225482
9799115と変化がないことからわかると思います。