Явное преобразование – когда преобразование типа А в тип В возможно только при определенных обстоятельствах либо правила преобразования настолько сложны, что возникает необходимость выполнения какихлибо дополнительных действий.
3.1.1.Неявные преобразования
Неявное преобразование не требует со стороны программиста никакой дополнительной работы и никакого дополнительного кода. Рассмотрим пример кода:
varl = var2;
Такое присваивание может включать в себя проведение неявного преобразования типов в том случае, если тип переменной var2 допускает проведение неявного преобразования в тип переменной varl; однако с тем же успехом в этом присваивании могут быть задействованы две переменные одного и того же типа, и тогда никакого неявного преобразования типов вообще не требуется.
Рассмотрим пример.
Значения типа ushort и char являются фактически взаимозаменяемыми, поскольку оба они предполагают хранение чисел в диапазоне между 0 и 65 535. Можно осуществлять преобразования этих типов друг в друга неявно, что иллюстрируется следующим кодом:
ushort destinationVar; char sourceVar = 'a'; destinationVar = sourceVar;Console.WriteLine("sourceVar val: {0}", sourceVar);
Console.WriteLine("destinationVar val: {0}", destinationVar};
Таблица3.1–Возможность неявных преобразований для простых типов | |
Тип | Допускает безопасное преобразование в |
byte | short, ushort, int, uint, long, ulong, float, double, decimal |
sbyte | short, int, long, float, double, decimal |
short | int, long, float, double, decimal |
ushort | int, uint, long, ulong, float, double, decimal |
int | long, float, double, decimal |
uint | long, ulong, float, double, decimal |
long | float, double, decimal |
ulong | float, double, decimal |
float | double |
char | ushort, int, uint, long, ulong, float, double, decimal |
В этом коде значение, хранящееся в переменной sourceVar, переносится в
переменную
destinationVar. Затем две команды Console.WriteLine() дают следующий выходной поток:
sourceVar val: a
destinationVar val: 97Хотя в обеих переменных хранится одна и та же информация, она интерпретируется совершенно по-разному в зависимости от типа переменной.
Для простых типов существует большое количество неявных преобразований; для типов bool и string неявных преобразований не существует, зато они имеются у численных типов. В таблице слева, приведенной для справки, показаны преобразования численных типов, которые могут выполняться компилятором неявно (значения типа char хранятся в виде чисел, поэтому рассматривается как численный тип).
Существует простой способ определять, какие именно преобразования компилятор может выполнять неявно. Если вернуться к лекции 1 (таб. 1.1 – 1.3), то там можно найти диапазоны допустимых значений для каждого простого численного типа. Правило неявных преобразований для рассматриваемых типов можно сформулировать следующим образом, любой тип А, диапазон допустимых значений которого целиком находится внутри диапазона допустимых значений типа В, может быть преобразован неявным образом в этот тип.
Это правило имеет очень простое обоснование. Если попытаться присвоить переменной некоторое значение, а это значение окажется выходящим за пределы тех значений, которые она может принимать, то возникнут определенные проблемы. Так, например, в переменной типа short могут храниться значения до 32 767, а максимально допустимое значение типа byte ограничено 255, поэтому если попытаться преобразовать значение типа short в значение типа byte, это может создать проблему. Если значение типа short лежит в диапазоне между 256 и 32 767, то оно просто не уместится в одном байте.
Однако в том случае, если точно известно, что значение типа short меньше 255, должна существовать какая-то возможность выполнить такое преобразование.
Такая возможность существует, но для этого необходимо воспользоваться явным преобразованием. При выполнении явного преобразования вы как бы говорите: "Хорошо, я предупрежден об опасности подобных действий, и я принимаю всю ответственность за возможные последствия на себя".
3.1.2.Явные преобразования
Как и предполагает их название, явные преобразования выполняются в тех случаях, когда явным образом обращаются к компилятору с просьбой преобразовать значение одного типа в другой. Именно поэтому для их выполнения требуется наличие дополнительного кода, причем формат этого кода может варьироваться в зависимости от конкретного метода выполнения преобразования. Прежде чем приступить к рассмотрению кода, применяемого для выполнения явных преобразований, давайте узнаем, что произойдет в том случае, если для явного преобразования не будет использоваться никакого кода.
В качестве примера, несколько модифицировав код из предыдущего раздела, попытаемся преобразовать значение типа short в значение типа byte.
byte destinationVar; | |||
short sourceVar = 7; | |||
destinationVar = sourceVar; | |||
Console.WriteLine("sourceVar val: {0}", sourceVar); | |||
Console.WriteLine("destinationVar val: {0}", destinationVar); |
При попытке выполнить компиляцию этого кода будет выведено следующее сообщение об ошибке:
Cannot implicitly convert type 'short' to 'byte' (Невозможно неявно преобразовать тип 'short' в 'byte').
Компилятор С# умеет обнаруживать отсутствие необходимых явных преобразований!
Для того чтобы такой код можно было откомпилировать, нужно добавить код, который выполнял бы это преобразование явно. Наиболее простой способ добиться этого в данном контексте – это привести переменную типа short к переменной типа byte. Приведение типа, как правило, означает принудительный перевод данных из одного типа в другой и имеет следующий синтаксис:
(destinationType) sourceVar
Эта запись приведет к преобразованию значения переменной sourceVar к типу destinationType.
Такое возможно только в определенных ситуациях. Для типов, мало похожих друг на друга или вообще не имеющих ничего общего, преобразование типов с помощью приведения, вероятнее всего, не будет определено.
Теперь можно модифицировать наш пример таким образом, чтобы добиться преобразования из типа short в тип byte:
byte destinationVar; | |||
short sourceVar = 7; | |||
destinationVar = (byte)sourceVar; | |||
Console.WriteLine("sourceVar val: {0}", sourceVar); | |||
Console.WriteLine("destinationVar val: {0}", destinationVar); |
Результатом будет: sourceVar val: 7 destinationVar val: 7
А что произойдет в тех случаях, когда пытаются загнать в переменную значение, которое там не помещается? Давайте модифицируем нашу программу:
byte destinationVar; | ||||
short sourceVar = 281; | ||||
destinationVar = (byte)sourceVar; | ||||
Console.WriteLine("sourceVar val: {0}", sourceVar); | ||||
Console.WriteLinel"destinationVar val: {0}", destinationVar); |
В этом случае мы получим следующий результат:
sourceVar val: 281 destinationVar val: 25
Итак, что же произошло? Посмотрим на двоичное представление этих двух чисел, а также максимально допустимого значения для типа byte, равного 255:
281 = 100011001
25 = 000011001
255 = 011111111
Легко заметить, что потерян самый левый бит исходного значения. Немедленно возникает вопрос: каким образом это можно предсказать? Совершенно очевидно, что будут встречаться ситуации, когда придется явно приводить один тип к другому, и было бы неплохо знать заранее, какие данные при этом будут утрачиваться. Не умея этого делать, можно столкнуться с серьезными ошибками, например, в приложениях для работы с банковскими счетами.
Один из способов предвидеть потерю битов – проверка исходного значения и его сравнение с известными предельными значениями новой переменной. Каким образом это можно выполнить, будет рассмотрено ниже при объяснении логики работы приложений и управления порядком выполнения. Но имеется и другой способ, который заставляет систему во время работы уделять особое внимание данному преобразованию. Попытка присвоить переменной слишком большое значение приводит к переполнению, и это как раз та ситуация, которую требуется контролировать Контекст проверки на переполнение задается с помощью двух ключевых слов: checked (проверяемое) и unchecked (непроверяемое), которые используются следующим образом:
checked(выражение) unchecked(выражение)
Потребуем осуществить проверку на переполнение в предыдущем примере:
byte destinationVar; | ||||
short sourceVar = 281; | ||||
destinationVar = checked((byte)sourceVar); | ||||
Console.WriteLine("sourceVar val: {0}", sourceVar); | ||||
Console.WriteLine("destinationVar val: {0}", destinationVar); |
Процесс выполнения кода будет прерван появлением сообщения об ошибке (OverflowCheck).
Однако в том случае, если заменить ключевое слово checked на unchecked, получится тот же результат и никакой ошибки не возникнет. Такой вариант идентичен поведению по умолчанию, который рассмотрен выше. Наряду с этими ключевыми словами имеется возможность поменять конфигурацию приложения, чтобы оно работало так, как если бы в любом выражении данного типа использовалось ключевое слово checked, кроме тех выражений, в которых явно использовано ключевое слово unchecked (другими словами, можно менять установки, которые используются по умолчанию для выполнения проверок на переполнение). Для этого потребуется изменить свойства приложения в VS. (Properties-> Configuration Properties-> Build -> Check for Arithmetic Overflow/Underflow)/ По умолчанию эта установка имеет значение false, однако при замене ее на true приложение будет работать в режиме checked, подробно описанном выше.