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

1304

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

Я столкнулся с этими правилами here:

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

Действуют ли эти правила? Что означает структура семантически?

  • 0
    Я могу согласиться только с первым пунктом, например, структуры часто используются в программировании игр.
  • 218
    System.Drawing.Rectangle нарушает все три этих правила.
Показать ещё 21 комментарий
Теги:
struct

28 ответов

584

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

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

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

  • Он логически представляет одно значение, подобное примитивным типам (целое, двойное и т.д.).
  • У него размер экземпляра меньше 16 байт.
  • Это неизменный.
  • Его не нужно часто вставлять в бокс.

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 будет использовать эти структуры:

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

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

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

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

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

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

Емкость: 312874
MemSize: 2660827 байт
Завершено Изменение размера: 5ms
Общее время для заполнения: 889ms

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

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

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

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

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

Емкость: 312874
MemSize: 2660827 байт
Завершено Изменение размера: 26ms
Общее время для заполнения: 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. Короче говоря:

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

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

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

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

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

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

  • 0
    Я не думаю, что среда выполнения вызывает какой-либо конструктор структуры при создании массива структуры. Причина, по которой большинство языков запрещает структурам иметь конструктор по умолчанию, заключается не в том, что их вызов сделает конструкцию массива неэффективной, а в том, что в некоторых случаях было бы странно вызывать конструкторы структуры, но не другие.
  • 6
    Что касается правил Microsoft, то, как представляется, правило об неизменности разработано так, чтобы препятствовать использованию типов значений таким образом, чтобы их поведение отличалось от поведения ссылочных типов, несмотря на тот факт, что кусочно-изменяемая семантика значений может быть полезной . Если наличие типа кусочно-изменяемого облегчило бы работу, и если места хранения типа должны быть логически отделены друг от друга, тип должен быть «изменяемой» структурой.
Показать ещё 15 комментариев
141

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

  • 1
    Это только одна «оговорка». Также следует учитывать «снятие» значений-типов и случаев, таких как (Guid)null (можно приводить null к ссылочному типу), среди прочего.
  • 1
    дороже, чем в C / C ++? в C ++ рекомендуется передавать объекты по значению
Показать ещё 4 комментария
131

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

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

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

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

  • Они отличаются от "обычных объектов" (ссылочных типов) при назначении и при передаче в качестве аргументов, что может привести к неожиданному поведению; это особенно опасно, если человек, смотрящий на код, не знаю, что они имеют дело со структурой.
  • Они не могут быть унаследованы.
  • Передача структур как аргументов дороже классов.
  • 4
    +1 Да, я полностью согласен с № 1 (это огромное преимущество при работе с такими вещами, как изображения и т. Д.) И за то, что они указывают на то, что они отличаются от «обычных объектов», и существует известный способ узнать это, кроме как с помощью существующих знаний. или исследуя сам тип. Кроме того, вы не можете привести нулевое значение к типу структуры :-) На самом деле это один из случаев, когда я почти хотел бы, чтобы на сайте объявления переменных было какое-то «венгерское» для неосновных типов значений или обязательное ключевое слово «struct». ,
  • 0
    @pst: Это правда, что нужно знать что-то, это struct чтобы знать, как она будет себя вести, но если что-то является struct с открытыми полями, это все, что нужно знать. Если объект предоставляет свойство типа открытого поля-структуры, и если код считывает эту структуру в переменную и изменяет ее, можно с уверенностью предсказать, что такое действие не повлияет на объект, свойство которого было прочитано, до тех пор, пока структура не будет записана назад. Напротив, если свойство было изменяемым типом класса, его чтение и изменение могут обновлять базовый объект, как и ожидалось, но ...
Показать ещё 5 комментариев
79

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

Изменить

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

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

  • 12
    Все это знают. Похоже, он ищет больше, чем ответ "структура является типом значения".
  • 23
    Не все это знают. Это явно один случай.
Показать ещё 9 комментариев
53

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

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

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

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

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

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

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

  • 0
    +1 интересный ответ. Хотели бы вы поделиться реальными анекдотами о таком подходе?
  • 0
    @Jordao на мобильном, но поиск в Google для: + Gravell + "Штурм GC"
Показать ещё 7 комментариев
37

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

1.7 Структуры

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

Структуры особенно полезны для небольших структур данных, которые имеют семантика значения. Сложные числа, точки в системе координат или пары ключ-значение в словаре являются хорошими примерами структур. использование структур, а не классов для небольших структур данных может большая разница в количестве распределений памяти приложения выполняет. Например, следующая программа создает и инициализирует массив из 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 a struct.

struct Point
{
   public int x, y;

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

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

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

С классами возможно, чтобы две переменные ссылались на один и тот же объект и, таким образом, можно было использовать операции с одной переменной для объекта, на который ссылается другая переменная. С помощью структур каждая переменная имеет свою собственную копию данных, и невозможно, чтобы операции над ними влияли на другую. Например, результат, созданный следующим фрагментом кода, зависит от того, является ли 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, невозможно создать ссылки на структуры, что исключает их использование в ряде ситуаций.

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

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

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

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

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

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

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; pX=4; qX=5; , pX будет иметь значение 4, если a является типом структуры, и 5, если это тип класса. Если кто-то хочет иметь возможность удобно изменять члены типа, следует выбрать «класс» или «структуру» в зависимости от того, хочет ли кто-либо изменить q чтобы повлиять на p .
  • 0
    Да, я согласен, что ссылочная переменная будет в стеке, но объект, на который она ссылается, будет существовать в куче. Хотя структуры и классы ведут себя по-разному, когда присваиваются другой переменной, но я не думаю, что это решающий фактор.
Показать ещё 5 комментариев
18

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

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

17

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

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

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

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

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

  • 0
    Не могли бы вы так же легко использовать для этого ссылочный тип?
15

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

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

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

Когда использовать struct over class,

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

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

Типы структуры на языках С# или других языках .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.

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

9

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

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

PS Другим эталоном относительно передачи структуры/класса через стек вызовов является https://stackoverflow.com/questions/13049/whats-the-difference-between-struct-and-class-in-net

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;
        }
    }
  • 0
    Вы выяснили, почему структуры намного медленнее, когда используются в списках и тому подобное? Вы упомянули скрытый бокс и распаковку? Если так, то почему это происходит?
  • 0
    Доступ к структуре в массиве должен быть быстрее только потому, что не требуется никаких дополнительных ссылок. Бокс / Распаковка это случай для linq.
9

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

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.

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

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
9

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

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

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

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

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

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

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

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

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

Мое правило

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

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

  • 0
    Существенный случай использования, который Microsoft игнорирует, - это когда кто-то хочет, чтобы переменная типа Foo инкапсулировала фиксированный набор независимых значений (например, координат точки), которые иногда хочется передавать как группу, а иногда - независимо. Я не нашел ни одного шаблона для использования классов, который сочетал бы обе цели почти так же хорошо, как простая структура с открытыми полями (которая, будучи фиксированной коллекцией независимых переменных, идеально подходит для этой цели).
  • 1
    @supercat: Я думаю, что не совсем справедливо обвинять в этом Microsoft. Реальная проблема здесь в том, что C # как объектно-ориентированный язык просто не фокусируется на простых типах записей, которые только предоставляют данные без особого поведения. C # не является языком мультипарадигмы в той же степени, как, например, C ++. При этом я также считаю, что очень немногие программируют чистый ООП, поэтому, возможно, C # - слишком идеалистический язык. (Я, например, недавно начал выставлять public readonly поля в моих типах, потому что создание свойств только для чтения - это просто слишком много работы практически без пользы.)
Показать ещё 3 комментария
5

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

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

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

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

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

  • 23
    Я бы не согласился с этим ответом. Структуры имеют законное использование во многих сценариях. Вот пример - маршалинг данных пересекает процессы атомарным способом.
  • 1
    Хороший момент, взаимодействие / сортировка является еще одной хорошей причиной.
Показать ещё 5 комментариев
4

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

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

3

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

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

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

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

3

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

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

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

3

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

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

2

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

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

-8

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

Он похож на класс, но больше всего его особенность. Это своего рода униженная версия класса. Много говорится о том, когда не использовать его, но очень мало, когда его использовать.

IMO, нет причин, по которым структура должна быть реализована на языке OO в первую очередь. На самом деле примитивный тип не должен существовать на чистом языке OO, но я отвлекаюсь.

Это может быть способ оптимизировать материал. Это своего рода бокс бесплатно, которые можно оптимизировать на некоторых сайтах.

Мои 2 цента, я бы сказал, что он нарушил KISS принципа языка и избегает его, насколько я могу.

-10

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню