C# NET: Class vs Struct или в чём различия между Классом и Структурой

Просто о NET | создано: 10.07.2011 | опубликовано: 10.07.2011 | обновлено: 13.01.2024 | просмотров: 124457 | всего комментариев: 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# и да прибудет с Вами сила… структур. Цитата из комментария: "С заключением тоже не согласен. Многие маститые западные авторы наоборот рекомендуют как можно меньше использовать структуры, предпочитая классы. Хотя, конечно, молиться на них и их мнение не стоит, лучше думать своей головой."

Поблагодарить

Хотите тоже получать донаты? Тогда заходите на boosty.to и регистрируйтесь!

Кстати, я использую хостинг reg.ru. Подключайся с промокодом 9A17-953A-8591-CF98 чтобы получить скидку 5%

Комментарии к статье (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-й строке? :)

Пару дополнений: структуры не могут быть абстрактными, структуры не имеют деструкторов, структуры не поддерживают наследование, поля структуры не могут помечаться как volatile.
sharok,
ОЧЕНЬ полезные дополнения!!! Просто не знаю как вылетело из головы. Спасибо друзья!!! Реально, спасибо!

> Можно ли создать конструктор по умолчанию?
Данный вопрос к значимому типу (которым является структура) является не корректным.
У значимого типа можно инициализировать поля значениями по умолчанию. Это делается при помощи ключевого слова 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 говорили, что в этом случае в куче.