Когда я должен использовать структуру вместо класса?
MSDN говорит, что вы должны использовать структуры, когда вам нужны легкие объекты. Существуют ли другие сценарии, когда структура предпочтительнее, чем класс?
Некоторые люди могли забыть, что:
- Структуры могут иметь методы.
- структуры не могут быть унаследованы.
Я понимаю технические различия между структурами и классами, я просто не понимаю, когда использовать структуру.
16 ответов
У MSDN есть ответ: Выбор между классами и структурами.
По сути, эта страница дает вам контрольный список из 4 пунктов и говорит, что следует использовать класс, если ваш тип не соответствует всем критериям.
Не определяйте структуру, если тип не имеет всех следующих характеристик:
- Логически представляет одно значение, похожее на примитивные типы (целое, двойное и т. Д.).
- Размер экземпляра меньше 16 байтов.
- Это неизменно.
- Это не должно быть упаковано часто.
Я удивлен, что я не читал ни в одном из предыдущих ответов, что я считаю наиболее важным аспектом:
Я использую структуры, когда я хочу тип без идентичности. Например, 3D-точка:
public struct ThreeDimensionalPoint
{
public readonly int X, Y, Z;
public ThreeDimensionalPoint(int x, int y, int z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
public override string ToString()
{
return "(X=" + this.X + ", Y=" + this.Y + ", Z=" + this.Z + ")";
}
public override int GetHashCode()
{
return (this.X + 2) ^ (this.Y + 2) ^ (this.Z + 2);
}
public override bool Equals(object obj)
{
if (!(obj is ThreeDimensionalPoint))
return false;
ThreeDimensionalPoint other = (ThreeDimensionalPoint)obj;
return this == other;
}
public static bool operator ==(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
{
return p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z;
}
public static bool operator !=(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
{
return !(p1 == p2);
}
}
Если у вас есть два экземпляра этой структуры, вам все равно, являются ли они одним фрагментом данных в памяти или двумя. Вы просто заботитесь о ценности, которую они имеют.
У Билла Вагнера есть глава об этом в его книге "Эффективный с #" ( http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660). В заключение он использует следующий принцип:
- Является ли основная ответственность за тип хранения данных?
- Его открытый интерфейс полностью определяется свойствами, которые обращаются к его элементам данных или изменяют их?
- Вы уверены, что у вашего типа никогда не будет подклассов?
- Вы уверены, что ваш тип никогда не будет лечиться полиморфно?
Если вы ответите "да" на все 4 вопроса: используйте структуру. В противном случае используйте класс.
Используйте класс, если:
- Его личность важна. Структуры неявно копируются при передаче по значению в метод.
- Это будет иметь большой объем памяти.
- Его поля нуждаются в инициализаторах.
- Вам нужно наследовать от базового класса.
- Вам нужно полиморфное поведение;
Используйте структуру, если:
- Он будет действовать как примитивный тип (int, long, byte и т. Д.).
- Он должен иметь небольшой объем памяти.
- Вы вызываете метод P/Invoke, который требует, чтобы структура передавалась по значению.
- Вам необходимо уменьшить влияние сборки мусора на производительность приложений.
- Его поля должны быть инициализированы только к их значениям по умолчанию. Это значение будет нулевым для числовых типов, ложным для логических типов и пустым для ссылочных типов.
- Обратите внимание, что в C# 6.0 структуры могут иметь конструктор по умолчанию, который можно использовать для инициализации полей структуры значениями не по умолчанию.
- Вам не нужно наследовать от базового класса (кроме ValueType, от которого наследуются все структуры).
- Вам не нужно полиморфное поведение.
Используйте структуру, когда вы хотите семантику типа значения вместо ссылочного типа. Структуры копируются по значению, поэтому будьте осторожны!
Также смотрите предыдущие вопросы, например
Я бы использовал структуры, когда:
объект должен быть только для чтения (каждый раз, когда вы передаете / присваиваете структуру, он копируется). Объекты только для чтения хороши, когда дело доходит до многопоточной обработки, поскольку в большинстве случаев они не требуют блокировки.
объект маленький и недолговечный. В таком случае есть хороший шанс, что объект будет размещен в стеке, что гораздо эффективнее, чем поместить его в управляемую кучу. Более того, память, выделенная объектом, будет освобождена, как только он выйдет за пределы своей области видимости. Другими словами, это меньше работы для сборщика мусора, а память используется более эффективно.
Это старая тема, но я хотел предоставить простой тест производительности.
Я создал два файла.cs:
public class TestClass
{
public long ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
а также
public struct TestStruct
{
public long ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Запустить тест:
- Создать 1 TestClass
- Создать 1 TestStruct
- Создать 100 TestClass
- Создать 100 TestStruct
- Создать 10000 TestClass
- Создать 10000 TestStruct
Полученные результаты:
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.101
[Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT [AttachedDebugger]
DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------- |---------------:|--------------:|--------------:|----------:|--------:|-----:|---------:|------:|------:|----------:|
| UseStruct | 0.0000 ns | 0.0000 ns | 0.0000 ns | 0.000 | 0.00 | 1 | - | - | - | - |
| UseClass | 8.1425 ns | 0.1873 ns | 0.1839 ns | 1.000 | 0.00 | 2 | 0.0127 | - | - | 40 B |
| Use100Struct | 36.9359 ns | 0.4026 ns | 0.3569 ns | 4.548 | 0.12 | 3 | - | - | - | - |
| Use100Class | 759.3495 ns | 14.8029 ns | 17.0471 ns | 93.144 | 3.24 | 4 | 1.2751 | - | - | 4000 B |
| Use10000Struct | 3,002.1976 ns | 25.4853 ns | 22.5920 ns | 369.664 | 8.91 | 5 | - | - | - | - |
| Use10000Class | 76,529.2751 ns | 1,570.9425 ns | 2,667.5795 ns | 9,440.182 | 346.76 | 6 | 127.4414 | - | - | 400000 B |
Если сущность будет неизменной, вопрос о том, использовать ли структуру или класс, обычно будет связан с производительностью, а не с семантикой. В 32/64-битной системе для ссылок на классы требуется 4/8 байт для хранения независимо от объема информации в классе; копирование ссылки на класс потребует копирования 4/8 байтов. С другой стороны, каждый отдельный экземпляр класса будет иметь 8/16 байтов служебной информации в дополнение к информации, которую он содержит, и стоимости памяти для ссылок на него. Предположим, что требуется массив из 500 объектов, каждый из которых содержит четыре 32-разрядных целых числа. Если объект является структурным типом, массиву потребуется 8000 байтов независимо от того, все ли 500 объектов идентичны, различны или где-то между ними. Если объект является типом класса, массив из 500 ссылок займет 4000 байтов. Если все эти ссылки указывают на разные объекты, для этих объектов потребуются дополнительные 24 байта каждый (12 000 байтов для всех 500), всего 16 000 байтов - вдвое больше, чем стоимость хранения типа структуры. С другой стороны, из кода, который создал один экземпляр объекта, а затем скопировал ссылку на все 500 слотов массива, общая стоимость составила бы 24 байта для этого экземпляра и 4000 для массива - всего 4024 байта. Основная экономия. Немногие ситуации сработают так же хорошо, как и последняя, но в некоторых случаях может оказаться возможным скопировать некоторые ссылки на достаточное количество слотов массива, чтобы сделать такое совместное использование полезным.
Если предполагается, что сущность является изменчивой, вопрос о том, использовать ли класс или структуру, в некотором смысле проще. Предположим, что "Thing" - это либо структура, либо класс, имеющий целочисленное поле с именем x, и каждый выполняет следующий код:
Вещь t1,t2; ... t2 = t1; t2.x = 5;
Желает ли последнее утверждение повлиять на t1.x?
Если Thing является типом класса, t1 и t2 будут эквивалентны, то есть t1.x и t2.x также будут эквивалентны. Таким образом, второе утверждение повлияет на t1.x. Если Thing является структурным типом, t1 и t2 будут разными экземплярами, то есть t1.x и t2.x будут ссылаться на разные целые числа. Таким образом, второе утверждение не повлияет на t1.x.
Изменчивые структуры и изменяемые классы имеют принципиально различное поведение, хотя.net имеет некоторые особенности в обработке структурных мутаций. Если кто-то хочет поведение типа значения (имеется в виду, что "t2=t1" будет копировать данные из t1 в t2, оставляя t1 и t2 как отдельные экземпляры), и если кто-то может согласиться с особенностями обработки значений в.net, используйте структура. Если кто-то хочет семантику типа значения, но причуды.net могут привести к нарушению семантики типа значения в своем приложении, используйте класс и бормотайте.
Я всегда использовал структуру, когда хотел сгруппировать несколько значений для передачи данных обратно из вызова метода, но мне не нужно будет использовать его для чего-либо после прочтения этих значений. Так же, как способ держать вещи в чистоте. Я склонен рассматривать вещи в структуре как "одноразовые", а вещи в классе - как более полезные и "функциональные"
✔️ РАССМАТРИВАЙТЕ определение структуры вместо класса, если экземпляры типа небольшие и обычно недолговечны или обычно встраиваются в другие объекты.
Кроме того, превосходные ответы выше:
Структуры являются типами значений.
Они никогда не могут быть установлены в Ничто.
Установка структуры = Ничего, установит для всех типов значений значения по умолчанию.
Как сказал @Simon, структуры предоставляют семантику "тип значения", поэтому, если вам нужно поведение, подобное встроенному типу данных, используйте struct. Поскольку структуры передаются посредством копирования, необходимо убедиться, что они имеют небольшой размер, около 16 байт.
Когда вам действительно не нужно поведение, но вам нужно больше структуры, чем простой массив или словарь.
Последующие действия Это то, как я думаю о структурах в целом. Я знаю, что у них могут быть методы, но мне нравится сохранять это общее умственное различие.
Хм...
Я бы не использовал сборку мусора в качестве аргумента за / против использования структур против классов. Управляемая куча работает во многом как стек - создание объекта просто помещает его на вершину кучи, что почти так же быстро, как и размещение в стеке. Кроме того, если объект является недолговечным и не выдерживает цикл GC, освобождение происходит бесплатно, поскольку GC работает только с памятью, которая все еще доступна. (Поищите в MSDN, есть серия статей по управлению памятью.NET, мне просто лень копаться в них).
Большую часть времени, когда я использую структуру, я начинаю пинать себя за это, потому что позже я обнаруживаю, что наличие ссылочной семантики сделало бы вещи немного проще.
В любом случае, эти четыре пункта в статье MSDN, опубликованной выше, кажутся хорошим руководством.
Структуры находятся в стеке, а не в куче, поэтому они являются потокобезопасными, и их следует использовать при реализации шаблона передаваемых объектов, вы никогда не захотите использовать объекты в куче, они являются энергозависимыми, в этом случае вы хотите использовать стек вызовов, это основной случай использования структуры, я удивлен всеми ответами здесь,
Я думаю, что лучший ответ - просто использовать struct, когда вам нужен набор свойств, класс, когда это набор свойств и поведений.