Страницы

Поиск по вопросам

воскресенье, 8 декабря 2019 г.

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

#c_sharp #net #строки #equals


коллеги! 

хотела бы спросить вопросы, которые у меня возникли при более подробном изучении
языка .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


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


Ответы

Ответ 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". Это почти бессмысленно для коротких строк, но может оказаться выгодно для длинных долгоживущих строк. Также нужно учитывать, что удалить строку из пула невозможно. Она так и будет висеть в памяти до конца работы приложения. Именно поэтому в рантайме строки не добавляются в пул по умолчанию.

Ответ 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

Комментариев нет:

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