none
Decimal.One って何のためにあるんですか? RRS feed

  • 質問

  • Decimal って、One とか Zero とか MinusOne ってメンバがあるんですが、これって一体何のためにあるんでしょうか?

    使いどころがいまいちわかりません。

     

    Code Snippet
    decimal a = 1m;
    decimal b = Decimal.One;

     

     

    では、意味が違ってくるのでしょうか。

    2007年9月26日 7:10

すべての返信

  • 意味は同じだと思います。

     

    Why does Decimal.MinusOne exist?
    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1761032&SiteID=1

     

    に、パフォーマンスを考慮しているのではないか? という話が載っています。
    例えば、String.Emptyと""の違いのようなものではないでしょうか? ""だとString型の空文字のインスタンスを生成することになるので、パフォーマンスが悪いそうです。

    2007年9月26日 15:29
    モデレータ

  • 例えば、String.Emptyと""の違いのようなものではないでしょうか? ""だとString型の空文字のインスタンスを生成することになるので、パフォーマンスが悪いそうです。

    いえ、文字列に関しては文字列インターンプールがあるので同じ文字列は同じインスタンスを共有します。

    コード ブロック
    string s1 = "aiueo";
    string s2 = "aiueo";

     

    は、たった一つのインスタンスを共有しています。空文字も同様です。

     

    パフォーマンスの面で言うと、むしろ String.Empty の方が悪いという気もします。IL だと

    コード ブロック
    string s1 = String.Empty;

     

    は、ldsfld

    コード ブロック
    string s2 = "";

     

    は、ldstr です。どちらの方が速いのか分かりませんが、後者の方が単純な命令っぽいです。


    本題ですが、Decimal は値型ですので、0m を使っても Decimal.Zero を使っても常に新しいオブジェクトを作っています。よって効率の違いは全くないかと思います。

     

    -1, 0, 1 のような値の場合

    コード ブロック
    Decimal(Int32)

     

    のコンストラクタが使用されて Decimal 型がつくられます。-1, 0, 1 のような整数の場合特別に短い IL オペコードでスタックに格納できます。それで -1, 0, 1は特別扱いしているのあかなぁと思ったんですが、特別に短い IL オペコードは -1~8 が対応していますので、Dicimal.Seven 等がないと合いませんね。

     

    完全に想像に依りますが、プラットフォームの違いによる Decimal 演算の単位元の違い(違いなんてあるのか知りませんが)を考慮してるのかなと思ったり。

    2007年9月27日 2:38
  • String.Emptyに関しては勘違いしていたかもしれません。どこかで読んだ気がして探してみたんですが、見つかりませんでした。すみません。考えてみれば、""と書いたところでオプティマイザがString.Emptyにしてしまえばいいような気がします。しかし、ILレベルでは違うんですよね。

    ""はよく使うものなので、何か特別な仕掛けがあるようにも思えます。ldsfldは静的フィールドのプッシュであり、ldstrは文字列参照のプッシュですから、String.Emptyはインスタンスを生成しない分、有利ということはないのでしょうか?

    2007年9月27日 5:49
    モデレータ

  • String.Emptyに関しては勘違いしていたかもしれません。どこかで読んだ気がして探してみたんですが、見つかりませんでした。すみません。考えてみれば、""と書いたところでオプティマイザがString.Emptyにしてしまえばいいような気がします。しかし、ILレベルでは違うんですよね。

    あとは、JIT コンパイラがどう解釈するかなんでしょうね。闇の領域なので分かりませんが、同じ処理として扱うかもしれません。(SSCLI を見ればある程度分かるかもしれませんが)。



    ""はよく使うものなので、何か特別な仕掛けがあるようにも思えます。ldsfldは静的フィールドのプッシュであり、ldstrは文字列参照のプッシュですから、String.Emptyはインスタンスを生成しない分、有利ということはないのでしょうか?

    文字列インターンプールの仕掛けがあるので、String.Empty も "" も同じインスタンスのはずです。どこかで String 型を使おうとする前(String はライブラリも使っているので、恐らく相当早い段階)に String.Empty は初期化されるので、"" を使う際は String.Empty が指しているインスタンスを使うと思います。

    そして、ldstr の方がどう考えても軽そうなので、物凄く厳密に言うと「"" を使う方が効率が良いのではないか」という妄想です^^;。

     

    2007年9月27日 7:03
  •  囚人 さんからの引用

    文字列インターンプールの仕掛けがあるので、String.Empty も "" も同じインスタンスのはずです。どこかで String 型を使おうとする前(String はライブラリも使っているので、恐らく相当早い段階)に String.Empty は初期化されるので、"" を使う際は String.Empty が指しているインスタンスを使うと思います。

    そして、ldstr の方がどう考えても軽そうなので、物凄く厳密に言うと「"" を使う方が効率が良いのではないか」という妄想です^^;。

     

    なるほど、そういうことだったのですね。詳細についてはヘルスバーグさんに聞いてみたいところですね。

    2007年9月27日 8:17
    モデレータ
  • 脱線してる話題に関してのみですが、
     
     囚人 さんからの引用
    いえ、文字列に関しては文字列インターンプールがあるので同じ文字列は同じインスタンスを共有します。
     
    これは「C# の文字列定数」に関することであれば真であり、文字列型全般に関しては誤りです。
    インターンされいてる文字列 X を参照する2つの文字列型のインスタンス a, b が同じインスタンスである必要はありません。
     
    また、インターンプールそのものは CLR の持つ機能で、多くの .NET 対応のプログラミング言語/環境では、文字列定数を初期値としてこのテーブルに登録しますが、すべての .NET 対応プログラミング言語/環境がそうなっていることを保障するものではないようです。
     
    このため、string.Empty は、プログラム言語等に依存しないで空文字列の参照を提供する手段として提供されている、という見方をすることもできます。Decimal 型の定数を表記できない .NET 対応プログラム言語も存在しうるでしょう。
    # 例えば、VB では日付時刻型の定数をソースコード上に記載する文法がありますが、C# にはありません。
     
    ちなみに、string.Empty のインスタンスはインターンプールを参照しておらず、ソースコード上に空文字列が "" という形で登場した場合には、string.Empty とは別個のインスタンスとして異なる string 型のインスタンスが生成されますので、文字列型のインスタンス生成の回数が増えるのは確実でしょう。また、インターンプールのエントリが1つ増加することで文字列型のインターンチェックにおいて検索する時間がわずかに増えることになりますので、インターンプールを検索するような文字列操作のパフォーマンスの低下が発生する可能性もあるかと思います。
     
    2007年9月27日 11:42
  • コメントがある事に気付きませんで遅くなりました。


    これは「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 に合うようにインプリメントするんじゃないでしょうか。プログラミング言語にその表現がなければないで良いって話なだけで。

    2007年10月1日 0:49
  • 「インターンされいてる文字列 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はインターンされた空文字列とは別のインスタンスを参照しています。

    2007年10月1日 2:10

  • 文字列Xは特定の文字列インスタンスじゃなくてあるXという「文字列」の意味でしょう。

    単にある文字列Xがインターンされていても、同じ文字列X(インスタンスじゃなくて)をあらわす

    インターンされていない文字列インスタンスも存在できる、という話だと思いますよ。

     

    このため、

     

    String.IsIntern(String.Empty);
    String.IsIntern("aiueo");
    の戻り値は、最初に使う時点から null 参照ではありません。

     

    ….NET Framework 2.0 では "" と String.Empty は同一のような同一でないような挙動ですね。

     

    これらはその場で書かれている各文字列インスタンスがインターンされたものであることを表しているわけではありません。

    実際にString.Emptyはインターンされていないようですね。


    「このため」の係りが全く分からないのですが、
    「これらはその場で書かれている各文字列インスタンスがインターンされたものであることを表しているわけではありません。」
    の根拠はなんでしょうか?
    インターンされたものであることを表しているのでなければ何を表しているものなのでしょうか?

     

    2007年10月1日 4:42
  •  囚人 さんからの引用

    「このため」の係りが全く分からないのですが、

     

    このためっていうのは、

    インターンされた「文字列」と同じ「文字列」を表す、インターンされていないインスタンスというのがありうるため、

    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

     

     囚人 さんからの引用

    インターンされたものであることを表しているのでなければ何を表しているものなのでしょうか?

    同じ文字列表現がすでにインターンプールに存在している場合に、インターンプールのインスタンスを返すのであって、

    引数のインスタンス自身がインターンされたものであるか、とは別です。

    ※引数のインスタンス自身がインターンされたものであれば、引数自身が返りますが。

     

    こういう意味で「その場で書かれている各文字列インスタンスが」という書き方をしたんですが、その辺が伝わらなかったかもしれません。

    2007年10月1日 8:18
  •  囚人 さんからの引用

    「インターンされいてる文字列 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メートル?」と首をかしげる人は少なくは無いはず
     
    2007年10月1日 17:16
  • 返事が遅くなりすみません。

     なちゃ さんからの引用

    インターンされた「文字列」と同じ「文字列」を表す、インターンされていないインスタンスというのがありうるため、

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

     

     

    って書いた時点で、"" は既にインターンテーブルに登録されているし、引数に新しいインスタンスを渡しているなんて事もありません。

    2007年10月3日 0:44
  • どうも誤解があるようですが、

     

    私はその都度作られるとは思っていませんが、なちゃんさんと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がインターンされているということではないですよ、というだけのことです。

    そのインスタンスがインターンされているかを確認するには、戻り値が引数と同じインスタンスであることを確認する必要がある、という意味です。

    2007年10月3日 2:01
  • 追加です。

     囚人 さんからの引用

    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かどうかだけでは不完全です。

    2007年10月3日 2:17

  • そのインスタンスがインターンされているかを確認するには、戻り値が引数と同じインスタンスであることを確認する必要がある、という意味です。

    「引数に渡したインスタンスが」という表現を再三しているので、「"" を使うたびにインスタンスを生成している」と考えていらっしゃると思ったのです。すみません。


    「そのインスタンスが」と言ってますが、"" や 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) の部分が False だから、String.Empty はインターンされていないと言いたいのでしょうけど、インターンテーブルに「String.Empty」と「""」の二つが別に登録されているだけです。


    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はインターンされているし、"" はコードに書いていたら、アプリケーション初期化時にインターンされます。

     

    2007年10月3日 4:10

  • IsInternedの戻り値がnullでないことで何を示そうとされているのかが良く分からないんですが、3行目はインターンされていません。

     

    [del]戻り値が null でない事で、3 行目は新しいインスタンスが作られない、という事を示しています。[/del]

     

    じゃない。間違えました。
    定数じゃないので、これを持ち出すと意味が分かりませんでしたね。すみません。



    戻り値が引数と同じインスタンスであることを確認しないと、引数のインスタンスがインターンされているかどうかは分かりません。

    戻り値と引数が同じインスタンスを確認するというか、そもそも、インターンされていたら「新しいインスタンス」が作られないです(定数は)。


    なので、何故、戻り値が null かどうかを確認するだけで不完全なのか分かりません。

    勿論、引数で渡した参照のインスタンスと戻り値で得た参照のインスタンスが別(全く同じ文字列表現)である事はありえるでしょうが、引数渡した文字列がイターンされていないという事はないでしょう?String.Empty と "" のように。

     

    2007年10月3日 4:17
  • ちょと確認ですが、どこまでが想像や推測でどこまでが事実ですか?

    --追記

    すみません、ちょっと書き方があれでしたが、

    上記の、(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でもなくなります。

    2007年10月3日 6:04
  • 検証した結果を考えているだけなので、推測と言えば推測ですし、事実と言えば事実です。
    逆に、なちゃさんの言っている事がはっきり書かれた出所を出してもらえますか?


    結果

    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

     

    2007年10月4日 0:49

  • じゃあ、私が最初に trapemiya さんに言ってた事は間違いで、「String.Empty を使う限りはパフォーマンスが良く、コード中に一度でも "" を使えば、以降 String.Empty も "" もインターンテーブルの空文字表現を使う」が正しいですね。

    じゃなく「String.Empty を普通に使う分には mscorlib.dll のネイティブイメージに固定化されたものを使い、"" はインターンテーブルを使う」です。

     

    2007年10月4日 4:57
  • すみません、

    >ちょと確認ですが、どこまでが想像や推測でどこまでが事実ですか?

    この書き方はよろしくなかったと思っています。私も完全に事実のみ書いているわけではありません。

     

    検証した結果を考えているだけなので、推測と言えば推測ですし、事実と言えば事実です。
    逆に、なちゃさんの言っている事がはっきり書かれた出所を出してもらえますか?

     

    書籍(プログラミング .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は、インターンプールとはまったく関与しない、ということですね。

    なるほどです。

    2007年10月4日 13:58
  • 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";
    }

     

     

     

    大変勉強になりました。ありがとうございます。

    2007年10月5日 5:28
  • 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
    58225482

     

    String.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

     

    と変化がないことからわかると思います。

     

     

     

    2007年10月7日 11:16