За формальное описание общей системы типов в net framework отвечает
Исходный код примеров из этой статьи можете скачать из нашего github-репозитория.
Общая система типов (CTS)
Типы значения
Переменные типа-значения располагаются в стеке, что позволяет их быстро создавать и уничтожать. Фактически время жизни такой переменной определяется контекстом, в которой она объявлена. Сама переменная представляется в виде локальной копии. Типы-значения являются классами наследниками от System.ValueType , который, в свою очередь, наследуется от System.Object . К типам-значениям относятся: простые типы, типы перечисления, типы структур, типы значений, допускающие NULL , типы значений кортежей. Далее, обзорно будут рассмотрены указанные выше типы.
Простые типы
К простым типа относятся:
- Целочисленные типы;
- Типы с плавающей точкой;
- Тип bool для представления логических значений;
- Тип char для представления символьных значений.
Целочисленные типы
Примеры работы с целыми числами:
Для явного указания, что число имеет тип long или ulong необходимо добавить суффикс L или l для long и UL и все возможные комбинации регистров эти символов для ulong .
Число может быть представлено в десятичном, шестнадцатеричном и двоичном виде:
Для всех целочисленных простых типов значение по умолчанию: 0 , оно присваивается переменной при инициализации с помощью ключевого слова new .
Типы с плавающей точкой
Сводная таблица с типами с плавающей точкой:
Для явного указания типа данных при записи числа, можно использовать литералы:
Для чисел с плавающей точкой значение по умолчанию: 0.0 с соответствующей литерой в конце.
Тип bool
Значение по умолчанию для типа bool : false .
Тип char
Переменной типа char можно задать значение:
- в виде символа:
- escape -последовательности Юникода (начинаются с префикса \u ):
- шестнадцатеричной escape -последовательности (начинается с префикса \x ):
- через приведение типа:
Значением по умолчанию для char : ‘ \u0000′ .
Типы перечисления (enum)
Перечисления являются набором целочисленных именованных констант. Переменные типа перечисления создаются с помощью ключевого слово enum, после которого следует имя типа и набор значений в фигурных скобках. Создадим enum для представления дней недели:
Каждому значению перечисления соответствует целое число.
Пример создания переменной типа Day :
Более подробно про работу с enum будет рассказано в одном из следующих уроков.
Типы структур
Структуры по своей внутренней организации похожи на классы, они содержат набор полей и методов. Как правило, их используют для объявления типов, которые определяются только значениями полей и не имеют индивидуальности. Например, объекты, описывающие транзакции, несмотря на то, что значения их полей могут совпадать не будут тождественными, то есть нам их нужно уметь различать несмотря на внешнее сходство. А точки на геометрической плоскости, которые задаются двумя координатами, такой индивидуальности не имеют, и если координаты двух точек совпадают, то это значит, что речь идет об одной и той же точке. Именно для таких типов хорошо подходят структуры. Для их объявления используется ключевое слово struct :
Типы значений, допускающие null
Про типы значений, допускающих null см. ниже “ Nullable -типы (нулевые типы) и операция ??” .
Типы значений кортежей
Кортежи используются для группировки данных, которые могут иметь разные типы в единую именованную сущность. Они являются объектами типа System.ValueTuple . Объявим кортеж, состоящий из двух элементов типа double:
Поля кортежа могут быть именованными:
Более подробно про кортежи типов System.ValueTuple (тип-значение) и System.Tuple (ссылочный тип) будет рассказано в одном из следующих уроков.
Универсальные типы
Использование параметра типа позволяет повторно использовать один же класс для хранения элементов любого типа, не преобразовывая каждый элемент в объект. Универсальные классы коллекций называются строго типизированными коллекциями, так как компилятору известен конкретный тип элементов коллекции и он может выдать ошибку во время компиляции, если, к примеру, вы попытаетесь добавить целое число в объект stringList , созданный в предыдущем примере. Дополнительные сведения см. в статье Универсальные шаблоны.
Типы значений
Типы значений являются производными от System.ValueType, который является производным от System.Object. Типы, производные от System.ValueType, имеют особое поведение в среде CLR. Переменные типа значения непосредственно содержат их значения. Память для структуры выделяется встроенным образом в любом контексте, объявленном переменной. Для переменных типа значения не предусмотрены раздельное размещение в куче или накладные расходы при сборке мусора. Можно объявить типы record struct , которые являются типами значений, и включить синтезированные члены для записей.
Существует две категории типов значений: struct и enum .
Встроенные числовые типы являются структурами и имеют поля и методы, к которым можно обращаться.
Но объявление и присвоение значений вы выполняете для них так, как если бы они были простыми нестатистическими типами:
Типы значений являются запечатанными. Тип не может быть производным от любого типа значений, например System.Int32. Вы не можете определить структуру для наследования из любого определенного пользователем класса или структуры, так как структура может наследовать только от System.ValueType. Тем не менее структура может реализовывать один или несколько интерфейсов. Можно выполнить приведение типа структуры к любому типу интерфейса, который он реализует. Это приведет к операции упаковки-преобразования, которая создаст программу-оболочку для структуры внутри объекта ссылочного типа в управляемой куче. Операции упаковки-преобразования выполняются при передаче типа значения в метод, принимающий System.Object или любой тип интерфейса в качестве входного параметра. Дополнительные сведения см. в разделе Упаковка-преобразование и распаковка-преобразование.
Используйте ключевое слово struct, чтобы создать собственные пользовательские типы значений. Как правило, структура используется как контейнер для небольшого набора связанных переменных, как показано в следующем примере:
См. сведения в описании типов структур. См. дополнительные сведения о типах значений.
Константа System.IO.FileMode.Create имеет значение 2. Так как имена намного лучше воспринимаются человеком при изучении исходного кода, мы рекомендуем всегда использовать перечисления вместо литеральных числовых констант. Для получения дополнительной информации см. System.IO.FileMode.
Все перечисления наследуют от System.Enum, который наследует от System.ValueType. К перечислениям применимы все те же правила, что к структурам. Дополнительные сведения о перечислениях см. в разделе Типы перечислений.
Связанные разделы
Дополнительные сведения см. в следующих статьях:
Типы классов
Давайте рассмотрим основные характеристики классов CTS:
Характеристика классов | Описание |
---|---|
Запечатанные | Запечатанные (sealed), или герметизированные, классы не могут выступать в роли базовых для других классов, т.е. не допускают наследования |
Реализующие интерфейсы | называется коллекция абстрактных членов, которые обеспечивают возможность взаимодействия между объектом и пользователем этого объекта. CTS позволяет реализовать в классе любое количество интерфейсов |
Абстрактные или конкретные | Экземпляры абстрактных (abstract) классов не могут создаваться напрямую, и предназначены для определения общих аспектов поведения для производных типов. Экземпляры же конкретных (concrete) классов могут создаваться напрямую |
Степень видимости | Каждый класс должен конфигурироваться с атрибутом видимости (visibility). По сути, этот атрибут указывает, должен ли класс быть доступным для использования внешним сборкам или только изнутри определяющей сборки |
Типы интерфейсов
Сами по себе интерфейсы мало чем полезны. Однако когда они реализуются в классах или структурах уникальным образом, они позволяют получать доступ к дополнительным функциональным возможностям за счет добавления просто ссылки на них в полиморфной форме.
Типы структур
представляют собой удобную программную конструкцию, которая позволяет группировать данные в пары "имя-значение". Например, предположим, что требуется создать приложение видеоигры, в котором игроку бы позволялось выбирать персонажа одной из трех следующих категорий: Wizard (маг), Fighter (воин) или Thief (вор). Вместо того чтобы использовать и отслеживать числовые значения для каждого варианта, в этом случае гораздо удобнее создать соответствующее перечисление с помощью ключевого слова enum:
По умолчанию для хранения каждого элемента выделяется блок памяти, соответствующий 32-битному целому, однако при необходимости (например, при программировании с расчетом на устройства, обладающие малыми объемами памяти, вроде мобильных устройств Windows) это значение можно изменить. Кроме того, в CTS необходимо, чтобы перечисляемые типы наследовались от общего базового класса System.Enum. В этом базовом классе присутствует ряд весьма интересных членов, которые позволяют извлекать, манипулировать и преобразовывать базовые пары "имя-значение" программным образом.
Типы делегатов
Встроенные типы
Встроенные типы данных
И, наконец, последним, что следует знать о спецификации CTS, является то, что в ней содержится четко определенный набор фундаментальных типов данных. Хотя в каждом отдельно взятом языке для объявления того или иного встроенного типа данных из CTS обычно предусмотрено свое уникальное ключевое слово, все эти ключевые слова в конечном итоге соответствуют одному и тому же типу в сборке mscorlib.dll.
Из-за того факта, что уникальные ключевые слова в любом управляемом языке являются просто сокращенными обозначениями реального типа из пространства имен System, больше не нужно беспокоиться ни об условиях переполнения и потери значимости (overflow/underflow) в случае числовых данных, ни о внутреннем представлении строк и булевских значений в различных языках.
Типы-значения представляют собой примитивные типы данных (целые числа и числа с плавающей запятой ). Существуют еще пользовательские типы-значения, но мы обсудим их позже.
Ссылочные типы описывают так называемые объектные ссылки ( object references ), которые представляют собой адреса объектов.
Значения любого типа хранятся в ячейках (location). В качестве ячеек могут выступать локальные и глобальные переменные, параметры методов, поля объектов и элементы массивов. Для каждой ячейки известен тип значений, которые она может содержать.
Особо важным является то обстоятельство, что ячейки не могут содержать объекты. Все объекты размещаются в специальной области памяти, называемой кучей ( heap ). Таким образом, в ячейках могут храниться только значения типов-значений или объектные ссылки.
Встроенные типы-значения
Для встроенных типов-значений определены правила преобразования значений одного типа в другой тип. Такие преобразования бывают сужающие ( narrowing ) и расширяющие (widening). При сужающих преобразованиях значение с большей разрядностью переводится в значение с меньшей разрядностью, что может приводить к потере значащих битов. Расширяющие преобразования никогда не приводят к такой потере.
Самоописывающие ссылочные типы
В некоторых объектно-ориентированных языках программирования (например, в C++) объекты могут храниться как в куче (в динамической памяти), так и в переменных: глобальных (в статической памяти) и локальных (на стеке). Поэтому системы типов в таких языках содержат отдельные типы для самого объекта и для объектной ссылки (указателя на объект).
Каждый объект в куче содержит информацию о своем типе. Поэтому ссылочные типы, представляющие объекты, называются самоописывающими (self- describing ).
Два самоописывающих типа являются встроенными - это System.Object (или просто object в текстовом представлении CIL) и System.String (или string ). Тип System.Object является общим базовым классом, от которого непосредственно или транзитивно наследует любой другой класс. Тип System.String используется для представления строковых данных в формате Unicode.
Поля являются ячейками, в которых хранятся значения других типов.
Методы представляют собой функции классов. Они бывают статическими ( static method ) и объектными ( instance method ). Вызываемый объектный метод всегда получает ссылку на объект, для которого он вызывается. Объектные методы делятся на виртуальные и невиртуальные.
Свойство представляет собой пару методов, один из которых возвращает некоторое значение, а другой устанавливает это значение.
События используются для асинхронного внесения изменений в объект.
Типы-массивы также относятся к самоописывающим типам, то есть каждый массив представляет собой объект в куче, доступ к которому осуществляется через объектную ссылку. Хотя, строго говоря, типы-массивы не являются классами, считается, что все они наследуют от библиотечного класса System.Array . Типы-массивы интересны тем, что, в отличие от классов, определяемых программистом самостоятельно, они формируются системой автоматически. То есть если мы имеем некоторый тип X, то тип массива, состоящего из элементов типа X, нам уже объявлять не нужно - об этом позаботится система выполнения.
Массивы бывают как одномерными (в этом случае они обрабатываются особенно эффективно благодаря использованию специальных инструкций CIL), так и многомерными. Кроме того, система поддерживает массивы, нижняя граница которых отлична от нуля.
Особого внимания заслуживают особенности представления массивов объектов и массивов типов-значений. Дело в том, что так как объекты не могут храниться в ячейках, мы вынуждены вместо массивов объектов использовать массивы объектных ссылок (см. рис. 1.6), в то время как значения типов-значений хранятся прямо в элементах массива.
Типы-интерфейсы
- Абстрактные методы .
- Статические методы.
- Статические поля .
- Абстрактные свойства .
- Абстрактные события.
Хотя любой класс может наследоваться только от одного базового класса, он может реализовывать произвольное количество интерфейсов. То есть, интерфейс определяет контракт, которому должен удовлетворять любой класс, реализующий этот интерфейс.
Нужно отметить, что интерфейс может содержать реализации статических методов, но все остальные методы, включая методы свойств и событий, должны оставаться абстрактными.
Совместимость ячеек по присваиванию
Значение может иметь сразу несколько типов. Например, если некоторый объект реализует несколько интерфейсов, то каждый из них является его типом. Та же ситуация наблюдается при наследовании: если класс имеет несколько суперклассов , то объект такого класса может выступать в качестве любого из этих суперклассов .
Как уже говорилось ранее, ячейки, в которых могут храниться значения, также имеют тип. Соответственно, ячейка может содержать только значение, совместимое с ее типом. То есть, общая система типов не допускает присваивание ячейке несовместимого с ее типом значения.
Формулируется следующее условие совместимости значения и ячейки: для того чтобы некоторое значение можно было присвоить заданной ячейке, необходимо и достаточно, чтобы хотя бы один из типов значения совпадал с типом ячейки.
Идентичность и равенство значений
Для объектных ссылок и значений типов-значений вводятся отношения идентичности (identity) и равенства (equality). Эти отношения являются отношениями эквивалентности , то есть они рефлексивны , симметричны и транзитивны.
Отношение идентичности для объектных ссылок вводится следующим образом: две объектных ссылки идентичны тогда и только тогда, когда они содержат адреса одного и того же объекта. На рис. 1.7 изображены три объектных ссылки A, B и C, а также два равных объекта-строки. Так как ссылки A и B содержат адрес одного и того же объекта, то они идентичны между собой, но при этом они не идентичны ссылке C, содержащей адрес другого объекта.
Отношение равенства для объектных ссылок формулируется так: две объектных ссылки равны тогда и только тогда, когда они содержат адреса равных объектов. Все три объектные ссылки, изображенные на рис. 1.7, равны между собой.
Отношение идентичности для типов-значений определяется следующим образом: два значения идентичны тогда и только тогда, когда они принадлежат одному и тому же типу-значению, и представляющие их последовательности битов равны.
Отношение равенства для примитивных типов -значений совпадает с отношением идентичности.
Типы-значения представляют собой примитивные типы данных (целые числа и числа с плавающей запятой ). Существуют еще пользовательские типы-значения, но мы обсудим их позже.
Ссылочные типы описывают так называемые объектные ссылки ( object references ), которые представляют собой адреса объектов.
Значения любого типа хранятся в ячейках (location). В качестве ячеек могут выступать локальные и глобальные переменные, параметры методов, поля объектов и элементы массивов. Для каждой ячейки известен тип значений, которые она может содержать.
Особо важным является то обстоятельство, что ячейки не могут содержать объекты. Все объекты размещаются в специальной области памяти, называемой кучей ( heap ). Таким образом, в ячейках могут храниться только значения типов-значений или объектные ссылки.
Встроенные типы-значения
Для встроенных типов-значений определены правила преобразования значений одного типа в другой тип. Такие преобразования бывают сужающие ( narrowing ) и расширяющие (widening). При сужающих преобразованиях значение с большей разрядностью переводится в значение с меньшей разрядностью, что может приводить к потере значащих битов. Расширяющие преобразования никогда не приводят к такой потере.
Самоописывающие ссылочные типы
В некоторых объектно-ориентированных языках программирования (например, в C++) объекты могут храниться как в куче (в динамической памяти), так и в переменных: глобальных (в статической памяти) и локальных (на стеке). Поэтому системы типов в таких языках содержат отдельные типы для самого объекта и для объектной ссылки (указателя на объект).
Каждый объект в куче содержит информацию о своем типе. Поэтому ссылочные типы, представляющие объекты, называются самоописывающими (self- describing ).
Два самоописывающих типа являются встроенными - это System.Object (или просто object в текстовом представлении CIL) и System.String (или string ). Тип System.Object является общим базовым классом, от которого непосредственно или транзитивно наследует любой другой класс. Тип System.String используется для представления строковых данных в формате Unicode.
Поля являются ячейками, в которых хранятся значения других типов.
Методы представляют собой функции классов. Они бывают статическими ( static method ) и объектными ( instance method ). Вызываемый объектный метод всегда получает ссылку на объект, для которого он вызывается. Объектные методы делятся на виртуальные и невиртуальные.
Свойство представляет собой пару методов, один из которых возвращает некоторое значение, а другой устанавливает это значение.
События используются для асинхронного внесения изменений в объект.
Типы-массивы также относятся к самоописывающим типам, то есть каждый массив представляет собой объект в куче, доступ к которому осуществляется через объектную ссылку. Хотя, строго говоря, типы-массивы не являются классами, считается, что все они наследуют от библиотечного класса System.Array . Типы-массивы интересны тем, что, в отличие от классов, определяемых программистом самостоятельно, они формируются системой автоматически. То есть если мы имеем некоторый тип X, то тип массива, состоящего из элементов типа X, нам уже объявлять не нужно - об этом позаботится система выполнения.
Массивы бывают как одномерными (в этом случае они обрабатываются особенно эффективно благодаря использованию специальных инструкций CIL), так и многомерными. Кроме того, система поддерживает массивы, нижняя граница которых отлична от нуля.
Особого внимания заслуживают особенности представления массивов объектов и массивов типов-значений. Дело в том, что так как объекты не могут храниться в ячейках, мы вынуждены вместо массивов объектов использовать массивы объектных ссылок (см. рис. 1.6), в то время как значения типов-значений хранятся прямо в элементах массива.
Типы-интерфейсы
- Абстрактные методы .
- Статические методы.
- Статические поля .
- Абстрактные свойства .
- Абстрактные события.
Хотя любой класс может наследоваться только от одного базового класса, он может реализовывать произвольное количество интерфейсов. То есть, интерфейс определяет контракт, которому должен удовлетворять любой класс, реализующий этот интерфейс.
Нужно отметить, что интерфейс может содержать реализации статических методов, но все остальные методы, включая методы свойств и событий, должны оставаться абстрактными.
Совместимость ячеек по присваиванию
Значение может иметь сразу несколько типов. Например, если некоторый объект реализует несколько интерфейсов, то каждый из них является его типом. Та же ситуация наблюдается при наследовании: если класс имеет несколько суперклассов , то объект такого класса может выступать в качестве любого из этих суперклассов .
Как уже говорилось ранее, ячейки, в которых могут храниться значения, также имеют тип. Соответственно, ячейка может содержать только значение, совместимое с ее типом. То есть, общая система типов не допускает присваивание ячейке несовместимого с ее типом значения.
Формулируется следующее условие совместимости значения и ячейки: для того чтобы некоторое значение можно было присвоить заданной ячейке, необходимо и достаточно, чтобы хотя бы один из типов значения совпадал с типом ячейки.
Идентичность и равенство значений
Для объектных ссылок и значений типов-значений вводятся отношения идентичности (identity) и равенства (equality). Эти отношения являются отношениями эквивалентности , то есть они рефлексивны , симметричны и транзитивны.
Отношение идентичности для объектных ссылок вводится следующим образом: две объектных ссылки идентичны тогда и только тогда, когда они содержат адреса одного и того же объекта. На рис. 1.7 изображены три объектных ссылки A, B и C, а также два равных объекта-строки. Так как ссылки A и B содержат адрес одного и того же объекта, то они идентичны между собой, но при этом они не идентичны ссылке C, содержащей адрес другого объекта.
Отношение равенства для объектных ссылок формулируется так: две объектных ссылки равны тогда и только тогда, когда они содержат адреса равных объектов. Все три объектные ссылки, изображенные на рис. 1.7, равны между собой.
Отношение идентичности для типов-значений определяется следующим образом: два значения идентичны тогда и только тогда, когда они принадлежат одному и тому же типу-значению, и представляющие их последовательности битов равны.
Отношение равенства для примитивных типов -значений совпадает с отношением идентичности.
Оператор default
Оператор default создает значение по умолчанию для указанного типа, используется оно следующим образом: default(T) , где T – это тип, для которого нужно создать соответствующее значение.
Объявим переменную типа int и присвоим ей значение по умолчанию с помощью new :
Тоже самое можно сделать с помощью оператора default :
Данный оператор полезен при разработке методов с обобщенным типом. Создадим метод, который выводит на консоль значение по умолчанию для типа переданного в нее аргумента:
Вызовем эту функцию:
Исходный код примеров из этой статьи можете скачать из нашего github-репозитория.
“Задание значения переменной можно произвести в момент инициализации:
int radius = 10;
string name = “John”;
“Задание значения переменной можно произвести в момент инициализации” – Присвоение значения ранее объявленной переменной и есть инициализация.
“либо после инициализаций: – *Либо после объявления, инициализация происходит во второй строке
В типах может храниться следующая информация:
- место, необходимое для хранения переменной этого типа;
- максимальное и минимальное значения, которые могут быть представлены;
- содержащиеся в типе члены (методы, поля, события и т. д.);
- базовый тип, от которого наследует этот тип;
- реализуемые им интерфейсы;
- разрешенные виды операций.
Компилятор использует сведения о типах, чтобы проверить, все ли операции, выполняемые в коде, являются типобезопасными. Например, при объявлении переменной типа int компилятор позволяет в дополнение использовать переменную и операции вычитания. При попытке выполнить эти же операции для переменной типа bool компилятор выдаст ошибку, как показано в следующем примере:
Компилятор внедряет сведения о типе в исполняемый файл в виде метаданных. Среда CLR использует эти метаданные во время выполнения для дальнейшего обеспечения безопасности типа при выделении и освобождении памяти.
Языки программирования и типы данных
В зависимости от принятой системы типов и способов с ней работать различают языки программирования:
- со статической и динамической типизацией;
- с сильной и слабой типизацией;
- с явной и неявной типизацией.
В языках со статической типизацией тип переменной выводится на этапе компиляции и в случае, если эта операция не может быть выполнена, то процесс компиляции не будет завершен. Динамическая типизация предполагает определение типа переменной во время выполнения программы, такой подход чаще всего встречается среди интерпретируемых языков.
Примеры языков с динамической типизацией: Python , PHP . Пример кода на Python :
В языках с сильной типизацией операции над значениями и присваивания можно производить только над переменными одного типа. Иногда это приведение выполняется автоматически, например:
В этом случае, при выполнении второй строки в первую очередь будет выполнено приведение переменной v1 к типу double , а потом сложение. Но следующий код вызовет ошибку:
Так как “4” – это значение строкового типа, а 0.123 имеет тип double .
В языках со слабой типизацией таких ограничений нет, например, на C вы можете написать следующее:
В результате код скомпилируется, если его запустить, то в переменной double будет лежать численное значение.
Языки со слабой типизацией – это C , C++ .
Явная типизация предполагает явное указание типа переменной:
В этом примере, мы объявляем переменную value типа int и явно это указываем.
В языке с неявной типизацией этого делать не нужно, пример на Python :
Ссылочные типы
Переменные ссылочного типа располагаются в куче, за их уничтожение отвечает сборщик мусора, поэтому про них нельзя точно сказать, когда занимаемая ими память будет освобождена. Переменная представляется в виде ссылки на соответствующее место в куче. Ссылочные типы являются наследниками от System.Object .
Типы классов
Типы интерфейсов
Создадим интерфейс для описания человека, у которого есть два свойства имя: Name , и возраст: Age :
Изменим объявление класса Persone, так, чтобы он представлял реализацию интерфейса IPersone:
Объявим переменную типа IPersone:
Более подробно про интерфейсы будет рассказано в одном из следующих уроков.
Типы массивов
Создание и инициализация одномерного массива:
Пример прямоугольного массива, в нем строки имеют одинаковую длину:
Пример зубчатого ( jagged ) массива, в нем строки могут иметь разную длину:
Более подробно про массивы будет рассказано в одном из следующих уроков.
Типы делегатов
Делегаты являются аналогом указателей на функции из языков C / C++ . Они используются в случаях, когда нужно передать некоторую функциональность как аргумент, перенаправлять вызовы и т.д.
Система общих типов CTS
Ниже показаны взаимоотношения между типами значения и ссылочными типами в CTS.
Как видно, все наиболее часто используемые типы организованы в пространство имен System. Но само по себе пространство имен, в котором размещен тип, никак не зависит от того, является ли он типом значения или ссылочным типом.
Объявление класса, структуры или записи представляет собой своего рода чертеж, на основе которого создаются экземпляры или объекты во время выполнения. Если вы определите класс, структуру или запись с именем Person , то Person здесь обозначает имя типа. Если вы объявите и инициализируете переменную p типа Person , принято говорить, что p является объектом (или экземпляром) Person . Можно создать несколько экземпляров одного типа Person , и каждый экземпляр будет иметь разные значения свойств и полей.
Класс является ссылочным типом. Когда вы создаете объект типа и назначаете его переменной, эта переменная содержит только ссылку на память объекта. Если ссылка на объект сохраняется в новую переменную, эта переменная также ссылается на исходный объект. Изменения, внесенные через одну переменную, отражаются и в другой переменной, поскольку обе они ссылаются на одни и те же данные.
Структура (struct) является типом значения. При создании структуры переменная, которой присвоена структура, содержит фактические данные этой структуры. Если структура присваивается новой переменной, все данные копируются. Таким образом, новая переменная и исходная переменная содержат две отдельные копии одинаковых данных. Изменения, внесенные в одну копию, не влияют на другую.
Типы записей могут быть либо ссылочными типами ( record class ), либо типами значений ( record struct ).
Как правило, классы используются для моделирования более сложного поведения. Классы обычно хранят данные, которые должны быть изменены после создания объекта класса. Структуры лучше всего подходят для небольших структур данных. Структуры обычно хранят данные, которые не должны изменяться после создания структуры. Типы записей — это структуры данных с дополнительными членами, синтезированными компилятором. Записи обычно хранят данные, которые не должны изменяться после создания объекта.
Объявление и инициализация переменных
Задание значения переменной можно произвести в момент инициализации:
либо после инициализаций:
Необходимо иметь ввиду, что переменную нельзя использовать до тех пор пока она не будет проинициализирована, Например, выполнение следующей программы завершится с ошибкой:
В примерах мы не будем приводить код импорта и объявления класса. В конце главы будет приведен листинг программы со всеми примерами из данного урока.
Неявные типы, анонимные типы и типы, допускающие значение NULL
Вы можете неявно типизировать локальную переменную (но не элементы класса) с помощью ключевого слова var . Такая переменная получает конкретный тип во время компиляции, но этот тип предоставляется компилятором. Дополнительные сведения см. в статье Implicitly Typed Local Variables (Неявно типизированные локальные переменные).
Иногда нет смысла создавать именованный тип для простых наборов связанных значений, которые не будут сохраняться или передаваться за пределами метода. В таких случаях можно применить анонимные типы. Дополнительные сведения см. в статье Анонимные типы.
Обычные типы значений не могут иметь значение null . Но вы можете создать специальные типы, допускающие значения NULL, добавив символ ? после имени типа. Например, тип int? является типом int , который может иметь значение null . Типы, допускающие значение NULL, представляют собой экземпляры универсального типа структуры System.Nullable . Типы, допускающие значение NULL, особенно полезны при передаче данных в базы данных, где могут использоваться числовые значения null , и из таких баз данных. Дополнительные сведения см. в разделе Типы, допускающие значение NULL.
Пользовательские типы
Ссылочные типы
Тип, который определен как class , record , delegate , массив или interface , является reference type .
Объявляемая переменная с типом reference type будет содержать значение null до тех пор, пока вы не назначите ей экземпляр такого типа или не создадите его с помощью оператора new . Создание и назначение класса демонстрируется в следующем примере:
Вы не можете создать экземпляр interface напрямую с помощью оператора new . Вместо этого создайте и назначьте экземпляр класса, который реализует интерфейс. Рассмотрим следующий пример.
При создании объекта выделяется память в управляемой куче, и переменная хранит только ссылку на расположение объекта. Хранение типов в управляемой куче требует дополнительных действий как при выделении памяти, так и при удалении, которое выполняется функцией автоматического управления памятью в среде CLR, известной как сборка мусора. Сборка мусора является хорошо оптимизированным процессом и в большинстве случаев не ухудшает производительность. Дополнительные сведения о сборке мусора см. в статье Автоматическое управление памятью.
Члены типов
Теперь, когда было приведено краткое описание каждого из сформулированных в CTS типов, пришла пора рассказать о том, что большинство из этих типов способно принимать любое количество . Формально в роли члена типа может выступать любой элемент из множества .
В спецификации CTS описываются различные "характеристики", которые могут быть ассоциированы с любым членом. Например, каждый член может обладать характеристикой, отражающей его доступность (т.е. общедоступный, приватный или защищенный). Некоторые члены могут объявляться как абстрактные (для навязывания полиморфного поведения производным типам) или как виртуальные (для определения фиксированной, но допускающей переопределение реализации). Кроме того, почти все члены также могут делаться статическими членами (привязываться на уровне класса) или членами экземпляра (привязываться на уровне объекта).
Nullable-типы (нулевые типы) и операция ??
Объявление и инициализация Nullable-переменных
В работе с типами-значениями есть одна особенность, они не могут иметь значение null . При наличии любой из следующих строк кода, компиляция программы не будет выполнена:
На практике, особенно при работе с базами данных, может возникнуть ситуация, когда в записи из таблицы пропущены несколько столбцов (нет данных), в этом случае, соответствующей переменной нужно будет присвоить значение null , но она может иметь тип int или double , что приведет к ошибке.
Можно объявить переменную с использованием символа ? после указания типа, тогда она станет nullable -переменной – переменной поддерживающей null-значение:
Использование символа ? является синтаксическим сахаром для конструкции Nullable , где T – это имя типа. Представленные выше примеры можно переписать так:
Проверка на null. Работа с HasValue и Value
Для того чтобы проверить, что переменная имеет значение null можно воспользоваться оператором is с шаблоном типа:
Также можно воспользоваться свойствами класса Nullable :
- Nullable.HasValue
- Возвращает true если переменная имеет значение базового типа. То есть если она не null .
- Возвращает значение переменной если HasValue равно true , иначе выбрасывает исключение InvalidOperationException .
Приведение Nullable-переменной к базовому типу
При работе с Nullable -переменными их нельзя напрямую присваивать переменным базового типа. Следующий код не будет скомпилирован:
Для приведения Nullable -переменной к базовому типу можно воспользоваться явным приведением:
В этом случае следует помнить, что если значение Nullable -переменной равно null , то при выполнении данной операции будет выброшено исключение InvalidOperationException .
Второй вариант – это использование оператора . при этом нужно дополнительно задаться значением, которое будет присвоено переменной базового типа если в исходной лежит значение null :
Второй вариант позволяет более лаконично обрабатывать ситуацию, когда вызов какого-то метода может возвращать null , а результат его работы нужно присвоить типу-значению, при этом заранее известно, какое значение нужно присвоить переменной в этой ситуации:
Ключевое слово var. Неявная типизация
При объявлении переменной вместо явного задания типа можно поставить ключевое слово var . В этом случае будет использована система вывода типов для определения типа переменной по ее значению.
При работе с var необходимо помнить следующее:
- использовать var можно только для объявления локальных переменных;
- var нельзя использоваться для объявления типа возвращаемого значения, типов полей и параметров;
- при объявлении переменной с использованием var она обязательно должна быть проинициализирована, при этом использовать для этого null запрещено;
- объявлять переменную допускающую null -значение с использованием лексемы ? через var нельзя.
Ключевое слово dynamic
Ниже приведены несколько примеров, на которых можно разобраться с тем, как работать с dynamic :
Как вы можете видеть значение и тип переменной dval1 менялись в процессе выполнения программы. При этом нужно помнить, что если вы присвоили переменной dynamic , какое-то значение, которое определило ее тип, а пытаетесь с ней работать как с переменной другого типа, то будет вызвано исключение:
Ключевое слово new
Ключевое слово new , как правило, используется при инициализации переменных, которые имеют ссылочный тип данных. О том, что это такое мы расскажем чуть ниже. Пусть у нас есть класс Rectangle :
Данный класс нам нужен только для демонстрации, при разработке собственных классов не стоит создать поля с ключевым словом public . О создании классов и основах объектно-ориентированного программирования будет рассказано в одном из ближайших уроков.
Создадим переменную класса Rectangle :
Переменные типа int , double и т.п. также можно проинициализировать с помощью ключевого слова new , в этом случае будет присвоено значение по умолчанию:
Задание типов в объявлениях переменных
Когда вы объявляете в программе переменную или константу, для нее нужно задать тип либо использовать ключевое слово var , чтобы компилятор определил тип самостоятельно. В следующем примере показаны некоторые объявления переменных, использующие встроенные числовые типы и сложные пользовательские типы:
Типы параметров и возвращаемых значений метода задаются в объявлении метода. Далее представлена сигнатура метода, который требует значение int в качестве входного аргумента и возвращает строку:
После объявления переменной вы не можете повторно объявить ее с новым типом и назначить ей значение, несовместимое с объявленным типом. Например, нельзя объявить переменную типа int и затем присвоить ей логическое значение true . Но значения можно преобразовать в другие типы, например при сохранении в других переменных или передаче в качестве аргументов метода. Если преобразование типов не приводит к потере данных, оно выполняется компилятором автоматически. Для преобразования, которое может привести к потере данных, необходимо выполнить приведение в исходном коде.
Дополнительные сведения см. в разделе Приведение и преобразование типов.
Тип времени компиляции и тип времени выполнения
Переменная может иметь разные типы времени компиляции и времени выполнения. Тип времени компиляции является объявленным или выведенным типом переменной в исходном коде. Тип времени выполнения является типом экземпляра, на который ссылается такая переменная. Часто эти два типа совпадают:
В других случаях тип времени компиляции отличается, как показано в следующих двух примерах:
В обоих предыдущих примерах используется тип времени выполнения string . Тип времени компиляции в первой строке — object , а во второй — IEnumerable .
Если два типа переменной отличаются, важно понимать, когда применяются типы времени компиляции и времени выполнения. Тип времени компиляции определяет все действия, выполняемые компилятором. Такие действия компилятора включают разрешение вызовов методов, разрешение перегрузки, а также доступные неявные и явные приведения. Тип времени выполнения определяет все действия, разрешаемые при выполнении. Такие действия времени выполнения включают отправку вызовов виртуальных методов, оценку выражений is и switch , а также другие API тестирования типов. Чтобы лучше понять, как ваш код взаимодействует с типами, определите, какие действия применяются к каждому типу.
Типы литеральных значений
Так как литералы являются типизированными и все типы в конечном счете являются производными от System.Object, можно написать и скомпилировать следующий код:
Читайте также: