понедельник, 13 апреля 2009 г.

Строки в C# и внутренний пул

Я не знаю, почему это людей так беспокоит вопрос о том, где формируются строки с C# и так радует ответ, что они формируются во внутреннем пуле(Intern pool). Но, видимо это очень важно, так как такие вопросы присутствуют в большинстве статей по подготовки к собеседованию по C# и такой вопрос, я сам на него отвечал, есть в тесте BrainBench.

Мне, честно говоря, было пофигу, где эти строки формируются и хранятся, главное, что это все работает. Я воспринимал это как аксиому, которую, точнее, нужно сформулировать так: «Все строковые литералы компилятор C# помещает во внутренний пул».

Но, когда я писал код, похожий на следующий код, то немного призадумался:

string name = textReader.ReadLine();
switch (name)
{
case "Bob":
Console.WriteLine("Hello, Bob!");
break;
case "Vasya":
Console.WriteLine("Privet, Vasya!");
break;
default:
Console.WriteLine("O_o Vasya!");
break;
}


Ведь в переменной name содержится строка, прочитанная из файла в момент выполнения, так как же эти строковые кейсы работают? Они же, по идеи, должны сравнивать ссылки. А чтобы моя переменная name совпала хотя бы с одним этим кейсом, то она тоже должна быть интернирована (помещена в пул), как и строковые литералы.

Оказывается, что да. И в каком месте это происходит, я пока еще не знаю. Но, строковые литералы помещаются в пул в момент компиляции, а новые, динамические строки взаимодействуют с этим пулом в момент выполнения. Получается, что каждая новая строка сравнивается со всеми уже существующими, что может влиять на производительность кода.

Я наивно пытался похачить это дело, но все-таки ничего не получилось:

using (TextReader tr = File.OpenText(@"C:\temp\strings.txt "))
{
string string1 = tr.ReadLine(); // This is a string
string string2 = tr.ReadLine(); // This is a string
string string3 = tr.ReadLine(); // This is another string

Console.WriteLine("First try:");
Console.WriteLine(" string1={0}\n string2={1}\n string3={2}", string1.GetHashCode(),
string2.GetHashCode(),
string3.GetHashCode());
StringBuilder sb = new StringBuilder();
string internPool = "Intern Pool";
sb.Append("Intern ").Append("Pool");
Console.WriteLine("Second try:\n string1={0}\n string2={1}", internPool.GetHashCode(),
sb.GetHashCode());

Console.WriteLine("Third try:\n string1={0}\n string2={1}", internPool.GetHashCode(),
sb.ToString().GetHashCode());

}


Текстовый файл:

This is a string
This is a string
This is another string


Вывод первой попытки, как не странно string1 и string2 равны:
First try:
string1=2035671784
string2=2035671784
string3=164170943


Вывод второй попытки! Ага все таки не равны! Но, на самом деле и типы тут разные:
Second try:
string1=-1024490162
string2=58225482


Вывод третий попытки. Приведем-ка это это добро .ToString. :( Все таки ссылки равны:
Third try:
string1=-1024490162
string2=-1024490162



Upd. Вот что значит дописывать код в 2 часа ночи :) На самом деле, ссылки не равны, а .GetHashCode это не правильный способ. Спасибо, Alex, за комментарий.

Если добавить следующие строки кода, то выясняется, что ссылки действительно не равны:

Console.WriteLine("Ref Eq string1 and string2 = {0}", object.ReferenceEquals(string1, string2));
Console.WriteLine("Ref Eq string2 and string3 = {0}", object.ReferenceEquals(string2, string3));
// и...
Console.WriteLine("Ref Eq internPool and sb.ToString() = {0}", object.ReferenceEquals(internPool, sb.ToString()));


Результат:

Ref Eq string1 and string2 = False
Ref Eq string2 and string3 = False

Ref Eq internPool and sb.ToString() = False

2 коммент.:

Shpuk комментирует...

[q]Ведь в переменной name содержится строка, прочитанная из файла в момент выполнения, так как же эти строковые кейсы работают? Они же, по идеи, должны сравнивать ссылки[/q]
Операция == для класса String сравнивает не ссылки, а значения!!!! Собственно это переопределенная операция. А вообще для любого объекта есть 1 метод ReferenceEquals, 2 метода Equals (по сути идентичные), и операция ==.
Во всех твоих примерах ты сравниваешь не ссылки а GetHashCode(). Эта операция тоже переопределена для String, так что hash берется не от ссылки, а от значения и когда хэши равны - это означает, что равны их значения (ссылки не обязательно равны. В твоем случае они все разные!!!).

[q]Но, строковые литералы помещаются в пул в момент компиляции, а новые, динамические строки взаимодействуют с этим пулом в момент выполнения.[/q]
В момент компиляции строковые литералы в пул не помещаются... Во время сравнения строк происходит упаковка (boxing) литералов в ссылочный тип, происходит сравнение двух ссылочных типов, а после сравнения, этот упакованный объект уничтожается сборщиком мусора.

DmytroZ комментирует...

Спасибо, Alex, все оказывается действительно так, как вы говорите. Вы спасли мой Mosk ;)

Отправить комментарий

 

.NET ate my MOSK;. Powered By Blogger © 2009 Bombeli | Theme Design: ooruc