По сути, структура - это не что иное, как совокупность полей. В .NET структура может «притворяться» объектом, и для каждого типа структуры .NET неявно определяет тип объекта кучи с теми же полями и методами, которые, будучи объектом кучи, будут вести себя как объект . Переменная, которая содержит ссылку на такой объект кучи ("упакованная" структура), будет демонстрировать семантику ссылки, но переменная, которая содержит непосредственно структуру, представляет собой просто агрегирование переменных.
Я думаю, что путаница между структурами и классами во многом проистекает из того факта, что у структур есть два очень разных варианта использования, которые должны иметь очень разные рекомендации по проектированию, но рекомендации MS не различают их. Иногда возникает потребность в чем-то, что ведет себя как объект; в этом случае рекомендации MS довольно разумны, хотя «ограничение в 16 байт», вероятно, должно быть больше похоже на 24-32. Однако иногда требуется агрегирование переменных. Структура, используемая для этой цели, должна просто состоять из группы общедоступных полей и, возможно, Equals
переопределения, ToString
переопределения иIEquatable(itsType).Equals
реализация. Структуры, которые используются в качестве агрегатов полей, не являются объектами и не должны претендовать на них. С точки зрения структуры, значение поля должно быть не более или менее, чем «последнее, что записывается в это поле». Любое дополнительное значение должно определяться клиентским кодом.
Например, если структура агрегирования переменных имеет члены Minimum
и Maximum
, сама структура не должна обещать этого Minimum <= Maximum
. Код , который получает такую структуру , как параметр должен вести себя так , как будто это было приняты отдельной Minimum
и Maximum
ценность. Требование, которое Minimum
должно быть не больше, чем Maximum
должно рассматриваться как требование, чтобы Minimum
параметр был не больше, чем отдельно переданныйMaximum
.
Полезный шаблон, который следует иногда учитывать, - это ExposedHolder<T>
определить для класса что-то вроде:
class ExposedHolder<T>
{
public T Value;
ExposedHolder() { }
ExposedHolder(T val) { Value = T; }
}
Если у кого-то есть List<ExposedHolder<someStruct>>
, где someStruct
- структура агрегирования переменных, можно делать такие вещи, как myList[3].Value.someField += 7;
, но передача myList[3].Value
другому коду даст ему содержимое, Value
а не средство для его изменения. Напротив, если бы кто-то использовал a List<someStruct>
, было бы необходимо использовать var temp=myList[3]; temp.someField += 7; myList[3] = temp;
. Если бы использовался изменяемый тип класса, для раскрытия содержимого myList[3]
внешнего кода потребовалось бы скопировать все поля в какой-либо другой объект. Если бы использовался неизменяемый тип класса или структура «объектного стиля», было бы необходимо построить новый экземпляр, который был бы похож, myList[3]
за исключениемsomeField
который был другим, а затем сохранить этот новый экземпляр в списке.
Еще одно замечание: если вы храните большое количество похожих вещей, может быть полезно хранить их в возможно вложенных массивах структур, желательно стараясь сохранить размер каждого массива от 1 до 64 КБ или около того. Массивы структур являются особенными, так как индексирование дает прямую ссылку на структуру внутри, поэтому можно сказать «a [12] .x = 5;». Хотя можно определять объекты, подобные массивам, C # не позволяет им использовать такой синтаксис совместно с массивами.