Почему Microsoft советует не читать только поля с изменяемыми значениями? - PullRequest
37 голосов
/ 10 мая 2010

В Руководстве по проектированию для разработки библиотек классов , Microsoft говорит:

Не назначать экземпляры изменяемых типов для полей только для чтения.

Объекты, созданные с использованием изменяемого типа, могут быть изменены после их создания. Например, массивы и большинство коллекций являются изменяемыми типами, в то время как Int32, Uri и String являются неизменяемыми типами. Для полей, которые содержат изменяемый ссылочный тип, модификатор только для чтения предотвращает перезапись значения поля, но не защищает изменяемый тип от модификации.

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

Мне неловко от этой рекомендации использовать «только для чтения» для обозначения чего-то другого, кроме его обычного значения, понятного компилятору. Я чувствую, что это побуждает людей неправильно понимать значение слова «только для чтения», и, кроме того, ожидать, что оно будет означать то, что автор кода может не намереваться. Я чувствую, что это исключает использование его в местах, где это может быть полезно - например, показать, что некоторые отношения между двумя изменяемыми объектами остаются неизменными в течение всего времени жизни одного из этих объектов. Представление о том, что читатели не понимают значения «только для чтения», также, по-видимому, противоречит другим советам Microsoft, таким как правило «Не инициализировать без необходимости» FxCop, , которое предполагает, что читатели вашего кода быть знатоками языка и знать, что (например) поля bool автоматически инициализируются как false, и мешает вам предоставить избыточность, которая показывает «да, это было сознательно установлено в false; я не просто забыл инициализировать это».

Итак, в первую очередь, почему Microsoft не рекомендует использовать readonly для ссылок на изменяемые типы? Мне также было бы интересно узнать:

  • Соблюдаете ли вы это Руководство по проектированию во всем своем коде?
  • Чего вы ожидаете, когда видите "только для чтения" в куске кода, который вы не написали?

Ответы [ 7 ]

25 голосов
/ 10 мая 2010

Кажется естественным, что если поле доступно только для чтения, вы ожидаете, что не сможете изменить значение или что-либо, имеющее отношение к нему . Если бы я знал, что Бар - это поле Фу, предназначенное только для чтения, я бы, очевидно, не сказал бы

Foo foo = new Foo();
foo.Bar = new Baz();

Но я могу сойти с ума, сказав

foo.Bar.Name = "Blah";

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

21 голосов
/ 10 мая 2010

Я полностью согласен с вами , а я делаю иногда использую readonly в моем коде для изменяемых ссылочных типов.

В качестве примера: у меня может быть какой-то член private или protected, скажем, List<T>, который я использую в методах класса во всей его изменчивой славе (вызывая Add, Remove , так далее.). Я могу просто захотеть применить меры безопасности, чтобы, несмотря ни на что, я всегда имел дело с одним и тем же объектом . Это защищает и меня, и других разработчиков от глупостей, а именно от назначения члена новому объекту.

Для меня это часто предпочтительная альтернатива использованию свойства с закрытым set методом. Зачем? Поскольку readonly означает , значение не может быть изменено после создания экземпляра даже базовым классом .

Другими словами, если бы у меня было это:

protected List<T> InternalList { get; private set; }

Тогда я все еще мог бы установить InternalList = new List<T>(); в любой произвольной точке кода в моем базовом классе. (Это потребует очень глупой ошибки с моей стороны, да; но это все еще будет возможно.)

С другой стороны, это:

protected readonly List<T> _internalList;

делает безошибочно ясным , что _internalList может только когда-либо ссылаться на один конкретный объект (тот, для которого _internalList установлен в конструкторе).

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

7 голосов
/ 10 мая 2010

НЕ присваивать экземпляры изменяемых типов полям readonly.

Я быстро просмотрел книгу Framework Design Guidelines (стр. 161-162), и она в основном говорит о том, что вы уже заметили сами. Есть дополнительный комментарий Джо Даффи, который объясняет смысл руководства:

То, от чего пытается защитить вас это руководство, - это то, что вы раскрыли глубоко неизменяемый граф объектов, когда на самом деле он неглубокий, а затем пишете код, предполагающий, что весь граф неизменен.
& mdash; Джо Даффи

Лично я считаю, что ключевое слово readonly было названо неверно. Тот факт, что оно указывает только на постоянство ссылки, а не на константу ссылочного объекта, легко создает вводящие в заблуждение ситуации.

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

Чтобы исправить эту печальную ситуацию, было составлено руководство. Хотя я думаю, что его совет является здравым с точки зрения человека (не всегда очевидно, какие типы являются изменчивыми, а какие нет, не ища их определения, и слово предлагает глубокую неизменность), я иногда хотелось бы, чтобы, когда дело доходит до объявления константности, C # предлагает свободу, аналогичную той, которая предлагается в C ++, где вы можете определить const либо по указателю, либо по указателю на объект, либо по обоим или по ничего .

2 голосов
/ 10 мая 2010

У Microsoft есть несколько таких своеобразных советов. Другой момент, который сразу приходит на ум - не вкладывать универсальные типы в открытые члены, например List<List<int>>. Я стараюсь избегать этих конструкций там, где это возможно, но игнорирую советы новичков, когда считаю, что использование оправдано.

Что касается полей только для чтения - я стараюсь избегать открытых полей как таковых, вместо этого обращаясь к свойствам. Я думаю, что тоже был совет по этому поводу, но, что более важно, время от времени бывают случаи, когда поле не работает, а свойство работает (в основном это связано с привязкой данных и / или визуальными дизайнерами). Делая свойства всех открытых полей, я избегаю любых потенциальных проблем.

1 голос
/ 11 мая 2010

Синтаксис, который вы ищете, поддерживается языком C ++ / CLI:

const Example^ const obj;

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

Test^ t = gcnew Test();
t->obj = gcnew Example();   // Error C3892
t->obj->field = 42;         // Error C3892
Example^ another = t->obj;  // Error C2440
another->field = 42; 

Однако дым и зеркала. Неизменность проверяется компилятором, а не CLR. Другой управляемый язык может изменить оба. В чем корень проблемы, CLR просто не поддерживает ее.

1 голос
/ 10 мая 2010

В конце концов, это всего лишь рекомендации. Я точно знаю, что сотрудники Microsoft часто не следуют всем рекомендациям.

0 голосов
/ 03 апреля 2019

Это допустимо в C # (простое консольное приложение)

readonly static object[] x = new object[2] { true, false };
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
        x[0] = false;
        x[1] = true;

        Console.WriteLine("{0} {1}", x[0], x[1]); //prints "false true"
        Console.ReadLine();
    }

это будет работать. но это не имеет смысла. помните, что переменная x доступна только для чтения и не изменилась (то есть ссылка x действительно не изменилась) но это не то, что мы имели в виду, когда говорили «только для чтения», не так ли? так что не используйте поля только для чтения с изменяемыми значениями. Это сбивает с толку и нелогично.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...