C# NET: Class vs Struct или в чём различия между Классом и Структурой
Мне в последнее время очень часто встречаются программисты, которые не только используют в обычной “программной” жизни структуры (struct), но вообще, ничего не знают об этом объекте. И зачастую, для простоты своей "программной" жизни используют всегда классы (class). В этой статье я бы хотел в очередной раз остановиться на различиях между структурами и классами.
Что такое struсture?
Структуры синтаксически очень похожи на классы, но существует принципиальное отличие, которое заключается в том, что класс – является ссылочным типом (reference type), а структуры – значимый класс (value type). А следовательно, классы всегда создаются в, так называемой, “куче” (heap), а структуры создаются в стеке (stack). Цитата из комментария: "Имхо, главное отличие структур и классов: структуры передаются по значению (то есть копируются), объекты классов - по ссылке. Именно это является существеннейшим различием в их поведении, а не то, где они хранятся. Примечание: структуру тоже можно передать по ссылке, используя модификаторы out и ref."
Ключевой момент статьи: Чем больше Вы будете использовать структуры вместо небольших (наверное, правильнее будет сказать – маленьких) классов, тем менее затратным по ресурсам будет использование памяти. Смею предположить, что не требуется объяснения почему… “куча”, “сборщик мусора”, “неопределенные временные интервалы прохода”, сложность “ручного” управления кучей и сборщиком мусора. Все ключевые моменты уже перечислены.
Так же как и классы, структуры могут иметь поля, методы и конструкторы. Хотя про конструкторы надо поговорить подробнее (будет дальше по теме), ибо это есть очень важное понятие при сравнивании классов и структур.
Не хочется думать, что следующая информация, для Вас сюрпризом. В языке C# примитивные числовые типы int, long, float являются альясами для структур System.Int32, System.Int64 и System.Single соответственно. Эти структуры имеют поля и методы. Вы обычно вызываете методы у переменных данных типов. Например, каждая из перечисленных структур имеет метод ToString. Также у перечисленных структур есть статичные поля, например, Int32.MaxValue или Int32.MinValue. Получается, что Вы уже в повседневной "программной" жизни используете структуры, а значит знакомы с ними.
Таблица классов и структур в Microsoft. NET Framework
В таблице указаны альясы и соответствующие им типы, а также дана информация о представляющем типе (структура или класс).
| Keyword | Type equivalent | Class or structure |
|---|---|---|
| bool | System.Boolean | Structure |
| byte | System.Byte | Structure |
| decimal | System.Decimal | Structure |
| double | System.Double | Structure |
| float | System.Single | Structure |
| int | System.Int32 | Structure |
| long | System.Int64 | Structure |
| object | System.Object | Class |
| sbyte | System.SByte | Structure |
| short | System.Int16 | Structure |
| string | System.String | Class |
| uint | System.UInt32 | Structure |
| ulong | System.UInt64 | Structure |
| ushort | System.UInt16 | Structure |
Объявление структур
Для объявления структуры используется зарезервированное слово struct, следом наименование структуры и фигурные скобки:
struct Time
{
public int hours, minites, seconds;
}
В отличие от классов, использование публичных полей в структурах в большинстве случаев не рекомендуется, потому что не существует способа контролирования значений в них. Например, кто-либо может установит значение минут или секунд более 60. Более правильный вариант в данном случае - использовать свойства, а в конструкторе осуществить проверку:
struct Time
{
public Time(int hh, int mm, int ss)
{
hours = hh % 24;
minutes = mm % 60;
seconds = ss % 60;
}
public int Hours()
{
return hours;
}
...
private int hours, minutes, seconds;
}
Кстати, …По умолчанию, Вы не можете использовать некоторые общие операторы в Ваших структурах. Например, Вы не можете использовать оператор сравнения (==) и противоположные ему (!=) на своих структурах. Однако, Вы можете явно объявить и реализовать операторы для своих структур.
И в чем же разница между структурами и классами
Давайте рассмотрим пример, в котором уже заложена ошибка:
struct Time
{
public Time() { ... } // compile-time error
...
}
Причина возникновении ошибки в том, что Вы не можете использовать конструктор по умолчанию (без параметров) для структуры, потому что компилятор всегда генерирует его сам. Что же касается класса, то компилятор создает конструктор по умолчанию, только в том случае, если Вы его не создали. Сгенерированный конструктор для структуры всегда устанавливает поля в 0, false или null – как и для классов. Поэтому Вы можете быть уверенными в том, что созданная структура всегда будет вести себя “адекватно” в соответствии со значениями по умолчанию в используемых типах. Если Вы не хотите использовать значения по умолчанию, Вы можете инициализировать поля своими значениями в конструкторе с параметрами для инициализации. Однако, если в этом конструкторе не будет инициализировано какое-нибудь значение, компилятор не будет его инициализировать за Вас и покажет ошибку.
struct Time
{
private int hours, minutes, seconds;
...
public Time(int hh, int mm)
{
this.hours = hh;
this.minutes = mm;
} // compile-time error: seconds not initialized
}
Первое правило Структуры: Всегда все переменные должны быть инициализированы!
В классах Вы можете инициализировать значение полей непосредственно в месте их объявления. В структурах такого сделать не получится, и поэтому данный код вызовет ошибку при компиляции:
struct Time
{
private int hours = 0; // compile-time error
private int minutes;
private int seconds;
...
}
Второе правило Структуры: Нельзя инициализировать переменные в месте их объявления!
Данная таблица в некотором роде подытоживает всё вышесказанное и отображает основные отличия между классами и структурами.
| Вопрос | Структура | Класс |
| И какого же типа экземпляр объекта? | Структура значимый (value) тип | Класс ссылочный (reference) тип |
| А где “живут” экземпляры этих объектов? | Экземпляры структуры называют значениями и “живут” они в стеке (stack). | Экземпляры классов называют объектами и “живут” они в куче (heap). |
| Можно ли создать конструктор по умолчанию? | Нет | Да |
| Если создается свой конструктор будет ли компилятор генерировать конструктор по умолчанию? | Да | Нет |
| Если в своём конструкторе не будут инициализированы некоторые поля, будут ли они автоматически инициализированы компилятором? | Нет | Да |
| Разрешается ли инициализировать переменные в месте их объявления? | Нет | Да |
Использование структур как переменных
После того как Вы создали структуры, Вы можете использовать ее также как классы и другие типы. Например, создав структуру Time, я могу использовать ее в классе:
struct Time
{
private int hours, minutes, seconds;
...
}
class Example
{
private Time currentTime;
public void Method(Time parameter)
{
Time localVariable;
...
}
}
Кстати, …
Вы можете создавать nullable версию переменной типа структуры использую модификатор “?” и потом присвоить ей значение null:
Time? currentTime = null;
Инициализация структур
В статье я не раз говорил, что поля структуры могут быть инициализированы при использования конструктора, причем не важно какого “собственного” или “по умолчанию”. К особенностям структур можно отнести еще и тот факт, что вследствие того, что структуры являются значимым типом, то можно создать структуру без использования конструктора, например:
Time now;
В таком случае, переменная создается, но поля не будут инициализированы в соответствии параметрами конструктора.
Заключение
Используйте структуры, это признак хорошего тона в программировании на C# и да прибудет с Вами сила… структур. Цитата из комментария: "С заключением тоже не согласен. Многие маститые западные авторы наоборот рекомендуют как можно меньше использовать структуры, предпочитая классы. Хотя, конечно, молиться на них и их мнение не стоит, лучше думать своей головой."