Что такое неявное преобразование типов
Явные и неявные преобразования (Visual Basic)
При явном преобразовании используется ключевое слово преобразования типа. Visual Basic предоставляет несколько таких ключевых слов, которые применяют выражение в круглых скобках к требуемому типу данных. Эти ключевые слова действуют как функции, но компилятор создает встроенный код, поэтому выполнение выполняется немного быстрее, чем с помощью вызова функции.
Ключевые слова преобразований
В следующей таблице приведены доступные ключевые слова для преобразования.
При преобразовании в составной тип данных интерфейсы, которые он реализует, и классы, от которых он наследует
Функция CType
Функция CType работает с двумя аргументами. Первое — выражение, которое необходимо преобразовать, а второе — целевой тип данных или класс объекта. Обратите внимание, что первый аргумент должен быть выражением, а не типом.
CType — Это Встроенная функция, то есть скомпилированный код выполняет преобразование, часто без создания вызова функции. Это повышает производительность.
Сравнение CType с другими ключевыми словами преобразования типов см. в разделе Оператор DirectCast и Оператор TryCast.
Простые типы
Составные типы
Можно использовать CType для преобразования значений в составные типы данных, а также в простые типы. Его также можно использовать для приведения класса объекта к типу одного из его интерфейсов, как показано в следующем примере.
Типы массивов
CType также может преобразовывать типы данных массива, как показано в следующем примере.
Дополнительные сведения и пример см. в разделе Преобразование массивов.
Типы, определяющие CType
Можно определить CType для класса или структуры, которые вы определили. Это позволяет преобразовать значения в тип класса или структуры и из него. Дополнительные сведения и пример см. в разделе инструкции. Определение оператора преобразования.
Значения, используемые с ключевым словом преобразования, должны быть допустимыми для целевого типа данных, иначе возникает ошибка. Например, при попытке преобразовать в Long объект Integer значение Long должно находиться в пределах допустимого диапазона для Integer типа данных.
При указании CType преобразования из одного типа класса в другой происходит сбой во время выполнения, если исходный тип не является производным от целевого типа. Такой сбой приводит к InvalidCastException возникновению исключения.
Выполнение явного преобразования также называется приведением выражения к заданному типу данных или классу объекта.
Каждое значение имеет соответствующий тип, который определяет атрибуты, такие как объем памяти, выделяемой значению, диапазон возможных значений и доступные элементы. Многие значения можно выразить несколькими типами. Например, число 4 можно выразить как целое число или как число с плавающей запятой. Преобразование типа создает значение нового типа, эквивалентное значению старого типа, но при этом не обязательно сохраняется идентичность (или точные значения) первоначального объекта.
Преобразование из производного класса в базовый класс. Это означает, например, что экземпляр любого класса или структуры может быть преобразован в экземпляр Object. Для этого преобразования не требуется оператор приведения или преобразования.
Преобразование из типа, реализующего интерфейс, в объект интерфейса, представляющий этот интерфейс. Для этого преобразования не требуется оператор приведения или преобразования.
Класс Convert, предоставляющий набор методов, реализующих методы интерфейса IConvertible. Дополнительные сведения см. в разделе Класс Convert.
Класс TypeConverter, являющийся базовым классом, который может быть расширен для поддержки преобразования указанного типа в любой другой тип. Дополнительные сведения см. в разделе Класс TypeConverter.
Неявное преобразование с помощью оператора implicit
Расширяющие преобразования предполагают создание нового значения из значения существующего типа, имеющего более ограничивающий диапазон или более ограниченный список членов, чем целевой тип. Расширяющие преобразования не могут привести к потере данных (хотя способны дать в результате меньшую точность). Поскольку потеря данных исключена, компиляторы могут обрабатывать преобразование неявно или прозрачно без необходимости использования метода явного преобразования или оператора приведения.
Хотя код, используемый для неявного преобразования, может вызывать метод преобразования или использовать оператор приведения, применение таких методов компиляторами, поддерживающими неявные преобразования, не требуется.
Например, тип Decimal поддерживает неявные преобразования значений из типов Byte, Char, Int16, Int32, Int64, SByte, UInt16, UInt32 и UInt64. В следующем примере демонстрируются некоторые неявные преобразования при присвоении значений переменной Decimal.
В клиентском коде затем можно объявить переменную ByteWithSign и присвоить ей значения Byte и SByte, не выполняя явные преобразования и не используя операторы приведения, как показано в следующем примере.
Явное преобразование с помощью оператора explicit
Сужающие преобразования предполагают создание нового значения из значения существующего типа, имеющего более широкий диапазон или список членов, чем у целевого типа. Поскольку сужающее преобразование может привести к потере данных, компиляторы, как правило, требуют выполнять преобразование явно посредством вызова метода преобразования или оператора приведения. Это означает, что преобразование должно обрабатываться явно в коде разработчика.
Основная цель требования явного использования метода преобразования или оператора приведения заключается в информировании разработчика о возможной потере данных или исключении OverflowException, чтобы это можно было учесть в коде. При этом некоторые компиляторы могут опускать это требование. Например, в Visual Basic, если параметр Option Strict выключен (по умолчанию), компилятор Visual Basic предпринимает попытку выполнения сужающего преобразования неявно.
Например, типы данных UInt32, Int64 и UInt64 имеют диапазоны, покрывающие диапазон типа данных Int32, как показано в таблице ниже.
Type | Сравнение с диапазоном Int32 |
---|---|
Int64 | Int64.MaxValue больше, чем Int32.MaxValue, а Int64.MinValue меньше (имеет больший отрицательный диапазон), чем Int32.MinValue. |
UInt32 | Значение UInt32.MaxValue больше значения Int32.MaxValue. |
UInt64 | Значение UInt64.MaxValue больше значения Int32.MaxValue. |
Большинство компиляторов допускают явное преобразование как с проверкой, так и без проверки. Если при выполнении преобразования с проверкой преобразуемое значение находится вне диапазона допустимых значений конечного типа, создается исключение OverflowException. Если в такой же ситуации выполнять преобразование без проверки, то исключение может не быть создано, но результат преобразования в данном случае неизвестен, и на выходе может быть получено неверное значение.
В следующем примере на C# используются ключевые слова checked и unchecked для демонстрации разницы поведения при преобразовании выходящего за пределы диапазона значения Byte в Byte. При выполнении преобразования с проверкой создается исключение, а при выполнении преобразования без проверки значение Byte.MaxValue присваивается переменой Byte.
Если присваивание включает оператор приведения или метод преобразования, как показано в следующем примере, в клиентском коде можно объявить переменную ByteWithSign и присвоить ей значения Int32 и UInt32.
Интерфейс IConvertible
Метод, возвращающий объект TypeCode реализующего типа.
Метод для преобразования реализующего типа в каждый из базовых типов среды CLR (Boolean, Byte, DateTime, Decimal, Double и т. д.).
Универсальный метод преобразования экземпляра реализующего типа в другой заданный тип. Неподдерживаемые преобразования должны порождать исключение InvalidCastException.
Все базовые типы среды CLR (т. е. Boolean, Byte, Char, DateTime, Decimal, Double, Int16, Int32, Int64, SByte, Single, String, UInt16, UInt32 и UInt64), а также типы DBNull и Enum реализуют интерфейс IConvertible. Тем не менее это явные реализации интерфейса; метод преобразования можно вызвать только посредством переменной интерфейса IConvertible, как показано в следующем примере. В этом примере показано преобразование значения Int32 в эквивалентное значение Char.
Требование вызова метода преобразования для интерфейса, а не для реализующего типа, делает явные реализации интерфейса относительно требовательными к ресурсам. Вместо этого для выполнения преобразования между базовыми типами среды CLR рекомендуется вызывать соответствующий член класса Convert. Дополнительные сведения см. в следующем разделе, Класс Convert.
Класс Convert
Хотя каждая реализация интерфейса IConvertible базового типа может быть вызвана для выполнения преобразования типа, вызов методов класса System.Convert является рекомендуемым и не зависящим от языка способом преобразования одного базового типа в другой. Кроме этого, метод Convert.ChangeType(Object, Type, IFormatProvider) может использоваться для преобразования указанного настраиваемого типа в другой тип.
Преобразования между базовыми типами
Класс Convert обеспечивает не зависящий от языка способ выполнения преобразований между базовыми типами и доступен во всех языках, предназначенных для среды CLR. Он предоставляет набор методов для расширяющих и сужающих преобразований и создает исключение InvalidCastException для преобразований, которые не поддерживаются (например, для преобразования значения DateTime в целочисленное значение). Сужающие преобразования выполняются в проверяемом контексте, а если преобразование завершается неудачей, порождается исключение OverflowException.
Поскольку класс Convert включает методы преобразования для каждого базового типа, он исключает необходимость вызова явной реализации интерфейса IConvertible каждого из базовых классов.
В некоторых случаях, особенно при преобразовании значений с плавающей запятой, преобразование может предполагать потерю точности, даже если исключение OverflowException не создается. В следующем примере показана потеря точности. В этом случае значение Decimal после преобразования в Double имеет меньшую точность (меньше значимых цифр). Во втором случае при преобразовании значения Double оно округляется с 42,72 до 43, чтобы было выполнено преобразование.
Настраиваемые преобразования, использующие метод ChangeType
В следующем примере показано несколько вызовов реализаций интерфейса IConvertible для преобразования объектов TemperatureCelsius в объекты TemperatureFahrenheit и наоборот.
Класс TypeConverter
.NET также позволяет определить преобразователь типов для пользовательского типа путем расширения класса System.ComponentModel.TypeConverter и сопоставления преобразователя с типом через атрибут System.ComponentModel.TypeConverterAttribute. В следующей таблице выделены различия между этим подходом и реализацией интерфейса IConvertible для настраиваемого типа.
Поддержка во время разработки может быть предоставлена для настраиваемого типа лишь при условии, что для него определен преобразователь типа.
8.1 – Неявное преобразование (принуждение) типов данных
Введение в преобразование типов
Так что же происходит, когда мы делаем что-то подобное?
Процесс преобразования значения из одного типа данных в другой тип данных называется преобразованием типа.
Преобразование типа может быть вызвано одним из двух способов: неявно (по требованию компилятора) или явно (по запросу программиста). В этом уроке мы рассмотрим неявное преобразование типов, а в следующем уроке «8.5 – Явное преобразование (приведение) типов данных и static_cast ».
Неявное преобразование типа
Неявное преобразование типа (также называемое автоматическим преобразованием типа или принуждением, англоязычный термин – «coecion») выполняется компилятором автоматически, когда требуется один тип данных, но предоставляется другой тип. Подавляющее большинство преобразований типов в C++ являются неявными преобразованиями типов. Например, неявное преобразование типа происходит во всех следующих случаях:
Что происходит, когда вызывается преобразование типа
Когда вызывается преобразование типа (неявно или неявно), компилятор определяет, может ли он преобразовать значение из текущего типа в запрашиваемый тип. Если допустимое преобразование может быть найдено, компилятор выдаст новое значение запрашиваемого типа. Обратите внимание, что преобразование типов не меняет значение или тип преобразуемого значения или объекта.
Если компилятор не может найти приемлемое преобразование, то компиляция завершится ошибкой. Преобразование типов может завершиться неудачей по любому количеству причин. Например, компилятор может не знать, как преобразовать значение между исходным и запрашиваемым типами. В других случаях инструкции могут запрещать определенные типы преобразований. Например:
Бывают также случаи, когда компилятор не может определить, какое из нескольких возможных преобразований типов однозначно является лучшим для использования. Примеры этого мы увидим в уроке «8.11 – Разрешение перегрузки функций и неоднозначные совпадения».
Так как же компилятор на самом деле определяет, может ли он преобразовать значение из одного типа в другой?
Стандартные преобразования
Стандарт языка C++ определяет, как различные базовые типы (и в некоторых случаях составные типы) могут быть преобразованы в другие типы. Эти правила преобразования называются стандартными преобразованиями.
Стандартные преобразования можно условно разделить на 4 категории, каждая из которых охватывает различные типы преобразований:
Когда требуется преобразование типа, компилятор увидит, есть ли стандартные преобразования, которые он может использовать для преобразования значения в запрашиваемый тип. В процессе преобразования компилятор может применить ноль, одно или несколько стандартных преобразований.
В качестве отступления.
Как может быть преобразование типа с нулем преобразований? Например, в архитектурах, где int и long имеют одинаковый размер и диапазон, для представления значений обоих типов используется одна и та же последовательность битов. Следовательно, для преобразования значения между этими типами фактического преобразования не требуется – значение можно просто скопировать.
Полный набор правил, описывающих, как работает преобразование типов, длинен и сложен, и по большей части преобразование типов «просто работает». В следующем наборе уроков мы рассмотрим наиболее важные вещи, которые вам нужно знать о преобразовании типов. Если в каком-то необычном случае требуются более мелкие подробности, полные правила подробно описаны в технической справочной документации для неявных преобразований.
Преобразования типов и безопасность типов
В этом документе описаны распространенные проблемы преобразования типов и описывается, как избежать их использования в коде C++.
Когда компилятор обнаруживает ненадежное преобразование, он выдает ошибку или предупреждение. Произошла ошибка при остановке компиляции. Предупреждение позволяет продолжить компиляцию, но указывает на возможную ошибку в коде. Однако даже если программа компилируется без предупреждений, она по-прежнему может содержать код, который вызывает неявные преобразования типов, приводящие к неправильным результатам. Ошибки типов также могут вводиться явными преобразованиями или приведениями в коде.
Неявные преобразования типов
Если выражение содержит операнды различных встроенных типов и явные приведения отсутствуют, компилятор использует встроенные стандартные преобразования для преобразования одного из операндов, чтобы типы совпадали. Компилятор пытается выполнить преобразования в четко определенной последовательности, пока она не завершится успешно. Если выбранное преобразование является повышением, компилятор не выдает предупреждение. Если преобразование является узким, компилятор выдает предупреждение о возможной утрате данных. Происходит ли фактическая потери данных, зависит от фактических значений, но рекомендуется считать это предупреждение как ошибку. Если включен определяемый пользователем тип, компилятор пытается использовать преобразования, указанные в определении класса. Если не удается найти допустимое преобразование, компилятор выдает ошибку и не компилирует программу. Дополнительные сведения о правилах, регулирующих стандартные преобразования, см. в разделе стандартные преобразования. Дополнительные сведения о пользовательских преобразованиях см. в разделе пользовательские преобразования (C++/CLI).
Расширяющие преобразования (продвижение)
В расширяющем преобразовании значение меньшей переменной присваивается более крупной переменной без потери данных. Поскольку расширяющие преобразования всегда являются надежными, компилятор выполняет их автоматически и не выдает предупреждения. Следующие преобразования являются расширяющими преобразованиями.
Сужающие преобразования (приведение)
Компилятор выполняет сужающие преобразования неявным образом, но предупреждает о возможной потере данных. Выведите эти предупреждения очень серьезно. Если вы уверены, что не произойдет потери данных, так как значения в переменной большего размера всегда помещаются в меньшую переменную, добавьте явное приведение, чтобы компилятор больше не выдавал предупреждение. Если вы не уверены, что преобразование является надежным, добавьте в код какую-либо проверку среды выполнения для обработки возможной потери данных, чтобы она не вызывала неправильные результаты.
Преобразование из типа с плавающей запятой в целочисленный тип является узким преобразованием, так как дробная часть значения с плавающей запятой отбрасывается и теряется.
В следующем примере кода показаны некоторые неявные сужающие преобразования и предупреждения, которые возникают компилятором.
Преобразования со знаком — без знака
Компилятор не предупреждает о неявных преобразованиях между целыми типами со знаком и без знака. Поэтому рекомендуется полностью избегать беззнаковых преобразований. Если вы не можете избежать их, добавьте проверку среды выполнения, чтобы определить, является ли преобразуемое значение большим или равным нулю и меньше или равно максимальному значению типа со знаком. Значения в этом диапазоне будут передаваться из входных файлов в неподписанный или из неподписанных в подписывание без переинтерпретации.
Преобразования указателей
Явные преобразования (приведения)
С помощью операции приведения можно указать компилятору преобразовать значение одного типа в другой тип. В некоторых случаях компилятор вызовет ошибку, если эти два типа полностью не связаны, но в других случаях не вызывает ошибку, даже если операция не является строго типизированной. Используйте приведение с осторожностью, так как любое преобразование из одного типа в другой является потенциальным источником ошибок программы. Однако иногда требуется выполнить приведения, а не все приведения являются опасными. Одно эффективное использование приведения заключается в том, что в коде выполняется понижающие преобразования и известно, что преобразование не приводит к созданию неверных результатов в программе. Фактически, это говорит компилятору о том, что вы делаете, а также о том, что вы выполняете предупреждения. Другой способ заключается в приведении из класса указателя на класс, производный от указатель на базовый. Другой способ — приведение к переменной постоянной, чтобы передать ее в функцию, для которой требуется аргумент, не являющийся константой. Большинство этих операций приведения к некоторым рискам требует определенного риска.
В программировании в стиле C для всех типов приведений используется один и тот же оператор приведения в стиле C.
Этот оператор приведения не используется так часто, как другие, и не гарантирует перенос в другие компиляторы.
Дополнительные сведения см. в разделе оператор.
Руководство по программированию на C#. Приведение и преобразование типов
Неявные преобразования. Специальный синтаксис не требуется, так как преобразование всегда завершается успешно и данные не будут потеряны. Примеры включают преобразования из меньших в большие целочисленные типы и преобразования из производных классов в базовые классы.
Пользовательские преобразования. Такие преобразования выполняются специальными методами, которые можно определить для включения явных и неявных преобразований между пользовательскими типами без связи «базовый класс — производный класс». Дополнительные сведения см. в разделе Операторы пользовательского преобразования.
Преобразования с использованием вспомогательных классов. Чтобы выполнить преобразование между несовместимыми типами, например целыми числами и объектами System.DateTime или шестнадцатеричными строками и массивами байтов, можно использовать классы System.BitConverter и System.Convert, а также методы Parse встроенных числовых типов, такие как Int32.Parse. Дополнительные сведения см. в руководствах по преобразованию массива байтов в значение типа int, преобразованию строки в число и преобразованию из шестнадцатеричных строк в числовые типы.
Неявные преобразования
Полный список всех неявных числовых преобразований см. в разделе Таблица неявных числовых преобразований в статье Встроенные числовые преобразования.
Для ссылочных типов неявное преобразование всегда предусмотрено из класса в любой из его прямых или косвенных базовых классов или интерфейсов. Никакой специальный синтаксис не требуется, поскольку производный класс всегда содержит все члены базового класса.
Явные преобразования
Тем не менее если преобразование нельзя выполнить без риска потери данных, компилятор требует выполнения явного преобразования, которое называется приведением. Приведение — это способ явно указать компилятору, что необходимо выполнить преобразование и что вам известно, что может произойти потеря данных или приведение может завершиться сбоем во время выполнения. Чтобы выполнить приведение, укажите тип, в который производится приведение, в круглых скобках перед преобразуемым значением или переменной. В следующей программе выполняется приведение типа double в int. Программа не будет компилироваться без приведения.
Полный список всех поддерживаемых явных числовых преобразований см. в разделе Таблица явных числовых преобразований в статье Встроенные числовые преобразования.
Для ссылочных типов явное приведение является обязательным, если необходимо преобразовать базовый тип в производный тип:
Операция приведения между ссылочными типами не меняет тип времени выполнения базового объекта; изменяется только тип значения, который используется в качестве ссылки на этот объект. Дополнительные сведения см. в разделе Полиморфизм.
Исключения преобразования типов во время выполнения
В некоторых преобразованиях ссылочных типов компилятор не может определить, будет ли приведение допустимым. Есть вероятность, что правильно скомпилированная операция приведения завершится сбоем во время выполнения. Как показано в следующем примере, приведение типа, завершившееся сбоем во время выполнения, вызывает исключение InvalidCastException.