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

С# и неизменяемость и поля только для чтения... ложь?

Я обнаружил, что Люди утверждают, что использование всех полей readonly в классе необязательно делает этот экземпляр класса неизменным, потому что есть "способы" изменить значения полей readonly даже после инициализации (построения).

Как? Какие способы?

Итак, мой вопрос в том, когда мы можем действительно иметь "реальный" неизменяемый объект в С#, который я могу безопасно использовать при потоковой передаче?

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

4b9b3361

Ответ 1

Ты спросил, как пять вопросов. Я отвечу на первый:

Наличие всех полей readonly в классе не обязательно делает этот экземпляр класса неизменным, потому что есть "способы" изменить значения поля readonly даже после построения. Как?

Можно ли изменить поле readonly после построения?

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

Как это работает?

Каждый бит пользовательской памяти в вашем процессе изменчив. Соглашения, такие как поля только для чтения, могут сделать определенные биты неизменными, но если вы попытаетесь достаточно усердно, вы можете их изменить. Например, вы можете взять экземпляр неизменяемого объекта, получить его адрес и напрямую изменить исходные биты. Для этого может потребоваться много умений и знаний о внутренних деталях реализации диспетчера памяти, но каким-то образом диспетчер памяти справляется с мутацией этой памяти, поэтому вы тоже можете это сделать, если попытаетесь достаточно сильно. Вы также можете использовать "частное отражение", чтобы разбить различные части системы безопасности, если вам достаточно доверять.

По определению полностью доверенный код может нарушать правила системы безопасности. То, что означает "полностью доверенное". Если ваш полностью доверенный код предпочитает использовать такие инструменты, как частное отражение или небезопасный код, чтобы нарушать правила безопасности памяти, этому может доверять полностью доверенный код.

Пожалуйста, не надо. Это опасно и запутанно. Система безопасности памяти предназначена для упрощения определения правильности кода; сознательное нарушение его правил - плохая идея.

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

Является только полем readonly для класса? Да, но , только если все подчиняются правилам. Итак, поля только для чтения не являются "ложью". Контракт состоит в том, что, если каждый подчиняется правилам системы, то поле наблюдается только для чтения. Если кто-то нарушает правила, возможно, это не так. Это не делает утверждение "если каждый подчиняется правилам, поле" только для чтения "ложь!

Вопрос, который вы не задавали, но, возможно, должен иметь, является ли "readonly" в полях структуры "ложью". См. Использует ли использование общедоступных полей readonly для неизменяемых структур? для некоторых мыслей по этому вопросу. Считываемые поля в структуре - это скорее ложь, чем поля только для чтения в классе.

Что касается остальных ваших вопросов - я думаю, вы получите лучшие результаты, если зададите один вопрос на вопрос, а не пять вопросов на вопрос.

Ответ 2

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

Это зависит от типа поля. Если само поле имеет неизменный тип (например, String), то это прекрасно. Если он StringBuilder, тогда объект может измениться, даже если сами поля не изменяют свои значения, потому что "внедренные" объекты могут меняться.

Анонимные типы точно такие же. Например:

var foo = new { Bar = new StringBuilder() };
Console.WriteLine(foo); // { Bar = }
foo.Bar.Append("Hello");
Console.WriteLine(foo); // { Bar = Hello }

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

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

Эрик Липперт написал большую вещь о неизменности - все это золото, как и следовало ожидать... прочитайте:) (I не заметил, что Эрик писал ответ на этот вопрос, когда я написал этот бит. Очевидно, прочитал его ответ тоже!)

Ответ 3

Я думаю, что ответ Эрика далеко выходит за рамки первоначального вопроса, до такой степени, что он даже не отвечает на него, поэтому я возьму его за удар:

Что такое readonly? Ну, если мы говорим о типах значений, это просто: как только тип значения инициализируется и получает значение, он никогда не может измениться (по крайней мере, до компилятора).

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

  • "ссылка" (переменная, указатель), которая указывает на адрес в памяти, где живет ваш объект
  • Память, содержащая данные, на которые ссылается ссылка

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

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

Ответ 4

Readonly и Thread Safety - это два разных понятия: Эрик Липперт объясняет это.

Ответ 5

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

Не только это, они не одноранговые. Конструктор класса может изменять поле столько раз, сколько ему нравится. Все без нарушения каких-либо "правил".

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

Вы можете попробовать сами:

using System;
class App
{
    class Foo 
    {
        readonly int x;
        public Foo() {  x = 1; Frob(); x = 2; Frob(); }
        void Frob() { Console.WriteLine(x); }
    }
    static void Main()
    {
        new Foo();
    }
}

Эта программа печатает 1, 2. "x" изменяется после того, как Frob прочитал ее.

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

Все вышеперечисленное относится к классам. Структуры - это еще одна история.