Відповіді:
У .NET є дві категорії типів, типи посилань та типи значень .
Структури - цінні типи, а класи - еталонні .
Загальна відмінність полягає в тому, що опорний тип живе в купі, а тип значення живе в рядці, тобто де б не була визначена ваша змінна чи поле.
Змінна, що містить тип значення, містить усе значення типу значення. Для структури це означає, що змінна містить всю структуру з усіма її полями.
Змінна, що містить тип посилання, містить вказівник або посилання на інше місце в пам'яті, де знаходиться фактичне значення.
Для цього є одна перевага:
Внутрішньо, посилання типу s реалізуються як покажчики, і, знаючи, що і знаючи, як працює присвоєння змінної, існують інші поведінкові моделі:
Оголошуючи змінні чи поля, ось як відрізняються два типи:
Короткий підсумок кожного:
Тільки заняття:
Тільки структури:
Класи та структури:
c# struct memory overhead
і знайшов цю відповідь Ганса Пассанта, який говорить, що ні, і це не так. То що ти маєш на увазі?
class
керованої пам'яті (обробляється смітником), тоді як випадки struct
не .
У .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.
Заняття можуть бути нічим - посилання може вказувати на нуль.
Структури - це фактичне значення - вони можуть бути порожніми, але ніколи не нульовими. З цієї причини у структур завжди є конструктор за замовчуванням без параметрів - їм потрібно "початкове значення".
Різниця між структурами та класами:
Від вибору Microsoft між класом і структурою ...
Як правило, більшість типів в рамках повинні бути класами. Однак є деякі ситуації, в яких характеристики типу значень роблять більш доцільним використання структур.
✓ ВАЖИМО структуру замість класу:
- Якщо екземпляри типу невеликі і зазвичай недовговічні або зазвичай вбудовані в інші об’єкти.
X Ухиляйте структуру, якщо тип не має всіх перелічених нижче характеристик:
- Він логічно представляє єдине значення, схоже на примітивні типи (int, double та ін.).
- Він має розмір примірника до 16 байт.
- Це незмінне. (неможливо змінити)
- Боксувати не доведеться часто.
На додаток до всіх відмінностей, описаних в інших відповідях:
Якщо ви знайдете відео, де пояснюються всі відмінності, ви можете перевірити частину 29 - Підручник з C # - Різниця між класами та структурами в C # .
Екземпляри класів зберігаються в керованій купі. Усі змінні, що містять екземпляр, є просто посиланням на екземпляр у купі. Передача об'єкта методу призводить до передачі копії посилання, а не самого об'єкта.
Структури (технічно цінні типи) зберігаються скрізь, де вони використовуються, як і примітивний тип. Вміст може бути скопійовано під час виконання в будь-який час і без виклику індивідуального конструктора копій. Передача типу значення методу включає копіювання всього значення, знову ж таки, без виклику будь-якого настроюваного коду.
Розрізнення покращується за іменами C ++ / CLI: "ref class" - клас, як описано по-перше, "class value" - клас, як описано другий. Ключові слова "клас" і "структура", використовувані C #, - це просто те, що потрібно вивчити.
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| | 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 |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
Структура проти класу
Структура є типом значення, тому вона зберігається у стеку, але клас є еталонним типом і зберігається у купі.
Структура не підтримує успадкування та поліморфізм, але клас підтримує обоє.
За замовчуванням всі члени структури є загальнодоступними, але члени класу за замовчуванням мають приватний характер.
Оскільки структура є типом значення, ми не можемо призначити null об’єкту структура, але це не стосується класу.
Щоб додати до інших відповідей, є одна принципова відмінність, яку варто відзначити, і це те, як дані зберігаються в масивах, оскільки це може мати великий вплив на продуктивність.
Отже, масив структур виглядає так у пам'яті
[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}");
}
Тільки щоб зробити його повною, є ще одна відмінність при використанні 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 людини.
Ну, а для початку, структура передається за значенням, а не за посиланням. Структури хороші для відносно простих структур даних, тоді як класи мають набагато більшу гнучкість з архітектурної точки зору через поліморфізм та успадкування.
Інші, ймовірно, можуть дати вам більше деталей, ніж я, але я використовую структури, коли структура, яку я збираюсь, проста.
Окрім основної різниці специфікатора доступу та декількох згаданих вище, я хотів би додати деякі основні відмінності, включаючи декілька згаданих вище, із зразком коду з результатом, що дасть більш чітке уявлення про довідку та значення
Структури:
Клас:
Зразок коду
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
Тут ви чітко бачите різницю між викликом за значенням та викликом за посиланням.
Події, оголошені в класі, мають + + і - = доступ автоматично блокується через блокування (це), щоб зробити їх безпечними для потоку (статичні події блокуються на тип класу). Події, оголошені в структурі, не мають автоматичного блокування доступу + = і - =. Блокування (це) для структури не працюватиме, оскільки ви можете заблокувати лише вираз базового типу.
Створення структурного екземпляра не може спричинити збір сміття (якщо конструктор прямо чи опосередковано не створює екземпляр посилального типу), тоді як створення екземпляра типу посилання може спричинити збір сміття.
У структурі завжди є вбудований конструктор за замовчуванням.
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
{
}
}
У структури не може бути деструктора. Деструктор - це лише перекриття об'єкта. Доопрацьовуйте під маскуванням, і конструкції, будучи типовими типами, не підлягають збору сміття.
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
Структура неявно запечатана, клас - ні.
Структура не може бути абстрактною, клас може.
Структура не може викликати: base () у своєму конструкторі, тоді як клас без явного базового класу не може.
Структура не може поширити інший клас, клас може.
Структура не може оголосити захищені члени (наприклад, поля, вкладені типи), якими може користуватися клас.
Структура не може оголошувати абстрактних членів функції, абстрактний клас може.
Структура не може оголосити членів віртуальної функції, клас може.
Структура не може оголосити закритих членів функції, клас може.
Структура не може оголосити членів функції заміни, клас може.
Єдиним винятком із цього правила є те, що структура може змінювати віртуальні методи System.Object, а саме: Equals (), GetHashCode () та ToString ().
Object
, яке містило б посилання на коробку копії структури.
Як вже було сказано раніше: Класи - це еталонний тип, тоді як структури - це типові значення з усіма наслідками.
Як правило, Рамкові рекомендації щодо дизайну рекомендують використовувати структури замість класів, якщо:
Є один цікавий випадок головоломки "клас проти структури" - ситуація, коли потрібно повернути кілька результатів із методу: вибрати, який використовувати. Якщо ви знаєте історію 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;
}
}
}
}
Структури - це фактичне значення - вони можуть бути порожніми, але ніколи не нульовими
Це правда, проте також зауважте, що, як .NET 2 структури підтримують версію Nullable, а C # постачає синтаксичний цукор, щоб полегшити його використання.
int? value = null;
value = 1;
(object)(default(int?)) == null
чого не можна робити з будь-яким іншим типом цінностей, тому що тут відбувається не тільки цукор. Єдиний цукор int?
для Nullable<int>
.
Кожна змінна або поле типу примітивного значення або тип структури містить унікальний екземпляр цього типу, включаючи всі його поля (державні та приватні). Навпаки, змінні або поля посилальних типів можуть містити нуль, або можуть посилатися на об'єкт, що зберігається в іншому місці, на який також може існувати будь-яка кількість інших посилань. Поля структури буде зберігатися в тому самому місці, що і змінна або поле цього типу структури, яке може бути або в стеку, або може бути частиною іншого об'єкта купи.
Створення змінної або поля примітивного типу значення створить його зі значенням за замовчуванням; створення змінної або поля типу структури створить новий екземпляр, створивши всі поля в ньому за замовчуванням. Створення нового примірника типу посилання розпочнеться зі створення всіх полів у ньому за замовчуванням, а потім запуску додаткового додаткового коду залежно від типу.
Копіюючи одну змінну або поле примітивного типу в іншу, буде скопійовано значення. Копіюючи одну змінну або поле типу структури в іншу, буде скопійовано всі поля (загальнодоступні та приватні) попереднього екземпляра до другого. Копіювання однієї змінної або поля посилання в інший призведе до того, що остання посилається на той самий екземпляр, що і перший (якщо такий є).
Важливо зазначити, що в деяких мовах, таких як C ++, семантична поведінка типу не залежить від способу його зберігання, але це не вірно з .NET. Якщо тип реалізує семантику змінного значення, копіювання однієї змінної цього типу в іншу копіює властивості першого в інший екземпляр, на який посилається другий, а використання другого члена для його мутації призведе до зміни цього другого примірника , але не перший. Якщо тип реалізує змінну опорну семантику, копіювання однієї змінної в іншу та використання члена другої для мутації об'єкта вплине на об'єкт, на який посилається перша змінна; типи з незмінною семантикою не допускають мутації, тому семантично не має значення, чи копіювання створює новий екземпляр чи створює інше посилання на перший.
У .NET, типи значень можуть реалізувати будь-яку з перерахованих вище семантики, за умови, що всі їхні поля можуть робити так само. Референтний тип, однак, може реалізовувати лише змінну еталонну семантику або незмінне семантику; Типи значень із полями змінних еталонних типів обмежуються або реалізацією змінної опорної семантики, або дивною гібридною семантикою.