MSDN говорит, что вы должны использовать структуры, когда вам нужны легкие объекты. Есть ли другие сценарии, когда структура предпочтительнее класса?
Некоторые люди могли забыть об этом:
Я понимаю технические различия между структурами и классами, я просто не чувствую, что когда использует структуру.





Используйте структуру, если вам нужна семантика типа значения вместо ссылочного типа. Структуры копируются по значению, поэтому будьте осторожны!
Также см. Предыдущие вопросы, например
когда вам действительно не нужно поведение, но вам нужно больше структуры, чем простой массив или словарь.
Следовать за Так я думаю о структурах в целом. Я знаю, что у них могут быть методы, но мне нравится сохранять это общее умственное различие.
Почему ты это сказал? У структур могут быть методы.
Как сказал @Simon, структуры обеспечивают семантику «типа значения», поэтому, если вам нужно поведение, аналогичное встроенному типу данных, используйте структуру. Поскольку структуры передаются по копии, вы хотите убедиться, что они имеют небольшой размер, около 16 байт.
Я бы использовал структуры, когда:
объект должен быть доступен только для чтения (каждый раз, когда вы передаете / назначаете структуру, она копируется). Объекты, доступные только для чтения, отлично подходят для многопоточной обработки, поскольку в большинстве случаев они не требуют блокировки.
объект маленький и недолговечный. В таком случае велика вероятность того, что объект будет размещен в стеке, что намного эффективнее, чем размещение его в управляемой куче. Более того, память, выделенная объектом, будет освобождена, как только она выйдет за пределы его области видимости. Другими словами, сборщик мусора работает меньше, а память используется более эффективно.
У MSDN есть ответ: Выбор между классами и структурами.
По сути, эта страница дает вам контрольный список из 4 пунктов и предлагает использовать класс, если ваш тип не соответствует всем критериям.
Do not define a structure unless the type has all of the following characteristics:
- It logically represents a single value, similar to primitive types (integer, double, and so on).
- It has an instance size smaller than 16 bytes.
- It is immutable.
- It will not have to be boxed frequently.
Может быть, я упускаю что-то очевидное, но я не совсем понимаю аргументы, лежащие в основе «неизменяемой» части. Зачем это нужно? Может кто-нибудь объяснить это?
Вероятно, они рекомендовали это, потому что, если структура неизменяема, то не имеет значения, что она имеет семантику значения, а не семантику ссылки. Различие имеет значение только в том случае, если вы изменяете объект / структуру после создания копии.
@DrJokepu: в некоторых ситуациях система создает временную копию структуры, а затем разрешает передачу этой копии по ссылке в код, который ее изменяет; поскольку временная копия будет удалена, изменение будет потеряно. Эта проблема особенно серьезна, если у структуры есть методы, которые ее изменяют. Я категорически не согласен с представлением о том, что изменчивость - это причина сделать что-то классом, поскольку - несмотря на некоторые недостатки в C# и vb.net, изменяемые структуры обеспечивают полезную семантику, которая не может быть достигнута никаким другим способом; нет никакой семантической причины предпочесть неизменяемую структуру классу.
Я предпочитаю принцип Скотта Мейерса. Он явно отклоняется от документации .NET, поскольку считает, что использование типа является лучшим фактором, чем размер типа.
Мне интересно, применим ли размер 16 байт к 64-битному коду? Я не уверен на 100%, откуда взялось 16-байтовое число.
@Chuu: При разработке JIT-компилятора Microsoft решила оптимизировать код для копирования структур размером 16 байт или меньше; это означает, что копирование 17-байтовой структуры может быть значительно медленнее, чем копирование 16-байтовой структуры. Я не вижу особых причин ожидать, что Microsoft расширит такую оптимизацию на более крупные структуры, но важно отметить, что хотя 17-байтовые структуры могут копировать медленнее, чем 16-байтовые структуры, во многих случаях большие структуры могут быть более эффективными, чем большие объекты класса, и где относительное преимущество структур растет с размером структуры.
@Chuu: применение тех же шаблонов использования к большим структурам, что и к классам, может привести к неэффективному коду, но правильное решение часто не заменять структуры классами, а вместо этого использовать структуры более эффективно; в частности, следует избегать передачи или возврата структур по значению. Передавайте их как параметры ref, когда это целесообразно. Передача структуры с 4000 полей в качестве параметра ref методу, который меняет одно, будет дешевле, чем передача структуры с 4 полями по значению методу, который возвращает измененную версию.
@supercat - подождите - каковы много случаев, когда большая структура более эффективна, чем экземпляр большого класса? Я вижу, что это возможно только в том случае, если было необходимо обеспечить неизменность, поэтому "классовый" подход закончился тем, что вам пришлось создавать новый экземпляр каждый раз, когда вы что-то делали ... Я никогда с этим не сталкивался, но мне было бы интересно узнать когда это происходит.
Я знаю, что это старый пост, но structs рекомендуется сделать неизменным, потому что они являются объектами типа значения обычно (помните ссылки на интерфейс), и значения копируются, а не обновляются. Хотя вы можете указать на структуру и изменить существующее значение; структура все еще копируется, а не обновляется на месте, как класс. Другими словами; это безопаснее и в большей степени соответствует тому, как работают существующие структуры, т.е. (int, char, DateTime, ETC)
Я всегда использовал структуру, когда хотел сгруппировать несколько значений для передачи вещей обратно из вызова метода, но мне не нужно будет использовать ее для чего-либо после того, как я прочитал эти значения. Просто как способ содержать вещи в чистоте. Я склонен рассматривать вещи в структуре как «одноразовые», а вещи в классе как более полезные и «функциональные».
Хм...
Я бы не стал использовать сборку мусора в качестве аргумента за / против использования структур и классов. Управляемая куча работает во многом как стек - создание объекта просто помещает его в верхнюю часть кучи, что почти так же быстро, как и выделение в стеке. Кроме того, если объект недолговечен и не переживает цикл сборщика мусора, освобождение сборщика мусора осуществляется бесплатно, поскольку сборщик мусора работает только с доступной памятью. (Поищите в MSDN, там есть серия статей по управлению памятью .NET, мне просто лень их искать).
В большинстве случаев, когда я использую структуры, я начинаю ругать себя за это, потому что позже я обнаружил, что наличие ссылочной семантики сделало бы вещи немного проще.
В любом случае, эти четыре пункта в опубликованной выше статье MSDN кажутся хорошим ориентиром.
Если вам иногда требуется ссылочная семантика со структурой, просто объявите class MutableHolder<T> { public T Value; MutableHolder(T value) {Value = value;} }, и тогда MutableHolder<T> будет объектом с изменяемой семантикой класса (это работает так же хорошо, если T является структурой или неизменяемым типом класса).
Я удивлен, что не прочитал ни одного из предыдущих ответов, которые я считаю наиболее важным аспектом:
Я использую структуры, когда мне нужен тип без идентичности. Например, 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);
}
}
Если у вас есть два экземпляра этой структуры, вам все равно, являются ли они одним или двумя фрагментами данных в памяти. Вы просто заботитесь о ценности, которую они имеют.
Интересная причина использовать struct. Я создал классы с GetHashCode и Equals, определенные аналогично тому, что вы здесь показываете, но тогда мне всегда приходилось быть осторожным, чтобы не изменить эти экземпляры, если я использовал их в качестве ключей словаря. Наверное, было бы безопаснее, если бы я определил их как структуры. (Потому что тогда ключ будет копировать полей на данный момент структура стала ключом словаря, поэтому ключ останется неизменным, если я позже изменю оригинал.)
В вашем примере это нормально, потому что у вас есть только 12 байтов, но имейте в виду, что если у вас много полей в этой структуре, которые превышают 16 байтов, вы должны подумать об использовании класса и переопределить методы GetHashCode и Equals.
Тип значения в DDD не означает, что вы обязательно должны использовать тип значения в C#.
У Билла Вагнера есть глава об этом в своей книге «Эффективный C#» (http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660). В заключение он использует следующий принцип:
- Is the main responsability of the type data storage?
- Is its public interface defined entirely by properties that access or modify its data members?
- Are you sure your type will never have subclasses?
- Are you sure your type will never be treated polymorphically?
If you answer 'yes' to all 4 questions: use a struct. Otherwise, use a class.
итак ... объекты передачи данных (DTO) должны быть структурами?
Я бы сказал да, если он соответствует 4 критериям, описанным выше. Зачем нужно обрабатывать объект передачи данных определенным образом?
@cruizer зависит от вашей ситуации. В одном проекте у нас были общие поля аудита в наших DTO, и поэтому мы написали базовый DTO, от которого унаследовали другие.
на основе определения DTO могут быть структурами, если вы не наследуете что-то от базового класса (что обычно делается с DTO)
Все, кроме (2), кажутся отличными принципами. Потребуется увидеть его рассуждения, чтобы знать, что именно он имеет в виду под (2) и почему.
@ToolmakerSteve: Для этого вам придется прочитать книгу. Не считайте справедливым копировать / вставлять большие части книги.
Если сущность будет неизменной, вопрос о том, использовать ли структуру или класс, обычно будет связан с производительностью, а не с семантикой. В 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, и выполняется следующий код:
Thing 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 могут привести к нарушению семантики типа значения в приложении, используйте класс и бормотайте.
В дополнение к отличным ответам выше:
Структуры - это типы значений.
Они никогда не могут быть установлены на Ничего.
Установка структуры = Nothing установит для всех ее типов значений значения по умолчанию.
Структуры находятся в стеке, а не в куче, поэтому они потокобезопасны и должны использоваться при реализации шаблона объекта передачи, вы никогда не хотите использовать объекты в куче, они являются изменчивыми, в этом случае вы хотите использовать стек вызовов, это базовый случай использования структуры, я удивлен всеми вариантами ответов здесь,
Используйте класс, если:
Используйте структуру, если:
Я думаю, что лучший ответ - просто использовать структуру, когда вам нужен набор свойств, класс, когда это набор свойств И поведения.
структуры тоже могут иметь методы
конечно, но если вам нужны методы, шансы на 99% вы неправильно используете структуру вместо класса. Единственные исключения, которые я обнаружил, когда можно использовать методы в структуре, - это обратные вызовы
Это старая тема, но я хотел предоставить простой тест производительности.
Я создал два файла .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; }
}
Запустить тест:
Полученные результаты:
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 |
Напоминаем, что большинство людей забывают в этом контексте, что в структурах C# также могут быть методы.