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

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

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

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

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


person Alex Baranosky    schedule 06.02.2009    source источник
comment
Я могу согласиться только с первым пунктом, например, структуры очень часто используются в программировании игр.   -  person BlackTigerX    schedule 06.02.2009
comment
System.Drawing.Rectangle нарушает все три правила.   -  person ChrisW    schedule 06.02.2009
comment
Да, по крайней мере, их части. Я знаю, что он используется в некоторых играх, таких как NWN2 World Creator. C по-прежнему обычно находится в ядре (движке). XNA Game Studio, погуглите :)   -  person Refracted Paladin    schedule 06.02.2009
comment
есть довольно много коммерческих игр, написанных на C #, дело в том, что они используются для оптимизированного кода   -  person BlackTigerX    schedule 06.02.2009
comment
Иногда я использую их для перечислений со строковыми значениями.   -  person Ishmael    schedule 06.02.2009
comment
Другая ситуация, когда вам следует использовать структуры, - это когда вашим объектам не нужна идентичность.   -  person Andrei Rînea    schedule 04.03.2009
comment
Я всегда думал во второй редакции вопроса: если у меня есть несколько связанных типов значений, я создам структуру. У меня может быть несколько таких небольших единиц данных, и я действительно не хочу создавать кучу дополнительных файлов классов для всего лишь нескольких десятков строк кода.   -  person Chris Tybur    schedule 04.03.2009
comment
Вы используете структуры, когда не хотите наследовать свой класс. Это замечательно, если вы хотите создавать объекты, содержащие только данные.   -  person    schedule 13.05.2010
comment
Структуры обеспечивают лучшую производительность, когда у вас есть небольшие коллекции типов значений, которые вы хотите сгруппировать. Это происходит постоянно в игровом программировании, например, вершина в 3D-модели будет иметь положение, координату текстуры и нормаль, она также обычно будет неизменной. Одна модель может иметь пару тысяч вершин или дюжину, но структуры обеспечивают меньше накладных расходов в этом сценарии использования. Я проверил это на собственном дизайне двигателя.   -  person Chris D.    schedule 21.07.2010
comment
Я бы использовал структуры для объединения неуправляемого кода и облегченных наборов данных статического типа. Компромисс между увеличением производительности за счет управления памятью и отсутствием расширяемости в дизайне.   -  person    schedule 22.03.2011
comment
Это очень неправильная причина выбирать структуру вместо класса   -  person Marc Gravell    schedule 22.10.2011
comment
@ErikForbes: я думаю, что это обычно считается самым большим упущением BCL   -  person    schedule 02.03.2012
comment
@ChrisW: Самый большой недостаток Rectangle в том, что его члены являются свойствами, а не полями. Использование свойств struct, когда полей было бы достаточно, вероятно, на 95% ответственно за изменяемые структуры, является злым понятием (есть законное использование для установщиков свойств в экземплярах структур только для чтения; из-за этого C # только недавно начал запрещать их использование (исключая законные варианты использования); структуры, которые обертывали члены в свойствах чтения и записи, эффективно превращали код, который должен был и должен был генерировать ошибки компилятора, в код, который будет компилироваться, но не работать.   -  person supercat    schedule 24.12.2012
comment
возможный дубликат Когда следует использовать структуру вместо класс?   -  person nawfal    schedule 27.05.2013
comment
Интересный момент может быть: Когда мне следует использовать структуры с методами, потому что каждый метод суммирует на 1 слово (16 байт) больше в ссылках .. так что, очевидно, эта структура будет достаточно тяжелой, чтобы нарушить правило № 2 и служебную программу производительности.   -  person boctulus    schedule 23.12.2014
comment
@ChrisW Разве System.Drawing.Rectangle не представляет собой одно значение? Не могли бы вы объяснить это?   -  person Marson Mao    schedule 28.07.2015
comment
@MarsonMao Он представляет собой левую, правую, верхнюю, нижнюю, высоту и ширину (т.е. 6 значений, по общему признанию взаимосвязанных значений).   -  person ChrisW    schedule 28.07.2015
comment
@ChrisW Я вижу, но разве эти значения не представляют собой прямоугольник, то есть одно значение? Как и Vector3D или Color, они также представляют собой несколько значений внутри, но я думаю, что они представляют собой отдельные значения?   -  person Marson Mao    schedule 29.07.2015
comment
@MarsonMao Одной из определяющих характеристик структуры является то, что это группа связанных значений, которые следует рассматривать как одно значение. Прямоугольник, безусловно, удовлетворяет этой черте. Я думаю, что Крис пытается придраться к формулировке этого пункта.   -  person weberc2    schedule 20.11.2015
comment
Обе ссылки не работают.   -  person Pontus Magnusson    schedule 25.07.2016
comment
@ weberc2 Крис говорит, что этот список не следует использовать для определения, следует ли использовать структуру. Контрпример - Rectangle: что-то, что определенно должно быть структурой, но нарушает все это. Таким образом, это не очень хорошие условия.   -  person Wolfzoon    schedule 11.09.2016


Ответы (30)


Источник, на который ссылается 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 Dictionary использует структуру Enumerator, которую он копирует каждый раз, когда запрашивается перечислитель ... имеет смысл.
  4. Внутри класса Dictionary. 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 события, которое я добавил в источник; тем не менее, все еще впечатляет заполнение 300k целых чисел при 15-кратном изменении размера во время операции. Просто из любопытства, сколько бы было времени на заполнение, если бы я уже знал емкость? 13 мс

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

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

Очевидно, большая разница в изменении размера. Есть ли разница, если словарь инициализирован с помощью емкости? Недостаточно, чтобы беспокоиться о ... 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: Structure Design < / а>. Суммируя:

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

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

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

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

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

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

person IAbstract    schedule 07.08.2011
comment
Я не думаю, что среда выполнения когда-либо вызывает какой-либо конструктор структуры при создании массива структур. Причина, по которой большинство языков запрещает структурам иметь конструктор по умолчанию, заключается не в том, что их вызов сделает построение массива неэффективным, а в том, что в некоторых случаях вызовет конструкторы структур, но не в других. - person supercat; 23.07.2012
comment
Что касается правил Microsoft, правило о неизменяемости, по-видимому, предназначено для предотвращения использования типов значений таким образом, чтобы их поведение отличалось от поведения ссылочных типов, несмотря на то, что кусочно-изменяемая семантика значений может быть полезной. . Если наличие кусочно-изменяемого типа упростило бы работу, и если места хранения типа должны быть логически отделены друг от друга, тип должен быть изменяемой структурой. - person supercat; 23.07.2012
comment
Имейте в виду, что только для чтения! = неизменяемый. - person Justin Morgan; 03.10.2012
comment
Тот факт, что многие типы Microsoft нарушают эти правила, не представляет проблемы для этих типов, а скорее указывает на то, что правила не должны применяться ко всем типам структур. Если структура представляет собой единый объект [как с Decimal или DateTime], то, если она не подчиняется трем другим правилам, ее следует заменить классом. Если структура содержит фиксированный набор переменных, каждая из которых может содержать любое значение, допустимое для ее типа [например, Rectangle], то он должен подчиняться другим правилам, некоторые из которых противоречат правилам для однозначных структур. - person supercat; 06.03.2013
comment
@supercat: ты неплохо резюмировал мою диссертацию в небольшом абзаце :) - person IAbstract; 06.03.2013
comment
@IAbstract: Некоторые люди оправдывают тип записи Dictionary на том основании, что это только внутренний тип, производительность считается более важной, чем семантика, или какое-то другое оправдание. Я хочу сказать, что для типа, подобного Rectangle, его содержимое должно отображаться как индивидуально редактируемые поля не потому, что преимущества производительности перевешивают возникающие семантические недостатки, а потому, что тип семантически представляет фиксированный набор независимых значений, и поэтому изменяемая структура более производительна и семантически лучше. - person supercat; 06.03.2013
comment
@IAbstract: Если бы у меня была возможность изменить один ранний документ из .net, вероятно, это был бы документ о структурах. Если бы я писал этот документ, я бы посоветовал, чтобы структуры, насколько это возможно, соответствовали одному из двух шаблонов - либо описанному, либо совокупности независимых общедоступных полей. В случаях, когда последнее является тем, что необходимо, я считаю безумием предлагать, чтобы структуры притворялись неизменными объектами класса, которые могут делать только неловко то, что хорошо могут делать структуры с открытым полем. - person supercat; 06.03.2013
comment
@supercat: Я согласен ... и весь смысл моего ответа заключался в том, что «руководящие принципы» довольно слабые и структуры следует использовать с полным знанием и пониманием поведения. См. Мой ответ о изменяемой структуре здесь: stackoverflow.com/questions/8108920/ - person IAbstract; 06.03.2013
comment
@IAbstract: Ага. Моя точка зрения заключалась в том, что неизменяемые структуры - это злые люди, которые делают исключение для типов, критичных к производительности только для внутреннего использования, таких как те, на которые вы ссылаетесь, но настоящее исключение должно быть намного шире. Кстати, вам нравится мой комментарий .net - это не Java? - person supercat; 06.03.2013
comment
это интересно, если вы сделаете контраст с C / C ++, где рекомендуемый способ - передавать объекты по значению - person ; 09.07.2013
comment
@IonTodirel: если бы у меня была квалификация, чтобы сделать контраст, я бы сделал это. :) Я недостаточно хорошо знаю C / C ++, чтобы туда идти. Однако именно поэтому вопрос помечен для C #. - person IAbstract; 09.07.2013
comment
@IonTodirel: я думал, что рекомендуемый подход C ++ заключается в передаче значений объекта либо по ссылке, либо по константной ссылке; подход C # при использовании типов классов эквивалентен передаче указателей на объекты, что игнорируется в C ++ в тех случаях, когда в этом нет необходимости. - person supercat; 25.07.2013
comment
@supercat, конечно, я имел в виду особые случаи, когда вам нужно переносить копии из функции / потока в другой, потому что оригинал подлежит уничтожению или признанию недействительным - person ; 26.07.2013
comment
@IonTodirel: В этом есть смысл. Настоящий ключ здесь в том, что в случаях, когда объект определяет разумные средства копирования, такие средства являются особенностями языка, а не несовместимо реализованным интерфейсом. - person supercat; 26.07.2013
comment
Интересно, влияет ли растущая тенденция к 64-битной архитектуре на 16-байтовый совет Microsoft? Если я скомпилирую структуру в dll с 32-разрядной версией, все получится как 4-байтовые члены. Если я скомпилирую его и скажу Visual Studio нацелить на любой процессор, все получится как 8-байтовые элементы. Я думаю, что технически Microsoft имела в виду не более 16 байтов, а не менее 16 байтов, но общее практическое правило, которому люди должны следовать (независимо от того, советует ли Microsoft или нет), гласит, что нужно просто использовать структуры с четырьмя или менее члены, являются ли они ссылками, значениями или и тем, и другим? - person Panzercrisis; 21.04.2016
comment
@Panzercrisis: Технически Microsoft имела в виду не более, чем 16 байт ... Я согласен, что это могло быть так. Если поддерживается как тип Value, то следование практическому правилу приведет к получению 4 или менее членов. Если структура приведена к интерфейсу (становится типом Reference), я бы лично полностью проигнорировал практическое правило. Это вполне может быть причиной того, что перечислитель Dictionary имеет элементы ссылочного типа и, безусловно, превышает 16 байт. - person IAbstract; 21.04.2016
comment
Спасибо за отличную информацию. Я бы добавил, что, хотя это правда, что вызов GetEnumerator, хотя перечислитель переместит его в кучу, на практике не произойдет, если вы вручную не примените его к интерфейсу. Компилятор C # не будет использовать интерфейс, если он не сможет найти методы с подходящими именами и соответствующими сигнатурами, в частности, чтобы избежать упаковки. Таким образом, они не нарушили здесь правило №4. Чтобы этого избежать, они изменили сам компилятор. - person Daniel; 06.05.2021

Когда бы ты не:

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

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

person dsimcha    schedule 06.02.2009
comment
Это только одна оговорка. Следует также рассмотреть возможность отмены типов значений и случаев, таких как (Guid)null (можно привести нулевое значение к ссылочному типу), среди прочего. - person ; 27.01.2011
comment
дороже, чем в C / C ++? в C ++ рекомендуется передавать объекты по значению - person ; 09.07.2013
comment
@IonTodirel Разве это не из соображений безопасности памяти, а не из-за производительности? Это всегда компромисс, но передача 32 Б по стеку всегда (TM) будет медленнее, чем передача ссылки 4 Б по регистру. Однако также обратите внимание, что использование значения / ссылки немного отличается в C # и C ++ - когда вы передаете ссылку на объект, вы все равно передаете по значению, даже если вы передаете ссылка (вы передаете значение ссылки, а не ссылку на нее, в основном). Это не семантика значения, а технически передача по значению. - person Luaan; 23.11.2015
comment
@Luaan Копирование - это только один аспект затрат. Дополнительное косвенное обращение из-за указателя / ссылки также стоит за доступ. В некоторых случаях структуру можно даже переместить, и поэтому ее даже не нужно копировать. - person Onur; 19.02.2016

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

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

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

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

  • Они ведут себя не так, как «обычные объекты» (ссылочные типы) при назначении и при передаче в качестве аргументов, что может привести к неожиданному поведению; это особенно опасно, если человек, просматривающий код, не знает, что имеет дело со структурой.
  • Они не могут передаваться по наследству.
  • Передача структур в качестве аргументов дороже классов.
person ILoveFortran    schedule 28.02.2009
comment
+1 Да, я полностью согласен с №1 (это огромное преимущество при работе с такими вещами, как изображения и т. Д.) И за то, что я указал, что они отличаются от обычных объектов и есть способ узнать это, кроме как на основе имеющихся знаний или изучения самого типа. Кроме того, вы не можете привести нулевое значение к типу структуры :-) На самом деле это тот случай, когда я почти хотел бы, чтобы был какой-то "венгерский" для неосновных типов значений или обязательный " struct 'ключевое слово на сайте объявления переменных. - person ; 27.01.2011
comment
@pst: Это правда, что нужно знать, что что-то struct, чтобы знать, как оно будет себя вести, но если что-то struct с открытыми полями, это все, что нужно знать. Если объект предоставляет свойство типа структуры открытого поля, и если код считывает эту структуру в переменную и изменяет, можно с уверенностью предсказать, что такое действие не повлияет на объект, свойство которого было прочитано, пока или пока структура не будет записана назад. Напротив, если бы свойство было изменяемым типом класса, его чтение и изменение могло бы обновить базовый объект, как ожидалось, но ... - person supercat; 25.07.2012
comment
... он также может ничего не изменить, или он может изменить или повредить объекты, которые никто не намеревался изменять. Имея код, семантика которого говорит, можно сколько угодно изменять эту переменную; изменения ничего не сделают, пока вы явно не сохраните их в каком-либо месте, что кажется более ясным, чем наличие кода, в котором говорится, что вы получаете ссылку на некоторый объект, который может использоваться совместно с любым количеством других ссылок или может не использоваться вообще; вам нужно выяснить, у кого еще могут быть ссылки на этот объект, чтобы знать, что произойдет, если вы его измените. - person supercat; 25.07.2012
comment
Пятно на №1. Список, полный структур, может втиснуть в кеш L1 / L2 гораздо больше релевантных данных, чем список, полный ссылок на объекты (для структуры нужного размера). - person Matt Stephenson; 11.09.2015
comment
Наследование редко бывает правильным инструментом для работы, и слишком много рассуждать о производительности без профилирования - плохая идея. Во-первых, структуры можно передавать по ссылке. Во-вторых, передача по ссылке или по значению редко является серьезной проблемой для производительности. Наконец, вы не учитываете дополнительное выделение кучи и сборку мусора, которые должны выполняться для класса. Лично я предпочитаю думать о структурах как о простых старых данных, а о классах как о вещах, которые делают вещи (объекты), хотя вы также можете определять методы для структур. - person weberc2; 20.11.2015
comment
@ILoveFortran Передача структур в качестве аргументов дороже классов. - В таком случае, почему примитивы, такие как Int32, реализованы как структуры вместо классов? - person Abdul; 22.12.2016
comment
@ILoveFortran, не могли бы вы так же легко сказать, что объекты ведут себя иначе, чем обычные структуры, и если человек не знает, что имеет дело с объектом, а не структурой, он может предположить, что значение копируется при передаче в качестве параметра в метод. - person David Klempfner; 21.11.2017

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

Edit

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

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

person JoshBerke    schedule 06.02.2009
comment
Все это знают. Похоже, он ищет больше, чем просто структура - это ответ типа значения. - person TheSmurf; 06.02.2009
comment
Это самый простой случай, и его следует указать всем, кто читает этот пост и не знает этого. - person JoshBerke; 06.02.2009
comment
Не то чтобы этот ответ неверен; это очевидно. Дело не в этом. - person TheSmurf; 06.02.2009
comment
@Josh: Для тех, кто этого еще не знает, просто сказать, что это недостаточный ответ, поскольку вполне вероятно, что они тоже не знают, что это значит. - person TheSmurf; 06.02.2009
comment
Я только что проголосовал против этого, потому что думаю, что один из других ответов должен быть вверху - любой ответ, который говорит о взаимодействии с неуправляемым кодом, в противном случае избегайте. - person Daniel Earwicker; 06.02.2009
comment
вздох Подобные ответы являются причиной того, почему я должен публиковать учебник по основным элементам языка в своих вопросах, потому что люди скорее дадут ответы на простые вещи, чем на самом деле читают вопрос. - person Eva; 10.03.2013
comment
+1 за акцент на семантике. Чем больше людей будет программировать больше на основе парадигм и семантики, у нас не будет всех этих войн языка / платформы / API / формата. - person MrDosu; 23.10.2014
comment
Класс можно проверить на нулевое значение, чтобы вы знали, правильно ли он инициализирован. Со структурой никогда нельзя быть уверенным (см. Default (struct)). См .: msdn.microsoft.com/nl-nl/library/xwth0h0d.aspx < / а> - person Mike de Klerk; 03.09.2015
comment
@MikedeKlerk Не уверен, почему это имеет значение. Если неинициализированные структуры вызывают проблемы в C #, значит, у вас проблемы посерьезнее. - person Tom Lint; 15.02.2018

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

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

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

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

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

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

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

person Marc Gravell    schedule 22.10.2011
comment
+1 Интересный ответ. Не могли бы вы поделиться какими-нибудь анекдотами из реального мира об используемом таком подходе? - person Jordão; 15.10.2012
comment
@Jordao на мобильном телефоне, но ищите в Google: + gravell + assault by GC - person Marc Gravell; 15.10.2012
comment
Большое спасибо. Я нашел его здесь. - person Jordão; 15.10.2012
comment
Интересно, какой была бы производительность, если бы кто-то определил ICustomer и имел структуру CustomerRef, которая реализовывала этот интерфейс, содержала единственный индекс int и действовала соответствующим образом с элементами массива? Я бы подумал, что если сделать методы, которые принимают Customer, а не принимают ICustomer в целом, можно будет получить производительность, сопоставимую с текущим подходом, без необходимости широко раскрывать базовый массив. - person supercat; 24.09.2013
comment
Такая вещь, вероятно, является ЯГНИ, но если, например, массив клиентов мог бы превысить ограничение в 2 ГБ, можно было бы изменить код доступа к массиву с customers[index] на customers[index >> 16][index & 65535], не затрагивая ничего за пределами CustomerRef. - person supercat; 24.09.2013
comment
@supercat или просто включите поддержку большого массива в 4.5 - person Marc Gravell; 24.09.2013
comment
@MarcGravell: Никогда не использовал это. В любом случае, моя точка зрения заключалась в том, что существуют различные средства, с помощью которых подход ICustomer позволит отказаться от использования монолитного массива, если возникнет необходимость (ограничение в 2 ГБ было одной из причин, почему он мог бы это сделать, но не единственной. ). Между прочим, одна вещь, о которой ваш блог не упоминает в качестве стоимости вашего подхода, заключается в том, что GC не имеет возможности узнать, какие слоты массива имеют ссылки на них. Это снижает стоимость сборки мусора, но означает, что приложению может потребоваться само отслеживать такие вещи. - person supercat; 24.09.2013
comment
@MarcGravell Почему вы упомянули: использовать массив (не список)? List Я полагаю, использует Array за кадром. нет ? - person Royi Namir; 11.02.2014
comment
@RoyiNamir Мне тоже было любопытно, но я считаю, что ответ находится во втором абзаце ответа Марка. Однако доступ к нему напрямую в массиве не копирует структуру - она ​​находится на месте (в отличие от индексатора списка, который копирует). - person user1323245; 30.09.2014

Из спецификации языка 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 хранятся в массиве в строке.

Конструкторы структуры вызываются с помощью оператора 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 переменной b создает копию значения, и на эту копию не влияет последующее присвоение a.x.

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

person Luke Baughan    schedule 17.09.2012
comment
Хотя тот факт, что ссылки на структуры не могут быть сохранены, иногда является ограничением, это также очень полезная характеристика. Одна из основных слабых сторон .net заключается в том, что нет достойного способа передать внешнему коду ссылку на изменяемый объект без постоянной потери контроля над этим объектом. Напротив, можно безопасно дать внешнему методу ref изменяемой структуре и знать, что любые мутации, которые внешний метод будет выполнять с ней, будут выполнены до того, как она вернется. Жаль, что .net не имеет понятия об эфемерных параметрах и возвращаемых значениях функций, поскольку ... - person supercat; 18.09.2012
comment
... что позволило бы достичь выгодной семантики структур, передаваемых ref, с помощью объектов класса. По сути, локальные переменные, параметры и возвращаемые значения функций могут быть постоянными (по умолчанию), возвращаемыми или эфемерными. Коду будет запрещено копировать эфемерные вещи во все, что переживет нынешний объем. Возвращаемые вещи будут похожи на эфемерные вещи, за исключением того, что их можно вернуть из функции. Возвращаемое значение функции будет связано строжайшими ограничениями, применимыми к любому из ее возвращаемых параметров. - person supercat; 18.09.2012

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

person Franci Penov    schedule 06.02.2009
comment
Да, но большие структуры могут быть дороже, чем ссылки на классы (при передаче в методы). - person Alex from Jitbit; 10.07.2013

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

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

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

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
}
person Usman Zafar    schedule 22.01.2014
comment
Неизменяемые ссылочные типы, такие как string, семантически эквивалентны значениям, и сохранение ссылки на неизменяемый объект в поле не влечет за собой выделение кучи. Разница между структурой с открытыми общедоступными полями и объектом класса с открытыми общедоступными полями заключается в том, что с учетом кодовой последовательности var q=p; p.X=4; q.X=5;, p.X будет иметь значение 4, если a является типом структуры, и 5, если это тип класса. Если кто-то желает иметь возможность удобно изменять члены типа, он должен выбрать «класс» или «структуру» в зависимости от того, хотите ли вы, чтобы изменения в q повлияли на p. - person supercat; 22.01.2014
comment
Да, я согласен, что ссылочная переменная будет в стеке, но объект, на который она ссылается, будет существовать в куче. Хотя структуры и классы ведут себя по-разному при назначении другой переменной, я не думаю, что это решающий фактор. - person Usman Zafar; 23.01.2014
comment
Изменяемые структуры и изменяемые классы ведут себя совершенно по-разному; если один прав, другой, скорее всего, ошибается. Я не уверен, как поведение не будет решающим фактором при определении того, использовать ли структуру или класс. - person supercat; 23.01.2014
comment
Я сказал, что это не решающий фактор, потому что часто, когда вы создаете класс или структуру, вы не уверены, как они будут использоваться. Таким образом, вы сосредотачиваетесь на том, что вещи имеют больше смысла с точки зрения дизайна. В любом случае я никогда не видел ни одного места в библиотеке .NET, где структура содержит ссылочную переменную. - person Usman Zafar; 24.01.2014
comment
Тип структуры ArraySegment<T> инкапсулирует T[], который всегда является типом класса. Тип структуры KeyValuePair<TKey,TValue> часто используется с типами классов в качестве общих параметров. - person supercat; 25.01.2014
comment
BitVector32 не работает в соответствии с вашим примером, поскольку он использует 32-байтовый массив, который является ссылочным типом. Это не простой и быстрый способ определить, нужна ли вам структура. - person IAbstract; 18.10.2014
comment
Это лучший ответ. Если вам нужна семантика значения, используйте структуру (передача по аргументу создает копию вместо разрешения редактирования оригинала). Возможно, для больших наборов данных struct действительно улучшится производительность, но это должно быть проверено профилированием и отправлено с модульными тестами, показывающими, что вы не изменили функциональность программы. Если вы хотите включить часть этого комментария в ответ в качестве редактирования, не стесняйтесь делать это ^^ - person CoffeDeveloper; 07.08.2015

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

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

person BC.    schedule 06.02.2009

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

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

person Maurice Flanagan    schedule 06.02.2009
comment
Атрибут StructLayoutAttribute может применяться к структурам или классам, поэтому это не причина для использования структур. - person Stephen Martin; 06.02.2009
comment
Почему это имеет смысл, если вы просто передаете аргумент неуправляемому вызову метода? - person David Klempfner; 21.11.2017

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

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

person mjfgates    schedule 06.02.2009
comment
Не могли бы вы с такой же легкостью использовать для этого ссылочный тип? - person David Klempfner; 21.11.2017

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

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

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

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

P.S. Другой эталон прохождения структуры / класса через стек вызовов - это 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;
        }
    }
person Roman Pokrovskij    schedule 24.07.2017
comment
Вы поняли, почему структуры работают намного медленнее при использовании в списках и т. Д.? Это из-за упомянутого вами скрытого бокса и распаковки? Если да, то почему это происходит? - person Marko Grdinic; 12.08.2017
comment
Доступ к структуре в массиве должен быть быстрее только потому, что не требуется дополнительных ссылок. Бокс / распаковка - это случай для linq. - person Roman Pokrovskij; 27.08.2017

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

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

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

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

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

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 могут увеличить вероятность ошибки кода. Если объект значения обрабатывается как изменяемый объект ссылки, разработчик может быть удивлен, когда внесенные изменения неожиданно потеряны.

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
person Jason Williams    schedule 20.07.2017

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

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

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

person supercat    schedule 24.07.2012

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

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

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

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

Я действительно стараюсь, чтобы они были небольшими, использую их в единичных случаях и не допускаю их изменения. Разумно знать о памяти, распределении и производительности. А тестирование так необходимо.

person SnapJag    schedule 06.02.2009
comment
Структуры можно разумно использовать для представления легких неизменяемых объектов, или их можно разумно использовать для представления фиксированных наборов связанных, но независимых переменных (например, координат точки). Совет на этой странице хорош для структур, которые предназначены для первой цели, но неверен для структур, которые предназначены для второй цели. В настоящее время я считаю, что структуры, у которых есть какие-либо частные поля, обычно должны соответствовать указанному описанию, но многие структуры должны раскрывать свое все свое состояние через общедоступные поля. - person supercat; 25.07.2013
comment
Если спецификация для типа трехмерной точки указывает, что все его состояние отображается через читаемые члены x, y и z, и можно создать экземпляр с любой комбинацией значений double для этих координат, такая спецификация заставит его вести себя семантически идентично структуре открытого поля, за исключением некоторых деталей многопоточного поведения (неизменяемый класс будет лучше в некоторых случаях, в то время как структура открытого поля будет лучше в других; так называемая неизменяемая структура будет хуже в каждый случай). - person supercat; 25.07.2013

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

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

МИФ № 2: СПРАВОЧНЫЕ ТИПЫ ЖИВЮТ НА КУЧЕ; ТИПЫ ЦЕННОСТЕЙ, ЖИВУЩИЕ В СТЕКЕ

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

МИФ № 3: ОБЪЕКТЫ ПЕРЕДАЮТСЯ ПО ССЫЛКЕ В C # ПО УМОЛЧАНИЮ

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

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

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

Ссылка: C # в подробностях, 3-е издание, Джон Скит

person habib    schedule 27.02.2019
comment
Очень хорошо, если вы правы. Также очень хорошо добавить ссылку. - person NoChance; 17.01.2020

Мое правило

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

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

person rockXrock    schedule 30.08.2013
comment
Существенный случай использования, который игнорирует Microsoft, - это когда нужно, чтобы переменная типа Foo инкапсулировала фиксированный набор независимых значений (например, координаты точки), которые иногда нужно передавать как группу, а иногда нужно изменять независимо. Я не нашел никакого шаблона для использования классов, который сочетал бы обе цели почти так же хорошо, как простая структура с открытым полем (которая, будучи фиксированным набором независимых переменных, полностью отвечает всем требованиям). - person supercat; 30.08.2013
comment
@supercat: Я считаю, что обвинять в этом Microsoft не совсем справедливо. Настоящая проблема здесь в том, что C # как объектно-ориентированный язык просто не фокусируется на простых типах записей, которые предоставляют только данные без особого поведения. C # не является многопарадигмальным языком в той же степени, что, например, C ++ есть. При этом я также верю, что очень мало людей программируют на чистом ООП, поэтому, возможно, C # слишком идеалистичный язык. (Я, например, недавно начал использовать public readonly поля в своих типах, потому что создание свойств, доступных только для чтения, - это слишком много работы и практически никакой пользы.) - person stakx - no longer contributing; 31.08.2013
comment
@stakx: нет необходимости сосредотачиваться на таких типах; было бы достаточно признать их такими, какие они есть. Самая большая слабость C # в отношении структур - это его самая большая проблема и во многих других областях: язык предоставляет неадекватные средства для указания того, когда определенные преобразования подходят или не подходят, а отсутствие таких средств приводит к неудачным проектным решениям. Например, 99% изменяемых структур являются злыми, происходящими из-за того, что компилятор превратил MyListOfPoint[3].Offset(2,3); в var temp=MyListOfPoint[3]; temp.Offset(2,3);, преобразование, которое является фиктивным при применении ... - person supercat; 01.09.2013
comment
... к методу Offset. Правильный способ предотвратить такой фиктивный код - это не делать структуры без нужды неизменяемыми, а вместо этого разрешать тегам, таким как Offset, использовать атрибут, запрещающий вышеупомянутое преобразование. Неявные числовые преобразования тоже могли бы быть намного лучше, если бы их можно было пометить так, чтобы они были применимы только в тех случаях, когда их вызов был бы очевиден. Если существуют перегрузки для foo(float,float) и foo(double,double), я бы сказал, что попытки использовать float и double часто не должны применять неявное преобразование, а вместо этого должны быть ошибкой. - person supercat; 01.09.2013
comment
Прямое присвоение значения double переменной float или передача его методу, который может принимать аргумент float, но не double, почти всегда будет делать то, что задумал программист. Напротив, присвоение float выражения double без явного приведения типа часто является ошибкой. Единственный раз, когда разрешенное неявное double->float преобразование могло бы вызвать проблемы, было бы тогда, когда оно привело бы к выбору неидеальной перегрузки. Я бы сказал, что правильным способом предотвратить это должно было быть не запрещение implcit double- ›float, а тегирование перегрузок атрибутами, чтобы запретить преобразование. - person supercat; 01.09.2013

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

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

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

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

person N_E    schedule 27.03.2016
comment
ИМХО, это лучшая подсказка. - person Ivan; 12.03.2017

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

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

person Saeed Dini    schedule 24.10.2017

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

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

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

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

person akazemis    schedule 26.05.2016

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

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

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

person Brian    schedule 06.02.2009
comment
Я бы не согласился с таким ответом. Структуры имеют законное использование во многих сценариях. Вот пример - маршалинг перекрестных процессов данных атомарным способом. - person Franci Penov; 06.02.2009
comment
Вам следует отредактировать свой пост и уточнить свои соображения - вы высказали свое мнение, но вы должны подкрепить его тем, почему вы так придерживаетесь. - person Erik Forbes; 06.02.2009
comment
Я думаю, им нужен эквивалент карты Totin 'Chip (en.wikipedia.org/wiki/Totin % 27_Chip) для использования структур. Шутки в сторону. - person Greg; 13.05.2010
comment
Как 87,5 тысяч человек публикует такой ответ? Он делал это в детстве? - person Rohit Vipin Mathews; 02.06.2015
comment
@Rohit - это было шесть лет назад; стандарты сайтов тогда были совсем другими. но это все равно плохой ответ, вы правы. - person Andrew Arnold; 02.06.2015

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

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

person Rabbit    schedule 03.07.2013

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

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

person Community    schedule 06.02.2009

Ниже приведены правила, определенные на веб-сайте Microsoft:

✔️ РАССМАТРИВАЙТЕ определение структуры вместо класса, если экземпляры типа небольшие и обычно недолговечны или обычно встраиваются в другие объекты.

❌ ИЗБЕГАЙТЕ определения структуры, если тип не обладает всеми следующими характеристиками:

Он логически представляет одно значение, аналогично примитивным типам (int, double и т. Д.).

Он имеет размер экземпляра менее 16 байт.

Это непреложно.

Его не придется часто ставить в коробку.

для дальнейшего чтение

person Rupesh    schedule 15.10.2020
comment
Этот ответ кажется просто повторением (части) этого существующего ответа. - person Pang; 16.10.2020

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

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

Eg.

  • Цвет или Точка имеют смысл, даже если для всех их полей установлены значения по умолчанию. RGB 0,0,0 - идеальный цвет, как и (0,0) как точка в 2D.
  • Но Address или PersonName не имеют разумного значения по умолчанию. Я имею в виду, можете ли вы понять PersonName, у которого FirstName = null и LastName = null?

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

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

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

person Vizu    schedule 02.05.2021

✔️ УЧИТЫВАЙТЕ использование структуры

  1. Создайте объект или не нужно создавать объект (напрямую вы можете назначать значения, он создает объект)
  2. Need Speed ​​или улучшение производительности
  3. Нет необходимости в конструкторах и дестракторах (доступен статический подрядчик)
  4. Нет необходимости в наследовании классов, но интерфейсы приемлемы
  5. Работа с небольшими объектами рабочей нагрузки, если она станет высокой, возникнет проблема с памятью.
  6. Вы не можете использовать значения по умолчанию для переменных.
  7. Struct также доступны методы, события, статические конструкторы, переменные и т. Д.
  8. Меньше нагрузки на сборщик мусора
  9. Нет необходимости в ссылочных типах, только тип значения (каждый раз, когда вы создаете новый объект)
  10. Нет неизменяемого объекта (строка является неизменяемым объектом, потому что любая операция не возвращает каждый раз новую строку без изменения оригинала)
person logeshpalani32    schedule 12.04.2021

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

person Cryo Erger    schedule 20.11.2014
comment
Структуры НЕ могут быть унаследованы, см. msdn.microsoft.com/en-us/library /0taef578.aspx - person HimBromBeere; 10.09.2015