Неизменяемость строк в C #

Мне было любопытно, как класс StringBuilder реализован внутри, поэтому я решил проверить исходный код Mono и сравнить его с Reflector ' Разобрали код реализации Microsoft. По сути, реализация Microsoft использует char [] для внутреннего хранения строкового представления и кучу небезопасных методов для управления им. Это просто и не вызывает никаких вопросов. Но я был озадачен, когда обнаружил, что Mono использует строку внутри StringBuilder:

private int _length;
private string _str;

Первой мыслью было: «Что за бессмысленный StringBuilder». Но потом я понял, что можно изменять строку с помощью указателей:

public StringBuilder Append (string value) 
{
     // ...
     String.CharCopy (_str, _length, value, 0, value.Length);
}

internal static unsafe void CharCopy (char *dest, char *src, int count) 
{
    // ...
    ((short*)dest) [0] = ((short*)src) [0]; dest++; src++;
}    

Я немного программировал на C / C ++, поэтому не могу сказать, что этот код меня сильно смутил, но я думал, что строки полностью неизменный (то есть нет абсолютно никакого способа изменить его). Итак, реальные вопросы:

  • Могу ли я создать полностью неизменный тип?
  • Есть ли какая-либо причина использовать такой код помимо проблем с производительностью? (небезопасный код для изменения неизменяемых типов)
  • Являются ли строки по сути потокобезопасными или нет?
25
задан Jonathan Leffler 28 August 2010 в 18:22
поделиться

6 ответов

Могу ли я создать полностью неизменяемый тип?

Вы можете создать тип, для которого среда CLR применяет неизменяемость. Затем вы можете использовать unsafe, чтобы отключить принудительные механизмы CLR. Вот почему "небезопасный" называется "небезопасным" - потому что он отключает систему безопасности. В небезопасном коде каждый отдельный байт памяти в процессе может быть доступен для записи, если вы достаточно постараетесь, , включая как неизменяемые байты, так и код в CLR, обеспечивающий неизменность.

Вы также можете использовать Reflection для нарушения неизменности. Как отражение, так и небезопасный код требуют чрезвычайно высокого уровня доверия.

Есть ли какие-либо причины для использования такого кода помимо соображений производительности?

Конечно, есть много причин для использования неизменяемых структур данных. Неизменяемые структуры данных рок. Несколько веских причин для использования неизменяемых структур данных:

  • неизменяемые структуры данных легче рассуждать, чем изменяемые структуры данных. Когда вы спрашиваете: «Этот список пуст?» и вы получаете ответ, тогда вы знаете, что ответ правильный не только сейчас, но и навсегда. С изменяемыми структурами данных вы не можете спросить: «Этот список пуст?» Все, что вы можете спросить, это «этот список сейчас пуст?» а затем ответ логически отвечает на вопрос «был ли этот список пустым в какой-то момент в прошлом?»

Тот факт, что ответ на вопрос об неизменяемом типе всегда остается верным, имеет последствия для безопасности.Предположим, у вас есть такой код:

void Frob(Bar bar)
{
    if (!IsSafe(bar)) throw something;
    DoSomethingDangerous(bar);
}

Если Bar является изменяемым типом, то здесь возникает состояние гонки; bar может быть сделан небезопасным в другом потоке после проверки, но до происходит что-то опасное. Если Bar является неизменяемым типом, то ответ на вопрос остается неизменным, что намного безопаснее. (Представьте, что вы могли бы изменить строку, содержащую путь после проверки безопасности, но до открытия файла.)

  • методы, которые принимают неизменяемые структуры данных в качестве аргументов. и возвращать их как результаты и не выполнять побочных эффектов, называются "чистыми методами". Чистые методы могут быть запомнены, что позволяет увеличить использование памяти за счет увеличения скорости, часто значительного увеличения скорости.

  • неизменяемые структуры данных часто можно использовать в нескольких потоках одновременно без блокировки. Блокировка предназначена для предотвращения создания несогласованного состояния объекта перед лицом мутации, но неизменяемые объекты не имеют мутаций. (Некоторые так называемые неизменяемые структуры данных логически неизменны, но на самом деле производят изменения внутри себя; представьте себе, например, таблицу поиска, которая не меняет своего содержимого, но реорганизует свою внутреннюю структуру, если она может сделать вывод о том, каким будет следующий запрос. Такая структура данных не будет автоматически потокобезопасной.)

  • неизменяемые структуры данных, которые эффективно повторно используют свои внутренние части, когда новая структура создается из старой, упрощают «снятие моментального снимка» состояния программы. не тратя много памяти.Это делает операции отмены и повтора тривиальными для реализации. Это упрощает написание инструментов отладки, которые могут показать вам, как вы пришли к определенному состоянию программы.

  • и так далее.

Являются ли строки по своей сути потокобезопасными или нет?

Если все играют по правилам, то так и есть. Если кто-то использует небезопасный код или частное отражение, то соблюдение правил больше не применяется. Вы должны верить, что если кто-то использует код с высокими привилегиями, то он делает это правильно и не мутирует строку. Используйте свою силу для запуска небезопасного кода только во благо; с большой властью приходит большая ответственность.

Так нужно использовать блокировки или нет?

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

Вы должны использовать блокировки, если согласованная стратегия блокировки для доступа к конкретному объекту в определенном месте хранения предполагает использование блокировок. Если это не согласованная стратегия блокировки, то использование блокировок бессмысленно; вы осторожно запираете и отпираете входную дверь, в то время как кто-то еще входит в открытую заднюю дверь.

Если у вас есть строка, которая, как вы знаете, изменяется небезопасным кодом, и вы не хотите видеть несогласованные частичные изменения, а код, который выполняет небезопасное изменение, документирует, что он снимает определенную блокировку во время этой мутации , тогда да, вам нужно использовать блокировки при доступе к этой строке.Но такая ситуация очень редка; в идеале никто не будет использовать небезопасный код для манипулирования строкой, доступной другому коду в другом потоке, потому что это невероятно плохая идея. Вот почему мы требуем, чтобы код, который делает это, был полностью доверенным. И именно поэтому мы требуем, чтобы в исходном коде C# для такой функции был большой красный флаг с надписью «этот код небезопасен, внимательно изучите его!»

43
ответ дан 28 November 2019 в 20:42
поделиться

Если вы небезопасны, строки в C# также можно изменить (IIRC).

3
ответ дан 28 November 2019 в 20:42
поделиться

Здесь нет черной магии. Строковый класс является неизменяемым просто потому, что в нем нет общедоступных полей, свойств или методов, позволяющих изменять внутреннюю строку. Любой метод, изменяющий строку, возвращает новый экземпляр строки. Вы, конечно, можете сделать это и со своими собственными классами.

2
ответ дан 28 November 2019 в 20:42
поделиться

Не существует полностью неизменяемого типа, класс, который является неизменяемым, потому что он не позволяет никакому внешнему коду изменять его. Используя отражение или небезопасный код, вы все равно можете изменить его значения.

Вы можете использовать ключевое слово readonly для создания неизменяемой переменной, но это работает только для типов значений. Если вы используете его для ссылочного типа, защищена будет только ссылка, а не объект, на который она указывает.

Существует несколько причин для неизменяемых типов, таких как производительность и надежность.

Тот факт, что строки известны как неизменяемые (за пределами StringBuilder), означает, что компилятор может выполнять оптимизацию на основе этого. Компилятору никогда не приходится создавать код для копирования строки, чтобы защитить ее от изменения при передаче в качестве параметра.

Объекты, созданные из неизменяемых типов, также можно безопасно передавать между потоками. Поскольку их нельзя изменить, нет риска, что разные потоки изменят их одновременно, поэтому нет необходимости синхронизировать доступ к ним.

Неизменяемые типы можно использовать, чтобы избежать ошибок кодирования. Если вы знаете, что значение не следует изменять, рекомендуется убедиться, что оно не может быть изменено по ошибке.

3
ответ дан 28 November 2019 в 20:42
поделиться

Могу ли я создать полностью неизменяемый тип?

Да. Имейте конструктор для установки частных полей, получайте только свойства и никаких методов.

Есть ли какие-либо причины для использования такого кода помимо соображений производительности?

Один пример: такие типы не требуют блокировки для безопасного использования из нескольких параллельных потоков, это упрощает написание правильного кода (нет блокировок, чтобы ошибиться).

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

Это верно вне .NET, привилегированный процесс (т. е. с токеном процесса или потока с одной из привилегий «Бога», например, с включенным Take Ownership) может взломать любой другой процесс загрузки dll, внедрить потоки, выполняющие произвольный код , чтение или запись в память (включая переопределение предотвращения выполнения и т. д.). Целостность системы зависит от сотрудничества владельца системы.

1
ответ дан 28 November 2019 в 20:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: