Как работает интернирование строк

232
29 ноября 2018, 07:40

коллеги!

хотела бы спросить вопросы, которые у меня возникли при более подробном изучении языка .NET и трудности касаются как минимум ссылочного типа string

1) Intern pool; (пулСтрок) Позволяет объединить строки с одинаковыми значениями в них в определенный пул памяти. В данном случае мы инициализировали две строки с одинаковыми значениями и они попали в один пул памяти. пример в моем понимании:

string a = "aaa";
string b = "aaa";
bool c = (object)a == (object) b; // И получаем true 

ОДНАКО код:

string a = "aaa"; 
string b = "aa"; 
b+="a"; 
    bool c = (object)a == (object) b; // И получаем false

Почему так? и как это работает? по сути мы получаем строку с одинаковыми значениями и все они должны ссылаться на один объект. почему мы по итогу получаем разные результаты? Я понимаю, что в данном случае мы сравниваем объекты, а не содержимое. Но почему они не объединились?

Answer 1

В первом вашем примере строки интернируются на этапе компиляции. Если посмотреть в утилите ILDASM, то в окне MetaInfo в разделе User Strings будет представлен всего один экземпляр:

70000001 : ( 3) L"aaa"

Соответственно, обе переменные: и a, и b будут указывать на этот адрес.

Во втором примере в результате конкатенации тоже получается строка "aaa", но она не заносится по умолчанию в пул интернированных строк, потому что для этого нужны дополнительные проверки, то есть тратится время.

Во втором примере можно добавить строку в пул вручную:

string a = "aaa";
string b = "aa";
b += "a";
b = string.Intern(b);
bool c = (object)a == (object)b; // True

Ответ на вопрос из комментария:

Я не уверен, но, думаю, выгода в том, что после интернирования переменная b станет указывать на тот же участок памяти, что и a. После чего сборщик мусора сможет убрать другой экземпляр строки "aaa". Это почти бессмысленно для коротких строк, но может оказаться выгодно для длинных долгоживущих строк.

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

Answer 2

Чтобы не гадать, глянем сразу IL-инструкции которые генерирует компилятор:

IL_0000:  nop         
IL_0001:  ldstr       "aaa"
IL_0006:  stloc.0     // a
IL_0007:  ldstr       "aaa"
IL_000C:  stloc.1     // b
IL_000D:  ldloc.0     // a
IL_000E:  ldloc.1     // b
IL_000F:  ceq         
IL_0011:  stloc.2     // c
IL_0012:  ldstr       "aaa"
IL_0017:  stloc.3     // a2
IL_0018:  ldstr       "aa"
IL_001D:  stloc.s     04 // b2
IL_001F:  ldloc.s     04 // b2
IL_0021:  ldstr       "a"
IL_0026:  call        System.String.Concat
IL_002B:  stloc.s     04 // b2
IL_002D:  ldloc.3     // a2
IL_002E:  ldloc.s     04 // b2
IL_0030:  ceq         
IL_0032:  stloc.s     05 // c2
IL_0034:  ret    

Как видим в данном случае в первом блоке мы записали одну и ту же строку в a(IL_0006) и b(IL_000C), и в данном случае строка является интернированной, во втором же блоке только 'a2'(IL_0017) является интернированной, b2 после конкатенации (IL_0026) и присваивания (IL_002B) таковой не является.

UPD: Следующий код также возвращает false:

Console.WriteLine(ReferenceEquals(b2, a2));

Метод ReferenceEquals определяет, совпадают ли указанные экземпляры Object. При сравнении строк. Если objA и objB являются строками, ReferenceEquals возвращает true если строки интернированы. Он не выполняет проверку на равенство значений. В следующем примере s1 и s2 равны, поскольку они являются двумя экземплярами одной интернированной строки. Тем не менее s3 и s4 не равны, поскольку несмотря на то, что они имеют идентичные строковые значения, эти строки не интернированы.

using System;
public class Example
{
   public static void Main()
   {
      String s1 = "String1";
      String s2 = "String1";
      Console.WriteLine("s1 = s2: {0}", Object.ReferenceEquals(s1, s2));
      Console.WriteLine("{0} interned: {1}", s1, 
                        String.IsNullOrEmpty(String.IsInterned(s1)) ? "No" : "Yes");
      String suffix = "A";
      String s3 = "String" + suffix;
      String s4 = "String" + suffix;
      Console.WriteLine("s3 = s4: {0}", Object.ReferenceEquals(s3, s4));
      Console.WriteLine("{0} interned: {1}", s3, 
                        String.IsNullOrEmpty(String.IsInterned(s3)) ? "No" : "Yes");
   }
}
// The example displays the following output:
//       s1 = s2: True
//       String1 interned: Yes
//       s3 = s4: False
//       StringA interned: No

P.s.: Метод также возвратит false при сравнении типов значений, если objA и objB являются типами значений, упакованы перед передачей в ReferenceEquals и представляют одно и то же значение.

int int1 = 3;
Console.WriteLine(Object.ReferenceEquals(int1, int1));    //False
Console.WriteLine(int1.GetType().IsValueType);            //True
READ ALSO
Linq Expressions параметризация уже существующего Expression

Linq Expressions параметризация уже существующего Expression

Пытаюсь преобразовать один Expression<Func<double, double>> в другой, однако при попытке компиляции полученного выражения, появляется ошибка

164
Использование Cookie при работе с HtmlAgilityPack

Использование Cookie при работе с HtmlAgilityPack

С помощью SimpleBrowser заполняю поля логина и пароля и имитирую нажатие кнопки, после чего происходит вход на сайтДалее из объекта извлекаю файлы...

153
Выполнить void в get/set

Выполнить void в get/set

Посоветуйте как без создания дополнительных переменных реализовать такое:

158
Не видит функцию С# Unity

Не видит функцию С# Unity

Я создал скрипт C# в котором есть несколько функций и функция ChooseResolution()Прицепил его на пустой объект UIManager, который засунул в поле для объектов...

165