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

Почему Microsoft рекомендует использовать поля readonly с изменяемыми значениями?

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

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

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

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

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

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

  • Вы следуете этому руководству по дизайну во всем своем коде?
  • Что вы ожидаете, когда видите "readonly" в фрагменте кода, который вы не писали?
4b9b3361

Ответ 1

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

В качестве примера: у меня может быть член private или protected - скажем, a 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.

Ответ 2

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

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

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

foo.Bar.Name = "Blah";

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

Ответ 3

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

Я быстро просмотрел книгу "Руководства по дизайну каркаса" (страницы 161-162), и в ней в основном говорится, что вы уже заметили сами. Там есть дополнительный комментарий Джо Даффи, в котором объясняется принцип raison-d'être:

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

Я лично считаю, что ключевое слово readonly было названо плохо. Тот факт, что он просто указывает на константу ссылки, а не на константу ссылочного объекта, легко создает ошибочные ситуации.

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

const Example^ const obj;

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

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 просто не поддерживает его.

Ответ 7

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

public Customer OrderCustomer
{
   get;
}

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

Ответ 8

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

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 действительно не изменилась). но это не то, что мы имели в виду, когда говорили "только для чтения", не так ли? так что не используйте поля только для чтения с изменяемыми значениями. Это сбивает с толку и нелогично.