String.Internは文字列をさらに面癜くしたす

翻蚳者からの序文



むンタビュヌに合栌/実斜するず、.NETがどのように機胜するかに぀いおの䞀般的な理解を明らかにする質問に出䌚いたす。 私の意芋では、「ガベヌゞコレクタヌ」の仕事に぀いおの質問はそのような質問の䞭で最も愛されおいたすが、䞀床ストリングむンタヌンに぀いお質問されたこずがありたす。 そしお圌は正盎に私を行き止たりにした。 Runetでの怜玢ではいく぀かの蚘事が返されたしたが、私が探しおいた質問に察する回答はありたせんでした。 Andrew Stellman 「Head First C」の著者 による蚘事の翻蚳がこのギャップを埋めるこずを願っおいたす。 この資料は、初心者、.NET開発者、および.NETの文字列の抑留ずは䜕かに興味を持぀人にずっお有甚だず思いたす。



String.Internは文字列をさらに面癜くしたす



すべおのC初心者開発者が最初に遭遇するこずの1぀は、文字列の操䜜です。 他のほずんどすべおのC本で行われおいるように、「Head First C」の冒頭で文字列を操䜜する基本を瀺したす。 したがっお、Cのゞュニアおよびミドルレベルの開発者が、かなり良いラむンごずのベヌスを手に入れたず感じるのは驚くべきこずではありたせん。 しかし、線は芋た目よりも興味深いものです。 Cおよび.NETの文字列の最も興味深い偎面の1぀は、String.Internメ゜ッドです。 この方法の仕組みを理解するず、C開発のスキルを向䞊させるこずができたす。 この投皿では、String.Internメ゜ッドの簡単なチュヌトリアルを䜜成しお、その仕組みを説明したす。



泚この投皿の最埌で、ILDasmを䜿甚しお「内郚」を衚瀺したす。 ILDasmを䜿甚したこずがない堎合、これは非垞に䟿利な.NETツヌルを知る良い機䌚になりたす。



文字列の操䜜の基本


System.Stringクラスが期埅するものの簡単な抂芁から始めたしょう。 詳现に぀いおは觊れたせん。誰かが.NETの文字列の基本に぀いお投皿したい堎合は、コメントを远加するか、 Building Better Softwareに連絡しおください。可胜な蚘事に぀いお䞀緒に話し合うこずができたす



Visual Studioで新しいコン゜ヌルアプリケヌションを䜜成したす。 csc.exeを䜿甚しおコヌドをコンパむルする堎合は、コマンドラむンからすべお同じように機胜したすが、マテリアルの認識を容易にするために、Visual Studioでの開発に固執したしょう。次に、 Mainメ゜ッドのコヌドを瀺したす-コン゜ヌルアプリケヌションの゚ントリポむント



Program.cs



using System; class Program { static void Main(string[] args) { string a = "hello world"; string b = a; a = "hello"; Console.WriteLine("{0}, {1}", a, b); Console.WriteLine(a == b); Console.WriteLine(object.ReferenceEquals(a, b)); } }
      
      





このコヌドに驚きはないはずです。 プログラムはコン゜ヌルに3行を衚瀺したすVisual Studioで䜜業しおいる堎合は、Ctrl-F5を䜿甚しおデバッガヌの倖郚でプログラムを実行したす。たた、コン゜ヌルりィンドりが閉じないように、 「Press any key ...」がプログラムに远加されたす



こんにちは、こんにちは

停

停



最初のWriteLineは 2行を出力したす。 2番目は、文字列が䞀臎しないためFalseを返す等倀挔算子==を䜿甚しおそれらを比范したす。 埌者はそれらを比范しお、䞡方の倉数が同じStringオブゞェクトを参照しおいるかどうかを確認したす。 そうではないため、メ゜ッドは倀Falseを衚瀺したす。

次に、これらの2行をMainメ゜ッドの最埌に远加したす。



  Console.WriteLine((a + " world") == b); Console.WriteLine(object.ReferenceEquals((a + " world"), b));
      
      





そしお再び、あなたはかなり明癜な答えを埗る。 䞡方の文字列が等しいため、等倀挔算子はTrueを返したす。 ただし、「Hello」および「world」文字列の連結を䜿甚した堎合、 +挔算子はそれらを連結し、 System.Stringの新しいむンスタンスを返したす 。 そのため、 object.ReferenceEqualsはかなり合理的にFalseを返したす。 ReferenceEqualsメ゜ッドは、䞡方の匕数が同じオブゞェクトを参照する堎合にのみTrueを返したす。

このメ゜ッドを䜿甚するず、オブゞェクトを通垞どおり操䜜できたす。 2぀の異なるオブゞェクトに同じ倀を蚭定できたす。 この動䜜は非垞に実甚的で予枬可胜です。 2぀の「ホヌム」オブゞェクトを䜜成し、すべおのプロパティを同じ倀に蚭定するず、2぀の同䞀の「ホヌム」オブゞェクトが䜜成されたすが、これらは異なるオブゞェクトになりたす。



これはただ少し混乱しおいるように芋えたすか もしそうなら、私は間違いなく“ Head First C”の最初のいく぀かの章に泚意を払うこずをお勧めしたす。 この本から無料の切り抜きずしおダりンロヌドできたす。

したがっお、文字列を䜿甚しおいる間は、すべお問題ありたせん。 しかし、行参照で遊んでいるずすぐに、少し奇劙になりたす。



このリンクに問題がありたす...


新しいコン゜ヌルアプリケヌションを䜜成したす。 以䞋のコヌドは圌のためです。 しかし、コンパむルしお実行する前に、コヌドを泚意深く芋おください。 コン゜ヌルに衚瀺されるものを掚枬しおみおください



Program.cs



 using System; class Program { static void Main(string[] args) { string hello = "hello"; string helloWorld = "hello world"; string helloWorld2 = hello + " world"; Console.WriteLine("{0}, {1}: {2}, {3}", helloWorld, helloWorld2, helloWorld == helloWorld2, object.ReferenceEquals(helloWorld, helloWorld2)); } }
      
      





次にプログラムを実行したす。 コン゜ヌルに衚瀺されるものは次のずおりです。



こんにちは䞖界、こんにちは䞖界True、False



そしお、これはたさに私たちが期埅したものです。 helloWorldおよびhelloWorld2オブゞェクトでは、文字列に「Hello world」が含たれおいるため、これらは同じですが、リンクは異なりたす。

次に、このコヌドをプログラムの䞋郚に远加したす。



  helloWorld2 = "hello world"; Console.WriteLine("{0}, {1}: {2}, {3}", helloWorld, helloWorld2, helloWorld == helloWorld2, object.ReferenceEquals(helloWorld, helloWorld2));
      
      





それを実行したす。 今回、コヌドはコン゜ヌルに次の行を衚瀺したす。



こんにちは䞖界、こんにちは䞖界真実、真実



埅っお、 HelloWorldずHelloWorld2が同じ行を参照しおいるこずがわかりたしたか おそらく、この振る舞いが奇劙であるか、少なくずも少し予想倖であるず感じるかもしれたせん。 helloWorld2の倀はたったく倉曎したせんでした。 倚くの人は、このようなこずを考えおしたいたす。「倉数は、すでにhello worldず同等でした。 Hello Worldにもう䞀床むンストヌルしおも䜕も倉わらないはずです。 それを理解したしょう。



String.Internずは䜕ですか 収容プヌルに飛び蟌む...


Cで文字列を䜿甚する堎合、CLRはトリッキヌなこずを行いたす。これは、文字列むンタヌンず呌ばれるものです。 これは、任意の文字列のコピヌを1぀保存する方法です。 100個、さらに悪い堎合は100䞇個の文字列倉数に栌玍する堎合、同じ倀を䜿甚するず、文字列倀を栌玍するためのメモリが䜕床も割り圓おられたす。 文字列むンタヌンは、この問題を回避する方法です。 CLRは、収容プヌルず呌ばれるテヌブルをサポヌトしたす。 このテヌブルには、プログラムの実行䞭にプログラムで宣蚀たたは䜜成された各行ぞの䞀意のリンクが1぀含たれおいたす。 .NET Frameworkは、むンタヌンプヌルず察話するための2぀の䟿利なメ゜ッドを提䟛したす String.InternおよびString.IsInterned 。



String.Internメ゜ッドは非垞に簡単な方法で機胜したす。 匕数ずしお文字列を枡したす。 この行が既に抑留プヌルにある堎合、メ゜ッドはこの行ぞのリンクを返したす。 ただ存圚しない堎合は、プヌルに行を远加し、そのリンクを返したす。 以䞋に䟋を瀺したす。



  Console.WriteLine(object.ReferenceEquals( String.Intern(helloWorld), String.Intern(helloWorld2)));
      
      





HelloWorldずHelloWorld2が2぀の異なる文字列オブゞェクトを参照しおいる堎合でも、このコヌドはTrueを衚瀺したす。これらは䞡方ずも文字列「Hello World」を含むためです。

しばらく停止したす。 String.Internを䜿甚しお少し理解する䟡倀がありたす。これは、このメ゜ッドが䞀芋わずかに非論理的な結果をもたらす堎合があるためです。 この動䜜の䟋を次に瀺したす。



  string a = new string(new char[] {'a', 'b', 'c'}); object o = String.Copy(a); Console.WriteLine(object.ReferenceEquals(o, a)); String.Intern(o.ToString()); Console.WriteLine(object.ReferenceEquals(o, String.Intern(a)));
      
      





コヌドを実行するず、コン゜ヌルに2行が出力されたす。 最初のWriteLineメ゜ッドはFalseを衚瀺したすが、これはString.Copyメ゜ッドが文字列の新しいコピヌを䜜成し、新しいオブゞェクトぞのリンクを返すため理解できたす。 しかし、最初にString.Internabout.ToStringを実行しおからString.Internaがaboutぞのリンクを返すのはなぜですか それに぀いお考えるために少し立ち止たっおください。 さらに3行远加するず、これはさらに盎感に反したす。



  object o2 = String.Copy(a); String.Intern(o2.ToString()); Console.WriteLine(object.ReferenceEquals(o2, String.Intern(a)));
      
      





これらのコヌド行は、新しいo2オブゞェクト倉数でのみ同じように芋えたす。 しかし、最埌に、 WriteLineはFalseを出力したす。 それで䜕が起こっおいるのでしょうか



この小さな混乱は、 String.Internず収容プヌルの内郚で䜕が起こっおいるかを把握するのに圹立ちたす。 自分で理解する最初のこずは、 ToStringの文字列オブゞェクトメ゜ッドが垞に自身ぞの参照を返すこずです。 倉数oは、倀 "abc"を含む文字列オブゞェクトを指しおいるため、独自のToStringメ゜ッドを呌び出すず、この文字列ぞのリンクが返されたす。 だからここで䜕が起こるかです。

最初は、「abc」を含む行番号1のオブゞェクトを指したす。 倉数oは、「abc」も含む行番号2の別のオブゞェクトを指したす。 String.Interno.ToStringを呌び出すず、行番号2ぞの参照がむンタヌンプヌルに远加されたす。 これで、行番号2のオブゞェクトが収容プヌルにある堎合、い぀でもパラメヌタヌ「abc」で呌び出したString.Internは行番号2のオブゞェクトぞの参照を返したす。

したがっお、倉数oずString.InternaをReferenceEqualsメ゜ッドに枡すず、 String.Internaは行番号2のオブゞェクトぞの参照を返したため、 Trueを返したす。 ここで、新しい倉数o2を䜜成し、 String.Copyメ゜ッドを䜿甚しお別のStringオブゞェクトを䜜成したした。 これは、行番号3のオブゞェクトになり、文字列「abc」も含たれたす。 String.Interno2.ToStringの呌び出しは、「abc」がすでに存圚するため、今回は収容プヌルに䜕も远加したせんが、行番号2ぞのポむンタヌを返したす。

したがっお、このInternの呌び出しは実際には行番号2ぞの参照を返したすが、倉数に割り圓おる代わりに砎棄したす。 次のようなこずができたす string q = String.Interno2.ToString 。これにより、 q倉数が行2のオブゞェクトぞの参照になりたす。 これが、最埌のWriteLineが Falseを出力する理由です。これは、3行目のリンクず2行目のリンクの比范であるためです。



String.IsInternedを䜿甚しお、文字列がむンタヌンプヌルにあるかどうかを確認したす


もう少し逆説的な名前のメ゜ッドがありたす。これは、むンタヌンされた文字列を操䜜するずきに䟿利です String.IsInterned 。 文字列オブゞェクトぞの参照を取りたす。 この文字列が収容プヌルにある堎合、収容された文字列の文字列ぞの参照を返したす;収容プヌルにただない堎合、メ゜ッドはnullを返したす 。

名前が少し非論理的に聞こえる理由は、このメ゜ッドは「Is」で始たるが、倚くのプログラマが期埅するようにブヌル型を返さないためです。

IsInternedメ゜ッドを䜿甚する堎合、null合䜓挔算子- ??を䜿甚しお、文字列が収容プヌルにないこずを衚瀺するず䟿利です。 。 たずえば、次のように曞く



  string o = String.IsInterned(str) ?? "not interned";
      
      





珟圚、 IsInternedの結果は、れロでない堎合は倉数oに戻り、収容プヌルに行がない堎合はストリング「not interned」に戻りたす。

これが行われない堎合、 Console.WriteLineメ゜ッドは空の行を出力したす nullが怜出されるずこのメ゜ッドが行いたす 。

String.IsInternedの仕組みの簡単な䟋を次に瀺したす。



  string s = new string(new char[] {'x', 'y', 'z'}); Console.WriteLine(String.IsInterned(s) ?? "not interned"); String.Intern(s); Console.WriteLine(String.IsInterned(s) ?? "not interned"); Console.WriteLine(object.ReferenceEquals( String.IsInterned(new string(new char[] { 'x', 'y', 'z' })), s));
      
      





最初のWriteLineステヌトメントは、「xyz」がむンタヌン メントプヌルにただないため、コン゜ヌルに「not interned 」ず衚瀺されたす。 2番目のWriteLineステヌトメントは、 むンタヌンメントプヌルに既に「xyz」が含たれおいるため、「xyz」を出力したす。 たた、3番目のWriteLineは、オブゞェクトsが収容プヌルに远加されたオブゞェクトを指すため、 Trueを出力したす。



リテラルむンタヌンを自動的に


メ゜ッドの最埌に1行だけ远加しお、プログラムを再床実行するこずにより



  onsole.WriteLine(object.ReferenceEquals("xyz", ));
      
      





完党に予期しないこずが起こりたす

プログラムは「むンタヌンしない」ず衚瀺するこずはなく、最埌の2぀のWriteLineメ゜ッドはFalseを衚瀺したす 最埌の行をコメントアりトするず、プログラムは期埅どおりに動䜜したす。 なぜ プログラムの最埌にコヌドを远加するず、その䞊のコヌドプログラムの動䜜がどのように倉わりたしたか これは非垞に奇劙です



これに初めお出䌚ったずきは本圓に奇劙に思えたすが、本圓に理にかなっおいたす。 プログラム党䜓の動䜜を倉曎する理由は、コヌドにリテラル「xyz」が含たれおいるためです。 そしお、プログラムにリテラルを远加するず、CLRはプログラムが開始する前であっおも、それを自動的にむンタヌンプヌルに远加したす。 この行にコメントしお、プログラムからリテラルを削陀するず、収容プヌルには文字列「xyz」が含たれなくなりたす。

「xyz」はプログラムの起動時に収容プヌルに既にあるこずを理解したす。この文字列はコヌド内でリテラルずしお衚瀺されるため、プログラムの動䜜のこのような倉曎はすぐに明らかになりたす。 String.IsInternedsはnullを返さなくなりたした。 代わりに、リテラル「xyz」ぞの参照を返したす。これは、 ReferenceEqualsがFalseを返す理由も説明したす。 これは、文字列sが収容プヌルに远加されないためです「xyz」はすでにプヌル内にあり、別のオブゞェクトを指しおいる。



コンパむラはあなたが思っおいるよりも賢いです


コヌドの最終行を次のように倉曎したす。



  Console.WriteLine( object.ReferenceEquals("x" + "y" + "z", s));
      
      





プログラムを実行したす。 リテラル「xyz」を䜿甚しおいるかのように機胜したす。 +は挔算子ではありたせんか これは、実行時にCLRで実行されるメ゜ッドではありたせんか その堎合、リテラル「xyz」の内圚化を防ぐコヌドが必芁です。

実際、これは"x" + "y" + "z"をString.Format "{0} {1} {2}"、 'x'、 'y'、 'z'に眮き換えるず起こりたす。 コヌドの䞡方の行は「xyz」を返したす。 なぜ、 +挔算子を連結しお、文字列 "xyz"を䜿甚しおいるように振る舞うのか、実行時にString.Formatが実行されるのはなぜですか

この質問に答える最も簡単な方法は、コヌド“ x” +“ y” +“ z”をコンパむルしたずきに実際に埗られるものを確認するこずです。



Program.cs



 using System; class Program { public static void Main() { Console.WriteLine("x" + "y" + "z"); } }
      
      





次のステップは、コンパむラヌが実行可胜なタむプのアプリケヌションをコンパむルしたこずを確認するこずです。 このために、MSIL逆アセンブラヌであるILDasm.exeを䜿甚したす。 このツヌルは、Visual StudioのすべおのバヌゞョンExpress゚ディションを含むず共にむンストヌルされたす。 たた、ILの読み方がわからなくおも、䜕が起こっおいるのかを理解できたす。



Ildasm.exeを実行したす。 64ビットバヌゞョンのWindowsを䜿甚しおいる堎合、次のコマンドを実行したす。 "ProgramFilesx86\ Microsoft SDKs \ Windows \ v7.0A \ Bin \ Ildasm.exe" 匕甚笊を含む、 [スタヌト] >> [実行]りィンドりから 、たたはコマンドラむンから。 32ビットバヌゞョンのWindowsを䜿甚しおいる堎合は、 「ProgramFiles\ Microsoft SDKs \ Windows \ v7.0A \ Bin \ ildasm.exe」コマンドを実行する必芁がありたす。



.NET Framework 3.5以前を䜿甚しおいる堎合
.NET Framework 3.5以前を䜿甚しおいる堎合、隣接するフォルダヌでildasm.exeを探す必芁がある堎合がありたす。 Explorerりィンドりを起動し、Program Filesフォルダヌに移動したす。 原則ずしお、目的のプログラムは「Microsoft SDKs \ Windows \ vX.X \ bin」フォルダヌにありたす。 さらに、[スタヌト]メニュヌにある[Visual Studioコマンドプロンプト]からコマンドラむンを実行し、「ILDASM」ず入力しお起動できたす。




これは、ILDasmの初回実行時の倖芳です。







次に、コヌドを実行可胜ファむルにコンパむルしたす。 ゜リュヌション゚クスプロヌラヌでプロゞェクトをクリックしたす。[ プロゞェクトフォルダヌ]フィヌルドは[ プロパティ]りィンドりにありたす。 それをダブルクリックしおコピヌしたす。 ILDasmりィンドりに移動しお、メニュヌで[ ファむル] >> [開く ]を遞択し、フォルダヌぞのパスを貌り付けたす。 次に、binフォルダヌに移動したす。 実行可胜ファむルは、 bin \ Debugたたはbin \ Releaseフォルダのいずれかになければなりたせん。 実行可胜ファむルを開きたす。 ILDasmはアセンブリの内容を衚瀺する必芁がありたす。







アセンブリの䜜成方法をブラッシュアップする必芁がある堎合は、 Cおよび.NETアセンブリず名前空間の理解に぀いおこの投皿を参照しおください。

Programクラスを展開し、 Mainメ゜ッドをダブルクリックしたす。 これらの手順の埌、逆アセンブルされたメ゜ッドコヌドが衚瀺されたす。







コヌド内のリテラル「xyz」の存圚を確認するためにILを知る必芁はありたせん。 ILDasmを閉じおから、「x」+「y」+「z」の代わりに「xyz」を䜿甚するようにコヌドを倉曎するず、ILコヌドは完党に同じように解析されたす。 これは、コンパむラがコンパむル時に「x」+「y」+「z」を「xyz」に眮き換えるのに十分なため、「xyz」が垞に返すメ゜ッド呌び出しに䜙分な操䜜を費やす必芁がないためです。 そしお、リテラルがプログラムでコンパむルされるず、CLRはプログラムの起動時にそれを収容プヌルに远加したす。



この蚘事の資料は、Cおよび.NETでのストリングむンタヌンに぀いおの良いアむデアを提䟛するものです。 原則ずしお、これはストリングむンタヌンの操䜜を理解するために必芁以䞊です。 さらに詳しく知りたい堎合は、MSDNのString.Internペヌゞのパフォヌマンスに関する考慮事項のセクションを参照しおください 。



PS翻蚳に察する厳しい校正ず客芳的な批刀に感謝したす。



All Articles