string s1 = new string (new char[] { 'H', 'e', 'l', 'l', 'o' });
string i1 = string.Intern(s1);
bool result1 = ReferenceEquals(s1, i1);
string s2 = new string(new char[] { 'H', 'e', 'l', 'l', 'o' });
string i2 = string.Intern(s2);
bool result2 = ReferenceEquals(s2, i2);
bool result3 = ReferenceEquals(i1, i2);
Console.WriteLine(result1); // Should be False
Console.WriteLine(result2); // Should be False
Console.WriteLine(result3); // Should be True
Результат этого небольшого теста: Правда, Ложь, Правда на моем компьютере и этом онлайн-компиляторе , но Ложь, Ложь, Правда в этом онлайн-компиляторе. Я спросил об этом GPT, и он указал на то, что второй результат также верен.
Если я правильно понял, s1 создает в куче новый строковый объект (который автоматически регистрируется в пуле стажеров) и хранит ссылку на этот строковый объект. i1 проверяет ЗНАЧЕНИЕ s1, чтобы узнать, существует ли он в пуле стажеров, и, поскольку это так, i1 берет ссылку из пула стажеров, заставляя s1 и i1 ссылаться на один и тот же строковый объект.
Аналогично, s2 создает в куче еще один строковый объект с тем же значением, что и s1 (хотя я не уверен, добавляется ли он сразу в пул стажеров). i2 проверяет ЗНАЧЕНИЕ s2 во внутреннем пуле, обнаруживает, что этот строковый объект уже создан, и, таким образом, берет ссылку из внутреннего пула. В результате i1 и i2 ссылаются на один и тот же объект и возвращают true.
Буду рад, если кто-нибудь объяснит, как работает стажер и почему результаты различаются? И каков вариант использования этой функции в командной среде?
PS: Я запускаю его в .net 7 в локальном и 8 в онлайн-компиляторах.
@Lennart, о, это потому, что это должен быть C# версии 7 или выше, я думаю, я это исправлю.
Отвечает ли это на ваш вопрос? Метод String.Intern просто добавляет ссылку на строку в пул стажеров или создает копию строки?
Если вы запустите его с помощью NET Framework 4.7.2 в NETFiddle, это также будет False, False, True. Это вопрос версии среды выполнения .NET.
Код, который принят в качестве ответа, является источником моего вопроса, потому что у меня были разные результаты с разными компиляторами в Интернете, и я также хотел знать, есть ли у кого-нибудь необходимость использовать эту неясную функцию. @КлаусГюттер
почему вы не использовали кнопку «Поделиться» во втором примере компилятора? onlinegdb.com/HnCybTQGdo
Отказ от ответственности: я тестировал это только на .NET 8 в режиме отладки. Вывод, который я получаю, — «истина», «ложь», «истина», и это можно объяснить, проследив это в WinDbg.
Поведение следующее: создание строки из char[]
не интернирует строку автоматически. Только при вызове Intern
ссылка добавляется в таблицу стажеров. Когда вы вызываете Intern
, таблица сканируется на наличие совпадений, и если совпадение найдено, возвращается ссылка. В противном случае он добавляется в таблицу, а затем возвращается.
Изначально мы создаем строку из char[]
. Эта строка не интернируется средой выполнения, поскольку не считается строковым литералом. Перечислив местных жителей, я получаю следующее:
LOCALS:
0x000000B35897E858 = 0x000002a4c0440708 <- this is the ref for our string
Выполнив gcroot
, я вижу, что он коренится только по локальной ссылке. Т.е. это не интернировано.
0:000> !gcroot 000002a4c0440708
Caching GC roots, this may take a while.
Subsequent runs of this command will be faster.
Thread 49b4:
rbp+50: 000000b35897e810
-> 02a4c0440708 System.String
rbp+98: 000000b35897e858
-> 02a4c0440708 System.String
Затем мы интернируем эту строку и возвращаем ссылку на интернированную строку. Отладчик показывает, что оба локальных адреса указывают на один и тот же адрес:
LOCALS:
0x000000B35897E858 = 0x000002a4c0440708
0x000000B35897E850 = 0x000002a4c0440708
gcroot
теперь указывает дополнительный корень.
0:000> !gcroot 000002a4c0440708
HandleTable:
000002a4bbf712d0 (strong handle)
-> 02a4bdc07ff0 System.Object[]
-> 02a4c0440708 System.String
Прочная ручка – стол для стажеров. Это означает, что ссылка выше была добавлена в статический массив, который является внутренней таблицей. Однако он не меняет ссылку, а просто добавляет ее, так что теперь строка также коренится в таблице стажеров.
Это соответствует выводу эталонного сравнения.
Далее мы генерируем новую строку. Поскольку это обычная строка, она размещается в куче и получает другой адрес.
PARAMETERS:
args (0x000000B35897E880) = 0x000002a4bfc08308
LOCALS:
0x000000B35897E858 = 0x000002a4c0440708
0x000000B35897E850 = 0x000002a4c0440708
0x000000B35897E84C = 0x0000000000000001
0x000000B35897E840 = 0x000002a4c0440790 <--- this is s2
Я аннотировал дамп выше, чтобы показать s2
. (третий адрес содержит логическое значение первого сравнения, т. е. result1
.
Далее мы пытаемся интернировать эту строку. При этом строки во внутренней таблице сравниваются, и мы обнаруживаем, что у нас уже есть ссылка (первая строка), поэтому второй вызов Intern
возвращает эту ссылку, как видно из локальных значений.
PARAMETERS:
args (0x000000B35897E880) = 0x000002a4bfc08308
LOCALS:
0x000000B35897E858 = 0x000002a4c0440708
0x000000B35897E850 = 0x000002a4c0440708
0x000000B35897E84C = 0x0000000000000001
0x000000B35897E840 = 0x000002a4c0440790
0x000000B35897E838 = 0x000002a4c0440708
Это означает, что адреса s2
и i2
различаются, что соответствует выводу false.
Я не могу сказать, почему вы видите другой результат в онлайн-компиляторе. Возможно, они используют другую среду выполнения, но результат соответствует тому, что я ожидал.
Спасибо за подробное объяснение темы. Сталкивались ли вы когда-нибудь с этой функцией в производственной среде или чувствовали необходимость ее использовать?
@FurkanBilal Добро пожаловать. Все литеральные строки по умолчанию интернированы, поэтому в этом смысле все используют его, но поскольку нет возможности удалить интернированные строки, делать это для динамически генерируемых строк редко бывает хорошей идеей.
Вторая ссылка («onlinegdb») даже не позволяет мне скомпилировать пример, поскольку она, похоже, не понимает новые выражения целевого типа.