Яка різниця між struct і класом у .NET?


Відповіді:


1058

У .NET є дві категорії типів, типи посилань та типи значень .

Структури - цінні типи, а класи - еталонні .

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

Змінна, що містить тип значення, містить усе значення типу значення. Для структури це означає, що змінна містить всю структуру з усіма її полями.

Змінна, що містить тип посилання, містить вказівник або посилання на інше місце в пам'яті, де знаходиться фактичне значення.

Для цього є одна перевага:

  • типи значень завжди містять значення
  • типи посилань можуть містити нульову посилання, тобто це означає, що на даний момент вони взагалі нічого не посилаються

Внутрішньо, посилання типу s реалізуються як покажчики, і, знаючи, що і знаючи, як працює присвоєння змінної, існують інші поведінкові моделі:

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

Оголошуючи змінні чи поля, ось як відрізняються два типи:

  • змінна: тип значення живе на стеку, тип посилання живе на стеці як вказівник на десь у купі пам'яті, де живе фактична пам’ять (хоча зверніть увагу на серію статей Еріка Ліпертса: Стек є деталізацією реалізації ).
  • клас / структурна сфера: тип значення живе повністю всередині типу, тип посилання живе всередині типу як вказівник на десь в купі пам'яті, де живе фактична пам'ять.

43
В інтересах повної повноти я повинен зазначити, що Ерік Ліпперт сказав, що стек - це деталь реалізації , щоразу, коли я згадую стек вище, майте на увазі дописи Еріка.
Лассе В. Карлсен

2
Чи все це справедливо і для C ++?
Корай Тугай

9
Ще одна важлива відмінність - використання. Від MSDN: "структури зазвичай використовуються для інкапсуляції невеликої групи пов'язаних змінних, таких як координати прямокутника. Структури також можуть містити конструктори, константи, поля, методи, властивості, індексатори, оператори, події та вкладені типи, хоча якщо таких декілька потрібні члени, ви можете замість цього зробити свій тип класом. "
thewpfguy

4
@KorayTugay Ні, це не так.
ZoomIn

9
@KorayTugay в C ++ структура та клас абсолютно рівноцінні, за винятком одного - обмеження доступу за замовчуванням (клас за замовчуванням приватний, структура має загальнодоступний)
berkus

207

Короткий підсумок кожного:

Тільки заняття:

  • Може підтримувати спадщину
  • Є еталонними (вказівними) типами
  • Посилання може бути нульовим
  • Майте накладні витрати на новий екземпляр

Тільки структури:

  • Неможливо підтримати успадкування
  • Є типи значень
  • Передаються за значенням (як цілі числа)
  • Не може мати нульову посилання (якщо не використовується Nullable)
  • Не мати накладних витрат на пам'ять для нового екземпляра - якщо тільки "коробку"

Класи та структури:

  • Чи використовуються складові типи даних, що містять кілька змінних, які мають певну логічну залежність
  • Може містити методи та події
  • Може підтримувати інтерфейси

16
Є деякі частини цієї відповіді, які не зовсім правильні. Заняття не завжди йдуть на купу, а структури не завжди йдуть на стеку. Поточні винятки включають поля структури на класі, захоплені змінні анонімними методами та лямбда-виразами, ітераторні блоки та вже згадані вікна значення. Але розподіл стека проти купи - це детальна інформація про реалізацію і може зазнавати змін. Ерік Ліппарт обговорює це тут . Я подав заявку, але з радістю видалить його, якщо оновите.
Саймон П Стівенс

1
stru не підтримують успадкування від інших стуктів / класів, але ви МОЖЕТЕ реалізувати інтерфейс на struct.
thewpfguy

2
Ви можете уточнити, що ви маєте на увазі, коли ви стверджуєте, що структури "Не мають накладних витрат на пам'ять на новий примірник" . Моя перша інтерпретація полягала в тому, що ви заявляли - очевидно, абсурдно - що структури використовують нульову пам'ять. Тоді я подумав, що, можливо, ви намагаєтесь сказати, що структура, на відміну від класу, вимагає стільки ж пам’яті, скільки сума полів її членів, і не більше. Але потім я заглянув c# struct memory overheadі знайшов цю відповідь Ганса Пассанта, який говорить, що ні, і це не так. То що ти маєш на увазі?
Марк Амерді

4
@MarkAmery У мене була така ж початкова реакція, як і ви на вираз "немає пам'яті над головою", але я думаю, що ОП посилається на те, що екземпляри classкерованої пам'яті (обробляється смітником), тоді як випадки structне .
Хатч

1
"Структура передається за значенням (як цілі числа)" є помилковою: всі змінні передаються за значенням, також тип посилання. Якщо ви хочете передати змінну за посиланням, вам слід скористатися ключовим словом "ref". jonskeet.uk/csharp/parameters.html#ref
Marco Staffoli

41

У .NET декларації структури та класу розрізняють типи посилань та типи значень.

Коли ви проходите навколо еталонного типу, зберігається лише один. Весь код, який звертається до екземпляра, має доступ до того самого.

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

Це можна показати на прикладі:

struct MyStruct 
{
    string MyProperty { get; set; }
}

void ChangeMyStruct(MyStruct input) 
{ 
   input.MyProperty = "new value";
}

...

// Create value type
MyStruct testStruct = new MyStruct { MyProperty = "initial value" }; 

ChangeMyStruct(testStruct);

// Value of testStruct.MyProperty is still "initial value"
// - the method changed a new copy of the structure.

Для класу це було б інакше

class MyClass 
{
    string MyProperty { get; set; }
}

void ChangeMyClass(MyClass input) 
{ 
   input.MyProperty = "new value";
}

...

// Create reference type
MyClass testClass = new MyClass { MyProperty = "initial value" };

ChangeMyClass(testClass);

// Value of testClass.MyProperty is now "new value" 
// - the method changed the instance passed.

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

Структури - це фактичне значення - вони можуть бути порожніми, але ніколи не нульовими. З цієї причини у структур завжди є конструктор за замовчуванням без параметрів - їм потрібно "початкове значення".


@ T.Todua Так, є кращі відповіді вище, що я проголосував і вибрав як відповідь після надання цього - це з ранньої бета-версії SO, коли ми ще розбирали правила.
Кіт

1
Я не знаю, чи правильно ви мене зрозуміли, я дійсно схвалив / прийняв вашу відповідь (на відміну від вищезазначених відповідей), тому що у ваших були хороші приклади (не тільки теоретичне пояснення, на відміну від вищезазначеної відповіді, який мав лише теоретичні пояснення без прикладів ).
Т.Тодуа

24

Різниця між структурами та класами:

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

21

Від вибору Microsoft між класом і структурою ...

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

ВАЖИМО структуру замість класу:

  • Якщо екземпляри типу невеликі і зазвичай недовговічні або зазвичай вбудовані в інші об’єкти.

X Ухиляйте структуру, якщо тип не має всіх перелічених нижче характеристик:

  • Він логічно представляє єдине значення, схоже на примітивні типи (int, double та ін.).
  • Він має розмір примірника до 16 байт.
  • Це незмінне. (неможливо змінити)
  • Боксувати не доведеться часто.

19

На додаток до всіх відмінностей, описаних в інших відповідях:

  1. У структур не може бути явного конструктора без параметрів, тоді як клас може
  2. У конструкцій не може бути деструкторів , тоді як клас може
  3. Структури не можуть успадкувати іншу структуру або клас, тоді як клас може успадкувати інший клас. (І структури, і класи можуть реалізовуватися через інтерфейс.)

Якщо ви знайдете відео, де пояснюються всі відмінності, ви можете перевірити частину 29 - Підручник з C # - Різниця між класами та структурами в C # .


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

15

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

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

Розрізнення покращується за іменами C ++ / CLI: "ref class" - клас, як описано по-перше, "class value" - клас, як описано другий. Ключові слова "клас" і "структура", використовувані C #, - це просто те, що потрібно вивчити.


11
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
|                        |                                                Struct                                                |                                               Class                                               |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| Type                   | Value-type                                                                                           | Reference-type                                                                                    |
| Where                  | On stack / Inline in containing type                                                                 | On Heap                                                                                           |
| Deallocation           | Stack unwinds / containing type gets deallocated                                                     | Garbage Collected                                                                                 |
| Arrays                 | Inline, elements are the actual instances of the value type                                          | Out of line, elements are just references to instances of the reference type residing on the heap |
| Aldel Cost             | Cheap allocation-deallocation                                                                        | Expensive allocation-deallocation                                                                 |
| Memory usage           | Boxed when cast to a reference type or one of the interfaces they implement,                         | No boxing-unboxing                                                                                |
|                        | Unboxed when cast back to value type                                                                 |                                                                                                   |
|                        | (Negative impact because boxes are objects that are allocated on the heap and are garbage-collected) |                                                                                                   |
| Assignments            | Copy entire data                                                                                     | Copy the reference                                                                                |
| Change to an instance  | Does not affect any of its copies                                                                    | Affect all references pointing to the instance                                                    |
| Mutability             | Should be immutable                                                                                  | Mutable                                                                                           |
| Population             | In some situations                                                                                   | Majority of types in a framework should be classes                                                |
| Lifetime               | Short-lived                                                                                          | Long-lived                                                                                        |
| Destructor             | Cannot have                                                                                          | Can have                                                                                          |
| Inheritance            | Only from an interface                                                                               | Full support                                                                                      |
| Polymorphism           | No                                                                                                   | Yes                                                                                               |
| Sealed                 | Yes                                                                                                  | When have sealed keyword                                                                          |
| Constructor            | Can not have explicit parameterless constructors                                                     | Any constructor                                                                                   |
| Null-assignments       | When marked with nullable question mark                                                              | Yes (+ When marked with nullable question mark in C# 8+)                                          |
| Abstract               | No                                                                                                   | When have abstract keyword                                                                        |
| Member Access Modifiers| public, private, internal                                                                            | public, protected, internal, protected internal, private protected                                |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+

1
Це насправді досить чудово: узагальнено та інформативно. Будь ласка, не забудьте хоча б раз перевірити прочитання своєї відповіді - ви поміняли пояснення структури та класу в деяких рядках, також є деякі помилки.
Роберт Синорадзкі

1
@ensisNoctis Вибачте за помилки і дякую за редагування. Я повинен перечитати свої відповіді 😅
0xaryan

8

Структура проти класу

Структура є типом значення, тому вона зберігається у стеку, але клас є еталонним типом і зберігається у купі.

Структура не підтримує успадкування та поліморфізм, але клас підтримує обоє.

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

Оскільки структура є типом значення, ми не можемо призначити null об’єкту структура, але це не стосується класу.


5
Щодо "всі члени структури є публічними": Якщо я не помиляюся, це неправильно. "Рівень доступу для членів класу та членів структури, включаючи вкладені класи та структури, за замовчуванням є приватним." msdn.microsoft.com/en-us/library/ms173121.aspx
Nate Cook

8

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

  • Маючи структуру, масив містить екземпляр структура
  • З класом масив містить вказівник на екземпляр класу в іншому місці пам'яті

Отже, масив структур виглядає так у пам'яті

[struct][struct][struct][struct][struct][struct][struct][struct]

Тоді як масив класів виглядає приблизно так

[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]

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

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

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

Ось код, який ви можете перевірити. На моїй машині ітерація через масив класів займає ~ 3x довше, ніж масив Stru.

    private struct PerformanceStruct
    {
        public int i1;
        public int i2;
    }

    private class PerformanceClass
    {
        public int i1;
        public int i2;
    }

    private static void DoTest()
    {
        var structArray = new PerformanceStruct[100000000];
        var classArray = new PerformanceClass[structArray.Length];

        for (var i = 0; i < structArray.Length; i++)
        {
            structArray[i] = new PerformanceStruct();
            classArray[i] = new PerformanceClass();
        }

        long total = 0;
        var sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < structArray.Length; i++)
        {
            total += structArray[i].i1 + structArray[i].i2;
        }

        sw.Stop();
        Console.WriteLine($"Struct Time: {sw.ElapsedMilliseconds}");
        sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < classArray.Length; i++)
        {
            total += classArray[i].i1 + classArray[i].i2;
        }

        Console.WriteLine($"Class Time: {sw.ElapsedMilliseconds}");
    }

-1; "Структури - це типи значень, тому вони зберігають значення, класи - це еталонні типи, тому вони посилаються на клас." є незрозумілим і навряд чи має сенс для тих, хто вже не зрозумів цього з інших відповідей тут, і "З класом клас, що містить, просто міститиме вказівник на новий клас в іншій області пам'яті". плутає класи з екземплярами класу.
Марк Амері

@MarkAmery Я намагався трохи уточнити. Справа, яку я справді намагався зробити, - це різниця в способах роботи масивів зі значеннями та еталонними типами, і вплив цього на продуктивність. Я не намагався пояснити, що таке значення та типи посилань, оскільки це робиться в багатьох інших відповідях.
Буде Калдервуд

7

Тільки щоб зробити його повною, є ще одна відмінність при використанні Equalsметоду, який успадковується всіма класами та структурами.

Скажімо, у нас є клас та структура:

class A{
  public int a, b;
}
struct B{
  public int a, b;
}

і в методі Main у нас є 4 об'єкти.

static void Main{
  A c1 = new A(), c2 = new A();
  c1.a = c1.b = c2.a = c2.b = 1;
  B s1 = new B(), s2 = new B();
  s1.a = s1.b = s2.a = s2.b = 1;
}

Тоді:

s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false

Отже , структури підходять для числових об'єктів, як точок (збереження координат x і y). А заняття підходять для інших. Навіть якщо 2 людини мають одне ім’я, зріст, вагу ..., вони все одно 2 людини.


6

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

Інші, ймовірно, можуть дати вам більше деталей, ніж я, але я використовую структури, коли структура, яку я збираюсь, проста.


4

Окрім основної різниці специфікатора доступу та декількох згаданих вище, я хотів би додати деякі основні відмінності, включаючи декілька згаданих вище, із зразком коду з результатом, що дасть більш чітке уявлення про довідку та значення

Структури:

  • Це типи значень і не потребують розподілу купи.
  • Розподіл пам’яті різний і зберігається в стеці
  • Корисно для невеликих структур даних
  • Впливаючи на продуктивність, коли ми передаємо значення методу, ми передаємо всю структуру даних і все передаємо в стек.
  • Конструктор просто повертає значення структури (як правило, у тимчасове розташування на стеку), і це значення потім копіюється за необхідності
  • Кожна змінна має свою власну копію даних, і неможливо, щоб операції над однією впливали на іншу.
  • Не підтримують вказане користувачем успадкування, і вони неявно успадковують від об'єкта типу

Клас:

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

Зразок коду

    static void Main(string[] args)
    {
        //Struct
        myStruct objStruct = new myStruct();
        objStruct.x = 10;
        Console.WriteLine("Initial value of Struct Object is: " + objStruct.x);
        Console.WriteLine();
        methodStruct(objStruct);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Struct Object is: " + objStruct.x);
        Console.WriteLine();

        //Class
        myClass objClass = new myClass(10);
        Console.WriteLine("Initial value of Class Object is: " + objClass.x);
        Console.WriteLine();
        methodClass(objClass);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Class Object is: " + objClass.x);
        Console.Read();
    }
    static void methodStruct(myStruct newStruct)
    {
        newStruct.x = 20;
        Console.WriteLine("Inside Struct Method");
        Console.WriteLine("Inside Method value of Struct Object is: " + newStruct.x);
    }
    static void methodClass(myClass newClass)
    {
        newClass.x = 20;
        Console.WriteLine("Inside Class Method");
        Console.WriteLine("Inside Method value of Class Object is: " + newClass.x);
    }
    public struct myStruct
    {
        public int x;
        public myStruct(int xCons)
        {
            this.x = xCons;
        }
    }
    public class myClass
    {
        public int x;
        public myClass(int xCons)
        {
            this.x = xCons;
        }
    }

Вихідні дані

Початкове значення об'єкта Struct становить: 10

Метод Inside Struct Внутрішній метод Value Object Struct дорівнює: 20

Після виклику методу значення об'єкта Struct дорівнює: 10

Початкова вартість об’єкта класу: 10

Метод внутрішнього класу Внутрішній метод значення об'єкта класу становить: 20

Значення після виклику методу об'єкта класу становить: 20

Тут ви чітко бачите різницю між викликом за значенням та викликом за посиланням.


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

  2. Створення структурного екземпляра не може спричинити збір сміття (якщо конструктор прямо чи опосередковано не створює екземпляр посилального типу), тоді як створення екземпляра типу посилання може спричинити збір сміття.

  3. У структурі завжди є вбудований конструктор за замовчуванням.

    class DefaultConstructor
    {
        static void Eg()
        {
            Direct     yes = new   Direct(); // Always compiles OK
            InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible
            //...
        }
    }

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

    class NonInstantiable
    {
        private NonInstantiable() // OK
        {
        }
    }
    
    struct Direct
    {
        private Direct() // Compile-time error
        {
        }
    }
  4. У структури не може бути деструктора. Деструктор - це лише перекриття об'єкта. Доопрацьовуйте під маскуванням, і конструкції, будучи типовими типами, не підлягають збору сміття.

    struct Direct
    {
        ~Direct() {} // Compile-time error
    }
    class InDirect
    {
        ~InDirect() {} // Compiles OK
    }
    
    And the CIL for ~Indirect() looks like this:
    
    .method family hidebysig virtual instance void
            Finalize() cil managed
    {
      // ...
    } // end of method Indirect::Finalize
  5. Структура неявно запечатана, клас - ні.
    Структура не може бути абстрактною, клас може.
    Структура не може викликати: base () у своєму конструкторі, тоді як клас без явного базового класу не може.
    Структура не може поширити інший клас, клас може.
    Структура не може оголосити захищені члени (наприклад, поля, вкладені типи), якими може користуватися клас.
    Структура не може оголошувати абстрактних членів функції, абстрактний клас може.
    Структура не може оголосити членів віртуальної функції, клас може.
    Структура не може оголосити закритих членів функції, клас може.
    Структура не може оголосити членів функції заміни, клас може.
    Єдиним винятком із цього правила є те, що структура може змінювати віртуальні методи System.Object, а саме: Equals (), GetHashCode () та ToString ().


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

@supercat Так, нестатична подія в структурі буде дуже дивною, і вона буде корисна лише для змінних структур, а сама подія (якщо це подія, схожа на поле) перетворює структуру на "мутабельну" "категорія, а також вводить поле посилання в структура. Нестатичні події в структурах повинні бути злими.
Jeppe Stig Nielsen

@JeppeStigNielsen: Єдиним шаблоном, який я міг би побачити, де було б сенс структури мати подію, було б, якщо мета структури полягала в тому, щоб містити незмінне посилання на об'єкт класу, для якого він поводився як проксі. Авто події були б абсолютно марними в такому сценарії; натомість події передплати та скасування підписки повинні бути ретрансльовані до класу за структурою. Я хотів би, щоб .NET мав (або дозволив би визначити) тип структури "кеш-скриньки" з початково нульовим прихованим полем типу Object, яке містило б посилання на коробку копії структури.
supercat

1
@JeppeStigNielsen: структурує перевершує класи у багатьох сценаріях використання проксі; Найбільша проблема використання конструкцій полягає в тому, що у випадках, коли бокс закінчується необхідністю, він часто закінчується перенесенням на внутрішню петлю. Якби було способом уникнути того, щоб структури неодноразово потрапляли в бокс , вони були б кращими за класи в багатьох інших сценаріях використання.
supercat

4

Як вже було сказано раніше: Класи - це еталонний тип, тоді як структури - це типові значення з усіма наслідками.

Як правило, Рамкові рекомендації щодо дизайну рекомендують використовувати структури замість класів, якщо:

  • Він має розмір примірника до 16 байт
  • Це логічно представляє єдине значення, схоже на примітивні типи (int, double та ін.)
  • Це незмінне
  • Боксувати не доведеться часто

3

Є один цікавий випадок головоломки "клас проти структури" - ситуація, коли потрібно повернути кілька результатів із методу: вибрати, який використовувати. Якщо ви знаєте історію ValueTuple - ви знаєте, що ValueTuple (структура) була додана, оскільки вона повинна бути більш ефективною, ніж Tuple (клас). Але що це означає в цифрах? Два тести: один є структурою / класом, який має 2 поля, інший із структурою / класом, який має 8 полів (розмірність більше 4-класу повинна стати більш ефективною, ніж структура з точки зору тикових процесорів, але, звичайно, також слід враховувати навантаження GC ).

PS Ще один орієнтир для конкретного випадку "штрих або клас з колекціями" є: https://stackoverflow.com/a/45276657/506147

BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 2 [1703, Creators Update] (10.0.15063.726)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233540 Hz, Resolution=309.2586 ns, Timer=TSC
.NET Core SDK=2.0.3
  [Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
  Clr    : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2115.0
  Core   : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


            Method |  Job | Runtime |     Mean |     Error |    StdDev |      Min |      Max |   Median | Rank |  Gen 0 | Allocated |
------------------ |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
  TestStructReturn |  Clr |     Clr | 17.57 ns | 0.1960 ns | 0.1834 ns | 17.25 ns | 17.89 ns | 17.55 ns |    4 | 0.0127 |      40 B |
   TestClassReturn |  Clr |     Clr | 21.93 ns | 0.4554 ns | 0.5244 ns | 21.17 ns | 23.26 ns | 21.86 ns |    5 | 0.0229 |      72 B |
 TestStructReturn8 |  Clr |     Clr | 38.99 ns | 0.8302 ns | 1.4097 ns | 37.36 ns | 42.35 ns | 38.50 ns |    8 | 0.0127 |      40 B |
  TestClassReturn8 |  Clr |     Clr | 23.69 ns | 0.5373 ns | 0.6987 ns | 22.70 ns | 25.24 ns | 23.37 ns |    6 | 0.0305 |      96 B |
  TestStructReturn | Core |    Core | 12.28 ns | 0.1882 ns | 0.1760 ns | 11.92 ns | 12.57 ns | 12.30 ns |    1 | 0.0127 |      40 B |
   TestClassReturn | Core |    Core | 15.33 ns | 0.4343 ns | 0.4063 ns | 14.83 ns | 16.44 ns | 15.31 ns |    2 | 0.0229 |      72 B |
 TestStructReturn8 | Core |    Core | 34.11 ns | 0.7089 ns | 1.4954 ns | 31.52 ns | 36.81 ns | 34.03 ns |    7 | 0.0127 |      40 B |
  TestClassReturn8 | Core |    Core | 17.04 ns | 0.2299 ns | 0.2150 ns | 16.68 ns | 17.41 ns | 16.98 ns |    3 | 0.0305 |      96 B |

Тест коду:

using System;
using System.Text;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using DashboardCode.Routines.Json;

namespace Benchmark
{
    //[Config(typeof(MyManualConfig))]
    [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkStructOrClass
    {
        static TestStruct testStruct = new TestStruct();
        static TestClass testClass = new TestClass();
        static TestStruct8 testStruct8 = new TestStruct8();
        static TestClass8 testClass8 = new TestClass8();
        [Benchmark]
        public void TestStructReturn()
        {
            testStruct.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn()
        {
            testClass.TestMethod();
        }


        [Benchmark]
        public void TestStructReturn8()
        {
            testStruct8.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn8()
        {
            testClass8.TestMethod();
        }

        public class TestStruct
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestClass
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestStruct8
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }

        public class TestClass8
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }
    }
}

2

Структури - це фактичне значення - вони можуть бути порожніми, але ніколи не нульовими

Це правда, проте також зауважте, що, як .NET 2 структури підтримують версію Nullable, а C # постачає синтаксичний цукор, щоб полегшити його використання.

int? value = null;
value  = 1;

1
Будьте в курсі, що це лише синтаксичний цукор, який читає "Nullable <int> value = null;"
Ерік ван Бракель

@ErikvanBrakel Це не просто синтаксичний цукор. Різні правила боксу означають, (object)(default(int?)) == nullчого не можна робити з будь-яким іншим типом цінностей, тому що тут відбувається не тільки цукор. Єдиний цукор int?для Nullable<int>.
Джон Ханна

-1; це не стосується питання про те, у чому полягає різниця між структурами та класами, і, як такий, він повинен був бути коментарем до відповіді, на який ви відповідаєте, а не окремою відповіддю. (Хоча, можливо, норми сайту були різними ще в серпні 2008 року!)
Марк Амеррі

1

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

Створення змінної або поля примітивного типу значення створить його зі значенням за замовчуванням; створення змінної або поля типу структури створить новий екземпляр, створивши всі поля в ньому за замовчуванням. Створення нового примірника типу посилання розпочнеться зі створення всіх полів у ньому за замовчуванням, а потім запуску додаткового додаткового коду залежно від типу.

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

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.