Коли використовувати структуру?


1390

Коли слід використовувати stru, а не клас у C #? Моя концептуальна модель полягає в тому, що структури використовуються в часи, коли предмет є лише сукупністю типів значень . Спосіб логічно утримувати їх усіх разом у єдине ціле.

Я зіткнувся з цими правилами тут :

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

Чи працюють ці правила? Що означає семантично структура?


247
System.Drawing.Rectangleпорушує всі три ці правила.
ChrisW

4
Є чимало комерційних ігор, написаних на C #, справа в тому, що вони використовуються для оптимізованого коду
BlackTigerX

25
Структури забезпечують кращу ефективність, коли у вас є невеликі колекції типів цінностей, які ви хочете згрупувати. Це трапляється весь час в ігровому програмуванні, наприклад, вершина в 3D-моделі матиме положення, координату текстури і нормальну норму, це також, як правило, буде непорушним. Одна модель може мати пару тисяч вершин, а може мати десяток, але конструкції забезпечують менше загальних накладних витрат у цьому сценарії використання. Я підтвердив це завдяки власній конструкції двигуна.
Кріс Д.


4
@ChrisW Я бачу, але чи не є ці значення прямокутником, тобто "єдиним" значенням? Як і Vector3D або Color, вони також є кількома значеннями всередині, але я думаю, що вони представляють єдині значення?
Марсон Мао

Відповіді:


604

Джерело, на яке посилається ОП, має певну надійність ... але як щодо 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ніколи не передається як параметр поза класом Словник. Подальше дослідження показує, що для задоволення реалізації IEnumerable, словник використовує Enumeratorструктуру, яку він копіює щоразу, коли запитувач перераховує ... має сенс.
  4. Внутрішній клас словника. Enumeratorє загальнодоступним, оскільки словник є численним і повинен мати однакову доступність до реалізації інтерфейсу IEnumerator - наприклад, Getter IEnumerator.

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

Що ми тут не бачимо, це будь-яка спроба чи доказ вимоги зберегти структури незмінні або підтримувати розмір екземпляра лише 16 байт або менше:

  1. Ніщо в зазначених вище структурах не оголошено readonly- не змінюється
  2. Розмір цієї структури міг би перевищувати 16 байт
  3. 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. Структура повинна бути безпечною у використанні
  2. Структура повинна виконувати свою функцію ефективно, якщо тільки це не порушує правило №1
  3. Під час використання конструкція повинна залишатися неушкодженою, якщо її руйнування не потрібно, щоб задовольнити правило №1

... що ми забираємо з цього : коротше кажучи, відповідальність за використання значень типів. Вони швидкі та ефективні, але здатні викликати багато несподіваних поведінки, якщо не підтримуються належним чином (тобто ненавмисні копії).


8
Що стосується правил Microsoft, то, як правило, правило про незмінність покликане відмовити від використання типів значень таким чином, щоб їх поведінка відрізнялася від поведінки референтних типів, незважаючи на те, що семантика значень величин може бути корисною . Якщо тип має бути кусочно-змінним, це полегшить роботу, і якщо місця зберігання типу повинні бути логічно відокремлені один від одного, тип повинен бути структурою, що "змінюється".
supercat

23
Майте на увазі, що лише на увазі ! = Незмінне.
Джастін Морган

2
Той факт, що багато типів Microsoft порушують ці правила, не представляє проблеми з цими типами, а швидше вказує на те, що правила не повинні застосовуватися до всіх типів структури. Якщо структура являє собою єдину сутність [як Decimalабо DateTime], то, якщо вона не буде дотримуватися інших трьох правил, її слід замінити класом. Якщо структура містить фіксовану колекцію змінних, кожна з яких може містити будь-яке значення, яке було б дійсним для її типу [наприклад Rectangle], то вона повинна дотримуватися різних правил, деякі з яких суперечать тим, що стосуються структур "однозначне" .
supercat

4
@IAbrief: Деякі люди виправдовуватимуть Dictionaryтип запису виходячи з того, що це лише внутрішній тип, продуктивність вважалася важливішою, ніж семантика чи якийсь інший привід. Моя думка полягає в тому, що тип типу подібний Rectangleповинен містити свій вміст як поля, що редагуються індивідуально, не "тому, що" користь від продуктивності переважає отримані семантичні недосконалості, а тому, що тип семантично являє собою фіксований набір незалежних значень , і тому змінна структура є обома більш виконавський і семантично вищий .
supercat

2
@supercat: Я погоджуюся ... і вся суть моєї відповіді полягала в тому, що «настанови» досить слабкі, і структури повинні використовуватися з повним знанням і розумінням поведінки. Дивіться моя відповідь на змінною структуру тут: stackoverflow.com/questions/8108920 / ...
IAbstract

154

Всякий раз, коли вам не потрібен поліморфізм, потрібно мати значення семантики і хочете уникати розподілу купи та пов'язаного з цим сміття. Застереження, однак, полягає в тому, що структури (як завгодно великі) обходиться дорожче, ніж посилання на клас (як правило, одне машинне слово), тому класи можуть бути швидшими на практиці.


1
Це лише один «застереження». Слід також розглянути питання про "скасування" типів цінностей та таких випадків, як (Guid)null(поряд із тим, щоб привести нуль до еталонного типу), серед іншого.

1
дорожче, ніж у C / C ++? в C ++ рекомендованим способом є передача об'єктів за значенням
Іон Тодірел

@IonTodirel Це було не з міркувань безпеки пам’яті, а не продуктивності? Це завжди компроміс, але проходження 32 B стеком завжди (TM) буде повільніше, ніж передача 4 B по реєстру. Однак також зауважте, що використання "value / reference" дещо відрізняється у C # та C ++ - коли ви передаєте посилання на об'єкт, ви все одно переходите за значенням, навіть якщо ви передаєте посилання (you ' повторно передаючи значення посилання, а не посилання на посилання, в основному). Це не значення семантики , але технічно це "прохідна вартість".
Луань

@Luaan Копіювання - це лише один аспект витрат. Додаткова опосередкованість через вказівник / посилання також коштує за доступ. У деяких випадках структуру можна навіть перемістити, і тому її навіть не потрібно копіювати.
Онур

@Onur, що цікаво. Як ви "рухаєтесь" без копіювання? Я думав, що інструкція asm "mov" насправді не "рухається". Це копії.
Вінгер Сендон

148

Я не згоден з правилами, наведеними в оригінальній публікації. Ось мої правила:

1) Ви використовуєте структури для продуктивності, зберігаючи їх у масивах. (див. також, коли відповіді на структури? )

2) Вам вони потрібні в коді, передаючи структуровані дані в / з C / C ++

3) Не використовуйте структури, якщо вони вам не потрібні:

  • Вони поводяться відмінно від "нормальних об'єктів" ( типових типів ) під присвоєнням і при передачі в якості аргументів, що може призвести до несподіваної поведінки; це особливо небезпечно, якщо людина, яка дивиться на код, не знає, що має справу зі структурою.
  • Вони не можуть бути успадковані.
  • Передача структур як аргументів дорожче, ніж класи.

4
+1 Так, я повністю погоджуюся з №1 (це величезна перевага під час роботи з такими речами, як зображення, тощо) і за те, що вони вказують на те, що вони відрізняються від "звичайних об'єктів", і існує спосіб знати це, крім існуючих знань або вивчення самого типу. Крім того, ви не можете присвоїти нульове значення типу структура :-) Це насправді один випадок, коли я майже хочу, щоб на сайті змінної декларації було якесь "угорське" для типів неосновних значень або обов'язкове ключове слово "struct" .

@pst: Це правда, що треба щось знати - це structзнати, як воно буде вести себе, але якщо щось є structз відкритими полями, це все, що потрібно знати. Якщо об'єкт виявляє властивість типу відкритого поля-структура, і якщо код читає цю структуру до змінної та модифікується, можна сміливо передбачити, що така дія не вплине на об'єкт, властивість якого було прочитано, якщо або поки структура не буде записана назад. Навпаки, якби властивість мала тип класу, що змінюється, читання та модифікація може оновити базовий об'єкт, як очікувалося, але ...
supercat

... це також може нічого не змінити, або може змінити або зіпсувати об'єкти, які ніхто не збирався змінювати. Код, семантика якого говорить "змінити цю змінну все, що тобі подобається; зміни не принесуть нічого, поки ти явно не збережеш їх кудись", здається більш зрозумілим, ніж код, який говорить "Ви отримуєте посилання на який-небудь об'єкт, який може бути спільним для будь-якого числа інших посилань або взагалі їх не можна поділити; вам доведеться з’ясувати, хто ще може мати посилання на цей об’єкт, щоб знати, що буде, якщо ви його зміните. "
supercat

Місце на №1. Список повних структур може видавити набагато більш релевантні дані в кеші L1 / L2, ніж список, повний посилань на об'єкти, (для потрібної структури розміру).
Метт Стівенсон

2
Спадщина рідко є правильним інструментом для роботи, а міркувати надто багато про продуктивність без профілювання - це погана ідея. По-перше, структури можна передати шляхом посилання. По-друге, проходження посилання або за значенням рідко є важливим питанням ефективності. Нарешті, ви не враховуєте додаткове розподілення купи та збирання сміття, яке має відбуватися для класу. Особисто я вважаю за краще структуру вважати простою старою інформацією та класи, як речі, які роблять речі (об'єкти), хоча ви також можете визначати методи для структур.
weberc2

87

Використовуйте структуру, коли потрібно значення семантики значень на відміну від референтної семантики.

Редагувати

Не впевнений, чому люди заперечують це, але це справедливий момент, і це було зроблено ще до того, як оператор роз'яснив своє питання, і це найголовніша основна причина структури.

Якщо вам потрібна довідкова семантика, вам потрібен клас, а не структура.


13
Всі це знають. Здається, він шукає більше, ніж відповідь "структура - це значення типу".
TheSmurf

21
Це найосновніший випадок і його слід зазначити для тих, хто читає цю публікацію і не знає цього.
JoshBerke

3
Не те, що ця відповідь не відповідає дійсності; це, очевидно, є. Це насправді не сенс.
TheSmurf

55
@Josh: Для тих, хто цього ще не знає, просто сказавши, що це недостатня відповідь, оскільки, ймовірно, вони теж не знають, що це означає.
TheSmurf

1
Я щойно спростував це, тому що я думаю, що одна з інших відповідей повинна бути вгорі - будь-яка відповідь, яка говорить "Для взаємодії з некерованим кодом, інакше уникайте".
Даніель Ервікер

59

Окрім відповіді "це значення", один конкретний сценарій використання структур - це коли ви знаєте, що у вас є набір даних, які викликають проблеми з вивезенням сміття, і у вас є багато об'єктів. Наприклад, великий список / масив екземплярів Person. Природна метафора тут - клас, але якщо у вас є велика кількість екземплярів довгоживої особи, вони можуть закінчитися засміченням GEN-2 і викликати зупинки GC. Якщо сценарій цього вимагає, одним із можливих підходів тут є використання масиву (не списку) структур Person , тобто Person[]. Тепер, замість того, щоб у GEN-2 були мільйони об'єктів, у вас є один фрагмент на LOH (я припускаю, що тут немає рядків тощо - тобто чисте значення без будь-яких посилань). Це дуже мало впливає на ГК.

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

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

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

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

Знову ж таки, це на місці - ми не скопіювали значення.

У дуже конкретних сценаріях ця тактика може бути дуже успішною; однак, це досить просунутий scernario, який слід намагатися здійснити, лише якщо ви знаєте, що ви робите і чому. За замовчуванням тут буде клас.


+1 Цікава відповідь. Чи готові ви поділитися будь-якими анекдотами в реальному світі щодо такого підходу?
Йордао

@Jordao в мобільному, але пошукайте в Google за: + grobll + "напад GC"
Marc Gravell

1
Дуже дякую. Я знайшов це тут .
Йордао

2
@MarcGravell Чому ви згадали: використовувати масив (не список) ? ListЯ вважаю, використовує Arrayкуліси. ні ?
Royi Namir

4
@RoyiNamir Мені було цікаво і про це, але я вважаю, що відповідь лежить у другому абзаці відповіді Марка. "Однак доступ до нього безпосередньо в масиві не копіює структуру - вона є на місці (на відміну від індексатора списку, який копіює)."
user1323245

40

Із специфікації мови 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 зберігаються в рядку в масиві.

Конструктори структур викликаються новим оператором, але це не означає, що виділяється пам'ять. Замість того, щоб динамічно розподіляти об'єкт і повертати посилання на нього, конструктор структури просто повертає значення структури (як правило, у тимчасове розташування на стеку), і це значення потім копіюється за необхідності.

За допомогою класів можна, щоб дві змінні посилалися на один і той самий об'єкт, і таким чином операції над однією змінною могли впливати на об'єкт, на який посилається інша змінна. У структурах кожна змінна має свою копію даних, і операції над однією з них не можуть впливати на іншу. Наприклад, результат, отриманий наступним фрагментом коду, залежить від того, точка є класом чи структурою.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Якщо Point - клас, вихід 20, тому що a і b посилаються на один і той же об'єкт. Якщо Point - структура, вихід 10 - тому, що присвоєння a b створює копію значення, і ця копія не впливає на подальше присвоєння ax

Попередній приклад висвітлює два обмеження конструкцій. По-перше, копіювання цілої структури зазвичай менш ефективно, ніж копіювання посилання на об'єкт, тому передача параметрів присвоєння та значення може бути дорожчою для структур, ніж для типів посилань. По-друге, крім параметрів ref і out, неможливо створити посилання на структури, що виключає їх використання в ряді ситуацій.


4
Хоча той факт, що посилання на структури не можна зберігати, іноді є обмеженням, це також є дуже корисною характеристикою. Однією з найважливіших недоліків .net є те, що не існує гідного способу передавати зовнішній код посилання на об'єкт, що змінюється, без вічного втрати контролю над цим об'єктом. Навпаки, можна сміливо дати зовнішній метод a refмутабельній структурі і знати, що будь-які мутації, які зовнішній метод буде виконувати на ньому, будуть зроблені до його повернення. Це занадто погано .net не має поняття ефемерних параметрів та функцій повернення значень, оскільки ...
supercat

4
... це дозволило б досягти вигідної семантики структур, що передаються, refз об'єктами класу. По суті, локальні змінні, параметри та значення повернення функції можуть бути стійкими (за замовчуванням), поверненими або ефемерними. Кодексу було б заборонено копіювати ефемерні речі на все, що пережило б нинішній обсяг. Повертаючі речі були б подібними до ефемерних речей, за винятком того, що їх можна було б повернути з функції. Повернене значення функції буде пов'язане з найсуворішими обмеженнями, застосовними до будь-якого з її "повертаються" параметрів.
supercat

34

Структури хороші для атомного представлення даних, де зазначені дані можуть бути скопійовані кілька разів кодом. Клонування об'єкта взагалі дорожче, ніж копіювання структури, оскільки передбачає розподіл пам’яті, запуск конструктора та взаємодію / вивезення сміття, коли це зроблено з ним.


4
Так, але великі структури можуть бути дорожчими, ніж посилання на клас (при переході до методів).
Алекс

27

Ось основне правило.

  • Якщо всі поля членів є типовими значеннями, створіть структуру .

  • Якщо якесь одне поле члена є еталонним типом, створіть клас . Це тому, що поле референтного типу все одно потребуватиме виділення купи.

Приклади

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; p.X=4; q.X=5;, p.Xбуде мати значення 4 , якщо aце тип структури, і 5 , якщо це тип класу. Якщо ви бажаєте зручно змінювати члени типу, слід вибрати "клас" або "структуру", залежно від того, чи qпотрібно впливати на зміни p.
supercat

Так, я погоджуюсь, що опорна змінна буде знаходитися в стеці, але об'єкт, на який вона посилається, буде існувати в купі. Хоча структури та класи поводяться по-різному при призначенні іншої змінної, але я не думаю, що це сильний вирішальний фактор.
Usman Zafar

Змінні структури та мутаційні класи поводяться абсолютно по-різному; якщо один правий, інший, швидше за все, помилиться. Я не впевнений, як поведінка не буде вирішальним фактором у визначенні того, чи використовувати структуру чи клас.
supercat

Я сказав, що це не сильний вирішальний фактор, тому що часто, коли ви створюєте клас або структуру, ви не знаєте, як це буде використано. Отже, ви зосереджуєтесь на тому, як з точки зору дизайну речі мають більше сенсу. У будь-якому разі я ніколи не бачив жодного місця в бібліотеці .NET, де структура містить змінну.
Усман Зафар

1
Тип структури ArraySegment<T>інкапсулює a T[], який завжди є класовим типом. Тип структури KeyValuePair<TKey,TValue>часто використовується з типами класів як загальними параметрами.
supercat

19

По-перше: сценарії Interop або коли потрібно вказати макет пам'яті

По-друге: коли дані майже будь-якого розміру, як еталонний вказівник.


17

Потрібно використовувати "структура" в ситуаціях, коли ви хочете чітко вказати макет пам'яті за допомогою StructLayoutAttribute - як правило, для PInvoke.

Редагувати: коментар вказує, що ви можете використовувати клас або структуру з StructLayoutAttribute, і це, безумовно, правда. На практиці ти зазвичай використовуєш структуру - вона розподіляється на стеку проти купи, що має сенс, якщо ти просто передаєш аргумент на виклик некерованого методу.


5
StructLayoutAttribute можна застосовувати до структур або класів, тому це не є приводом для використання структур.
Стівен Мартін

Чому це має сенс, якщо ви просто передаєте аргумент на виклик некерованого методу?
Девід Клемффнер

16

Я використовую структури для упаковки або розпакування будь-якого формату двійкового зв'язку. Це включає читання або запис на диск, списки вершин DirectX, мережеві протоколи або обробку зашифрованих / стислих даних.

Три наведені вами рекомендації не були корисними для мене в цьому контексті. Коли мені потрібно виписати чотириста байт речей у Конкретному порядку, я визначу чотиристобайтну структуру, і я наповнити її будь-якими незв'язаними значеннями, які він повинен мати, і я збираюся налаштовувати його будь-яким способом має найбільш сенс на той час. (Гаразд, чотириста байт було б досить дивно ... але тоді, коли я писав файли Excel для життя, я мав справу зі структурами розміром до сорока байтів у всьому, тому що настільки великі деякі записи BIFF.)


Не могли б ви так просто використати для цього посилання?
Девід Клемффнер

15

За винятком значень типів, які використовуються безпосередньо під час виконання та різними іншими цілями для цілей PInvoke, вам слід використовувати вартієтипи лише у двох сценаріях.

  1. Коли вам потрібна семантика копіювання.
  2. Коли вам потрібна автоматична ініціалізація, як правило, в масивах цих типів.

# 2, здається, є частиною причини поширеності структур у класах колекціонування .Net ..
IAb Abstract

Якщо перше, що слід зробити при створенні місця зберігання типу класу, було б створити новий екземпляр цього типу, зберегти посилання на нього в цьому місці і ніколи не копіювати посилання ніде і не перезаписувати його, то структура і клас поводився б однаково. Структури мають зручний стандартний спосіб копіювання всіх полів з одного екземпляра в інший і, як правило, пропонують кращу ефективність у випадках, коли ніколи не буде дублювати посилання на клас (за винятком ефемерного thisпараметра, який використовується для виклику його методів); класи дозволяють дублювати посилання.
supercat

13

.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з цих типів.


Копіювання невеликих структур або передача за значенням настільки ж дешева, як і копіювання або передача посилань на клас, або передача конструкцій ref. Передача будь-якої структури розмірів за refвартістю така ж, як передача посилання на клас за значенням. Копіювати будь-яку структуру розміру або передавати за значенням дешевше, ніж виконувати захисну копію об’єкта класу та зберігати чи передавати посилання на нього. Класи великих часів кращі, ніж структури для зберігання значень, є (1), коли класи незмінні (щоб уникнути оборонного копіювання), і кожен створений екземпляр буде передано багато, або ...
supercat

... (2) коли структура з різних причин просто не може бути використана [наприклад, тому, що потрібно використовувати вкладені посилання на щось на зразок дерева або тому, що потрібен поліморфізм]. Зауважте, що при використанні типів значень, як правило, слід виставляти поля, які безпосередньо відсутні у певній причині цього не робити (тоді як для більшості типів поля поля повинні бути зафіксовані у властивостях). Багато так званих "злищ" типів змінних значень виникають із непотрібного загортання полів у властивості (наприклад, хоча деякі компілятори дозволяють викликати налаштування властивостей на структурі лише для читання, тому що це іноді ...
supercat

... зробіть правильно, усі компілятори правильно відхилили б спроби безпосередньо встановити поля на такі структури; найкращий спосіб забезпечити відхилення компіляторів readOnlyStruct.someMember = 5;- це не зробити someMemberвластивість лише для читання, а замість цього зробити поле.
supercat

12

Структура є типом значення. Якщо призначити структуру новій змінній, нова змінна міститиме копію оригіналу.

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 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

12

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

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

PS Ще один орієнтир щодо проходження структури / класу через стек викликів - 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;
        }
    }

Ви зрозуміли, чому структури настільки повільніші, коли вони використовуються в списках тощо? Це через приховані бокси та розпакування, про які ви згадали? Якщо так, чому це відбувається?
Марко Грдиніч

Доступ до структури в масиві повинен бути швидшим саме тому, що не потрібно додаткового посилання. Бокс / Unboxing є випадком для linq.
Роман Покровський

10

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

Зауважте, що можна сконструювати тип структури так, щоб він по суті поводився як тип класу, якщо структура містить приватне поле типу класу та перенаправляє власні члени до об’єкта загорнутого класу. Наприклад, PersonCollectionможе запропонувати властивості, SortedByNameі SortedByIdобидва вони містять "незмінне" посилання на PersonCollection(встановлений у їх конструкторі) та реалізують GetEnumerator, викликаючи або creator.GetNameSortedEnumeratorабо creator.GetIdSortedEnumerator. Такі структури будуть поводитись так само, як посилання на a PersonCollection, за винятком того, що їхні GetEnumeratorметоди будуть пов'язані з різними методами в PersonCollection. Можна також мати структуру, яка обгортає частину масиву (наприклад, можна визначити ArrayRange<T>структуру, яка міститиме T[]виклик Arr, int Offsetта intLength, З індексним майна , які за індексом idxв діапазоні від 0 до Length-1, буде доступ Arr[idx+Offset]). На жаль, якщо fooекземпляр такої структури є лише для читання, поточні версії компілятора не дозволять виконувати такі операції, як foo[3]+=4;тому, що вони не мають змоги визначити, чи намагалися б такі операції записувати в поля foo.

Можна також спроектувати структуру, яка поводиться як тип значення, який містить колекцію змінного розміру (яка, здається, буде скопійована кожного разу, коли структура), але єдиний спосіб зробити цю роботу - забезпечити відсутність об'єкта, до якого Структура посилань коли-небудь буде піддаватися чому-небудь, що може його мутувати. Наприклад, може бути структура, схожа на масив, яка містить приватний масив, і індексований метод "put" створює новий масив, вміст якого схожий на оригінальний, за винятком одного зміненого елемента. На жаль, зробити так, щоб такі структури були ефективними, може бути дещо складно. Хоча буває, що семантика структури може бути зручною (наприклад, можливість передавати колекцію, схожу на масив, у звичайну програму, причому абонент і абонент обидва знають, що зовнішній код не змінює колекцію,


10

Ні - я не повністю згоден з правилами. Вони є гарними рекомендаціями, які слід враховувати з ефективністю та стандартизацією, але не з огляду на можливості.

Як ви бачите у відповідях, існує багато творчих способів їх використання. Отже, ці вказівки повинні бути саме такими, завжди заради продуктивності та ефективності.

У цьому випадку я використовую класи, щоб представити об'єкти реального світу в їх більшій формі, я використовую структури для представлення менших об'єктів, які мають більш точне використання. Як ви це сказали, "більш згуртоване ціле". Ключове слово є згуртованим. Класи будуть більш об'єктно-орієнтованими елементами, тоді як структури можуть мати деякі з цих характеристик, хоча в меншому масштабі. ІМО.

Я їх багато використовую в тегах Treeview і Listview, де до загальних статичних атрибутів можна отримати доступ дуже швидко. Я завжди намагався отримати цю інформацію іншим способом. Наприклад, у своїх додатках для баз даних я використовую Treeview, де у мене є таблиці, SP-адреси, функції або будь-які інші об'єкти. Я створюю та заповнюю свою структуру, вкладаю її в тег, витягую її, отримую дані про вибір і так далі. Я б не робив цього з класом!

Я намагаюся тримати їх невеликими, використовую їх в окремих ситуаціях і не дозволяю їм змінюватися. Доцільно бути в курсі пам’яті, розподілу та продуктивності. І тестування так необхідне.


Структури можуть бути розумно використані для представлення легких незмінних об'єктів, або вони можуть бути розумно використані для представлення фіксованих наборів пов'язаних, але незалежних змінних (наприклад, координат точки). Поради на цій сторінці корисні для конструкцій, які призначені для виконання першої мети, але неправильні для структур, призначених для виконання другої мети. Моє теперішнє мислення полягає в тому, що структури, які мають будь-які приватні поля, зазвичай повинні відповідати зазначеному опису, але багато структур повинні відкривати весь їхній стан через публічні поля.
supercat

Якщо специфікація типу "3d точка" вказує на те, що весь її стан піддається впливу читаних елементів x, y та z, і можливо створити екземпляр з будь-якою комбінацією doubleзначень для цих координат, така специфікація примусить його до поводяться семантично ідентично структурі відкритого поля, за винятком деяких деталей багатопотокової поведінки (незмінний клас був би кращим у деяких випадках, тоді як структура відкритого поля була б кращою в інших; так звана "незмінна" структура бути гіршим у кожному випадку).
supercat

8

Моє правило таке

1, Завжди використовувати клас;

2, Якщо є якісь проблеми продуктивності, я намагаюся змінити деякий клас на структуру залежно від правил, про які згадується @IAbrief, а потім зробити тест, щоб побачити, чи можуть ці зміни покращити продуктивність.


Важливий випадок використання, який Microsoft ігнорує, коли людина хоче, щоб змінна типу Fooвключала фіксовану колекцію незалежних значень (наприклад, координат точки), яку іноді хочеться пройти навколо як групу, а іноді хочеться змінити самостійно. Я не знайшов жодної схеми використання класів, яка поєднує обидві цілі майже так само добре, як просту структуру відкритого поля (яка, будучи фіксованою колекцією незалежних змінних, ідеально підходить до рахунку).
supercat

1
@supercat: Я думаю, що не зовсім справедливо звинувачувати Microsoft у цьому. Справжня проблема тут полягає в тому, що C # як об'єктно-орієнтована мова просто не зосереджується на звичайних типах записів, які викривають дані лише без особливої ​​поведінки. C # - це не парадигмальна мова в такій мірі, як, наприклад, C ++. Попри це, я також вважаю, що дуже мало людей програмують чистий OOP, тому, можливо, C # занадто ідеалістична мова. (Я для одного з них нещодавно почав виставляти public readonlyполя і для моїх типів, тому що створення властивостей лише для читання - це занадто багато роботи, щоб практично не отримати користі.)
stakx - більше не сприяє

1
@stakx: Не потрібно "фокусуватись" на таких типах; визнання їх за те, що вони є, було б достатньо. Найбільшою слабкістю C # у відношенні конструкцій є його найбільша проблема і в багатьох інших областях: мова надає неадекватні умови для вказівки, коли певні перетворення є чи не підходять, а відсутність таких засобів призводить до невдалих дизайнерських рішень. Так , наприклад, 99% від «змінювані структури є злом» виникає з компілятора перетворення MyListOfPoint[3].Offset(2,3);в var temp=MyListOfPoint[3]; temp.Offset(2,3);, перетворення , яке є підробленими , коли застосовується ...
Supercat

... до Offsetметоду. Правильний спосіб запобігти такому неправдивому коду не повинен робити структури непотрібними, а натомість, щоб дозволити методам, як Offsetтеги, позначати атрибутом, що забороняє вищезгадане перетворення. Неявні чисельні перетворення теж могли бути набагато кращими, якби вони могли бути позначені тегами, щоб бути застосовними лише у випадках, коли їх виклик був би очевидним. Якщо для перевантажень існує foo(float,float)і foo(double,double), я б стверджував, що намагаючись використовувати a floatі a doubleчасто, не слід застосовувати неявну конверсію, а натомість має бути помилкою.
supercat

Пряме присвоєння doubleзначення a floatабо передача його методу, який може приймати floatаргумент, але ні double, майже завжди робив би те, що програміст задумав. Навпаки, присвоєння floatвиразу doubleбез явного набору тексту часто є помилкою. Єдиний час, що дозволяє неявну double->floatконверсію, викликав би проблеми, коли це призведе до вибору менш ніж ідеального перевантаження. Я вважаю, що правильним способом запобігти це не повинно було забороняти імпровізований подвійний>> float, а мітка перевантаження атрибутами, щоб заборонити перетворення.
supercat

8

Клас - еталонний тип. Коли створюється об’єкт класу, змінна, якій призначений об'єкт, містить лише посилання на цю пам'ять. Коли посилання на об'єкт присвоюється новій змінній, нова змінна відноситься до вихідного об'єкта. Зміни, внесені через одну змінну, відображаються в іншій змінній, оскільки вони обоє посилаються на одні і ті ж дані. Структура - це тип значення. Коли структура створена, змінна, якій призначається структура, містить фактичні дані структури. Коли структура присвоюється новій змінній, вона копіюється. Нова змінна та оригінальна змінна тому містять дві окремі копії одних і тих же даних. Зміни, внесені до однієї копії, не впливають на іншу. Взагалі класи використовуються для моделювання більш складної поведінки або даних, які призначені для зміни після створення об’єкта класу.

Класи та структури (Посібник з програмування C #)


Структури також дуже хороші в тих випадках, коли необхідно скріпити кілька пов'язаних, але незалежних змінних разом із стрічкою каналів (наприклад, координати точки). Настанови MSDN є розумними, якщо намагаються створити структури, що ведуть себе як об'єкти, але набагато менш доцільні при проектуванні агрегатів; деякі з них майже точно помиляються в останній ситуації. Наприклад, чим більший ступінь незалежності змінних, інкапсульованих типом, тим більша перевага використання структури відкритого поля, а не незмінного класу.
supercat

6

МІФ №1: СТРУКТИ - ЛЕГКИЙ КЛАСС

Цей міф виходить у найрізноманітніших формах. Деякі люди вважають, що типи значень не можуть або не повинні мати методів чи іншої значущої поведінки - їх слід використовувати як прості типи передачі даних із просто публічними полями або простими властивостями. Тип DateTime є хорошим контрприкладом до цього: має сенс бути типом значення з точки зору того, що він є основною одиницею, як число чи символ, а також має сенс мати можливість виконувати обчислення на основі його значення. Якщо дивитися на речі з іншого напрямку, типи передачі даних у будь-якому випадку часто повинні бути еталонними типами - рішення повинно базуватися на бажаному значенні або семантиці референсного типу, а не простоті типу. Інші люди вважають, що типи значень „легші”, ніж еталонні типи за показниками продуктивності. Правда полягає в тому, що в деяких випадках типи значень є більш ефективними - вони не вимагають збирання сміття, якщо вони не встановлені в коробці, не мають накладних ідентифікацій типу і, наприклад, не потребують перенаправлення. Але іншими способами типи посилань є більш ефективними: передача параметрів, присвоєння значень змінним, повернення значень та подібні операції вимагають скопіювати лише 4 або 8 байт (залежно від того, чи використовується 32-бітний або 64-бітний CLR ), а не копіювання всіх даних. Уявіть, якби ArrayList якось був "чистим" типом значень, і передаючи вираз ArrayList методу, який передбачав копіювання всіх його даних! Майже у всіх випадках ефективність дійсно не визначається таким рішенням. Залишки майже не бувають там, де ви думаєте, що вони будуть, і перш ніж приймати проектне рішення на основі продуктивності, слід виміряти різні варіанти. Варто зазначити, що поєднання двох переконань також не працює. Не має значення, скільки методів має тип (будь то клас або структура) - пам'ять, взята на екземпляр, не впливає. (Існує вартість з точки зору пам’яті, зайнятої для самого коду, але це виникає один раз, а не для кожного примірника.)

МІФ № 2: ТИПИ ДОВІДКУВАННЯ, ЩО ВЖИВУЮТЬСЯ НА СУПІ; ЦІННІ ВИДИ ЖИВІ НА СТАЛІ

Ця часто викликана лінню з боку людини, що повторює це. Перша частина правильна - екземпляр еталонного типу завжди створюється на купі. Друга частина викликає проблеми. Як я вже зазначав, значення змінної живе там, де вона оголошена, тому, якщо у вас є клас зі змінною екземпляра типу int, значення цієї змінної для будь-якого даного об'єкта завжди буде там, де решта даних для об'єкта - на купу. На стеку живуть лише локальні змінні (змінні, задекларовані в методах) та параметри методу. У C # 2 та пізніших версіях навіть деякі локальні змінні насправді не живуть у стеці, як ви побачите, коли ми подивимось на анонімні методи у главі 5. Чи є ці поняття відповідними зараз? Можна сперечатися, що якщо ви пишете керований код, ви повинні дозволити роботі виконуватись за те, як найкраще використовувати пам'ять. Дійсно, мовна специфікація не дає гарантій того, що живе де; майбутня програма виконання може створити деякі об'єкти в стеці, якщо він знає, що він може піти з нього, або компілятор C # може генерувати код, який навряд чи використовує стек. Наступний міф - це лише питання термінології.

МІФ №3: ОБ'ЄКТИ ПЕРЕМОГАЮТЬСЯ ПО СПРАВКИ В C # ЗА ЗАВДАННЯМ

Це, мабуть, найпоширеніший міф. Знову ж таки, люди, які часто заявляють про це (хоча і не завжди), знають, як насправді поводиться C #, але вони не знають, що насправді означає "пройти посилання". На жаль, це заплутано для людей, які знають, що це означає. Офіційне визначення пропуску за посиланням є досить складним, включаючи l-значення та подібну термінологію інформатики, але важливим є те, що якщо ви передаєте змінну за посиланням, метод, який ви викликаєте, може змінити значення змінної абонента змінивши його значення параметра. Тепер пам’ятайте, що значенням змінної типу посилання є посилання, а не сам об’єкт. Ви можете змінити вміст об'єкта, на який посилається параметр, не передаючи сам параметр посиланням. Наприклад,

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

Коли цей метод викликається, значення параметра (посилання на StringBuilder) передається за значенням. Якщо ви повинні змінити значення змінної builder в рамках методу - наприклад, з оператором builder = null; - то зміни, які викликає користувач, не побачать, всупереч міфу. Цікаво зауважити, що міф "посилання" не лише неточний, але й біт "об'єктів передається". Самі об’єкти ніколи не передаються ні за посиланням, ні за значенням. Коли задіяний тип посилання, або змінна передається за посиланням, або значення аргументу (посилання) передається за значенням. Окрім нічого іншого, це відповідає на питання, що відбувається, коли null використовується як аргумент за значенням - якщо об’єкти передавались навколо, це спричинило б проблеми, оскільки не було б об’єкта, який потрібно передавати! Натомість нульове посилання передається за значенням так само, як і будь-яке інше посилання. Якщо це швидке пояснення залишило вас збентеженим, ви можете подивитися на мою статтю "Параметр, що проходить у C #" (http://mng.bz/otVt ), де йдеться про набагато детальніше. Ці міфи не єдині навколо. Бокс і розблокування приходять за свою справедливу частку нерозуміння, яку я спробую вияснити наступним.

Довідка: C # у глибині 3-го видання Джона Скіта


1
Дуже добре, якщо припустити, що ви праві. Також дуже добре додати посилання.
NoChance

5

Я думаю, гарне перше наближення - це "ніколи".

Я думаю, що гарне друге наближення - "ніколи".

Якщо ви відчайдушно відчуваєте парфуми, розгляньте їх, але тоді завжди міряйте.


24
Я б не погодився з цією відповіддю. Конструкції мають легальне використання у багатьох сценаріях. Ось приклад - марширування даних перетинає процеси атомним способом.
Франці Пенов

25
Ви повинні відредагувати свою публікацію та уточнити свої пункти - ви висловили свою думку, але ви повинні її підкріпити, чому ви приймаєте цю думку.
Ерік Форбс

4
Я думаю, їм потрібен еквівалент картки Totin 'Chip ( en.wikipedia.org/wiki/Totin%27_Chip ) для використання конструкцій. Серйозно.
Грег

4
Як людина 87,5 К опублікує подібну відповідь? Чи робив він це ще в дитинстві?
Rohit Vipin Mathews

3
@Rohit - це було шість років тому; Стандарти сайтів тоді були дуже різними. це все-таки погана відповідь, хоча ти маєш рацію.
Ендрю Арнольд

5

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


1
Це найкраща підказка з усіх, ІМХО.
Іван

4

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

Отже, вам слід використовувати структури, коли ви хочете представляти більш прості структури даних, і особливо якщо ви знаєте, що ви будете інстанціювати їх багато. У структурі .NET є багато прикладів, де Microsoft використовує структури замість класів, наприклад, Point, Rectangle та Color structure.


3

Структуру можна використовувати для поліпшення продуктивності вивезення сміття. Хоча зазвичай не потрібно турбуватися про продуктивність GC, існують сценарії, коли це може бути вбивцею. Як і великі кеші в додатках із низькою затримкою. Дивіться приклад для цієї публікації:

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


3

Типи структури або значення можуть використовуватися в наступних сценаріях -

  1. Якщо ви хочете не допустити збирання об’єкта шляхом вивезення сміття.
  2. Якщо це простий тип, і жодна функція-член не змінює поля примірника
  3. Якщо немає необхідності виходити з інших типів або бути похідним до інших типів.

Ви можете дізнатися більше про типи значень та типи значень тут за цим посиланням


3

Коротко, скористайтеся структурою, якщо:

1- ваші властивості / поля об'єкта не потрібно змінювати. Я маю на увазі, що ви просто хочете дати їм початкове значення, а потім прочитати їх.

2- властивості та поля у вашому об’єкті є типом значень, і вони не такі великі.

Якщо це так, ви можете скористатися структурами для кращої продуктивності та оптимізованого розподілу пам'яті, оскільки вони використовують лише стеки, а не стеки та купи (у класах)


2

Я рідко використовую структуру для речей. Але це тільки я. Це залежить від того, чи потрібен мені предмет, який буде нульовим, чи ні.

Як зазначено в інших відповідях, я використовую класи для об'єктів реального світу. Я також маю на увазі, що структури використовуються для зберігання невеликої кількості даних.


-11

Структури подібні до класів / об'єктів. Структура може містити функції, члени і може передаватися у спадок. Але структури в C # використовуються тільки для зберігання даних . Структури займають менше оперативної пам’яті, ніж класи, і збирання сміття простіше збирати . Але коли ви використовуєте функції у своїй структурі, компілятор насправді сприймає цю структуру дуже аналогічно класу / об’єкту, тож якщо ви хочете чогось із функціями, тоді використовуйте клас / об’єкт .


2
Структури НЕ можуть бути успадковані, див. Msdn.microsoft.com/en-us/library/0taef578.aspx
HimBromBeere
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.