Вопрос: Когда использовать struct?


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

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

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

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


1191


источник


Ответы:


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

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

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

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

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

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

[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 словарь использует Enumeratorstruct, который он копирует каждый раз, когда запрашивается счетчик ... имеет смысл.
  4. Внутренний класс словаря. Enumeratorявляется общедоступным, поскольку словарь перечислим и должен иметь равную доступность для реализации интерфейса IEnumerator - например, IEnumerator getter.

Обновить - Кроме того, поймите, что когда структура реализует интерфейс - как это делает 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 мс
Общее время для заполнения: 889ms

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


537



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


142



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

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

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

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

  • Они ведут себя иначе, чем «обычные объекты» ( ссылочные типы ) по заданию и при передаче в качестве аргументов, что может привести к неожиданному поведению; это особенно опасно, если человек, смотрящий на код, не знаю, что они имеют дело со структурой.
  • Они не могут быть унаследованы.
  • Передача структур в качестве аргументов дороже классов.

132



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

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

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

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


81



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

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

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

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

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

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

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


54



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

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


36



Structs are good for atomic representation of data, where the said data can be copied multiple times by the code. Cloning an object is in general more expensive than copying a struct, as it involves allocating the memory, running the constructor and deallocating/garbage collection when done with it.


31



Here is a basic rule.

  • If all member fields are value types create a struct.

  • If any one member field is a reference type, create a class. This is because the reference type field will need the heap allocation anyway.

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
}

24