Источник, на который ссылается 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
является общедоступным, поскольку Dictionary является перечислимым и должен иметь равный доступ к реализации интерфейса IEnumerator, например IEnumerator getter.
Обновление - Кроме того, следует понимать, что когда структура реализует интерфейс - как это делает Enumerator - и приводится к этому реализованному типу, структура становится ссылочным типом и перемещается в кучу. Внутренний по отношению к классу Dictionary, перечислитель является по-прежнему типом значения. Однако, как только метод вызывает GetEnumerator()
, возвращается ссылочный тип IEnumerator
.
Здесь мы не видим никаких попыток или доказательств необходимости сохранять неизменяемыми структуры или поддерживать размер экземпляра не более 16 байт:
- Ничто в вышеприведенных структурах не объявляется
readonly
- не неизменным
- Размер этой структуры может быть больше 16 байт.
Entry
имеет неопределенный срок службы (от Add()
до Remove()
, Clear()
или сборки мусора);
И ...
4. Обе структуры хранят TKey и TValue, которые, как мы все знаем, вполне могут быть ссылочными типами (дополнительная информация о бонусе)
Несмотря на хеширование ключей, словари работают быстро, потому что создание структуры происходит быстрее, чем ссылочный тип. Здесь у меня есть Dictionary<int, int>
, в котором хранится 300 000 случайных целых чисел с последовательно увеличенными ключами.
Вместимость: 312874
MemSize: 2660827 байт
Завершено Изменение размера: 5 мс
Общее время заполнения: 889мс
Capacity : количество элементов, доступных до изменения размера внутреннего массива.
MemSize : определяется путем сериализации словаря в MemoryStream и получения длины в байтах (достаточно точной для наших целей).
Completed Resize : время, необходимое для изменения размера внутреннего массива с 150862 элементов до 312874 элементов. Когда вы понимаете, что каждый элемент последовательно копируется через Array.CopyTo()
, это не так уж и плохо.
Общее время для заполнения : по общему признанию, искажено из-за регистрации и события OnResize
, которое я добавил к источнику; Тем не менее, по-прежнему впечатляет заполнить 300 тысяч целых чисел при изменении размера в 15 раз во время операции. Просто из любопытства, сколько будет общего времени, чтобы заполнить, если бы я уже знал емкость? 13мс
Итак, что если Entry
был классом? Будут ли эти времена или показатели действительно сильно отличаться?
Емкость: 312874
MemSize: 2660827 байт
Завершено Изменение размера: 26 мс
Общее время заполнения: 964мс
Очевидно, большая разница в изменении размера. Есть ли разница, если словарь инициализируется с Capacity? Недостаточно, чтобы беспокоиться о ... 12 мс .
Что происходит, потому что Entry
является структурой, она не требует инициализации, как ссылочный тип. Это и красота, и проклятие типа значения. Чтобы использовать Entry
в качестве ссылочного типа, мне пришлось вставить следующий код:
/*
* Added to satisfy initialization of entry elements --
* this is where the extra time is spent resizing the Entry array
* **/
for (int i = 0 ; i < prime ; i++)
{
destinationArray[i] = new Entry( );
}
/* *********************************************** */
Причину, по которой я должен был инициализировать каждый элемент массива Entry
в качестве ссылочного типа, можно найти по адресу MSDN: Структура структуры . Короче говоря:
Не предоставлять конструктор по умолчанию для структуры.
Если структура определяет конструктор по умолчанию, когда массивы
структура создана, общеязыковая среда выполнения автоматически
выполняет конструктор по умолчанию для каждого элемента массива.
Некоторые компиляторы, такие как компилятор C #, не позволяют структурам
есть конструкторы по умолчанию.
Это на самом деле довольно просто, и мы заимствуем у Трима Роботизма Азимова :
- Структура должна быть безопасной для использования
- Структура должна эффективно выполнять свою функцию, если только это не нарушит правило # 1
- Структура должна оставаться неизменной во время ее использования, если только ее уничтожение не требуется для удовлетворения правила # 1
... что мы отнимем у этого : короче, отвечайте за использование типов значений. Они бывают быстрыми и эффективными, но имеют способность вызывать много неожиданных действий, если не будут должным образом поддерживаться (то есть непреднамеренные копии).