Когда мне следует использовать структуру вместо класса?

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

Некоторые люди могли забыть об этом:

  1. структуры может иметь методы.
  2. структуры не может быть унаследован.

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

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

petr k. 17.09.2008 21:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
314
1
84 972
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

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

Также см. Предыдущие вопросы, например

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

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

Следовать за Так я думаю о структурах в целом. Я знаю, что у них могут быть методы, но мне нравится сохранять это общее умственное различие.

Почему ты это сказал? У структур могут быть методы.

Esteban Araya 17.09.2008 21:23

Как сказал @Simon, структуры обеспечивают семантику «типа значения», поэтому, если вам нужно поведение, аналогичное встроенному типу данных, используйте структуру. Поскольку структуры передаются по копии, вы хотите убедиться, что они имеют небольшой размер, около 16 байт.

Я бы использовал структуры, когда:

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

  2. объект маленький и недолговечный. В таком случае велика вероятность того, что объект будет размещен в стеке, что намного эффективнее, чем размещение его в управляемой куче. Более того, память, выделенная объектом, будет освобождена, как только она выйдет за пределы его области видимости. Другими словами, сборщик мусора работает меньше, а память используется более эффективно.

Ответ принят как подходящий

У 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.

Может быть, я упускаю что-то очевидное, но я не совсем понимаю аргументы, лежащие в основе «неизменяемой» части. Зачем это нужно? Может кто-нибудь объяснить это?

Tamas Czinege 22.01.2009 22:11

Вероятно, они рекомендовали это, потому что, если структура неизменяема, то не имеет значения, что она имеет семантику значения, а не семантику ссылки. Различие имеет значение только в том случае, если вы изменяете объект / структуру после создания копии.

Stephen C. Steel 27.03.2010 01:07

@DrJokepu: в некоторых ситуациях система создает временную копию структуры, а затем разрешает передачу этой копии по ссылке в код, который ее изменяет; поскольку временная копия будет удалена, изменение будет потеряно. Эта проблема особенно серьезна, если у структуры есть методы, которые ее изменяют. Я категорически не согласен с представлением о том, что изменчивость - это причина сделать что-то классом, поскольку - несмотря на некоторые недостатки в C# и vb.net, изменяемые структуры обеспечивают полезную семантику, которая не может быть достигнута никаким другим способом; нет никакой семантической причины предпочесть неизменяемую структуру классу.

supercat 21.01.2011 00:23

Я предпочитаю принцип Скотта Мейерса. Он явно отклоняется от документации .NET, поскольку считает, что использование типа является лучшим фактором, чем размер типа.

Bart Gijssens 15.06.2011 13:59

Мне интересно, применим ли размер 16 байт к 64-битному коду? Я не уверен на 100%, откуда взялось 16-байтовое число.

Chuu 27.02.2012 22:57

@Chuu: При разработке JIT-компилятора Microsoft решила оптимизировать код для копирования структур размером 16 байт или меньше; это означает, что копирование 17-байтовой структуры может быть значительно медленнее, чем копирование 16-байтовой структуры. Я не вижу особых причин ожидать, что Microsoft расширит такую ​​оптимизацию на более крупные структуры, но важно отметить, что хотя 17-байтовые структуры могут копировать медленнее, чем 16-байтовые структуры, во многих случаях большие структуры могут быть более эффективными, чем большие объекты класса, и где относительное преимущество структур растет с размером структуры.

supercat 02.01.2013 20:12

@Chuu: применение тех же шаблонов использования к большим структурам, что и к классам, может привести к неэффективному коду, но правильное решение часто не заменять структуры классами, а вместо этого использовать структуры более эффективно; в частности, следует избегать передачи или возврата структур по значению. Передавайте их как параметры ref, когда это целесообразно. Передача структуры с 4000 полей в качестве параметра ref методу, который меняет одно, будет дешевле, чем передача структуры с 4 полями по значению методу, который возвращает измененную версию.

supercat 02.01.2013 20:18

@supercat - подождите - каковы много случаев, когда большая структура более эффективна, чем экземпляр большого класса? Я вижу, что это возможно только в том случае, если было необходимо обеспечить неизменность, поэтому "классовый" подход закончился тем, что вам пришлось создавать новый экземпляр каждый раз, когда вы что-то делали ... Я никогда с этим не сталкивался, но мне было бы интересно узнать когда это происходит.

ToolmakerSteve 02.04.2018 22:10

Я знаю, что это старый пост, но structs рекомендуется сделать неизменным, потому что они являются объектами типа значения обычно (помните ссылки на интерфейс), и значения копируются, а не обновляются. Хотя вы можете указать на структуру и изменить существующее значение; структура все еще копируется, а не обновляется на месте, как класс. Другими словами; это безопаснее и в большей степени соответствует тому, как работают существующие структуры, т.е. (int, char, DateTime, ETC)

Michael Puckett II 21.12.2018 16:00

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

Хм...

Я бы не стал использовать сборку мусора в качестве аргумента за / против использования структур и классов. Управляемая куча работает во многом как стек - создание объекта просто помещает его в верхнюю часть кучи, что почти так же быстро, как и выделение в стеке. Кроме того, если объект недолговечен и не переживает цикл сборщика мусора, освобождение сборщика мусора осуществляется бесплатно, поскольку сборщик мусора работает только с доступной памятью. (Поищите в MSDN, там есть серия статей по управлению памятью .NET, мне просто лень их искать).

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

В любом случае, эти четыре пункта в опубликованной выше статье MSDN кажутся хорошим ориентиром.

Если вам иногда требуется ссылочная семантика со структурой, просто объявите class MutableHolder<T> { public T Value; MutableHolder(T value) {Value = value;} }, и тогда MutableHolder<T> будет объектом с изменяемой семантикой класса (это работает так же хорошо, если T является структурой или неизменяемым типом класса).

supercat 02.02.2013 04:08

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

Я использую структуры, когда мне нужен тип без идентичности. Например, 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, определенные аналогично тому, что вы здесь показываете, но тогда мне всегда приходилось быть осторожным, чтобы не изменить эти экземпляры, если я использовал их в качестве ключей словаря. Наверное, было бы безопаснее, если бы я определил их как структуры. (Потому что тогда ключ будет копировать полей на данный момент структура стала ключом словаря, поэтому ключ останется неизменным, если я позже изменю оригинал.)

ToolmakerSteve 02.04.2018 22:25

В вашем примере это нормально, потому что у вас есть только 12 байтов, но имейте в виду, что если у вас много полей в этой структуре, которые превышают 16 байтов, вы должны подумать об использовании класса и переопределить методы GetHashCode и Equals.

Daniel Botero Correa 21.02.2019 14:31

Тип значения в DDD не означает, что вы обязательно должны использовать тип значения в C#.

Darragh 04.04.2019 20:31

У Билла Вагнера есть глава об этом в своей книге «Эффективный C#» (http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660). В заключение он использует следующий принцип:

  1. Is the main responsability of the type data storage?
  2. Is its public interface defined entirely by properties that access or modify its data members?
  3. Are you sure your type will never have subclasses?
  4. 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) должны быть структурами?

cruizer 02.08.2011 12:34

Я бы сказал да, если он соответствует 4 критериям, описанным выше. Зачем нужно обрабатывать объект передачи данных определенным образом?

Bart Gijssens 02.08.2011 13:23

@cruizer зависит от вашей ситуации. В одном проекте у нас были общие поля аудита в наших DTO, и поэтому мы написали базовый DTO, от которого унаследовали другие.

Andrew Grothe 04.03.2013 22:52

на основе определения DTO могут быть структурами, если вы не наследуете что-то от базового класса (что обычно делается с DTO)

Juan Zamora 22.03.2018 16:20

Все, кроме (2), кажутся отличными принципами. Потребуется увидеть его рассуждения, чтобы знать, что именно он имеет в виду под (2) и почему.

ToolmakerSteve 02.04.2018 22:32

@ToolmakerSteve: Для этого вам придется прочитать книгу. Не считайте справедливым копировать / вставлять большие части книги.

Bart Gijssens 03.04.2018 14:20

Если сущность будет неизменной, вопрос о том, использовать ли структуру или класс, обычно будет связан с производительностью, а не с семантикой. В 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 установит для всех ее типов значений значения по умолчанию.

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

Используйте класс, если:

  • Его личность важна. Структуры копируются неявно при передаче по значению в метод.
  • У него будет большой объем памяти.
  • Его поля нуждаются в инициализаторах.
  • Вам нужно унаследовать от базового класса.
  • Вам нужно полиморфное поведение;

Используйте структуру, если:

  • Он будет действовать как примитивный тип (int, long, byte и т. д.).
  • Он должен иметь небольшой объем памяти.
  • Вы вызываете метод P / Invoke, который требует передачи структуры ценить.
  • Вам необходимо уменьшить влияние сборки мусора на производительность приложения.
  • Его поля необходимо инициализировать только значениями по умолчанию. Это значение будет равно нулю для числовых типов, false для логических типов и null для ссылочных типов.
    • Обратите внимание, что в структурах C# 6.0 может быть конструктор по умолчанию, который можно использовать для инициализации поля структуры к значениям, отличным от значений по умолчанию.
  • Вам не нужно наследовать от базового класса (кроме ValueType, от которого все структуры наследуются).
  • Полиморфное поведение не требуется.

Я думаю, что лучший ответ - просто использовать структуру, когда вам нужен набор свойств, класс, когда это набор свойств И поведения.

структуры тоже могут иметь методы

Beingnin 18.06.2019 14:10

конечно, но если вам нужны методы, шансы на 99% вы неправильно используете структуру вместо класса. Единственные исключения, которые я обнаружил, когда можно использовать методы в структуре, - это обратные вызовы

Lucian Gabriel Popescu 28.07.2019 11:56

Это старая тема, но я хотел предоставить простой тест производительности.

Я создал два файла .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 |

Другие вопросы по теме