Джерело, на яке посилається ОП, має певну надійність ... але як щодо Microsoft - яка позиція щодо використання структури? Я шукав додаткового навчання у Microsoft , і ось що я знайшов:
Поміркуйте, як визначити структуру замість класу, якщо екземпляри типу невеликі і зазвичай недовговічні або зазвичай вбудовані в інші об'єкти.
Не визначайте структуру, якщо тип не має всіх наведених нижче характеристик:
- Він логічно представляє єдине значення, подібне до примітивних типів (ціле число, подвійне і так далі).
- Він має розмір екземпляра менше 16 байт.
- Це незмінне.
- Боксувати не доведеться часто.
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 використовує ці структури:
- Кожна структура
Entry
та Enumerator
, представляють окремі значення.
- Швидкість
Entry
ніколи не передається як параметр поза класом Словник. Подальше дослідження показує, що для задоволення реалізації IEnumerable, словник використовує Enumerator
структуру, яку він копіює щоразу, коли запитувач перераховує ... має сенс.
- Внутрішній клас словника.
Enumerator
є загальнодоступним, оскільки словник є численним і повинен мати однакову доступність до реалізації інтерфейсу IEnumerator - наприклад, Getter IEnumerator.
Оновлення - Крім того, зрозумійте, що коли структура реалізує інтерфейс - як це робить Enumerator - і передається цьому реалізованому типу, структура стає еталонним типом і переміщується до купи. Внутрішній клас словника, Enumerator все ще є типом значення. Однак, як тільки метод викликає GetEnumerator()
, IEnumerator
повертається тип посилання .
Що ми тут не бачимо, це будь-яка спроба чи доказ вимоги зберегти структури незмінні або підтримувати розмір екземпляра лише 16 байт або менше:
- Ніщо в зазначених вище структурах не оголошено
readonly
- не змінюється
- Розмір цієї структури міг би перевищувати 16 байт
Entry
має невизначений термін служби (від Add()
, до Remove()
, Clear()
або сміття);
І ... 4. Обидва структури зберігають TKey і TValue, які всі ми знаємо, що цілком можуть бути еталонними типами (додана інформація про бонус)
Незважаючи на стихійні ключі, словники частково швидкі, оскільки інстанція структури швидша, ніж еталонний тип. Тут у мене є, Dictionary<int, int>
що зберігає 300 000 випадкових цілих чисел з послідовно нарощеними ключами.
Місткість: 312874
MemSize: 2660827 bytes
Завершено Розмір: 5ms
Загальний час заповнення: 889ms
Ємність : кількість елементів, доступних перед внутрішнім масивом, слід змінити розмір.
MemSize : визначається шляхом серіалізації словника в MemoryStream та отримання довжини байтів (достатньо точно для наших цілей).
Виконано зміни розміру : час, необхідний для зміни розміру внутрішнього масиву з 150862 елементів до 312874 елементів. Коли ви зрозумієте, що кожен елемент послідовно копіюється через Array.CopyTo()
, це не надто пошарпано.
Загальний час для заповнення : зізнається, скасований через реєстрацію та OnResize
подію, яку я додав до джерела; однак все ще вражає заповнення цілих 300 к цілих чисел, змінюючи розмір 15 разів під час операції. Щось із цікавості, яким би був загальний час для заповнення, якби я вже знав ємність? 13 мс
Отже, що робити, якщо це Entry
був клас? Чи справді ці часи чи показники дійсно сильно різняться?
Місткість: 312874
MemSize: 2660827 байт
Завершено Розмір: 26ms
Загальний час для заповнення: 964ms
Очевидно, велика різниця полягає в зміні розміру. Будь-яка різниця, якщо Словник ініціалізований з 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: Structure Design . Коротко:
Не надайте конструктор за замовчуванням для структури.
Якщо структура визначає конструктор за замовчуванням, коли масиви структури створюються, загальна мова виконання автоматично виконує конструктор за замовчуванням для кожного елемента масиву.
Деякі компілятори, такі як компілятор C #, не дозволяють структурам мати конструктори за замовчуванням.
Це насправді досить просто, і ми запозичимо з трьох законів робототехніки Асімова :
- Структура повинна бути безпечною у використанні
- Структура повинна виконувати свою функцію ефективно, якщо тільки це не порушує правило №1
- Під час використання конструкція повинна залишатися неушкодженою, якщо її руйнування не потрібно, щоб задовольнити правило №1
... що ми забираємо з цього : коротше кажучи, відповідальність за використання значень типів. Вони швидкі та ефективні, але здатні викликати багато несподіваних поведінки, якщо не підтримуються належним чином (тобто ненавмисні копії).
System.Drawing.Rectangle
порушує всі три ці правила.