При рассмотрении арифметических операторов нам пришлось немного забежать вперед и узнать, что в C# производится некое преобразование типов данных. О том, что из себя представляет преобразование типов и как оно работает мы и поговорим в этой части.
Итак, на данный момент, мы уже знаем, что C# является типизированным языком программирования и каждая переменная в нашей программе должна иметь имя и тип. При этом, мы не можем изменять тип переменной или присваивать ей значение другого типа. Например,
int i = 10; //Ошибка CS0128 Локальная переменная или функция с именем "i" уже определена в этой области. string i = "строка"; //попытались изменить тип переменной
Если вы работаете в Visual Studio, то сразу увидите ошибку так как в коде выше мы попытались заново определить переменную с именем i
, но другим типом данных. Следующий код также приведет к ошибке:
int i; //Ошибка CS0029 Не удается неявно преобразовать тип "string" в "int" i = "строка";
При этом, красной волнистой линией в Visual Studio будет подчеркнуто новое значение, которое мы пытаемся присвоить переменной. Именно в этом примере (точнее в тексте ошибки) мы и подходим к сегодняшней теме — преобразование типов данных.
В C# преобразования типов данных условно можно разделить на два вида — это неявные преобразования и явные преобразования. В чём их суть — разберемся далее.
Неявные преобразования
Для того, чтобы произошло неявное преобразование специальный синтаксис не требуется. Такое преобразование всегда выполняется успешно, и данные не теряются. Неявные преобразования могут выполняться, например, из меньших в большие целочисленные типы. Рассмотрим следующий код:
byte a = 255; int b = a; a = b;
в первой строке мы инициализировали переменную типа byte
. Как мы уже знаем, этот тип данных может принимать значения от 0 до 255. Далее мы пытаемся присвоить значение переменной a
новой переменной b
у которой определен тип int
. Вот в этой строке и будет происходить неявное преобразование. Так как переменная типа int
может принимать значения от -2 147 483 648 до 2 147 483 647, то значение типа byte
явно входит в этот диапазон, поэтому производится неявное преобразование из меньшего (byte
) в больший (int
) целочисленный тип данных без какой-либо потери данных. Также, такое преобразование называется расширяющим — мы расширяем размер объекта в памяти.
А вот на третьей строке кода мы пытаемся произвести обратную операцию — присвоить переменной a
значение переменной b
:
a = b;
и мы получим ошибку преобразования так как мы пытаемся переменной типа byte
присвоить значение типа int
и здесь уже неявное преобразование не срабатывает так как это может привести к потере данных. Рассмотрим другой пример, который демонстрирует ошибку преобразования данных:
byte a = 10; a = a + 100;
Этот пример не столь очевидный, как предыдущий так как, на первый взгляд может показаться, что всё в порядке — переменная типа byte
сразу после инициализации имеет значение 10
и во второй строке мы пытаемся прибавить к этому значению ещё 100
, то есть получить число 110
, которое точно входит в диапазон byte
(0 — 255). Однако, Visual Studio также выдаст нам ошибку следующего вида:
Всё дело в том, что мы пытаемся выполнить сложение двух чисел и в C# результатом сложения двух целых чисел по умолчанию выступает тип int
. А так как тип int
имеет более широкий диапазон значений, чем byte
(тип данных для переменной), то Visual Studio вполне справедливо выдает нам ошибку. С тем, как мы можем исправить такие ошибки мы разберемся чуть ниже. А пока стоит представить небольшую таблицу, демонстрирующую то из какого типа в какие типы может производиться неявное преобразование числовых данных:
Тип | В какие типы данных может неявно преобразовываться |
sbyte |
short , int , long , float , double , decimal или nint |
byte |
short , ushort , int , uint , long , ulong , float , double , decimal , nint или nuint |
short |
int , long , float , double или decimal либо nint |
ushort |
int , uint , long , ulong , float , double или decimal , nint или nuint |
int |
long , float , double или decimal , nint |
uint |
long , ulong , float , double или decimal либо nuint |
long |
float , double или decimal |
ulong |
float , double или decimal |
float |
double |
nint |
long , float , double или decimal |
nuint |
ulong , float , double или decimal |
Здесь стоит обратить внимание на то, что любой целочисленный тип данных свободно преобразуется в вещественный. Например, int
в double
или byte
в float
и так далее. При этом не поддерживается неявное преобразования:
- в типы
byte
иsbyte
. - из типов
double
иdecimal
. - между типом
decimal
и типамиfloat
илиdouble
.
Явные преобразования
Явные преобразования также называют приведением типов. Для явных преобразований требуется выражение приведения. Приведение требуется, если в ходе преобразования данные могут быть утрачены или преобразование может завершиться сбоем по другим причинам. Типичными примерами являются числовое преобразование в тип с меньшей точностью или меньшим диапазоном допустимых значений. То есть, в случае явного преобразования мы берем на себя все возможные риски, связанные с потерей данных и возможными сбоями в работе приложения.
Опять же, вернемся к нашему последнему примеру с типами int
и byte
.
byte a = 10; a = a + 100;
Здесь у нас нет никакого риска потери данных и мы явно видим, что значение переменной a
не выйдет за допустимый диапазон значений для типа byte
. Чтобы Visual Studio перестала выдавать нам ошибку преобразования, перепишем этот пример следующим образом:
byte a = 10; a = (byte)(a + 100);
здесь мы во второй строке использовали выражение приведения. То есть, чтобы произвести явное приведение мы в круглых скобках указываем тип данных к которому необходимо привести какое-либо значение или переменную. В нашем случае мы производим явное преобразование всего выражения a+100, поэтому мы все выражение также заключили в круглые скобки.
Теперь изменим наш пример следующим образом:
byte a = 10; a = (byte)(a + 300); Console.WriteLine(a);
Какой результат мы увидим в консоли? А увидим мы значение 54. Дело в том, что тип int
— имеет разрядность 32 бита, а тип byte
— всего 8 бит, поэтому при преобразовании компилятор отбросит старшие биты числа, представленного в двоичной системе счисления и, в результате число: 00000100110110
(310 в десятичной системе) превратится в 00110110
(54 в десятичной системе).
Этот пример, несмотря на свою простоту, достаточно наглядно демонстрирует нам, что может произойти, если неверно выбрать тип данных и произвести явное преобразование, не считаясь с допустимыми диапазонами типов данных и их разрядностью.
Итого
При работе с типами данных в C# мы можем преобразовывать данные из одного типа в другой. При этом, платформа .NET может сама произвести необходимое преобразование базового типа, если такое преобразование является расширяющим, например, из byte
в int
. Также, мы можем использовать выражения преобразования для того, чтобы самостоятельно (явно) преобразовать один тип данных в другой. При явном преобразовании мы (разработчики) берем на себя все риски, связанные с потерей данных и возможными ошибками преобразования.