Чтобы лучше понять, когда использовать класс или структуру, рассмотрим различия между структурами и классами. Дополнительный пример см. здесь.
Структуры синтаксически очень похожи на классы, но существует принципиальное отличие, которое заключается в том, что класс – является ссылочным типом (reference type), а структуры – значимым типом (value type) (см. статью «Типы данных«). Следовательно, классы всегда создаются в так называемой “куче” (heap), а структуры создаются в стеке (stack).
Но это справедливо в очень простых случаях, главное отличие структур и классов: структуры, указываемые в списке параметров метода, передаются по значению (то есть копируются), объекты классов — по ссылке. Именно это является главным различием в их поведении, а не то, где они хранятся. Примечание: структуру тоже можно передать по ссылке, используя модификаторы out и ref.
Чем больше вы будете использовать структуры вместо маленьких классов, тем менее затратным по ресурсам будет использование памяти.
Так же как и классы, структуры могут иметь поля, методы и конструкторы. О конструкторах структур уже говорилось, ибо это важный критерий при сравнивании классов и структур.
Мы выяснили, что все встроенные типы значений задаются структурами, например, числовые типы int, long, float определены структурами System.Int32, System.Int64 и System.Single соответственно. Эти структуры имеют поля и методы. Мы уже вызывали методы у переменных этих типов. Например, каждая из перечисленных структур имеет метод ToString( ). Также у перечисленных структур есть статичные поля, например, Int32.MaxValue или Int32.MinValue. Получается, что мы уже многократно использовали структуры.
В отличие от классов, использование публичных полей в структурах в большинстве случаев не рекомендуется, потому что не существует способа контролирования значений в них. Например, кто-либо может установить значение минут или секунд более 60. Более правильный вариант в данном случае использовать свойства, а в конструкторе осуществить проверку:
using System; namespace ConsoleApplication1 { struct Time { private int hours, minutes, seconds; public Time(int hh, int mm, int ss) { hours = hh % 24; minutes = mm % 60; seconds = ss % 60; } public int Hours() { return hours; } } class Program { static void Main() { Time t = new Time(30,69,59); Console.WriteLine(t.Hours()); Console.ReadKey(); } } }
В результате будет напечатано число часов: 6 (остаток от деления 30 на 24).
Заменим конструктор Time(…) конструктором без параметров:
public Time() { hours = 7; minutes = 4; seconds = 0; }
Получим сообщение об ошибке:
«Структуры не могут содержать явных конструкторов без параметров»
Причина возникновения ошибки в том, что вы не можете использовать конструктор по умолчанию (без параметров) для структуры, потому что компилятор всегда генерирует его сам.
Что же касается класса, то компилятор создает конструктор по умолчанию только в том случае, если Вы его не создали. При объявлении класса нет проблем создать собственный конструктор без параметров (замените в программе ключевое слово struct на class, вы получите результат — 7).
Еще один эксперимент. Замените снова class на struct и удалите полностью конструктор Time(). Запустите программу, она будет выполнена, результат – 0. Вы получите три предупреждения типа:
«Полю «ConsoleApplication1.Time.hours» нигде не присваивается значение, поэтому оно всегда будет иметь значение по умолчанию 0».
Это означает, что был сгенерирован конструктор (без параметров) для структуры, который всегда устанавливает поля в 0, false или null (для объектов) – как и для классов. Поэтому Вы можете быть уверенными в том, что созданная структура всегда будет вести себя адекватно в соответствии со значениями по умолчанию в используемых типах.
Если Вы не хотите использовать значения по умолчанию, то можете инициализировать поля своими значениями в конструкторе с параметрами для инициализации.
Однако если в этом конструкторе не будет инициализировано какое-нибудь значение, компилятор не будет его инициализировать и покажет ошибку.
Два правила структур
Первое правило структуры: Всегда все переменные должны быть инициализированы.
В классах Вы можете инициализировать значение полей непосредственно при их объявлении. В структурах такого сделать нельзя, и поэтому данный код вызовет ошибку при компиляции. Поэтому:
Второе правило структуры: Нельзя инициализировать переменные в том месте, где они объявляются.
Сравнение классов и структур в сжатом виде:
Вопрос | Структура | Класс |
Какого же типа экземпляр объекта? | Значимый (value) тип | Ссылочный (reference) тип |
Где “живут” экземпляры этих объектов? | Экземпляры структуры называют значениями и “живут” они в стеке (stack). | Экземпляры классов называют объектами и “живут” они в куче (heap). |
Можно ли создать конструктор по умолчанию? | Нет | Да |
Если создается свой конструктор, будет ли компилятор генерировать конструктор по умолчанию? | Да | Нет |
Если в своём конструкторе не будут инициализированы некоторые поля, будут ли они автоматически инициализированы компилятором? | Нет | Да |
Разрешается ли инициализировать переменные там, где их объявляют? | Нет | ДА |
Поля структуры могут быть инициализированы при использовании конструктора (если объект объявляется с помощью оператора new), причем не важно, какого «собственного» или «по умолчанию».
К особенностям структур можно отнести еще и тот факт, что вследствие того, что структуры являются значимым типом, то можно создать структуру без использования конструктора, например:
using System; namespace ConsoleApplication1 { struct Time { public int hours, minutes, seconds; } class Program { static void Main() { Time t; t.hours=7; Console.WriteLine(t.hours.ToString()); Console.ReadKey(); } } }
В таком случае, переменная t создается, но поля не будут инициализированы конструктором (нет оператора Time t = new Time();). Правда, теперь поля структуры придется объявлять только с модификатором public.
Замечания, над которыми стоит подумать (проверить практикой):
1. Используйте структуры, это признак хорошего тона в программировании на C#, хотя некоторые авторы наоборот рекомендуют как можно меньше использовать структуры, предпочитая классы.
2. Дополнения: структуры не могут быть абстрактными, структуры не имеют деструкторов, структуры не поддерживают наследование.
3. Главное отличие структур и классов: структуры передаются по значению (то есть копируются), объекты классов — по ссылке. Именно это является существенным различием в их поведении, а не то, где они хранятся.
4. Структуру тоже можно передать по ссылке, используя модификаторы out и ref.
Примечание. Как получить размер объекта в .Net.
В .Net оператор sizeof(тип) умеет возвращать размеры только простых значимых типов. При попытке направить его на любой управляемый в куче объект вы получите ошибку. Единственный более-менее достоверный способ узнать объем памяти, занимаемый объектом, это сравнение размеров используемой памяти до инициализации объекта-массива и после, например:
long mem = GC.GetTotalMemory(true); int[] intArray = new int[100000]; mem = GC.GetTotalMemory(true) - mem; // В mem - размер объекта в байтах: 4 х 100000 + 16 (?).
Ответ: 400016 байт. Конец примечания.
Теперь после обсуждения структур перейдем к рассмотрению массивов — важнейших конструкций данных, всегда размещаемых в управляемой куче и являющихся объектами.
NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.
Всё коротко и по-существу. Благодарю!