Подтвердить что ты не робот

Непрерывность строк в С#

Мне было любопытно, как класс 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/С++, поэтому я не могу сказать, что этот код меня очень смутил, но я думал, что строки абсолютно неизменяемы (т.е. нет абсолютно никакого способа его мутировать). Итак, актуальные вопросы:

  • Могу ли я создать полностью неизменяемый тип?
  • Есть ли причина использовать такой код помимо проблем с производительностью? (небезопасный код для изменения неизменяемых типов)
  • Являются ли строки неотъемлемо потокобезопасными или нет?
4b9b3361

Ответ 1

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

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

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

Есть ли причина использовать такой код помимо проблем с производительностью?

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

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

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

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

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

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

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

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

  • и т.д.

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

Ответ 5

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

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

Есть ли причина использовать такой код помимо проблем с производительностью?

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

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

Это справедливо вне .NET, привилегированный процесс (т.е. с токеном процесса или потока с одной из привилегий "Бога", например, "Включение в собственность" ), может прорваться в любые другие DLL файлы процесса, вставлять потоки с произвольным кодом, чтение или запись памяти (включая отмену предотвращения выполнения и т.д.). Целостность системы так же сильна, как и сотрудничество владельца системы.

Ответ 6

Вы можете прочитать эти сообщения Неизменяемые типы: понять их преимущества и использовать их

и Управление состояниями в многопоточной среде без сбоев синхронизации

Также инструмент NDepend поставляется с некоторыми возможностями для коп с неизменяемыми типами и чистыми методами.