На этом шаге мы рассмотрим особенности выполнения такого преобразования.
Нередко в выражения входят значения разных типов. Скажем, может возникнуть необходимость к значению типа float прибавить значение типа int и полученный результат присвоить переменной типа double. Математически здесь все корректно, поскольку речь идет об арифметической операции с числами, результат этой операции представляется в виде действительного числа, а целые числа являются подмножеством чисел действительных. Но в плане программной реализации подобной операции мы имеем дело со значениями трех разных типов. К счастью, в языке C# есть встроенные механизмы, позволяющие сводить к минимуму неудобства, связанные с использованием в выражениях значений разных типов. В ситуациях, подобных описанной выше, выполняется автоматическое преобразование типов. Обычно речь идет о преобразованиях различных числовых типов. Есть несколько правил выполнения таких преобразований. Итак, допустим, имеются два операнда, которые относятся к разным числовым типам. Алгоритм выполнения преобразований такой:
Эти правила применяются последовательно. Например, если в выражении один из операндов относится к типу decimal, то выполняется попытка преобразовать второй операнд к тому же типу, и если попытка успешная, то вычисляется результат. Он также будет относиться к типу decimal. До выполнения прочих правил дело не доходит. Но если в выражении нет операндов типа decimal, тогда в игру вступает второе правило: при наличии операнда типа double другой операнд преобразуется к этому же тину, такого же типа будет результат. Далее, если в выражении нет ни операнда типа decimal, ни операнда типа double, то применяется третье правило (для типа float), и так далее.
В этой схеме стоит обратить внимание на два момента. Во-первых, общий принцип состоит в том, что автоматическое преобразование выполняется к большему (в плане диапазона допустимых значений) типу, так, чтобы потеря значения исключалась - то есть автоматическое преобразование выполняется всегда с расширением типа. Отсюда становится понятно, почему при попытке преобразовать значения типа double и float к значению типа decimal возникает ошибка. Хотя для значений типа decimal выделяется 128 битов (и это больше, чем 64 бита для типа double и 32 бита для типа float), но диапазон допустимых значений для типа decimal меньше, чем диапазоны допустимых значений для типов double и float. Поэтому теоретически преобразуемое значение может быть потеряно, а значит, такое автоматическое преобразование не допускается.
Во-вторых, если в выражении оба операнда целочисленные, то даже если ни один из них не относится к типу int (например, два операнда типа short), то каждый из операндов приводится к типу int, и, соответственно, результат также будет типа int. Это простое обстоятельство имеет серьезные последствия. Допустим, мы командой short а=10 объявили переменную а типа short со значением 10. Если после этого мы попытаемся использовать команду а=а+1, то такая команда будет некорректной и программный код не скомпилируется. Причина в том, что числовой литерал 1, использованный в команде, относится к типу int. Поэтому в выражении а+1 текущее значение переменной а преобразуется к типу int, и результат выражения а+1 также вычисляется как int-значение. Таким образом, получается, что мы пытаемся присвоить значение int-типа переменной типа short. А это запрещено, поскольку может произойти потеря значения.
Решение проблемы может состоять в том, чтобы использовать явное приведение типа. Корректная команда выглядит так: а=(short)(а+1). Здесь идентификатор short в круглых скобках перед выражением (а+1) означает, что после вычисления значения выражения полученный результат (значение тина int) следует преобразовать в значение типа short.
Есть еще одно важное обстоятельство: при инициализации переменной short командой short а=10 переменной типа short присваивается литерал 10, который является значением типа int. Хотя формально здесь тоже вроде бы противоречие, но при присваивании целочисленной переменной целочисленного литерала, если последний не выходит за допустимые пределы для переменной данного типа, приведение типов выполняется автоматически.
Еще один пример: командой byte а=1, b=2, с объявляем три переменные (а, b и с) типа byte. Переменные а и b инициализируются со значениями 1 и 2 соответственно. Но если мы захотим воспользоваться командой с=а+b, то такая команда также будет некорректной. В отличие от предыдущего случая здесь нет литерала типа int. Здесь вычисляется сумма а+b двух переменных типа byte, и результат присваивается (точнее, выполняется попытка присвоить) переменной типа byte. Проблема же в том, что, хотя в выражении а+b оба операнда относятся к типу byte, они автоматически преобразуются к типу int (в соответствии с последним пунктом в списке правил автоматического преобразования типов), и поэтому результатом выражения а+b является значение типа int. Получается, что мы пытаемся присвоить byte-переменной int-значение, а это неправильно. Чтобы исправить ситуацию, необходимо использовать явное приведение типа. Корректная версия команды присваивания выглядит как с=(byte)(a+b).
Арифметическое выражение может содержать не только числовые, но и символьные операнды. В таких случаях char-операнд автоматически преобразуется к int-типу. Например, выражение 'А'+'В' имеет смысл. Результат такого выражения - целое число 131. Объяснение простое: значения тина char (то есть символы) хранятся в виде целых чисел. Это последовательность из 16 битов. Вопрос только в том, как ее интерпретировать. Для целых чисел такая последовательность интерпретируется как двоичное представление числа. Для символов "преобразование" двоичного кода выполняется в два этапа: сначала по двоичному коду определяется число, а затем но этому числу определяется символ в кодовой таблице. Проще говоря, 16 битов для char-значения - это код символа в кодовой таблице. Если символ нужно преобразовать в целое число, то вместо символа используется его код из кодовой таблицы. У буквы 'А' код 65, а у буквы 'В' код 66, поэтому сумма символов 'А'+'В' дает целочисленное значение 131.
Преобразование типа char в тип int в случае необходимости выполняется автоматически. Обратное преобразование (из целого числа в тип char) выполняется в явном виде. Например, результатом команды char s=(char)65 является объявление символьной переменной s со значением 'А'. Принцип преобразования целого числа в символ такой: преобразуемое целочисленное значение определяет код символа в кодовой таблице. Так, поскольку у буквы 'А' код 65, то значением выражения (char)65 будет символ 'А'.
На следующем шаге мы рассмотрим объявление переменных.