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

Неизменяемый класс vs struct

Ниже перечислены только пути, по которым классы отличаются от структур на С# (пожалуйста, поправьте меня, если я ошибаюсь):

  • Переменные класса - это ссылки, а структурные переменные - значения, поэтому все значения struct копируются в назначениях и пропусках параметров
  • Переменные класса - это указатели, хранящиеся в стеке, которые указывают на память в куче, тогда как структурные переменные хранятся в хранимой куче в качестве значений

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

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

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

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

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

Где я ошибаюсь?

4b9b3361

Ответ 1

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

Это не всегда так. Копирование ссылки будет состоять из 8 байтов на 64-битной ОС, которая потенциально больше многих структур.

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

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

Говоря, я часто основываю свое решение больше на предполагаемом использовании и значении рассматриваемого типа. Типы значений должны использоваться для обозначения одного значения (опять же, см. Рекомендации) и часто имеют смысловое значение и ожидаемое использование, отличное от классов. Это часто так же важно, как и характеристики производительности при выборе между классом или структурой.

Ответ 2

Рид-ответ неплохой, но просто добавьте несколько дополнительных очков:

пожалуйста, поправьте меня, если я ошибаюсь

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

Но все это не имеет особого отношения к вашему вопросу, что сводится к просьбе об опровержении этого силлогизма:

  • Переменные структуры являются злыми.
  • Справочное копирование дешевле, чем структурированное копирование, поэтому неизменяемые структуры всегда хуже.
  • Поэтому для структур никогда не используется.

Мы можем опровергнуть силлогизм несколькими способами.

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

Во-вторых, копирование ссылок не обязательно дешевле, чем структурное копирование. Ссылки обычно реализуются как 4 или 8 байтовые управляемые указатели (хотя это детализация реализации, они могут быть реализованы как непрозрачные дескрипторы). Копирование структуры ссылочного размера не является ни дешевле, ни более дорогостоящим, чем копирование ссылочной ссылки.

В-третьих, даже если копирование ссылок дешевле, чем структурированное копирование, ссылки должны быть разыменованы, чтобы попасть в их поля. Разыменование - это не нулевая стоимость! Мало того, что требуется машинные циклы для разыменования ссылки, это может испортить кэш процессора, и это может сделать дальнейшие разыгрывания намного более дорогими!

В-четвертых, даже если ссылочное копирование дешевле, чем структурированное копирование, кому это нужно? Если это не узкое место, которое создает неприемлемые эксплуатационные расходы, а то, что быстрее, совершенно не имеет значения.

В-пятых, ссылки в пространстве памяти намного дороже, чем структуры.

В-шестых, ссылки добавляют затраты, потому что сеть ссылок должна периодически отслеживаться сборщиком мусора; "blittable" structs может полностью игнорироваться сборщиком мусора. Сбор мусора - большие расходы.

В-седьмых, неизменяемые типы значений не могут быть нулевыми, в отличие от ссылочных типов. Вы знаете, что каждое значение является хорошим значением. И, как указал Рид, чтобы получить хорошее значение ссылочного типа, вам нужно запустить как распределитель, так и конструктор. Это не дешево.

Восьмой, типы значений представляют значения, а программы часто связаны с манипуляцией значениями. Имеет смысл "испечь" метафоры как "ценности", так и "ссылки" на языке, независимо от того, что "дешевле".

Ответ 3

Из MSDN;

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

Не определяйте структуру, если тип имеет все следующие характеристики:

  • Он логически представляет одно значение, подобное примитивным типам (целое, двойное и т.д.).

  • У него размер экземпляра меньше 16 байт.

  • Он неизменен.

  • Его не нужно часто вставлять в бокс.

Итак, вы всегда должны использовать класс вместо struct, если ваша структура будет содержать более 16 байт. Также читайте из http://www.dotnetperls.com/struct

Ответ 4

Существует два случая использования для структур. Непрозрачные структуры полезны для вещей, которые могут быть реализованы с использованием неизменяемых классов, но достаточно малы, что даже в лучших обстоятельствах не будет много - если таковые имеются - выгодно использовать класс, особенно если частота, с которой они создаются и отбрасываются - это значительная часть частоты, с которой они будут просто скопированы. Например, Decimal - это 16-байтовая структура, поэтому для хранения значений в миллион Decimal потребуется 16 мегабайт. Если бы это был класс, каждая ссылка на экземпляр Decimal занимала бы 4 или 8 байт, но каждый отдельный экземпляр, вероятно, принимал бы еще 20-32 байта. Если у кого-то было много больших массивов, элементы которых были скопированы из небольшого количества различных экземпляров Decimal, класс мог бы выиграть, но в большинстве сценариев более вероятно, что будет иметь массив с миллионным ссылками на миллион различных экземпляров Decimal, что означало бы, что структура выиграет.

Использование структур таким образом, как правило, полезно только в том случае, если применяются руководящие принципы, приведенные в MSDN (хотя руководство по неизменности является главным образом следствием того факта, что еще нет способа, посредством которого методы структуры могут указывать на то, что они изменяют базовые структура). Если какое-либо из трех последних правил не применяется, вероятно, лучше использовать неизменяемый класс, чем структуру. Однако, если первое правило не применяется, это означает, что нельзя использовать структуру opaque, но не следует вместо этого использовать класс.

В некоторых ситуациях целью типа данных является просто связать группу переменных вместе с клейкой лентой, чтобы их значения могли быть переданы как единое целое, но они по-прежнему остаются семантически в виде различных переменных. Например, многим методам может потребоваться передать группы из трех чисел с плавающей запятой, представляющих 3d-координаты. Если вы хотите нарисовать треугольник, гораздо удобнее передать три параметра Point3d, чем девять чисел с плавающей запятой. Во многих случаях целью таких типов является не предоставление какого-либо поведения, связанного с конкретным доменом, а просто предоставление возможности для удобства общения. В таких случаях структуры могут предлагать большие преимущества по производительности над классами, если их правильно использовать. Структура, которая должна представлять три разновидности типа double, закрепленные вместе с клейкой лентой, должна иметь только три открытых поля типа double. Такая структура позволит эффективно выполнять две общие операции:

  • С учетом экземпляра сделайте снимок своего состояния, чтобы экземпляр мог быть изменен без нарушения моментального снимка
  • Учитывая экземпляр, который больше не нужен, каким-то образом возникает экземпляр, который немного отличается

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

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

Обратите внимание, что в зависимости от шаблонов использования большие структуры с открытым полем могут быть намного эффективнее, чем непрозрачные структуры или типы классов. Структура размером более 17 байт часто менее эффективна, чем более мелкие, но они все еще могут быть значительно более эффективными, чем классы. Кроме того, стоимость передачи структуры как параметра ref не зависит от ее размера. Большие структуры неэффективны, если они обращаются к ним через свойства, а не по полям, без необходимости передают их по значению и т.д., Но если кто-то осторожен, чтобы избежать избыточных операций "копирования", существуют шаблоны использования, где нет точек безубыточности для классов против structs-structs будет просто работать лучше.

Некоторые люди могут в ужасе отбросить идею типа, который имеет открытые поля, но я бы предположил, что такую ​​структуру, как я описываю, не следует воспринимать как сущность для себя, а скорее расширение вещи, которые его читают или пишут. Например:

public struct SlopeAndIntercept
{
   public double Slope,Intercept;
}
public SlopeAndIntercept FindLeastSquaresFit() ...

Коду, который будет выполнять пометку наименьших квадратов из кучки точек, должен будет выполнить значительную работу, чтобы найти либо наклон, либо Y-перехват полученной строки; поиск обоих не будет стоить намного дороже. Код, который вызывает метод FindLeastSquaresFit, скорее всего, захочет иметь наклон в одной переменной и перехват в другой. Если такой код делает:

var resultLine = FindLeastSquaresFit();

результатом будет эффективное создание двух переменных resultLine.Slope и resultLine.Intercept, которые метод может манипулировать по своему усмотрению. Поля resultLine не относятся к SlopeIntercept, а не к FindLeastSquaresFit; они принадлежат коду, объявляющему resultLine. Ситуация немного отличается от того, был ли метод использован как:

double Slope, Intercept;
FindLeastSquaresFit(out Slope, out Intercept);

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

Есть ситуации, когда лучше возвращать данные с использованием неизменяемого класса, а не прозрачной структуры. Помимо прочего, использование класса облегчит будущие версии функции, которая возвращает Foo, чтобы вернуть что-то, что содержит дополнительную информацию. С другой стороны, существует много ситуаций, когда код собирается рассчитывать на конкретный набор дискретных вещей, и изменение этого набора вещей в корне изменит то, что клиенты должны с ним делать. Например, если у вас есть куча кода, который имеет дело с (x, y) точками, добавление координаты "z" потребует, чтобы код был перезаписан, и нет ничего, что может сделать "точечный" тип, чтобы смягчить это.