Когда я должен использовать структуру, а не класс в C #?

1391

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

Я столкнулся с этими правилами здесь :

  • Структура должна представлять одно значение.
  • Структура должна иметь объем памяти менее 16 байт.
  • Структура не должна быть изменена после создания.

Эти правила работают? Что означает семантически структура?

Алекс Бараноский
источник
248
System.Drawing.Rectangleнарушает все три этих правила.
ChrisW
4
Есть довольно много коммерческих игр, написанных на C #, суть в том, что они используются для оптимизированного кода
BlackTigerX
25
Структуры обеспечивают лучшую производительность, когда у вас есть небольшие коллекции типов значений, которые вы хотите сгруппировать вместе. Это происходит постоянно при программировании игры, например, вершина в трехмерной модели будет иметь позицию, текстурную координату и нормаль, она также обычно будет неизменной. У одной модели может быть пара тысяч вершин или дюжина, но структуры обеспечивают меньшие накладные расходы в целом в этом сценарии использования. Я проверил это через мой собственный дизайн двигателя.
Крис Д.
4
@ChrisW Я вижу, но разве эти значения не представляют собой прямоугольник, то есть «одиночное» значение? Как и Vector3D или Color, они также содержат несколько значений, но я думаю, что они представляют отдельные значения?
Марсон Мао

Ответы:

604

Источник, на который ссылается OP, заслуживает доверия ... но как насчет Microsoft - какова позиция по использованию структуры? Я искал дополнительное обучение у Microsoft , и вот что я нашел:

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

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

  1. Он логически представляет одно значение, похожее на примитивные типы (целое, двойное и т. Д.).
  2. Размер экземпляра меньше 16 байт.
  3. Это неизменно.
  4. Это не должно быть упаковано часто.

Microsoft постоянно нарушает эти правила

Хорошо, № 2 и № 3 в любом случае. Наш любимый словарь имеет 2 внутренних структуры:

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

* Справочный источник

Источник 'JonnyCantCode.com' получил 3 из 4 - вполне простительно, поскольку № 4, вероятно, не будет проблемой. Если вы обнаружите, что занимаетесь структурой, переосмыслите свою архитектуру.

Давайте посмотрим, почему Microsoft будет использовать эти структуры:

  1. Каждая структура EntryиEnumerator представляет отдельные значения.
  2. скорость
  3. Entryникогда не передается как параметр вне класса Dictionary. Дальнейшие исследования показывают, что для удовлетворения реализации IEnumerable словарь использует Enumeratorструктуру, которую он копирует каждый раз при запросе перечислителя ... имеет смысл.
  4. Внутренний в классе словаря. Enumeratorявляется общедоступным, поскольку Dictionary является перечислимым и должен иметь равный доступ к реализации интерфейса IEnumerator, например, к получателю IEnumerator.

Обновление. Кроме того, следует понимать, что когда структура реализует интерфейс - как это делает Enumerator - и приводится к этому реализованному типу, структура становится ссылочным типом и перемещается в кучу. Внутренний по отношению к классу Dictionary, Enumerator по- прежнему является типом значения. Однако, как только метод вызывает GetEnumerator(), ссылочный типIEnumerator возвращается .

Здесь мы не видим каких-либо попыток или доказательств необходимости сохранять неизменяемыми структуры или поддерживать размер экземпляра не более 16 байт:

  1. Ничто в вышеприведенных структурах не заявлено readonly- не является неизменным
  2. Размер этой структуры может быть более 16 байтов
  3. Entryимеет неопределенный срок службы (от Add(), до Remove(), Clear()или мусора);

И ... 4. Обе структуры хранят TKey и TValue, которые, как мы все знаем, вполне могут быть ссылочными типами (дополнительная информация о бонусе)

Несмотря на хешированные ключи, словари работают быстро, потому что создание структуры быстрее, чем ссылочного типа. Здесь у меня есть, Dictionary<int, int>который хранит 300 000 случайных целых чисел с последовательно увеличенными ключами.

Емкость: 312874
MemSize: 2660827 байт
Выполнено Размер: 5мс
Общее время заполнения: 889мс

Вместимость : количество элементов, доступных до изменения размера внутреннего массива.

Memsize : определяется путем сериализации словаря в MemoryStream и получения длины в байтах (достаточно точной для наших целей).

Завершено Изменение размера : время, необходимое для изменения размера внутреннего массива с 150862 элементов до 312874 элементов. Когда вы понимаете, что каждый элемент последовательно копируется черезArray.CopyTo() , это не так уж и плохо.

Общее время для заполнения : по общему признанию искажено из-за регистрации и OnResizeсобытия, которое я добавил к источнику; Тем не менее, по-прежнему впечатляет заполнить 300 тысяч целых чисел при изменении размера в 15 раз во время операции Просто из любопытства, сколько будет общего времени, чтобы заполнить, если бы я уже знал емкость? 13 мс

Итак, что теперь, если бы Entryбыл класс? Будут ли эти времена или показатели действительно сильно отличаться?

Емкость: 312874
MemSize: 2660827 байт
Выполнено Размер: 26мс
Общее время заполнения: 964мс

Очевидно, большая разница заключается в изменении размера. Есть ли разница, если словарь инициализируется с Capacity? Не достаточно, чтобы беспокоиться о ... 12 мс .

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

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

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

Не предоставляйте конструктор по умолчанию для структуры.

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

Некоторые компиляторы, такие как компилятор C #, не позволяют структурам иметь конструкторы по умолчанию.

Это на самом деле довольно просто , и мы будем брать из Азимова три закона робототехники :

  1. Структура должна быть безопасной для использования
  2. Структура должна эффективно выполнять свою функцию, если это не нарушит правило № 1
  3. Структура должна оставаться неизменной во время ее использования, если ее уничтожение не требуется для удовлетворения правила # 1

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

IAbstract
источник
8
Что касается правил Microsoft, то, как представляется, правило об неизменности разработано так, чтобы препятствовать использованию типов значений таким образом, чтобы их поведение отличалось от поведения ссылочных типов, несмотря на тот факт, что кусочно-изменяемая семантика значений может быть полезной . Если наличие типа кусочно-изменяемого облегчило бы работу, и если места хранения типа должны быть логически отделены друг от друга, тип должен быть «изменяемой» структурой.
суперкат
23
Имейте в виду, что только для чтения!
Джастин Морган
2
Тот факт, что многие типы Microsoft нарушают эти правила, не представляет проблемы с этими типами, а скорее указывает на то, что правила не должны применяться ко всем типам структур. Если структура представляет отдельную сущность [как с Decimalили DateTime], то, если она не будет соблюдать другие три правила, она должна быть заменена классом. Если структура содержит фиксированный набор переменных, каждая из которых может содержать любое значение, которое было бы допустимо для ее типа [например Rectangle], тогда она должна соблюдать другие правила, некоторые из которых противоречат правилам для структур с «одним значением» ,
Суперкат
4
@IAbstract: некоторые люди оправдывают Dictionaryтип записи на основании того, что это только внутренний тип, производительность считается более важной, чем семантика, или какое-то другое оправдание. Моя точка зрения заключается в том, что типу типа Rectangleдолжно быть предоставлено его содержимое как редактируемые по отдельности поля не "потому что" преимущества производительности перевешивают возникающие семантические недостатки, а потому что тип семантически представляет фиксированный набор независимых значений , и поэтому изменяемая структура более производительный и семантически превосходящий .
суперкат
2
@supercat: Я согласен ... и весь смысл моего ответа заключался в том, что «руководящие принципы» довольно слабы, и структуры должны использоваться с полным знанием и пониманием поведения. Смотрите мой ответ по изменяемой структуре здесь: stackoverflow.com/questions/8108920/…
IAbstract
155

Всякий раз, когда вам:

  1. не нужен полиморфизм,
  2. хотите семантику значения, и
  3. хочу избежать выделения кучи и связанных с этим издержек сборки мусора.

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

dsimcha
источник
1
Это только одна «оговорка». Также следует учитывать «снятие» значений-типов и таких случаев, как (Guid)null(допустимо приводить ноль к ссылочному типу), среди прочего.
1
дороже, чем в C / C ++? в C ++ рекомендуемый способ - передавать объекты по значению
Ион Тодирел
@IonTodirel Разве это не из соображений безопасности памяти, а не производительности? Это всегда компромисс, но передача 32 B стека всегда (TM) будет медленнее, чем передача ссылки 4 B регистром. Однако также обратите внимание, что использование «значение / ссылка» немного отличается в C # и C ++ - когда вы передаете ссылку на объект, вы все равно передаете по значению, даже если вы передаете ссылку (вы пересылка значения ссылки, а не ссылки на ссылку, в принципе). Это не семантика значения , но технически это «передача по значению».
Луаан
@Luaan Копирование - это только один аспект затрат. Дополнительная косвенность из-за указателя / ссылки также стоит за доступ. В некоторых случаях структура может даже быть перемещена и, таким образом, даже не должна быть скопирована.
Онур
@ Ну, это интересно. Как вы «двигаетесь» без копирования? Я думал, что инструкция asm "mov" на самом деле не "перемещается". Это копирует.
Вингер
148

Я не согласен с правилами, приведенными в оригинальном посте. Вот мои правила:

1) Вы используете структуры для производительности при хранении в массивах. (см. также, Когда составляется ответ? )

2) Они нужны вам при передаче кода структурированных данных в / из C / C ++

3) Не используйте структуры, если они вам не нужны:

  • Они ведут себя не так, как «нормальные объекты» ( ссылочные типы ) при присваивании и при передаче в качестве аргументов, что может привести к неожиданному поведению; это особенно опасно, если человек, смотрящий на код, не знает, что имеет дело со структурой.
  • Они не могут быть унаследованы.
  • Передача структур в качестве аргументов дороже, чем классы.
ILoveFortran
источник
4
+1 Да, я полностью согласен с № 1 (это огромное преимущество при работе с такими вещами, как изображения и т. Д.) И за то, что они указывают на то, что они отличаются от «обычных объектов», и существует известный способ узнать это, кроме как с помощью существующих знаний. или исследуя сам тип. Кроме того, вы не можете привести нулевое значение к типу структуры :-) На самом деле это один из случаев, когда я почти хотел бы, чтобы на сайте объявления переменных было какое-то «венгерское» для неосновных значений или обязательное ключевое слово «struct». ,
@pst: это правда, что нужно знать, что-то struct чтобы знать, как оно будет себя вести, но если что-то structс открытыми полями, это все, что нужно знать. Если объект предоставляет свойство типа открытого поля-структуры, и если код считывает эту структуру в переменную и изменяет ее, можно с уверенностью предсказать, что такое действие не повлияет на объект, свойство которого было прочитано, до тех пор, пока структура не будет записана обратно. Напротив, если бы свойство было изменяемым типом класса, его чтение и изменение могло бы обновить базовый объект, как и ожидалось, но ...
суперкат
... это может также привести к тому, что ничего не изменится, или это может изменить или повредить объекты, которые никто не собирался менять. Наличие кода, семантика которого гласит: «Измените эту переменную так, как вам нравится; изменения не будут делать ничего, пока вы явно не сохраните их где-то», - кажется более понятным, чем наличие кода, который говорит: «Вы получаете ссылку на некоторый объект, который может быть использован совместно с любым числом. других ссылок, или не могут быть переданы вообще; вам придется выяснить, кто еще может иметь ссылки на этот объект, чтобы знать, что произойдет, если вы измените его ".
суперкат
Пятно на # 1. Список, полный структур, может втиснуть гораздо больше релевантных данных в кэши L1 / L2, чем список, полный ссылок на объекты (для правильной структуры размера).
Мэтт Стивенсон,
2
Наследование редко является подходящим инструментом для работы, и слишком много рассуждает о производительности без профилирования. Во-первых, структуры могут быть переданы по ссылке. Во-вторых, передача по ссылке или по значению редко является значительной проблемой производительности. Наконец, вы не учитываете дополнительное выделение кучи и сборку мусора, которые должны выполняться для класса. Лично я предпочитаю рассматривать структуры как простые старые данные и классы как вещи, которые делают вещи (объекты), хотя вы также можете определять методы для структур.
weberc2
88

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

редактировать

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

Если вам нужна ссылочная семантика, вам нужен класс, а не структура.

JoshBerke
источник
13
Все это знают. Похоже, он ищет больше, чем ответ "структура является типом значения".
TheSmurf
21
Это самый простой случай, и его следует указывать всем, кто читает этот пост и не знает этого.
JoshBerke
3
Не то чтобы этот ответ не был правдивым; это очевидно. Дело не в этом.
TheSmurf
55
@Josh: Для тех, кто этого еще не знает, просто сказать, что это недостаточный ответ, поскольку вполне вероятно, что они тоже не знают, что это значит.
TheSmurf
1
Я только что проголосовал за это, потому что я думаю, что один из других ответов должен быть наверху - любой ответ, который говорит: «Для взаимодействия с неуправляемым кодом, иначе избегайте».
Даниэль Уорвикер,
59

В дополнение к ответу «это значение», один конкретный сценарий использования структур - это когда вы знаете, что у вас есть набор данных, вызывающий проблемы со сборкой мусора, и у вас много объектов. Например, большой список / массив экземпляров Person. Естественной метафорой здесь является класс, но если у вас есть большое количество долгоживущих экземпляров Person, они могут в конечном итоге засорить GEN-2 и вызвать остановку GC. Если варранты сценарии, один из возможных подходов здесь использовать массив (не список) Person структура , то есть Person[]. Теперь, вместо миллионов объектов в GEN-2, у вас есть отдельный кусок в LOH (я предполагаю, что здесь нет строк и т. Д., Т.е. чистое значение без каких-либо ссылок). Это имеет очень небольшое влияние GC.

Работать с этими данными неудобно, поскольку данные, вероятно, слишком велики для структуры, и вы не хотите постоянно копировать жирные значения. Однако доступ к нему напрямую в массиве не копирует структуру - она ​​на месте (в отличие от индексатора списка, который копирует). Это означает большую работу с индексами:

int index = ...
int id = peopleArray[index].Id;

Обратите внимание, что поддержание неизменности самих значений поможет здесь. Для более сложной логики используйте метод с параметром by-ref:

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

Опять же, это на месте - мы не скопировали значение.

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

Марк Гравелл
источник
+1 интересный ответ. Хотели бы вы поделиться реальными анекдотами о таком подходе?
Джордао
@Jordao на мобильном, но поиск в Google для: + Gravell + "Штурм GC"
Марк Гравелл
1
Большое спасибо. Я нашел это здесь .
Джордао
2
@MarcGravell Почему вы упомянули: использовать массив (не список) ? ListЯ полагаю, использует Arrayзакулисные. нет?
Рой Намир
4
@RoyiNamir Мне тоже было любопытно, но я полагаю, что ответ лежит во втором абзаце ответа Марка. «Однако доступ к нему напрямую в массиве не копирует структуру - она ​​на месте (в отличие от индексатора списка, который копирует)».
user1323245
40

Из спецификации языка C # :

1.7 Структуры

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

Структуры особенно полезны для небольших структур данных, которые имеют семантику значений. Комплексные числа, точки в системе координат или пары ключ-значение в словаре - все это хорошие примеры структур. Использование структур, а не классов для небольших структур данных, может существенно повлиять на количество выделяемых памяти приложением. Например, следующая программа создает и инициализирует массив из 100 точек. С помощью Point, реализованного в виде класса, создается 101 отдельный объект - один для массива и один для 100 элементов.

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

Альтернатива - сделать Point структурой.

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

Теперь создается только один объект - один для массива - и экземпляры Point хранятся в виде строки в массиве.

Конструкторы Struct вызываются оператором new, но это не означает, что память выделяется. Вместо того, чтобы динамически размещать объект и возвращать ссылку на него, конструктор структуры просто возвращает само значение структуры (обычно во временном месте в стеке), и это значение затем копируется по мере необходимости.

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

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Если Point является классом, результат равен 20, потому что a и b ссылаются на один и тот же объект. Если Point является структурой, выходное значение равно 10, поскольку при присваивании a to b создается копия значения, и на эту копию не влияет последующее присвоение ax

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

Люк Боуган
источник
4
Хотя тот факт, что ссылки на структуры не могут быть сохранены, иногда является ограничением, это также очень полезная характеристика. Одним из основных недостатков .net является то, что не существует достойного способа передать внешнему коду ссылку на изменяемый объект без потери контроля над этим объектом. Напротив, можно безопасно передать внешний метод a refизменяемой структуре и знать, что любые мутации, которые внешний метод выполнит с ним, будут выполнены до его возврата. Это очень плохо. Net не имеет никакого понятия о эфемерных параметрах и возвращаемых значениях функций, поскольку ...
суперкат
4
... это позволило бы достичь преимущественной семантики передаваемых структур refс помощью объектов классов. По сути, локальные переменные, параметры и возвращаемые значения функций могут быть постоянными (по умолчанию), возвратными или эфемерными. Код будет запрещено копировать эфемерные вещи во что-либо, что переживет нынешнюю сферу. Возвращаемые вещи будут похожи на эфемерные, за исключением того, что они могут быть возвращены из функции. Возвращаемое значение функции будет связано с самыми жесткими ограничениями, применимыми к любому из его «возвращаемых» параметров.
суперкат
34

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

Франси Пенов
источник
4
Да, но большие структуры могут быть дороже, чем ссылки на классы (при переходе к методам).
Алекс
27

Вот основное правило.

  • Если все поля-члены являются типами значений, создайте структуру .

  • Если какое-либо одно поле-член является ссылочным типом, создайте класс . Это связано с тем, что поле ссылочного типа в любом случае будет нуждаться в выделении кучи.

Exmaples

public struct MyPoint 
{
    public int X; // Value Type
    public int Y; // Value Type
}

public class MyPointWithName 
{
    public int X; // Value Type
    public int Y; // Value Type
    public string Name; // Reference Type
}
Усман Зафар
источник
3
Неизменяемые ссылочные типы, подобные stringсемантически эквивалентным значениям, и сохранение ссылки на неизменяемый объект в поле не влечет за собой выделения кучи. Разница между структурой с открытыми общественными полями и объектом класса с открытыми общественными полями, что , учитывая последовательность кода var q=p; p.X=4; q.X=5;, p.Xбудет иметь значение 4 , если aэто тип структуры, и 5 , если это тип класса. Если кто-то хочет иметь возможность легко модифицировать элементы типа, следует выбрать «класс» или «структуру» в зависимости от того, хотят ли изменения qповлиять p.
суперкат
Да, я согласен, что ссылочная переменная будет в стеке, но объект, на который она ссылается, будет существовать в куче. Хотя структуры и классы ведут себя по-разному, когда присваиваются другой переменной, но я не думаю, что это решающий фактор.
Усман Зафар
Изменяемые структуры и изменяемые классы ведут себя совершенно по-разному; если один прав, другой скорее всего будет неправ. Я не уверен, что поведение не будет решающим фактором при определении, использовать ли структуру или класс.
суперкат
Я сказал, что это не является решающим фактором, потому что часто, когда вы создаете класс или структуру, вы не знаете, как она будет использоваться. Таким образом, вы концентрируетесь на том, как вещи имеют больше смысла с точки зрения дизайна. В любом случае, я никогда не видел ни одного места в библиотеке .NET, где структура содержит ссылочную переменную.
Усман Зафар
1
Тип структуры ArraySegment<T>инкапсулирует a T[], который всегда является типом класса. Тип структуры KeyValuePair<TKey,TValue>часто используется с типами классов в качестве общих параметров.
суперкат
19

Первое: взаимодействие сценариев или когда вам нужно указать расположение памяти

Второе: когда данные в любом случае имеют почти такой же размер, что и ссылочный указатель.

ДО Н.Э.
источник
17

Вам необходимо использовать "struct" в ситуациях, когда вы хотите явно указать макет памяти с помощью StructLayoutAttribute - обычно для PInvoke.

Редактировать: Комментарий указывает, что вы можете использовать класс или структуру со StructLayoutAttribute, и это, безусловно, верно. На практике вы обычно используете struct - она ​​распределяется в стеке, а не в куче, что имеет смысл, если вы просто передаете аргумент в вызов неуправляемого метода.

Морис Фланаган
источник
5
Атрибут StructLayoutAttribute можно применять к структурам или классам, поэтому это не является причиной для использования структур.
Стивен Мартин
Почему имеет смысл, если вы просто передаете аргумент в вызов неуправляемого метода?
Дэвид Клемпфнер
16

Я использую структуры для упаковки или распаковки любого двоичного формата связи. Это включает чтение или запись на диск, списки вершин DirectX, сетевые протоколы или работу с зашифрованными / сжатыми данными.

Три указанных вами руководства не были полезны для меня в этом контексте. Когда мне нужно выписать четыреста байтов материала в определенном порядке, я определю структуру размером четыреста байтов и заполню ее любыми несвязанными значениями, которые она должна иметь, и я собираюсь установить его любым способом, который имеет больше всего смысла в то время. (Хорошо, четыреста байтов было бы довольно странно - но когда я писал для жизни файлы Excel, я имел дело со структурами размером до сорока байтов всего, потому что это то, насколько велики некоторые записи BIFF.)

mjfgates
источник
Не могли бы вы так же легко использовать ссылочный тип для этого?
Дэвид Клемпфнер
15

За исключением типов значений, которые используются непосредственно средой выполнения и различными другими для целей PInvoke, значения типов типов следует использовать только в 2 сценариях.

  1. Когда вам нужно скопировать семантику.
  2. Когда вам нужна автоматическая инициализация, обычно в массивах этих типов.
leppie
источник
# 2 , кажется, часть из причин распространенности структуры в коллекции классов .Net ..
IAbstract
Если первое, что нужно сделать при создании места хранения типа класса, это создать новый экземпляр этого типа, сохранить ссылку на него в этом месте и никогда не копировать ссылку где-либо еще и не перезаписывать ее, тогда структура и класс будет вести себя одинаково. Структуры имеют удобный стандартный способ копирования всех полей из одного экземпляра в другой и, как правило, обеспечивают более высокую производительность в тех случаях, когда никогда не будет дублироваться ссылка на класс (за исключением эфемерного thisпараметра, используемого для вызова его методов); классы позволяют дублировать ссылки.
Суперкат
13

.NET поддерживает value typesи reference types(в Java вы можете определять только ссылочные типы). Экземпляры reference typesget выделяются в управляемой куче и собираются, когда на них нет ожидающих ссылок. Экземпляры value types, с другой стороны, выделяются в stack, и, следовательно, выделенная память восстанавливается, как только заканчивается их область действия. И, конечно же, value typesпередать по значению, иreference types по ссылке. Все примитивные типы данных C #, за исключением System.String, являются типами значений.

Когда использовать структуру над классом,

В C # structsесть value typesклассы reference types. Вы можете создавать типы значений в C #, используя enumключевое слово и structключевое слово. Использование value typeвместо a reference typeприведет к уменьшению количества объектов в управляемой куче, что приведет к меньшей нагрузке на сборщик мусора (GC), менее частым циклам GC и, следовательно, к повышению производительности. Впрочем, value typesесть и свои минусы тоже. Передача большого числа structопределенно обходится дороже, чем передача ссылки, это одна очевидная проблема. Другая проблема связана с накладными расходами boxing/unboxing. Если вам интересно, что boxing/unboxingозначает, перейдите по этим ссылкам для хорошего объясненияboxing иunboxing . Помимо производительности, бывают случаи, когда вам просто нужно, чтобы типы имели семантику значений, что было бы очень сложно (или уродливо) реализовать, еслиreference typesвсе, что у вас есть. Вы должны использовать value typesтолько, когда вам нужна семантика копирования или требуется автоматическая инициализация, обычно в arraysэтих типах.

Sujit
источник
Копирование небольших структур или передача по значению так же дешевы, как копирование или передача ссылки на класс, или передача структур по ref. Передача любой структуры размера по refзатратам аналогична передаче ссылки на класс по значению. Копирование структуры любого размера или передача по значению дешевле, чем выполнение защитной копии объекта класса и сохранение или передача ссылки на него. Классы больших времен лучше, чем структуры для хранения значений: (1) когда классы неизменны (чтобы избежать защитного копирования), и каждый созданный экземпляр будет много передаваться, или ...
суперкат
... (2) когда по разным причинам структура просто не может быть использована (например, потому что нужно использовать вложенные ссылки для чего-то вроде дерева или потому что нужен полиморфизм). Обратите внимание, что при использовании типов значений, как правило, следует открывать поля, напрямую отсутствующие по определенной причине (в то время как в большинстве типов классов поля должны быть заключены в свойства). Многие из так называемых «зол» изменчивых типов значений происходят из-за ненужного переноса полей в свойствах (например, в то время как некоторые компиляторы позволяют вызывать установщик свойств в структуре только для чтения, потому что это иногда ...
суперкат
... сделайте правильно, все компиляторы будут правильно отклонять попытки напрямую устанавливать поля в таких структурах; лучший способ гарантировать отклонение компиляторами readOnlyStruct.someMember = 5;- не создавать someMemberсвойство только для чтения, а вместо этого сделать его полем.
суперкат
12

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

public struct IntStruct {
    public int Value {get; set;}
}

Исключение следующих результатов приводит к 5 экземплярам структуры, хранящейся в памяти:

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

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

public class IntClass {
    public int Value {get; set;}
}

Исключение следующих результатов приводит только к одному экземпляру объекта класса в памяти.

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

Struct s может увеличить вероятность ошибки кода. Если объект значения обрабатывается как изменяемый ссылочный объект, разработчик может удивиться, когда сделанные изменения будут неожиданно потеряны.

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1
Джейсон Уильямс
источник
12

Я сделал небольшой тест с BenchmarkDotNet чтобы лучше понять преимущества "struct" в числах. Я тестирую циклический просмотр массива (или списка) структур (или классов). Создание этих массивов или списков выходит за рамки теста - ясно, что более тяжелый «класс» будет использовать больше памяти и будет включать GC.

Итак, вывод таков: будьте осторожны с LINQ и боксом / распаковкой скрытых структур, а использование структур для микрооптимизации строго придерживается массивов.

PS Есть еще один тест для прохождения struct / class через стек вызовов https://stackoverflow.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

Код:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }
Роман Покровский
источник
Вы выяснили, почему структуры намного медленнее, когда используются в списках и тому подобное? Вы упомянули скрытый бокс и распаковку? Если так, то почему это происходит?
Марко Грдинич,
Доступ к структуре в массиве должен быть быстрее только потому, что не требуется никаких дополнительных ссылок. Бокс / Распаковка это случай для linq.
Роман Покровский
10

Структурные типы в C # или других языках .net обычно используются для хранения вещей, которые должны вести себя как группы значений фиксированного размера. Полезный аспект типов структуры состоит в том, что поля экземпляра типа структуры могут быть изменены путем изменения места хранения, в котором он хранится, и никаким другим способом. Можно закодировать структуру таким образом, что единственный способ изменить любое поле - это создать совершенно новый экземпляр, а затем использовать присвоение структуры, чтобы изменить все поля цели, перезаписав их значениями из нового экземпляра, но если структура не предоставляет средств для создания экземпляра, где ее поля имеют значения не по умолчанию, все ее поля будут изменяемыми, если и если сама структура хранится в изменяемой папке.

Обратите внимание, что можно спроектировать тип структуры так, чтобы он по существу вел себя как тип класса, если структура содержит частное поле типа класса и перенаправляет своих собственных членов на объект обернутого класса. Например, a PersonCollectionможет предлагать свойства SortedByNameи SortedById, оба из которых содержат «неизменяемую» ссылку на PersonCollection(установленную в их конструкторе) и реализуют GetEnumerator, вызывая либо creator.GetNameSortedEnumeratoror, либо creator.GetIdSortedEnumerator. Такие структуры будут вести себя как ссылка на a PersonCollection, за исключением того, что их GetEnumeratorметоды будут связаны с различными методами в PersonCollection. Можно также иметь структуру для переноса части массива (например, можно определитьArrayRange<T> структуру, которая будет содержать T[]вызываемый Arr, intOffset и intLength, с индексированным свойством, которое для индекса idxв диапазоне от 0 до Length-1будет иметь доступ Arr[idx+Offset]). К сожалению, если fooэкземпляр такой структуры предназначен только для чтения, текущие версии компилятора не допускают таких операций, как, foo[3]+=4;потому что у них нет способа определить, будут ли такие операции пытаться записывать в поля foo.

Также возможно спроектировать структуру, которая будет вести себя как тип значения, который содержит коллекцию переменного размера (которая будет копироваться всякий раз, когда структура является структурой), но единственный способ выполнить эту работу - убедиться, что ни один объект, которому Структура содержит ссылку, которая будет когда-либо подвергаться воздействию всего, что может ее изменить. Например, можно иметь массивоподобную структуру, которая содержит закрытый массив, и чей индексированный метод «put» создает новый массив, содержимое которого аналогично содержимому оригинала, за исключением одного измененного элемента. К сожалению, может быть довольно сложно заставить такие структуры работать эффективно. Хотя бывают случаи, когда семантика структуры может быть удобной (например, возможность передавать массивоподобную коллекцию в подпрограмму, когда вызывающая сторона и вызываемая сторона знают, что внешний код не изменит коллекцию,

Supercat
источник
10

Нет, я не совсем согласен с правилами. Это хорошие рекомендации для оценки производительности и стандартизации, но не в свете возможностей.

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

В этом случае я использую классы для представления объектов реального мира в их большей форме, я использую структуры для представления более мелких объектов, которые имеют более точное использование. То, как вы это сказали, «более сплоченное целое». Ключевое слово является связным. Классы будут более объектно-ориентированными элементами, в то время как структуры могут иметь некоторые из этих характеристик, хотя и в меньшем масштабе. ИМО.

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

Я стараюсь держать их маленькими, использую их в единичных ситуациях и не допускаю их изменения. Целесообразно осознавать память, распределение и производительность. И тестирование так необходимо.

SnapJag
источник
Структуры могут разумно использоваться для представления легких неизменяемых объектов, или они могут разумно использоваться для представления фиксированных наборов связанных, но независимых переменных (например, координат точки). Рекомендации на этой странице хороши для структур, которые предназначены для использования в первой цели, но не подходят для структур, которые предназначены для выполнения второй цели. Мое нынешнее мнение состоит в том, что структуры, которые имеют какие-либо частные поля, должны в целом соответствовать указанному описанию, но многие структуры должны раскрывать все свое состояние через открытые поля.
суперкат
Если спецификация для типа «3d-точка» указывает на то, что все ее состояние доступно через читаемые элементы x, y и z, и возможно создать экземпляр с любой комбинацией doubleзначений для этих координат, такая спецификация заставит его ведут себя семантически идентично структуре с открытым полем, за исключением некоторых деталей многопоточного поведения (в некоторых случаях неизменный класс был бы лучше, в то время как структура с открытым полем была бы лучше в других; так называемая «неизменяемая» структура быть хуже в каждом случае).
суперкат
8

Мое правило

1, всегда используйте класс;

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

rockXrock
источник
Существенный вариант использования, который Microsoft игнорирует, - это когда кто-то хочет, чтобы переменная типа Fooинкапсулировала фиксированный набор независимых значений (например, координат точки), которые иногда нужно передавать как группа, а иногда - независимо. Я не нашел ни одного шаблона для использования классов, который бы сочетал обе цели почти так же хорошо, как простая структура с открытыми полями (которая, будучи фиксированной коллекцией независимых переменных, идеально подходит для этой цели).
суперкат
1
@supercat: Я думаю, что не совсем справедливо обвинять в этом Microsoft. Реальная проблема здесь заключается в том, что C # как объектно-ориентированный язык просто не фокусируется на простых типах записей, которые только представляют данные без особого поведения. C # не является мультипарадигмальным языком в той же степени, как, например, C ++. При этом я также считаю, что очень немногие программируют чистый ООП, поэтому, возможно, C # - слишком идеалистический язык. (Я, например, недавно тоже начал выставлять public readonlyполя в моих типах, потому что создание свойств, доступных только для чтения, - это слишком много работы, практически бесполезной.)
stakx - больше не вносит вклад
1
@stakx: ему не нужно «фокусироваться» на таких типах; достаточно признать их такими, какие они есть. Самым большим недостатком C # в отношении структур является его самая большая проблема и во многих других областях: язык предоставляет неадекватные средства для указания того, когда определенные преобразования подходят или не подходят, а отсутствие таких средств приводит к неудачным проектным решениям. Так , например, 99% от «изменяемые структуры являются злом» проистекает из компилятора превращения MyListOfPoint[3].Offset(2,3);в var temp=MyListOfPoint[3]; temp.Offset(2,3);, преобразование , которое является поддельным , когда применяется ...
Supercat
... к Offsetметоду. Правильный способ предотвращения такого поддельного кода должен состоять не в том, чтобы сделать структуры без необходимости неизменяемыми, а в том, чтобы позволить таким методам, как Offsetтеги, быть помечены атрибутом, запрещающим вышеупомянутое преобразование. Неявные числовые преобразования тоже могли бы быть намного лучше, если бы их можно было пометить так, чтобы они были применимы только в тех случаях, когда их вызов был бы очевиден. Если существуют перегрузки для foo(float,float)и foo(double,double), я бы сказал, что попытка использовать a floatи doubleчасто не должна применять неявное преобразование, а должна быть ошибкой.
суперкат
Прямое присвоение doubleзначения floatили передача его методу, который может принимать floatаргумент, но не doubleможет, почти всегда делает то, что задумал программист. В отличие от этого, присваивание floatвыражения doubleбез явного преобразования типов часто является ошибкой. Единственное время, позволяющее неявное double->floatпреобразование, может вызвать проблемы, - это когда будет выбрана не идеальная перегрузка. Я бы сказал, что правильный способ предотвратить это не должен был запрещать implcit double-> float, но помечать перегрузки атрибутами, чтобы запретить преобразование.
суперкат
8

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

Классы и структуры (Руководство по программированию в C #)

J_hajian_nzd
источник
Структуры также очень хороши в случаях, когда необходимо скрепить несколько связанных, но независимых переменных вместе с клейкой лентой (например, координаты точки). Рекомендации MSDN разумны, если кто-то пытается создать структуры, которые ведут себя как объекты, но гораздо менее уместны при проектировании агрегатов; некоторые из них почти точно не правы в последней ситуации. Например, чем выше степень независимости переменных, инкапсулированных в тип, тем больше преимущество использования структуры с открытым полем, а не неизменяемого класса.
суперкат
6

МИФ № 1: СТРУКТЫ - ЛЕГКИЕ КЛАССЫ

Этот миф бывает разных форм. Некоторые люди считают, что типы значений не могут или не должны иметь методы или другое существенное поведение - их следует использовать как простые типы передачи данных, просто с открытыми полями или простыми свойствами. Тип DateTime является хорошим контрпримером к этому: имеет смысл для него быть типом значения с точки зрения фундаментальной единицы, такой как число или символ, и также имеет смысл иметь возможность выполнять вычисления на основе его ценность. Если посмотреть на вещи с другой стороны, типы передачи данных часто должны быть ссылочными типами, так как решение должно основываться на желаемом значении или семантике ссылочного типа, а не на простоте типа. Другие люди считают, что типы значений «легче», чем ссылочные типы с точки зрения производительности. Правда в том, что в некоторых случаях типы значений более производительны - им не требуется сборка мусора, если они не упакованы, не имеют накладных расходов на идентификацию типов и, например, не требуют разыменования. Но в других отношениях ссылочные типы более производительны - передача параметров, присвоение значений переменным, возвращение значений и аналогичные операции требуют только 4 или 8 байтов для копирования (в зависимости от того, используете ли вы 32-разрядный или 64-разрядный CLR ) вместо того, чтобы копировать все данные. Представьте, что ArrayList был каким-то «чистым» типом значения, и передача выражения ArrayList методу включала бы копирование всех его данных! Практически во всех случаях производительность не зависит от такого решения в любом случае. Узкие места почти никогда не бывают такими, какими вы их считаете, и прежде чем вы примете проектное решение, основанное на производительности, Вы должны измерить различные варианты. Стоит отметить, что сочетание двух убеждений тоже не работает. Не имеет значения, сколько методов имеет тип (будь то класс или структура) - память, взятая для каждого экземпляра, не затрагивается. (С точки зрения памяти, затрачиваемой на сам код, существует определенная стоимость, но это происходит один раз, а не для каждого экземпляра.)

МИФ № 2: СПРАВОЧНЫЕ ТИПЫ ЖИТЬ НА КАРТЕ; ЦЕННЫЕ ТИПЫ ЖИТЬ НА СТЕКЕ

Это часто вызвано ленью со стороны человека, повторяющего это. Первая часть верна - экземпляр ссылочного типа всегда создается в куче. Это вторая часть, которая вызывает проблемы. Как я уже отмечал, значение переменной живет везде, где она объявлена, поэтому, если у вас есть класс с переменной экземпляра типа int, значение этой переменной для любого данного объекта всегда будет там, где находятся остальные данные для объекта - в кучу. В стеке живут только локальные переменные (переменные, объявленные в методах) и параметры метода. В C # 2 и более поздних версиях даже некоторые локальные переменные в действительности не живут в стеке, как вы увидите, когда мы рассмотрим анонимные методы в главе 5. СООТВЕТСТВУЮТ ЛИ ЭТИ КОНЦЕПЦИИ СЕЙЧАС? Можно утверждать, что если вы пишете управляемый код, вы должны позволить среде выполнения беспокоиться о том, как лучше всего использовать память. Верно, спецификация языка не дает никаких гарантий относительно того, что и где живет; будущая среда выполнения может создать некоторые объекты в стеке, если знает, что это сойдет с рук, или компилятор C # может сгенерировать код, который вообще не использует стек. Следующий миф обычно является просто вопросом терминологии.

Миф № 3: объекты передаются по ссылке в C # по умолчанию

Это, наверное, самый распространенный миф. Опять же, люди, которые делают это заявление часто (хотя и не всегда), знают, как на самом деле ведет себя C #, но они не знают, что на самом деле означает «передача по ссылке». К сожалению, это сбивает с толку людей, которые знают, что это значит. Формальное определение передачи по ссылке является относительно сложным, включающим l-значения и похожую компьютерную терминологию, но важно то, что если вы передаете переменную по ссылке, вызываемый вами метод может изменить значение переменной вызывающей стороны изменив значение параметра. Теперь запомните, что значением переменной ссылочного типа является ссылка, а не сам объект. Вы можете изменить содержимое объекта, на который ссылается параметр, без передачи самого параметра по ссылке. Например,

void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}

Когда этот метод вызывается, значение параметра (ссылка на StringBuilder) передается по значению. Если бы вы изменили значение переменной построителя внутри метода - например, с помощью оператора builder = null; - это изменение не было бы замечено вызывающей стороной, вопреки мифу. Интересно отметить, что не только бит «по ссылке» мифа неточен, но и бит «объекты переданы». Сами объекты никогда не передаются ни по ссылке, ни по значению. Когда задействован ссылочный тип, либо переменная передается по ссылке, либо значение аргумента (ссылка) передается по значению. Помимо всего прочего, это отвечает на вопрос о том, что происходит, когда null используется в качестве аргумента по значению - если объекты передавались, это вызывало бы проблемы, поскольку не было бы объекта для передачи! Вместо, нулевая ссылка передается по значению так же, как и любая другая ссылка. Если это быстрое объяснение оставило вас в замешательстве, вы можете посмотреть мою статью «Передача параметров в C #» (http://mng.bz/otVt ), в котором речь пойдет гораздо подробнее. Эти мифы не единственные вокруг. Бокс и распаковка приходят из-за их значительного недопонимания, которое я постараюсь прояснить позже.

Ссылка: C # в глубине 3-е издание Джон Скит

хабиб
источник
1
Очень хорошо, если вы правы. Также очень хорошо добавить ссылку.
NoChance
5

Я думаю, что хорошее первое приближение - «никогда».

Я думаю, что хорошее второе приближение - «никогда».

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

Брайан
источник
24
Я бы не согласился с этим ответом. Структуры имеют законное использование во многих сценариях. Вот пример - маршалинг данных пересекает процессы атомарным способом.
Франси Пенов
25
Вы должны отредактировать свой пост и уточнить свои пункты - вы высказали свое мнение, но вы должны подтвердить это тем, почему вы принимаете это мнение.
Эрик Форбс
4
Я думаю, что им нужен эквивалент карты Тотин Чип ( en.wikipedia.org/wiki/Totin%27_Chip ) для использования структур. Шутки в сторону.
Грег
4
Как 87.5K человек публикует такой ответ? Он делал это, когда был ребенком?
Рохит Випин Мэтьюз
3
@Rohit - это было шесть лет назад; Стандарты сайта были очень разные тогда. Это все еще плохой ответ, но вы правы.
Эндрю Арнольд
5

Я имел дело с именованным каналом Windows Communication Foundation [WCF] и заметил, что имеет смысл использовать Structs, чтобы гарантировать, что обмен данными имеет тип значения вместо ссылочного типа .

N_E
источник
1
Это лучшая подсказка из всех, ИМХО.
Иван
4

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

Таким образом, вы должны использовать структуры, когда вы хотите представить более простые структуры данных, особенно если вы знаете, что будете создавать их экземпляры. Существует множество примеров в .NET Framework, где Microsoft использует структуры вместо классов, например структуру Point, Rectangle и Color.

Саид Дини
источник
3

Struct может быть использован для повышения производительности сборки мусора. Хотя вам обычно не нужно беспокоиться о производительности GC, есть сценарии, где это может быть убийственно. Как большие кэши в приложениях с низкой задержкой. Смотрите этот пост для примера:

http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/

Кролик
источник
3

Типы структуры или значения могут использоваться в следующих сценариях:

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

Вы можете узнать больше о типах значений и типах значений здесь по этой ссылке

Викрам
источник
3

Вкратце, используйте struct, если:

1- свойства / поля вашего объекта не нужно менять. Я имею в виду, вы просто хотите дать им начальное значение, а затем прочитать их.

2- свойства и поля в вашем объекте являются типом значения, и они не так велики.

В этом случае вы можете воспользоваться преимуществами структур для повышения производительности и оптимизации распределения памяти, поскольку они используют только стеки, а не стеки и кучи (в классах).

akazemis
источник
2

Я редко использую структуру для вещей. Но это только я. Это зависит от того, нужен ли мне объект или нет.

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

Флексо
источник
-11

Структуры во многом похожи на классы / объекты. Структура может содержать функции, члены и может быть унаследована. Но структуры в C # используются только для хранения данных . Структуры занимают меньше ОЗУ, чем классы, и их легче собирать сборщику мусора . Но когда вы используете функции в своей структуре, компилятор фактически принимает эту структуру очень похоже на класс / объект, поэтому, если вам нужно что-то с функциями, используйте класс / объект .

Крио Эргер
источник
2
Структуры НЕ могут быть унаследованы, см. Msdn.microsoft.com/en-us/library/0taef578.aspx
HimBromBeere