Ответ 1
Могу ли я создать полностью неизменяемый тип?
Вы можете создать тип, в котором CLR обеспечивает неизменность. Затем вы можете использовать "небезопасно", чтобы отключить механизмы обеспечения CLR. Поэтому "небезопасно" называется "небезопасным" - потому что он отключает систему безопасности. В небезопасном коде каждый байт памяти в процессе может быть доступен для записи, если вы достаточно стараетесь, включая как неизменные байты, так и код в CLR, который обеспечивает неизменность.
Вы также можете использовать Reflection для прерывания неизменности. Оба рефлекса и небезопасного кода требуют чрезвычайно высокого уровня доверия.
Есть ли причина использовать такой код помимо проблем с производительностью?
Конечно, существует множество причин использовать неизменяемые структуры данных. Неизменяемые структуры данных. Некоторые веские причины использовать неизменяемые структуры данных:
- неизменяемые структуры данных легче рассуждать, чем изменяемые структуры данных. Когда вы спрашиваете: "Этот список пуст?" и вы получите ответ, тогда вы знаете, что ответ правильный не только сейчас, но и навсегда. С изменчивыми структурами данных вы действительно не можете спросить: "Этот список пуст?" Все, что вы можете задать, это "этот список пуст прямо сейчас?" и тогда ответ логически отвечает на вопрос "был ли этот список пустым в какой-то момент в прошлом?"
Тот факт, что ответ на вопрос о неизменяемом типе остается верным навсегда, имеет последствия для безопасности. Предположим, у вас есть такой код:
void Frob(Bar bar)
{
if (!IsSafe(bar)) throw something;
DoSomethingDangerous(bar);
}
Если Bar является изменяемым типом, то здесь есть условие гонки; бар может быть небезопасным в другом потоке после проверки, но прежде чем произойдет что-то опасное. Если Bar является неизменным типом, то ответ на вопрос остается неизменным во всем, что намного безопаснее. (Предположим, если вы можете изменить строку, содержащую путь после проверки безопасности, но до того, как файл был открыт, например.)
-
методы, которые берут неизменные структуры данных в качестве их аргументов и возвращают их как их результаты и не выполняют никаких побочных эффектов, называются "чистыми методами". Могут быть сохранены чистые методы, которые торгуют увеличением использования памяти для увеличения скорости, часто чрезвычайно высокой скоростью.
-
неизменяемые структуры данных часто могут использоваться на нескольких потоках одновременно без блокировки. Блокировка там, чтобы предотвратить создание несогласованного состояния объекта перед мутацией, но неизменяемые объекты не имеют мутаций. (Некоторые так называемые неизменные структуры данных логически неизменяемы, но на самом деле делают мутации внутри себя, представьте, например, таблицу поиска, которая не меняет ее содержимое, но реорганизует ее внутреннюю структуру, если она может определить, каким будет следующий запрос. Такая структура данных не будет автоматически потоковой.)
-
неизменяемые структуры данных, которые эффективно повторно используют свои внутренние части, когда новая структура построена из старой, упрощает "делать снимок" состояния программы, не тратя много памяти. Это делает операции отмены-повтора тривиальными для реализации. Это упрощает создание инструментов отладки, которые могут показать вам, как вы попали в конкретное состояние программы.
-
и т.д.
Являются ли строки тогда неотъемлемо потокобезопасными или нет?
Если все играют по правилам, они есть. Если кто-то использует небезопасный код или частное отражение, то больше нет правил. Вы должны верить, что если кто-то использует код с высокими привилегиями, то они делают это правильно и не изменяют строку. Используйте свою силу для запуска небезопасного кода только для хорошего; с большой силой приходит большая ответственность.
Так что мне нужно использовать блокировки или нет?
Это странный вопрос. Помните, что замки являются кооперативными. Блокировки работают только в том случае, если каждый доступ к определенному объекту согласуется с стратегией блокировки, которая должна использоваться.
Вы должны использовать блокировки, если стратегия блокировки согласованная для доступа к определенному объекту в определенном месте хранения - это использование блокировок. Если это не согласованная стратегия блокировки, то использование блокировок бессмысленно; вы осторожно запираете и отпираете входную дверь, а кто-то другой идет по открытой задней двери.
Если у вас есть строка, которая, как вы знаете, мутируется небезопасным кодом, и вы не хотите видеть противоречивые частичные мутации, а также код, который выполняет небезопасные мутационные документы, в которых он принимает конкретную блокировку во время этой мутации, то да, вам нужно использовать блокировки при доступе к этой строке. Но эта ситуация очень редка; в идеале никто не использовал бы небезопасный код для манипулирования строкой, доступной другим кодом в другом потоке, потому что это невероятно плохая идея. Вот почему мы требуем, чтобы тот код, который делает это, полностью доверял. И поэтому мы требуем, чтобы исходный код С# для такой функции отображал большой красный флаг, в котором говорится, что "этот код небезопасен, внимательно просмотрите его!"