Если одно из полей структуры относится к типу класса, это поле будет содержать идентификатор объекта класса или пустую ссылку. Если рассматриваемый объект класса является неизменным (например, string
), сохранение его идентичности будет также эффективно хранить его содержимое. Однако, если рассматриваемый объект класса является изменяемым, сохранение идентификатора будет эффективным средством хранения содержимого тогда и только тогда, когда ссылка никогда не попадет в руки какого-либо кода, который может изменить его после сохранения в поле .
Как правило, следует избегать хранения изменяемых типов классов в структуре, если не применяется одна из двух ситуаций:
- Что нас интересует, так это, фактически, идентичность объекта класса, а не его содержимое. Например, можно определить структуру `FormerControlBounds`, которая содержит поля типов` Control` и `Rectangle` и представляет` Bounds`, которые элемент управления имел в какой-то момент времени, с целью последующей возможности восстановления элемента управления к своей прежней позиции. Цель поля `Control` будет состоять не в том, чтобы хранить копию состояния элемента управления, а в том, чтобы идентифицировать элемент управления, положение которого должно быть восстановлено. Обычно структура должна избегать доступа к любым изменяемым элементам объекта, на который она ссылается, за исключением случаев, когда ясно, что такой доступ ссылается на текущее изменяемое состояние рассматриваемого объекта (например, в `CaptureControlPosition` или` RestoreControlToCapturedPosition` или свойство ControlHasMoved).
- Поле является `private`, единственные методы, которые его читают, делают это с целью изучения его свойств, не подвергая сам объект его внешнему коду, и единственные методы, которые пишут его, создадут новый объект, выполнят все мутаций, которые когда-либо произойдут с ним, а затем сохранить ссылку на этот объект. Можно, например, спроектировать `struct`, которая бы во многом походила на массив, но с семантикой значений, если бы структура содержала массив в закрытом поле, и при каждой попытке записать массив создавал новый массив с данными от старого, измените новый массив и сохраните измененный массив в это поле. Обратите внимание, что даже если сам массив будет изменяемым типом, каждый экземпляр массива, который когда-либо будет храниться в поле, будет эффективно неизменным, поскольку он никогда не будет доступен любому коду, который может его изменить.
Обратите внимание, что сценарий № 1 довольно распространен для универсальных типов; например, очень часто есть словарь, «значениями» которого являются идентификаторы изменяемых объектов; перечисление этого словаря вернет экземпляры KeyValuePair
, чье поле Value
содержит этот изменяемый тип.
Сценарий № 2 встречается реже. Увы, нет никакого способа сообщить компилятору, что методы структуры, отличные от установщиков свойств, изменят структуру, и поэтому их использование должно быть запрещено в контексте только для чтения; можно иметь структуру, которая ведет себя как List<T>
, но с семантикой значений и включает метод Add
, но попытка вызова Add
для экземпляра структуры только для чтения приведет к созданию поддельного кода, а не ошибки компилятора , Кроме того, методы мутирования и установки свойств на таких структурах, как правило, работают довольно плохо. Такие структуры могут быть полезны, когда они существуют как неизменяемая оболочка в иначе изменяемом классе; если такая структура никогда не упакована, производительность часто будет лучше, чем у класса. Если поместить его в коробку ровно один раз (например, путем приведения к типу интерфейса), производительность, как правило, будет сопоставима с классом. Если в штучной упаковке несколько раз, производительность может быть намного хуже, чем класс.