В чем разница между структурой и классом в .NET?

Ответы:

1058

В .NET есть две категории типов: ссылочные типы и типы значений .

Структуры являются типами значений, а классы являются ссылочными типами .

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

Переменная, содержащая тип значения, содержит полное значение типа значения. Для структуры это означает, что переменная содержит всю структуру со всеми ее полями.

Переменная, содержащая ссылочный тип, содержит указатель или ссылку на другое место в памяти, где находится фактическое значение.

Это имеет одно преимущество, для начала:

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

Внутренне ссылочные типы s реализованы как указатели, и, зная это и зная, как работает присвоение переменной, существуют другие поведенческие паттерны:

  • копируя содержимое переменной типа значения в другую переменную, копирует все содержимое в новую переменную, выделяя две разные. Другими словами, после копирования изменения одного не повлияют на другое
  • копируя содержимое переменной ссылочного типа в другую переменную, копирует ссылку, что означает, что теперь у вас есть две ссылки на одно и то же другое хранилище фактических данных. Другими словами, после копирования изменение данных в одной ссылке также повлияет на другую, но только потому, что вы действительно просто просматриваете одни и те же данные в обоих местах.

Когда вы объявляете переменные или поля, вот как эти два типа различаются:

  • переменная: тип значения живет в стеке, ссылочный тип живет в стеке как указатель на место где-то в динамической памяти, где живет реальная память (хотя обратите внимание на серию статей Эрика Липпертса: «Стек - деталь реализации» ).
  • class / struct-field: тип значения живет полностью внутри типа, ссылочный тип живет внутри типа как указатель на место в кучной памяти, где живет реальная память.
Лассе В. Карлсен
источник
43
В интересах полной полноты я должен упомянуть, что Эрик Липперт сказал, что стек - это деталь реализации , всякий раз, когда я упоминаю стек выше, помните о постах Эрика.
Лассе В. Карлсен
2
Это все верно и для C ++?
Корай Тугай
9
Другое важное отличие - это использование. Из MSDN: «Структуры обычно используются для инкапсуляции небольшой группы связанных переменных, таких как координаты прямоугольника. Структуры также могут содержать конструкторы, константы, поля, методы, свойства, индексаторы, операторы, события и вложенные типы, хотя, если несколько таких требуются члены, вы должны вместо этого сделать свой тип классом. "
thewpfguy
4
@ KorayTugay Нет, это не так.
ZoomIn
9
@KorayTugay в C ++ struct и class абсолютно эквивалентны, за исключением одного - ограничение доступа по умолчанию (класс имеет приватный по умолчанию, структура имеет публичный)
berkus
207

Краткое резюме каждого:

Только классы:

  • Может поддерживать наследование
  • Являются ссылочными (указательными) типами
  • Ссылка может быть нулевой
  • Иметь накладные расходы памяти на новый экземпляр

Только структуры:

  • Не может поддерживать наследование
  • Типы значений
  • Передаются по значению (например, целые числа)
  • Не может иметь нулевую ссылку (если не используется Nullable)
  • Не иметь накладных расходов памяти для каждого экземпляра - если не «в штучной упаковке»

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

  • Составные типы данных обычно используются, чтобы содержать несколько переменных, которые имеют некоторые логические отношения
  • Может содержать методы и события
  • Может поддерживать интерфейсы
Томас Братт
источник
16
Есть некоторые части этого ответа, которые не совсем верны. Классы не всегда идут в кучу, а структуры не всегда идут в стек. Текущие исключения включают в себя поля структуры в классе, захваченные переменные в анонимных методах и лямбда-выражениях, блоки итераторов и уже упомянутые коробочные значения. Но выделение стека и кучи является деталью реализации и может быть изменено. Эрик Липпарт обсуждает это здесь . Я проголосовал, но с удовольствием удалим его, если вы обновите.
Саймон П Стивенс
1
Структура не поддерживает наследование от других структур / классов, но вы МОЖЕТЕ реализовать интерфейс на структуре.
thewpfguy
2
Возможно, вы захотите уточнить, что вы имеете в виду, когда утверждаете, что структура «Не требует дополнительной памяти на новый экземпляр» . Моя первая интерпретация заключалась в том, что вы утверждали - очевидно, абсурдно - что структуры используют нулевую память. Тогда я подумал, что, возможно, вы пытаетесь сказать, что структура, в отличие от класса, требует ровно столько же памяти, сколько сумма его полей-членов, и не более. Но потом я погуглил c# struct memory overheadи нашел ответ Ханса Пассанта, который говорит, что нет, это тоже не так. Так что же вы имеете в виду?
Марк Эмери
4
@MarkAmery У меня была такая же начальная реакция, как и у вас, на выражение «нет дополнительной памяти», но я думаю, что OP ссылается на тот факт, что экземпляры classявляются управляемой памятью (обрабатывается сборщиком мусора), тогда как экземпляры structне являются ,
Хатч
1
«Структура передается по значению (например, целые числа)» ложно: все переменные передаются по значению, а также ссылочный тип. Если вы хотите передать переменную по ссылке, вы должны использовать ключевое слово «ref». jonskeet.uk/csharp/parameters.html#ref
Марко
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 байт.
  • Это неизменно. (не может быть изменено)
  • Это не должно быть упаковано часто.
Sunsetquest
источник
19

Помимо всех отличий, описанных в других ответах:

  1. Структуры не могут иметь явного конструктора без параметров, тогда как класс может
  2. Структуры не могут иметь деструкторов , тогда как класс может
  3. Структуры не могут наследовать от другой структуры или класса, тогда как класс может наследовать от другого класса. (И структуры, и классы могут быть реализованы из интерфейса.)

Если вы после видео, объясняющего все различия, вы можете проверить Часть 29 - Учебник по C # - Различия между классами и структурами в C # .

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

Экземпляры классов хранятся в управляемой куче. Все переменные, «содержащие» экземпляр, являются просто ссылкой на экземпляр в куче. Передача объекта в метод приводит к тому, что передается копия ссылки, а не сам объект.

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

Различия лучше сделаны именами C ++ / CLI: «ref class» - это класс, который описан первым, «value class» - это класс, который описан вторым. Ключевые слова «класс» и «структура», используемые в C #, - это просто то, что нужно усвоить.

Zooba
источник
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                                |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
0xaryan
источник
1
Это на самом деле довольно великолепно: обобщенно и информативно. Пожалуйста, не забывайте проверять свой ответ хотя бы один раз - вы поменяли объяснения struct и class в некоторых строках, также есть некоторые опечатки.
Роберт Синорадски
1
@ensisNoctis Извините за эти ошибки и спасибо за редактирование. Я должен перечитать свои ответы 😅
0xaryan
8

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

Структура является типом значения, поэтому она хранится в стеке, но класс является ссылочным типом и хранится в куче.

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

По умолчанию все члены структуры являются открытыми, но члены класса по умолчанию являются закрытыми по своей природе.

Поскольку структура является типом значения, мы не можем присвоить null объекту struct, но это не относится к классу.

Свагатика дхал
источник
5
Относительно "все члены структуры являются публичными": если я не ошибаюсь, это неправильно. «Уровень доступа для членов класса и членов структуры, включая вложенные классы и структуры, по умолчанию является закрытым». msdn.microsoft.com/en-us/library/ms173121.aspx
Нейт Кук
8

Чтобы добавить к другим ответам, есть одно фундаментальное различие, которое стоит отметить, и это то, как данные хранятся в массивах, так как это может существенно повлиять на производительность.

  • Со структурой массив содержит экземпляр структуры
  • С классом массив содержит указатель на экземпляр класса в другом месте в памяти

Таким образом, массив структур выглядит так в памяти

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

В то время как массив классов выглядит следующим образом

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

С массивом классов интересующие вас значения хранятся не в массиве, а в другом месте в памяти.

Для подавляющего большинства приложений это различие не имеет большого значения, однако в высокопроизводительном коде это повлияет на локальность данных в памяти и окажет большое влияние на производительность кэша ЦП. Использование классов, когда вы могли / должны были использовать структуры, значительно увеличит количество кеш-пропусков в процессоре.

Самым медленным, что делает современный ЦП, является не сокращение чисел, а извлечение данных из памяти, а попадание в кэш L1 во много раз быстрее, чем чтение данных из ОЗУ.

Вот код, который вы можете проверить. На моей машине перебор массива классов занимает в 3 раза больше времени, чем массив struct.

    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 человека.

Ning
источник
6

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

Другие, вероятно, могут дать вам больше деталей, чем я, но я использую структуры, когда структура, к которой я иду, проста.

Эд С.
источник
4

Помимо основных различий в спецификаторе доступа и нескольких упомянутых выше, я хотел бы добавить некоторые из основных отличий, включая несколько упомянутых выше, с примером кода с выводом, который даст более четкое представление о ссылке и значении

Структуры:

  • Являются типами значений и не требуют выделения кучи.
  • Распределение памяти отличается и хранится в стеке
  • Полезно для небольших структур данных
  • Влияет на производительность, когда мы передаем значение методу, мы передаем всю структуру данных, и все передается в стек.
  • Конструктор просто возвращает само значение структуры (обычно во временном месте в стеке), и это значение затем копируется по мере необходимости
  • Каждая из переменных имеет свою собственную копию данных, и операции с одной из них не могут повлиять на другую.
  • Не поддерживают пользовательское наследование, и они неявно наследуются от объекта типа

Учебный класс:

  • Значение типа ссылки
  • Хранится в куче
  • Хранить ссылку на динамически размещенный объект
  • Конструкторы вызываются оператором new, но это не выделяет память в куче
  • Несколько переменных могут иметь ссылку на один и тот же объект
  • Операции с одной переменной могут влиять на объект, на который ссылается другая переменная

Пример кода

    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 Значение метода Inside объекта Struct: 20

После вызова метода значение объекта Struct: 10

Начальное значение Class Object: 10

Метод внутри класса Значение метода внутри объекта Class: 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, viz, Equals () и GetHashCode () и ToString ().

Заин Али
источник
При каких обстоятельствах можно использовать событие со структурой? Я могу себе представить, что очень тщательно написанная программа может использовать события со структурой таким образом, чтобы это работало, но только если структура никогда не копировалась и не передавалась по значению, и в этом случае она также может быть классом.
Суперкат
@supercat Да, нестатическое событие в структуре будет очень странным, и оно будет полезно только для изменяемых структур, а само событие (если это событие типа «поля») превращает структуру в «изменяемое» ", а также вводит поле ссылочного типа в структуре. Нестатичные события в структурах должны быть злыми.
Джеппе Стиг Нильсен
@JeppeStigNielsen: Единственный шаблон, который я мог видеть, где было бы целесообразно, чтобы структура имела событие, была бы, если бы целью структуры было держать неизменную ссылку на объект класса, для которого она вела себя как прокси. Авто-события были бы совершенно бесполезны в таком сценарии, хотя; вместо этого события подписки и отписки должны быть переданы классу за структурой. Мне бы хотелось, чтобы .NET имел (или позволил бы определить) структурный тип 'cache-box' с изначально нулевым скрытым полем типа Object, который
содержал
1
@JeppeStigNielsen: структурирует опережающие классы во многих сценариях использования прокси; самая большая проблема с использованием структур состоит в том, что в случаях, когда бокс оказывается необходимым, он часто заканчивается переносом во внутренний цикл. Если бы был способ избежать многократной упаковки структур , они были бы лучше, чем классы во многих других сценариях использования.
суперкат
4

Как уже упоминалось ранее: классы являются ссылочным типом, в то время как структуры являются типами значений со всеми вытекающими последствиями.

Как правило, Framework Design Guidelines рекомендует использовать Structs вместо классов, если:

  • Размер экземпляра менее 16 байт.
  • Логически представляет одно значение, подобное примитивным типам (int, double и т. Д.)
  • Это неизменное
  • Это не должно быть в штучной упаковке часто
kb9
источник
3

Есть один интересный случай головоломки «класс против структуры» - ситуация, когда вам нужно вернуть несколько результатов из метода: выбрать, какой использовать. Если вы знаете историю ValueTuple - вы знаете, что ValueTuple (структура) был добавлен, потому что он должен быть более эффективным, чем Tuple (класс). Но что это значит в цифрах? Два теста: один - это struct / class, который имеет 2 поля, другой - с struct / class, который имеет 8 полей (с размерностью более 4 - класс должен стать более эффективным, чем struct с точки зрения тактов процессора, но, конечно, следует учитывать и загрузку GC ).

PS Еще один тест для конкретного случая «sturct или class with collection»: 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

Каждая переменная или поле типа примитивного значения или типа структуры содержит уникальный экземпляр этого типа, включая все его поля (публичные и приватные). Напротив, переменные или поля ссылочных типов могут иметь значение null или могут ссылаться на объект, хранящийся в другом месте, на который также может существовать любое количество других ссылок. Поля структуры будут храниться в том же месте, что и переменная или поле этого типа структуры, которые могут находиться либо в стеке, либо частью другого объекта кучи.

Создание переменной или поля типа примитивного значения создаст его со значением по умолчанию; Создание переменной или поля типа структуры создаст новый экземпляр, создавая все поля в нем способом по умолчанию. Создание нового экземпляра ссылочного типа начнется с создания всех полей в нем по умолчанию, а затем запускается необязательный дополнительный код в зависимости от типа.

Копирование одной переменной или поля типа примитива в другое приведет к копированию значения. Копирование одной переменной или поля типа структуры в другое приведет к копированию всех полей (открытых и закрытых) первого экземпляра во второй. Копирование одной переменной или поля ссылочного типа в другое приведет к тому, что последняя будет ссылаться на тот же экземпляр, что и первый (если есть).

Важно отметить, что в некоторых языках, таких как C ++, семантическое поведение типа не зависит от того, как он хранится, но это не относится к .NET. Если тип реализует семантику изменяемых значений, копирование одной переменной этого типа в другую копирует свойства первого экземпляра в другой, на который ссылается второй, и использование элемента второго для его изменения приведет к изменению этого второго экземпляра. , но не первый. Если тип реализует изменяемую ссылочную семантику, копирование одной переменной в другую и использование элемента второй для изменения объекта будет влиять на объект, на который ссылается первая переменная; типы с неизменяемой семантикой не допускают мутации, поэтому семантически не имеет значения, создает ли копирование новый экземпляр или создает другую ссылку на первый.

В .NET типы значений могут реализовывать любую из вышеуказанных семантик при условии, что все их поля могут действовать аналогичным образом. Однако ссылочный тип может реализовывать только изменяемую ссылочную семантику или неизменяемую семантику; Типы значений с полями изменяемых ссылочных типов ограничены либо реализацией изменяемой ссылочной семантики, либо странной гибридной семантикой.

Supercat
источник