C# NET: Class vs Struct или в чём различия между Классом и Структурой
Просто о NET | создано: 10.07.2011 | опубликовано: 10.07.2011 | обновлено: 13.01.2024 | просмотров: 124872 | всего комментариев: 30
Мне в последнее время очень часто встречаются программисты, которые не только используют в обычной “программной” жизни структуры (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# и да прибудет с Вами сила… структур. Цитата из комментария: "С заключением тоже не согласен. Многие маститые западные авторы наоборот рекомендуют как можно меньше использовать структуры, предпочитая классы. Хотя, конечно, молиться на них и их мнение не стоит, лучше думать своей головой."
Комментарии к статье (30)
>> Экземпляры структуры называют значениями и “живут” они в стеке (stack).
Уточнение. Экземпляры структур также могут жить в полях классов и других структур. А при упаковке - в куче
Приношу свои извинения. Только что при публикации, совершенно случайно (по вине провайдера) был удален один из комментариев. Если автора не затруднит повторить, я буду бесконечно признателен. Еще раз приношу свои личные извинения. Обещаю больше этого не повториться.
В догонку к извинениям: Комментарий был опубликован в момент смены тарифного плана, то есть в старой БД на старом тарифном плане. Еще раз приношу свои глубчайшие извинения.
И как часто вы меняете тарифные планы? а может коментарий не понравился7?
Спасибо за статью.
Не хватает нюансов упаковки/распаковки, наследования.
И ссылки на Рихтера :)
>> В отличие от классов использование публичных полей в структурах в большинстве случаях не рекомендуется, потому что не существует способа контролирования значений в них.
А в классах рекомендуется использовать публичные поля?
>> Более правильный вариант в данном случае использовать свойства, а в конструкторе осуществить проверку:
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; }
А где здесь свойство? Спрятано в 14-й строке? :)
ОЧЕНЬ полезные дополнения!!! Просто не знаю как вылетело из головы. Спасибо друзья!!! Реально, спасибо!
> Можно ли создать конструктор по умолчанию?
Данный вопрос к значимому типу (которым является структура) является не корректным.
У значимого типа можно инициализировать поля значениями по умолчанию. Это делается при помощи ключевого слова default. Ваше сравнение является очень поверхностным (особенно в отношении значимых типов).
Я вам крайне советую перечитать спецификацию на C#, а также болг Эрика Липперта (Eric Lippert) http://blogs.msdn.com/b/ericlippert/ (русский перевод http://blogs.msdn.com/b/ruericlippert/ ) и еще раз пересмотреть свою статью.
Обратите внимание на такие стать как
http://blogs.msdn.com/b/ruericlippert/archive/2010/11/22/debunking-another-myth-about-value-types.aspx
http://blogs.msdn.com/b/ruericlippert/archive/2009/11/23/9986435.aspx
Леонид
Для начала, сапсибо за комментарий, такой важный и полезный. Именно с максимальной простотой и хотелось описать различия (обратите внимание на категорию, в которой опубликована статья :) или название блога). Но я готов дать предоставить права "блоггера" на своем сайте любому кто напишет статью в новую категорию "Серьёзно о NET" можно с таким же названием. :)
Еще важное отличие, в структурах не допускается объявление полей того же типа.
Т.е.:
struct Time
{
Time time; // недопустимо.
}
--- а структуры создаются в стеке (stack).
ой не так все просто ....а если в структуре поля-строки к примеру? где будет храниться структура а?)
Михаил,
Ответ на Ваш вопрос: Ссыллочные типы храняться в куче, значимые в стеке.
я разве спросил где хранятся ссылочные типы а где значимые? пасибо конечно ..сам догадывался. в структуре есть поля типа строка,одно , два не важно.где такая структура поле будет хранится по вашему?
так не много по другому поставлю вопрос: как будет хранится в таком случае? я понимаю что calabonga:Ссыллочные типы храняться в куче, значимые в стеке..Это общая фраза которую 90% людей скажет.само значение строкового поле тож в стек уйдет или как?
Михаил,
Скажу честно, сдаюсь, не знаю ответа на этот вопрос. Вы меня поставили в тупик, я даже не знаю... а как по-вашему...???
Чтоб внести ясность можно поставить вопрос по другому: не "где" хранится, а "что" хранится
Т.о.:
Для ссылочных типов в стеке, в поле класса, в поле струкктуры (где угодно) хранится ссылка (на объект, который сам по себе хранится в куче)
Для значимых типов в стеке, в поле класса, в поле структуры (где угодно) хранится значение/экземпляр
Именно так я и предполагал, просто когда-то читал в одной из книжек и автора не смог вспомнить, чтобы состаться на него. Да и было это давно ... и читал про NET 2.0
Всё равно спасибо, Михаил, Вы подтвердили мои догадки.
Структуры являются ссылочными их размер с данными превышает кажется 64 байта
Михаил,
> Кстати вроде если структуру через new объявить, то она будет в куче храниться, а не в стеке.
Это не так.
Иван,
> Структуры являются ссылочными их размер с данными превышает кажется 64 байта
Откуда информация?
Я дико извиняюсь за глупый вопрос, но основной смысл использования структур вместо классов в том случае, если необходима производительность (из текста: "...тем менее затратным по ресурсам будет использование памяти.")? И если да, то насколько эффективнее использовать структуры?
набросал статью. не то что бы в ответ, больше по мотивам данной статьи ... http://dtimofeev.blogspot.com/2011/07/blog-post_13.html
> А следовательно, классы всегда создаются в, так называемой, “куче” (heap), а структуры создаются в стеке (stack).
Задолбало уже это определение! Из блога в блог, из статьи в статью кочует это неверное утверждение.
В первом же комментарии пояснено, что структура, являющаяся полем класса, будет храниться в куче.
Имхо, главное отличие структур и классов: структуры передаются по значению (то есть копируются), объекты классов - по ссылке. Именно это является существеннейшим различием в их поведении, а не то, где они хранятся.
Примечание: структуру тоже можно передать по ссылке, используя модификаторы out и ref.
На мой придирчивый взгляд, стиль изложения хромает. Много неграмотных предложений. Например:
> К особенностям структур можно отнести еще и тот факт, что вследствие того, что структуры являются значимым типом, то создать структуру без использования конструктора, например:
Таки создать структуру можно или нельзя? Какое слово пропущено? Это называется, угадай сам.
С заключением тоже не согласен. Многие маститые западные авторы наоборот рекомендуют как можно меньше использовать структуры, предпочитая классы. Хотя, конечно, молиться на них и их мнение не стоит, лучше думать своей головой.
Моё заключение: статью - в /dev/null, автора - в биореактор.
P.S. Желаю к написанию новых статей подходить более тщательно, продуманно.
Кстати, вот ещё глобальная ошибка: название статьи начинается со слов .NET 4.0, а описание ведётся исключительно по C#. Calabonga, при всём моём к вам уважении, я не могу не заклеймить позором этот материал. В MC++ (C++.NET) классы тоже можно объявлять на стеке. Думаю, этого примера достаточно.
P.S. Что-то я злой сегодня. Не принимайте близко к сердцу. Но ошибки прошу исправить.
Уважаемый, petalvik, просто и не знаю как Вас благодарить. Спасибо за хорошие дельные комментарии и критику. Обычно именно такие комментарии я и жду. Исправил ошибки, внес изменения в материал. Еще раз спасибо.
>> В таблице указаны альясы и соответствующие им типы
Убило наповал - что такое "альясы"?! Может "псевдонимы"? Зачем засорять русский язык, если есть "родной" термин.
В целом статья ничего,а комменты внесли еще большую ясность в предмет обсуждения
"Вы можете создавать nullable версию..."
Думка блогера : "What is nullable? Эээ... как это по русски (чешет репу). А хер с ним, так и напишим нулябл". (ржот пот сталом). Версию статьи давно пора обнуляблить :))
Спасибо за статью, очень помогла разобраться.
Тут упомянули Рихтера - вот он мне мозги и заморочил, хотя в целом книги его бесценны.
The common language runtime (CLR) always allows the creation of value type instances, and there
is no way to prevent a value type from being instantiated. For this reason, value types don’t actually even need to have a constructor defined within them, and the C# compiler doesn't emit default
parameterless constructors for value types.
Я правильно понимаю, что тут сказано "C# не создает дефолтный конструктор" ??
А судя по статье, и по тому как вообще структуры себя ведут - дефолтный к-р таки создается!!!
Я по ходу Рихтера не так поняла?
Тоже вопрос по Рихтеру:
при создании экземпляра структуры с ключевым словом new, где она будет, на куче или в стеке. В книге написано, что всегда в стеке, а на ITVDN говорили, что в этом случае в куче.