На заметку! Разрешенные расширяющие и сужающие (обсуждаются далее) преобразования, поддерживаемые для каждого типа данных С#, описаны в разделе "Type Conversion Tables in .NET" ("Таблицы преобразования типов в .NET") документации по .NET Core.
Несмотря на то что неявное расширение типов благоприятствовало в предыдущем примере, в других ситуациях оно может стать источником ошибок на этапе компиляции. Например, пусть для переменных
numb1
и
numb2
установлены значения, которые (при их сложении) превышают максимальное значение типа
short
. Кроме того, предположим, что возвращаемое значение метода
Add
сохраняется в новой локальной
переменной
short
, а не напрямую выводится на консоль.
static void Main(string[] args)
{
Console.WriteLine("***** Fun with type conversions *****");
// Следующий код вызовет ошибку на этапе компиляции!
short numb1 = 30000, numb2 = 30000;
short answer = Add(numb1, numb2);
Console.WriteLine("{0} + {1} = {2}",
numb1, numb2, answer);
Console.ReadLine;
}
В данном случае компилятор сообщит об ошибке:
Cannot implicitly convert type 'int' to 'short'. An explicit conversion exists (are you missing a cast?)
He удается неявно преобразовать тип int в short. Существует явное преобразование (возможно, пропущено приведение)
Проблема в том, что хотя метод
Add
способен возвратить значение
int
, равное 60 000 (которое умещается в допустимый диапазон для
System.Int32
), это значение не может быть сохранено в переменной
short,
потому что выходит за пределы диапазона допустимых значений для типа
short
. Выражаясь формально, среде CoreCLR не удалось применить сужающую операцию. Нетрудно догадаться, что сужающая операция является логической противоположностью расширяющей операции, поскольку предусматривает сохранение большего значения внутри переменной типа данных с меньшим диапазоном допустимых значений.
Важно отметить, что все сужающие преобразования приводят к ошибкам на этапе компиляции, даже когда есть основание полагать, что такое преобразование должно пройти успешно. Например, следующий код также вызовет ошибку при компиляции:
// Снова ошибка на этапе компиляции!
static void NarrowingAttempt
{
byte myByte = 0;
int myInt = 200;
myByte = myInt;
Console.WriteLine("Value of myByte: {0}", myByte);
}
Здесь значение, содержащееся в переменной типа
int(myInt)
, благополучно умещается в диапазон допустимых значений для типа
byte
; следовательно, можно было бы ожидать, что сужающая операция не должна привести к ошибке во время выполнения. Однако из-за того, что язык C# создавался с расчетом на безопасность в отношении типов, все-таки будет получена ошибка на этапе компиляции.
Если нужно проинформировать компилятор о том, что вы готовы мириться с возможной потерей данных из-за сужающей операции, тогда потребуется применить явное приведение, используя операцию приведения
языка С#. Взгляните на показанную далее модификацию класса
Program
:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with type conversions *****");
short numb1 = 30000, numb2 = 30000;
//
Явно привести int к short (и разрешить потерю данных).
short answer = (short)Add(numb1, numb2);
Console.WriteLine("{0} + {1} = {2}",
numb1, numb2, answer);
NarrowingAttempt;
Console.ReadLine;
}
static int Add(int x, int y)
{
return x + y;
}
static void NarrowingAttempt
{
byte myByte = 0;
int myInt = 200;
// Явно привести int к byte (без потери данных).
myByte = (byte)myInt;
Console.WriteLine("Value of myByte: {0}", myByte);
}
}
Теперь компиляция кода проходит успешно, но результат сложения оказывается совершенно неправильным:
***** Fun with type conversions *****
30000 + 30000 = -5536
Value of myByte: 200
Как вы только что удостоверились, явное приведение заставляет компилятор применить сужающее преобразование, даже когда оно может вызвать потерю данных. В случае метода
NarrowingAttempt
это не было проблемой, т.к. значение 200 умещалось в диапазон допустимых значений для типа
byte
. Тем не менее, в ситуации со сложением двух значений типа
short
внутри
Main
конечный результат получился полностью неприемлемым (30000 + 30000 = -5536?).
Для построения приложений, в которых потеря данных не допускается, язык C# предлагает ключевые слова
checked
и
unchecked
, которые позволяют гарантировать, что потеря данных не останется необнаруженной.
Использование ключевого слова checked
Давайте начнем с выяснения роли ключевого слова
checked
. Предположим, что в класс
Program
добавлен новый метод, который пытается просуммировать две переменные типа
byte
, причем каждой из них было присвоено значение, не превышающее допустимый максимум (255). По идее после сложения значений этих двух переменных (с приведением результата
int
к типу
byte
) должна быть получена точная сумма.
static void ProcessBytes
{
byte b1 = 100;
byte b2 = 250;
byte sum = (byte)Add(b1, b2);
// В sum должно содержаться значение 350.
// Однако там оказывается значение 94!
Console.WriteLine("sum = {0}", sum);
}
Удивительно, но при просмотре вывода приложения обнаруживается, что в переменной sum содержится значение 94 (а не 350, как ожидалось). Причина проста. Учитывая, что
System.Byte
может хранить только значение в диапазоне от 0 до 255 включительно, в
sum
будет помещено значение переполнения (350-256 = 94). По умолчанию, если не предпринимаются никакие корректирующие действия, то условия переполнения и потери значимости происходят без выдачи сообщений об ошибках.