Почему я никогда не должен использовать небезопасный блок для изменения строки?
У меня есть строка, которую я хотел бы изменить каким-либо образом. Например: переверните это или поставьте на нет.
Я обнаружил, что самый быстрый способ сделать это - использовать небезопасный блок и указатели.
Например:
unsafe
{
fixed (char* str = text)
{
*str = 'X';
}
}
Есть ли причины, по которым я никогда не должен делать это?
5 ответов
Платформа.Net требует, чтобы строки были неизменяемыми. Благодаря этому требованию он может оптимизировать все виды операций.
Строковое интернирование является одним из ярких примеров этого требования. Для ускорения некоторых сравнений строк (и уменьшения потребления памяти).Net Framework поддерживает словарь указателей, все предопределенные строки будут жить в этом словаре или любых строках, где вы вызываете String.intern
метод на. Когда вызывается инструкция IL ldstr, она проверяет встроенный словарь и избегает выделения памяти, если у нас уже есть выделенная строка, обратите внимание: String.Concat не будет проверять интернированные строки.
Это свойство.net framework означает, что если вы начнете перелистывать строки напрямую, вы можете испортить вашу таблицу-интернат и, в свою очередь, испортить другие ссылки на эту же строку.
Например:
// these strings get interned
string hello = "hello";
string hello2 = "hello";
string helloworld, helloworld2;
helloworld = hello;
helloworld += " world";
helloworld2 = hello;
helloworld2 += " world";
unsafe
{
// very bad, this changes an interned string which affects
// all app domains.
fixed (char* str = hello2)
{
*str = 'X';
}
fixed (char* str = helloworld2)
{
*str = 'X';
}
}
Console.WriteLine("hello = {0} , hello2 = {1}", hello, hello2);
// output: hello = Xello , hello2 = Xello
Console.WriteLine("helloworld = {0} , helloworld2 = {1}", helloworld, helloworld2);
// output : helloworld = hello world , helloworld2 = Xello world
Есть ли причины, по которым я никогда не должен делать это?
Да, очень просто: потому что.NET опирается на тот факт, что строки являются неизменяемыми. Некоторые операции (например, s.SubString(0, s.Length)
) на самом деле вернуть ссылку на исходную строку. Если это теперь будет изменено, все другие ссылки будут также.
Лучше использовать StringBuilder
изменить строку, так как это способ по умолчанию.
Скажем так: как бы вы себя чувствовали, если бы другой программист решил заменить 0 на 1 везде в вашем коде во время выполнения? Это сыграло бы в ад со всеми вашими предположениями. То же самое относится и к строкам. Все ожидают, что они будут неизменными, и кодируют с этим предположением. Если вы нарушите это, вы, вероятно, внесете ошибки - и их будет действительно трудно отследить.
О, Господи, да.
1) Потому что этот класс не предназначен для подделки.
2) Поскольку строки спроектированы и ожидаются во всей структуре, будут неизменными. Это означает, что код, который пишут все остальные (включая MSFT), ожидает, что базовое значение строки никогда не изменится.
3) Потому что это преждевременная оптимизация, а это E V I L.
Договорились о StringBuilder, или просто конвертируйте свою строку в массив символов / байтов и работайте там. Кроме того, вы привели пример "upcasing" - у класса String есть метод ToUpper, и если это не так быстро, как ваш небезопасный "upcasing", я съем свою шляпу.