C явное преобразование string в int. Преобразования типов

Преобразования типов

В программировании нередко значения переменных одного типа присваиваются переменным другого типа. Например, в приведенном ниже фрагменте кода целое значение типа int присваивается переменной с плавающей точкой типа float:

Int i; float f; i = 10; f = i; // присвоить целое значение переменной типа float

Если в одной операции присваивания смешиваются совместимые типы данных, то значение в правой части оператора присваивания автоматически преобразуется в тип, указанный в левой его части. Поэтому в приведенном выше фрагменте кода значение переменной i сначала преобразуется в тип float, а затем присваивается переменной f. Но вследствие строгого контроля типов далеко не все типы данных в C# оказываются полностью совместимыми, а следовательно, не все преобразования типов разрешены в неявном виде. Например, типы bool и int несовместимы. Правда, преобразование несовместимых типов все-таки может быть осуществлено путем приведения . Приведение типов, по существу, означает явное их преобразование.

Автоматическое преобразование типов

Когда данные одного типа присваиваются переменной другого типа, неявное преобразование типов происходит автоматически при следующих условиях:

  • оба типа совместимы
  • диапазон представления чисел целевого типа шире, чем у исходного типа

Если оба эти условия удовлетворяются, то происходит расширяющее преобразование . Например, тип int достаточно крупный, чтобы вмещать в себя все действительные значения типа byte, а кроме того, оба типа, int и byte, являются совместимыми целочисленными типами, и поэтому для них вполне возможно неявное преобразование.

Числовые типы, как целочисленные, так и с плавающей точкой, вполне совместимы друг с другом для выполнения расширяющих преобразований. Рассмотрим пример:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string args) { short num1, num2; num1 = 10; num2 = 15; Console.WriteLine("{0} + {1} = {2}",num1,num2,Sum(num1,num2)); Console.ReadLine(); } static int Sum(int x, int y) { return x + y; } } }

Обратите внимание на то, что метод Sum() ожидает поступления двух параметров типа int. Тем не менее, в методе Main() ему на самом деле передаются две переменных типа short. Хотя это может показаться несоответствием типов, программа будет компилироваться и выполняться без ошибок и возвращать в результате, как и ожидалось, значение 25.

Причина, по которой компилятор будет считать данный код синтаксически корректным, связана с тем, что потеря данных здесь невозможна. Поскольку максимальное значение (32767), которое может содержать тип short, вполне вписывается в рамки диапазона типа int (максимальное значение которого составляет 2147483647), компилятор будет неявным образом расширять каждую переменную типа short до типа int. Формально термин "расширение" применяется для обозначения неявного восходящего приведения (upward cast), которое не приводит к потере данных.

Приведение несовместимых типов

Несмотря на всю полезность неявных преобразований типов, они неспособны удовлетворить все потребности в программировании, поскольку допускают лишь расширяющие преобразования совместимых типов. А во всех остальных случаях приходится обращаться к приведению типов. Приведение - это команда компилятору преобразовать результат вычисления выражения в указанный тип. А для этого требуется явное преобразование типов. Ниже приведена общая форма приведения типов:

(целевой_тип) выражение

Здесь целевой_тип обозначает тот тип, в который желательно преобразовать указанное выражение.

Если приведение типов приводит к сужающему преобразованию , то часть информации может быть потеряна. Например, в результате приведения типа long к типу int часть информации потеряется, если значение типа long окажется больше диапазона представления чисел для типа int, поскольку старшие разряды этого числового значения отбрасываются. Когда же значение с плавающей точкой приводится к целочисленному, то в результате усечения теряется дробная часть этого числового значения. Так, если присвоить значение 1,23 целочисленной переменной, то в результате в ней останется лишь целая часть исходного числа (1), а дробная его часть (0,23) будет потеряна. Давайте рассмотрим пример:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string args) { int i1 = 455, i2 = 84500; decimal dec = 7.98845m; // Приводим два числа типа int // к типу short Console.WriteLine((short)i1); Console.WriteLine((short)i2); // Приводим число типа decimal // к типу int Console.WriteLine((int)dec); Console.ReadLine(); } } }

Результатом работы данной программы будет:

Обратите внимание, что переменная i1 корректно преобразовалась в тип short, т.к. ее значение входит в диапазон этого типа данных. Преобразование переменной dec в тип int вернуло целую часть этого числа. Преобразование переменной i2 вернуло значение переполнения 18964 (т.е. 84500 - 2*32768).

Перехват сужающих преобразований данных

В предыдущем примере приведение переменной i2 к типу short не является приемлемым, т.к. возникает потеря данных . Для создания приложений, в которых потеря данных должна быть недопустимой, в C# предлагаются такие ключевые слова, как checked и unchecked , которые позволяют гарантировать, что потеря данных не окажется незамеченной.

По умолчанию, в случае, когда не предпринимается никаких соответствующих исправительных мер, условия переполнения (overflow) и потери значимости (underflow) происходят без выдачи ошибки. Обрабатывать условия переполнения и потери значимости в приложении можно двумя способами. Это можно делать вручную, полагаясь на свои знания и навыки в области программирования.

Недостаток такого подхода в том, что даже в случае приложения максимальных усилий человек все равно остается человеком, и какие-то ошибки могут ускользнуть от его глаз.

К счастью, в C# предусмотрено ключевое слово checked. Если оператор (или блок операторов) заключен в контекст checked, компилятор C# генерирует дополнительные CIL-инструкции, обеспечивающие проверку на предмет условий переполнения, которые могут возникать в результате сложения, умножения, вычитания или деления двух числовых типов данных.

В случае возникновения условия переполнения во время выполнения будет генерироваться исключение System.OverflowException . Давайте рассмотрим пример, в котором будем передавать в консоль значение исключения:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string args) { byte var1 = 250; byte var2 = 150; try { byte sum = checked((byte)(var1+var2)); Console.WriteLine("Сумма: {0}", sum); } catch (OverflowException ex) { Console.WriteLine(ex.Message); Console.ReadLine(); } } } }

Результат работы данной программы:

Настройка проверки на предмет возникновения условий переполнения в масштабах проекта

Если создается приложение, в котором переполнение никогда не должно проходить незаметно, может выясниться, что обрамлять ключевым словом checked приходится раздражающе много строк кода. На такой случай в качестве альтернативного варианта в компиляторе C# поддерживается флаг /checked . При активизации этого флага проверки на предмет возможного переполнения будут автоматически подвергаться все имеющиеся в коде арифметические операции, без применения для каждой из них ключевого слова checked. Обнаружение переполнения точно так же приводит к генерации соответствующего исключения во время выполнения.

Для активизации этого флага в Visual Studio 2010 необходимо открыть страницу свойств проекта, перейти на вкладку Build (Построение), щелкнуть на кнопке Advanced (Дополнительно) и в открывшемся диалоговом окне отметить флажок Check for arithmetic overflow/underflow (Проверять арифметические переполнения и потери точности) :

Важно отметить, что в C# предусмотрено ключевое слово unchecked , которое позволяет отключить выдачу связанного с переполнением исключения в отдельных случаях.

Итак, чтобы подвести итог по использованию в C# ключевых слов checked и unchecked, следует отметить, что по умолчанию арифметическое переполнение в исполняющей среде.NET игнорируется. Если необходимо обработать отдельные операторы, то должно использоваться ключевое слово checked, а если нужно перехватывать все связанные с переполнением ошибки в приложении, то понадобится активизировать флаг /checked. Что касается ключевого слова unchecked, то его можно применять при наличии блока кода, в котором переполнение является допустимым (и, следовательно, не должно приводить к генерации исключения во время выполнения).

Последнее обновление: 30.07.2018

В предыдущей главе мы говорили о преобразованиях объектов простых типов. Сейчас затронем тему преобразования объектов классов. Допустим, у нас есть следующая иерархия классов:

Class Person { public string Name { get; set; } public Person(string name) { Name = name; } public void Display() { Console.WriteLine($"Person {Name}"); } } class Employee: Person { public string Company { get; set; } public Employee(string name, string company) : base(name) { Company = company; } } class Client: Person { public string Bank { get; set; } public Client(string name, string bank) : base(name) { Bank = bank; } }

В этой иерархии классов мы можем проследить следующую цепь наследования: Object (все классы неявно наследуются от типа Object) -> Person -> Employee|Client.

Причем в этой иерархии классов базовые типы находятся вверху, а производные типы - внизу.

Восходящие преобразования. Upcasting

Объекты производного типа (который находится внизу иерархии) в то же время представляют и базовый тип. Например, объект Employee в то же время является и объектом класса Person. Что в принципе естественно, так как каждый сотрудник (Employee) является человеком (Person). И мы можем написать, например, следующим образом:

Static void Main(string args) { Employee employee = new Employee("Tom", "Microsoft"); Person person = employee; // преобразование от Employee к Person Console.WriteLine(person.Name); Console.ReadKey(); }

В данном случае переменной person, которая представляет тип Person, присваивается ссылка на объект Employee. Но чтобы сохранить ссылку на объект одного класса в переменную другого класса, необходимо выполнить преобразование типов - в данном случае от типа Employee к типу Person. И так как Employee наследуется от класса Person, то автоматически выполняется неявное восходящее преобразование - преобразование к типу, которые находятся вверху иерархии классов, то есть к базовому классу.

В итоге переменные employee и person будут указывать на один и тот же объект в памяти, но переменной person будет доступна только та часть, которая представляет функционал типа Person.

Подобным образом поизводятся и другие восходящие преобразования:

Person person2 = new Client("Bob", "ContosoBank"); // преобразование от Client к Person

Здесь переменная person2, которая представляет тип Person, хранит ссылку на объект Client, поэтому также выполняется восходящее неявное преобразование от производного класса Client к базовому типу Person.

Восходящее неявное преобразование будет происходить и в следующем случае:

Object person1 = new Employee("Tom", "Microsoft"); // от Employee к object object person2 = new Client("Bob", "ContosoBank"); // от Client к object object person3 = new Person("Sam"); // от Person к object

Так как тип object - базовый для всех остальных типов, то преобразование к нему будет производиться автоматически.

Нисходящие преобразования. Downcasting

Но кроме восходящих преобразований от производного к базовому типу есть нисходящие преобразования или downcasting - от базового типа к производному. Например, в следующем коде переменная person хранит ссылку на объект Employee:

Employee employee = new Employee("Tom", "Microsoft"); Person person = employee; // преобразование от Employee к Person

И может возникнуть вопрос, можно ли обратиться к функционалу типа Employee через переменную типа Person. Но автоматически такие преобразования не проходят, ведь не каждый человек (объект Person) является сотрудником предприятия (объектом Employee). И для нисходящего преобразования необходимо применить явное преобразования, указав в скобках тип, к которому нужно выполнить преобразование:

Employee employee = new Employee("Tom", "Microsoft"); Person person = employee; // преобразование от Employee к Person //Employee employee2 = person; // так нельзя, нужно явное преобразование Employee employee2 = (Employee)person; // преобразование от Person к Employee

Рассмотрим некоторые примеры преобразований:

// Объект Employee также представляет тип object object obj = new Employee("Bill", "Microsoft"); // чтобы обратиться к возможностям типа Employee, приводим объект к типу Employee Employee emp = (Employee) obj; // объект Client также представляет тип Person Person person = new Client("Sam", "ContosoBank"); // преобразование от типа Person к Client Client client = (Client)person;

В первом случае переменной obj присвоена ссылка на объект Employee, поэтому мы можем преобразовать объект obj к любому типу который располагается в иерархии классов между типом object и Employee.

Если нам надо обратиться к каким-то отдельным свойствам или методам объекта, то нам необязательно присваивать преобразованный объект переменной:

// Объект Employee также представляет тип object object obj = new Employee("Bill", "Microsoft"); // преобразование к типу Person для вызова метода Display ((Person)obj).Display(); // либо так // ((Employee)obj).Display(); // преобразование к типу Employee, чтобы получить свойство Company string comp = ((Employee)obj).Company;

В то же время необходимо соблюдать осторожность при подобных преобразованиях. Например, что будет в следующем случае:

// Объект Employee также представляет тип object object obj = new Employee("Bill", "Microsoft"); // преобразование к типу Client, чтобы получить свойство Bank string bank = ((Client)obj).Bank;

В данном случае мы получим ошибку, так как переменная obj хранит ссылку на объект Employee. Данный объект является также объектом типов object и Person, поэтому мы можем преобразовать его к этим типам. Но к типу Client мы преобразовать не можем.

Другой пример:

Employee emp = new Person("Tom"); // ! Ошибка Person person = new Person("Bob"); Employee emp2 = (Employee) person; // ! Ошибка

В данном случае мы пытаемся преобразовать объект типа Person к типу Employee, а объект Person не является объектом Employee.

Существует ряд способов, чтобы избежать подобных ошибок преобразования.

Способы преобразований

Во-первых, можно использовать ключевое слово as . С помощью него программа пытается преобразовать выражение к определенному типу, при этом не выбрасывает исключение. В случае неудачного преобразования выражение будет содержать значение null:

Person person = new Person("Tom"); Employee emp = person as Employee; if (emp == null) { Console.WriteLine("Преобразование прошло неудачно"); } else { Console.WriteLine(emp.Company); }

Второй способ заключается в отлавливании исключения InvalidCastException , которое возникнет в результате преобразования:

Person person = new Person("Tom"); try { Employee emp = (Employee)person; Console.WriteLine(emp.Company); } catch (InvalidCastException ex) { Console.WriteLine(ex.Message); }

Третий способ заключается в проверке допустимости преобразования с помощью ключевого слова is :

Person person = new Person("Tom"); if(person is Employee) { Employee emp = (Employee)person; Console.WriteLine(emp.Company); } else { Console.WriteLine("Преобразование не допустимо"); }

Выражение person is Employee проверяет, является ли переменная person объектом типа Employee. Но так как в данном случае явно не является, то такая проверка вернет значение false , и преобразование не сработает.

В программировании зачастую возникает нужда провести вычисления над переменными одного типа, а в результате получить другой. Например, при делении целых чисел получить вещественное. Когда значение одного типа заносится в переменную другого типа, выполняется преобразование типов данных. В C# это реализуется несколькими механизмами.

Неявное преобразование типов данных

Неявное преобразование происходит тогда, когда значение заносится в переменную иного типа без указаний этого в коде.

Для того, чтобы компилятор C# автоматически преобразовал тип данных, должны выполняться два условия:

  • Тип значения и тип переменной должны быть совместимы между собой;
  • Диапазон возможных значений типа переменной должен быть не меньше чем у типа значения.

Byte a = 15; byte b = 120; int c = a + b;

В данном примере выполняется неявное преобразование , и переменная c в итоге будет иметь тип int. Это возможно благодаря тому, что byte и int являются целочисленными, а тип int полностью включает диапазон byte.

Еще один пример:

Short a = 815; ushort b = a;//Error

Несмотря на то, что число 815 входит в диапазон типа ushort, в этом случае появится сообщение об ошибке . Связано это с тем, что компилятор для неявного преобразования проверяет только вышеуказанные условия и смотрит только на типы, не обращая внимания на конкретные значения.

Явное преобразование типов данных

Если условия неявного преобразования не выполняются, но преобразование типов выполнить необходимо, используется приведение типов. Это указание целевого типа, в который должен быть преобразован результат вычислений, записываемое в скобках перед выражением.

Int d = 13; int c = 4; var e = (double) d / c;

Таким образом, переменная e получит тип double и значение 3,25. Если бы мы не использовали преобразование, то у этой переменной был бы тип int и значение 3 согласно правилам деления целых чисел в C#.

Еще пример:

Ushort num = 257; double dbl = 34.9318; int rnd = (short) dbl; // Будет присвоено значение 34 byte what = (byte) num; // Будет присвоено значение 1

В случае с rnd , выполняется следующее правило: при преобразовании чисел с плавающей запятой в целые числа, их дробная часть отбрасывается.

А в случае с what все немного сложнее. При явном преобразовании компилятор не обращает внимания на диапазоны типов. А при исполнении программы, если происходит попытка занести в переменную значение, не находящееся в ее диапазоне (с более высокой разрядностью), то все старшие разряды стираются. Число 257 в двоичном представлении типа ushort выглядит как 00000001 00000001. При преобразовании в byte остается только последний байт 00000000, то есть 1.

Преобразование, при котором диапазон типа переменной не полностью покрывает диапазон типа значения, называется сужающим. Дабы избегать потери данных, как в предыдущем примере, нужно использовать проверку на переполнение.

Переполнение при сужающем преобразовании данных

Очевидно, что нельзя оставлять без внимания места в коде, в которых может иметь место потеря данных из-за переполнения. Иначе переменные могут принять неожиданное значение, что отразится на работе программы, а причину ошибки будет довольно трудно отследить.

По умолчанию, возникновение переполнения игнорируется. Для того чтобы в таком случае возникало соответствующее исключение, используется ключевое слово checked. Его можно поместить как перед определенным выражением, так и обозначить ним блок кода. Далее при возникновении исключения его можно обработать конструкцией try & catch.

Random rnd = new Random(); int bigger = rnd.Next(99999); // Генерирует случайное число от 0 до 99999 short smaller; try { smaller = checked((short) bigger); } catch (System.OverflowException e) { Console.WriteLine(‘Overflow raised by checked on smaller: ‘ + e.ToString()); // Выводит //сообщение о том, что было обработано исключение smaller = -1; }

bigger является случайным числом от 0 до 99999. В случае если это значение превысит диапазон типа short, то будет обработано исключение System.OverflowException и переменной smaller будет присвоено значение -1. Если же переполнение не произойдет, исключения не будет, и переменная smaller просто примет значение переменной bigger, но уже в типе short .

Оператор is

Для проверки возможности приведения объекта к некоторому типу в C# используется оператор is . Он проверяет, является ли объект экземпляром самого указанного типа или производного от него. Результатом бинарной операции с оператором is является логическое значение (true или false).

Пример использования оператора is:

Class Samle1 {} class Samle2 {} : Sample1 {} // Наследует класс Sample1 class Sample3 {} Samle1 t1 = new Samle1(); Sample2 t2 = new Samle2(); Sample3 t3 = new Sample3(); char t4 = ‘t’; if (t1 is Sample1) { Console.WriteLine(“t1 is Sample1”); } // Будет выведено if (t2 is Sample1) { Console.WriteLine(“t2 is Sample1”); } // Будет выведено if (t3 is Sample1) { Console.WriteLine(“t3 is Sample1”); } // Не будет выведено if (t4 is Sample1) { Console.WriteLine(“t4 is Sample1”); } // Не будет выведено

Оператор as

Для преобразования совместимых ссылочных типов используется оператор as. Следует отметить, что при невозможности преобразования исключение не вызывается, а возвращается значение null. Это нужно учитывать при обработке результата преобразования с помощью as.

Class SamleClass {} object Arr = new object; Arr = new SampleClass(); Arr = “welcome”; Arr = (short) 654; Arr = null; for (byte i = 0; i < Arr.Length; i++) { string t1 = Arr[i] as string; SampleClass t2 = Arr[i] as SampleClass; if (t1 != null) { Console.WriteLine(“{0}: it’s a string ‘” + t1 + “’”, i+1); } else if (t2 != null) { Console.WriteLine(“{0}: it’s a SampleClass”, i+1); } else { Console.WriteLine(“{0}: it’s not string or SampleClass”, i+1); } }

Вывод примера будет следующим:

1: it’s a SampleClass

2: it"s a string "welcome"

3: it’s not string or SampleClass

4: it’s not string or SampleClass

Преобразование типов с помощью класса Convert

Системный класс System.Convert содержит в себе методы, с помощью которых можно преобразовывать значения базовых типов данных, а также системного типа DateTime.

Convert содержит набор методов вида ToType , где вместо Type стоит системное название целевого типа значения (например ToChar, ToInt32, ToBoolean ). Эти методы позволяют выполнять все возможные преобразования базовых типов C#.

Метод Convert.ChangeType преобразует любой объект в любой указанный тип. При несоответствии типов или переполнении метод выдает исключение.

В программировании нередко значения переменных одного типа присваиваются переменным другого типа. Например, в приведенном ниже фрагменте кода целое значение типа int присваивается переменной с плавающей точкой типа float :
int i; float f; i = 10; f = i;

Если в одной операции присваивания смешиваются совместимые типы данных, то значение в правой части оператора присваивания автоматически преобразуется в тип, указанный в левой его части. Поэтому в приведенном выше фрагменте кода значение переменной i сначала преобразуется в тип float , а затем присваивается переменной f .

Зададимся вопросом, всегда ли возможно преобразование типов? Когда будут возникать сообщения об ошибках, как это повлияет на надежность разрабатываемых программ?

Вследствие строгого контроля типов далеко не все типы данных в C# оказываются полностью совместимыми, и, следовательно, не все преобразования типов разрешены в неявном виде.

Например, типы bool и int несовместимы. Правда, преобразование несовместимых типов все-таки может быть осуществлено путем приведения . Приведение типов , по существу, означает явное их преобразование.

Автоматическое преобразование типов

Когда данные одного типа присваиваются переменной другого типа, неявное преобразование типов происходит автоматически при следующих условиях:
1) оба типа совместимы;
2) диапазон представления чисел целевого типа шире, чем у исходного типа.
Если оба эти условия удовлетворяются, то происходит расширяющее преобразование .
Например, тип int достаточно крупный, чтобы вмещать в себя все действительные значения типа byte , а кроме того, оба типа, int и byte , являются совместимыми целочисленными типами, и поэтому для них вполне возможно неявное преобразование.
Числовые типы, как целочисленные, так и с плавающей точкой, вполне совместимы друг с другом для выполнения расширяющих преобразований.

Рассмотрим пример:

using System; namespace ConsoleApplication1 { classProgram { static void Main(string args) { short num1, num2; num1 =10; num2 =15; Console.WriteLine("{0} + {1} = {2}",num1,num2,Sum(num1,num2)); Console.ReadLine(); } staticintSum(int x,int y) { return x + y; } } }

Обратите внимание на то, что метод Sum() ожидает поступления двух параметров типа int. Тем не менее, в методе Main() ему на самом деле передаются две переменных типа short. Хотя это может показаться несоответствием типов, программа будет компилироваться и выполняться без ошибок и возвращать в результате, как и ожидалось, значение 25.
Причина, по которой компилятор будет считать данный код синтаксически корректным, связана с тем, что потеря данных здесь невозможна.
Поскольку максимальное значение (32767), которое может содержать тип short, вполне вписывается в рамки диапазона типа int (максимальное значение которого составляет 2147483647), компилятор будет неявным образом расширять каждую переменную типа short до типа int.
Формально термин «расширение» применяется для обозначения неявного восходящего приведения (upward cast ), которое не приводит к потере данных.

Приведение несовместимых типов

Несмотря на всю полезность неявных преобразований типов, они неспособны удовлетворить все потребности в программировании, поскольку допускают лишь расширяющие преобразования совместимых типов. А во всех остальных случаях приходится обращаться к приведению типов. Приведение - это команда компилятору преобразовать результат вычисления выражения в указанный тип. А для этого требуется явное преобразование типов.

Ниже приведена общая форма приведения типов:
(целевой_тип) выражение
Здесь целевой_тип обозначает тот тип, в который желательно преобразовать указанное выражение.

Если приведение типов приводит к сужающему преобразованию , то часть информации может быть потеряна. Например, в результате приведения типа long к типу int часть информации потеряется, если значение типа long окажется больше диапазона представления чисел для типа int, поскольку старшие разряды этого числового значения отбрасываются.
Когда же значение с плавающей точкой приводится к целочисленному типу, то в результате усечения теряется дробная часть этого числового значения. Так, если присвоить значение 1,23 целочисленной переменной, то в результате в ней останется лишь целая часть исходного числа (1), а дробная его часть (0,23) будет потеряна.

Давайте рассмотрим пример:

using System; namespace ConsoleApplication1 { classProgram { static void Main(string args) { int i1 =455,i2 =84500; decimal dec =7.98845m; // Приводим два числа типа int к типу short Console.WriteLine((short) i1); Console.WriteLine((short) i2); // Приводим число типа decimal к типу int Console.WriteLine((int) dec); Console.ReadLine(); } } }

Результатом работы данной программы будет:
455
18964
7

Обратите внимание, что переменная i1 корректно преобразовалась в тип short, т.к. ее значение входит в диапазон этого типа данных. Преобразование переменной dec в тип int вернуло целую часть этого числа. Преобразование переменной i2 вернуло значение переполнения 18964 (т.е. 84500 — 2*32768).

Роль класса System.Convert

В завершении темы преобразования типов данных стоит отметить, что в пространстве имен System имеется класс Convert , который тоже может применяться для расширения и сужения данных:
byte sum = Convert.ToByte(var1 + var2);

Одно из преимуществ подхода с применением класса System.Convert связано с тем, что он позволяет выполнять преобразования между типами данных нейтральным к языку образом.

Однако, поскольку в C# есть операция явного преобразования, использование класса Convert для преобразования типов данных обычно является делом вкуса.

Другое полезное назначение методов класса Convert состоит в преобразовании строковой переменной в переменную числового типа. Я его использую достаточно часто! Ведь в консольном приложении возможен только ввод строк, и поскольку оператор Console.ReadLine() возвращает строку, то далее ее можно преобразовать в число, используя соответствующий метод, например:
int k = Convert.ToInt32(Console.ReadLine());
При невозможности преобразования строки в число возникает исключительная ситуация (далее исключение – exception ), которое может быть также обработано.

Перейдем к изучению операторов языка С#, и начнем мы с .

Перейдем к примерам. КлассTesting , содержащий примеры, представляет собой набор данных разного типа, над которыми выполняются операции, иллюстрирующие преобразования типов. Вот описание класса

Testing: using System;
namespace TypesProject
{
public class Testing {
/// < summary >
/// набор скалярных полей разного типа.
///
private byte b = 255;
private int x = 11 ;
private uint ux = 1111 ;
private float y = 5.5f;
private double dy = 5.55;
private string s = "Hello!";
private string si = "25";
private object obj = new Object();
// Далее идут методы класса, приводимые по ходу
// описания примеров
}
}

В набор данных класса входят скалярные данные арифметического типа, относящиеся к значимым типам, переменные строкового типа и типаobject , принадлежащие ссылочным типам. Рассмотрим закрытый(private ) метод этого класса - процедуруWholsWho с формальным аргументом классаObject . Процедура выводит на консоль переданное ей имя аргумента, его тип и значение.

Вот ее текст:

/// < summary >
/// Метод выводит на консоль информацию о типе и
/// значении фактического аргумента. Формальный
/// аргумент имеет тип object . Фактический аргумент
/// может иметь любой тип, поскольку всегда
/// допустимо неявное преобразование в тип object .
///
/// Имя второго аргумента
/// Допустим аргумент любого типа
private void WhoIsWho(string name, object any) {
Console.WriteLine("type {0} is {1} , value is {2}",
name, any.GetType(), any.ToString());
}

Вот открытый(public ) метод классаTesting , в котором многократно вызывается методWholsWho с аргументами разного типа:

/// < summary >
/// получаем информацию о типе и значении
/// переданного аргумента - переменной или выражения
/// summary >
public void WhoTest() {
WholsWho("x", x);
WholsWho("ux", ux);
WhoIsWho("y", y);
WhoIsWho("dy", dy);
WhoIsWho("s", s);
WhoIsWho("11 + 5.55 + 5.5f", 11 + 5.55 + 5.5f);
obj = 11 + 5.55 + 5.5f;
WhoIsWho (" obj ", obj );
}

Отметим, что сущностьany - формальный аргумент классаObject , который при каждом вызове динамически изменяет тип, связываясь с объектом, заданным фактическим аргументом. Поэтому тип аргумента, выдаваемый на консоль, - это тип фактического аргумента. Отметим также, что наследуемый от классаObject методGetType возвращает тип FCL, то есть тот тип, на который отражается тип языка и с которым реально идет работа при выполнении модуля. В большинстве вызовов фактическим аргументом является переменная - соответствующее свойство классаTesting , но в одном случае передается обычное арифметическое выражение, автоматически преобразуемое в объект. Аналогичная ситуация имеет место и при выполнении присваивания в рассматриваемой процедуре.

На рис. 11 показаны результаты вывода на консоль, полученные при вызове методаWhoTest в приведенной выше процедуреMain классаClassi .

Рисунок 11. Вывод на печать результатов теста WhoTest

Где, как и когда выполняются преобразования типов?

Необходимость в преобразовании типов возникает в выражениях, присваиваниях, замене формальных аргументов метода фактическими.

Если при вычислении выражения операнды операции имеют разные типы, то возникает необходимость приведения их к одному типу. Такая необходимость возникает и тогда, когда операнды имеют один тип, но он несогласован с типом операции. Например, при выполнении сложения операнды типаbyte должны быть приведены к типуint , поскольку сложение не определено над байтами. При выполнении присваиванияx = e тип источникаe и тип целиx должны быть согласованы. Аналогично, при вызове метода также должны быть согласованы типы источника и цели - фактического и формального аргументов.

Преобразования ссылочных типов

Поскольку операции над ссылочными типами не определены (исключением являются строки, но операции над ними, в том числе и присваивание, выполняются как над значимыми типами), то необходимость в них возникает только при присваиваниях и вызовах методов.

Преобразования типов в выражениях

В C# такие преобразования делятся на неявные и явные. К неявным относятся те преобразования, результат выполнения которых всегда успешен и не приводит к потере точности данных. Неявные преобразования выполняются автоматически. Для арифметических данных это означает, что в неявных преобразованиях диапазон типа назначения содержит в себе диапазон исходного типа. Например, преобразование из типаbyte в типint относится к неявным, поскольку диапазон типаbyte является подмножеством диапазонаint . Это преобразование всегда успешно и не может приводить к потере точности.

К явным относятся разрешенные преобразования, успех выполнения которых не гарантируется или может приводить к потере точности. Такие потенциально опасные преобразования должны быть явно заданы программистом. Преобразование из типаint в типbyte относится к явным, поскольку оно небезопасно и может приводить к потере значащих цифр. Отметим, не для всех типов существуют явные преобразования. В этом случае требуются другие механизмы преобразования типов, которые будут рассмотрены позже.

Преобразования внутри арифметического типа

Арифметический тип, как показано в таблице типов данных, распадается на 11 подтипов. На рис. 12 показана схема преобразований внутри арифметического типа.

Рисунок 12. Иерархия преобразований внутри арифметического типа

Диаграмма, приведенная на рисунке, позволяет ответить на ряд важных вопросов, связанных с существованием преобразований между типами. Если на диаграмме задан путь (стрелками) от типа А к типу В, то это означает существование неявного преобразования из типа А в тип В. Все остальные преобразования между подтипами арифметического типа существуют, но являются явными. Отметим, что циклов на диаграмме нет, все стрелки односторонние, так что преобразование, обратное к неявному, всегда должно быть задано явным образом.

Путь, указанный на диаграмме, может быть достаточно длинным, но это вовсе не означает, что выполняется вся последовательность преобразований на данном пути. Наличие пути говорит лишь о существовании неявного преобразования, а само преобразование выполняется только один раз, - из типа источника А в тип назначения В.

Иногда возникает ситуация, при которой для одного типа источника может одновременно существовать несколько типов назначений и необходимо осуществить выбор цели - типа назначения. Такие проблемы выбора возникают, например, при работе с перегруженными методами в классах.

Правило выбора реализации при вызове метода таково: выбирается та реализация, для которой путь преобразований, заданный на диаграмме, короче. Если есть точное соответствие параметров по типу (путь длины 0), то, естественно, именно эта реализация и будет выбрана.

Рассмотрим еще один тестовый пример. В классTesting включена группа перегруженных методовOLoad с одним и двумя аргументами. Вот эти методы:

/// < summary >
/// Группа перегруженных методов OLoad
/// с одним или двумя аргументами арифметического типа.
/// Если фактический аргумент один, то будет вызван один из
/// методов, наиболее близко подходящий по типу аргумента.
/// При вызове метода с двумя аргументами возможен
/// конфликт выбора подходящего метода, приводящий
/// к ошибке периода компиляции.
///
private void OLoad(float par) {
Console.WriteLine("float value {0}", par);
}
/// < summary >
/// Перегруженный метод OLoad с одним параметром типа long
///
///
private void OLoad(long par) {
Console.WriteLine("long value {0}", par);
}
/// < summary >
/// Перегруженный метод OnLoad с одним параметром типа ulong
///
///
private void OLoad(ulong par) {
Console.WriteLine("ulong value {0}", par);
}
/// < summary >
/// Перегруженный метод OLoad с одним параметром типа double
///
///
private void OnLoad(double par) {
Console.WriteLine("double value {0}", par);
}
/// < summary >
/// Перегруженный метод OLoad с двумя параметрами типа long и long
///
///
///
private void OLoad(long par1, long par2) {
Console.WriteLine("long par1 {0}, long par2 {1}", par1, par2);
}
/// < summary >
/// Перегруженный метод OLoad с двумя параметрами типа
/// double и double
///
///
///
private void OLoad(double par1, double par2) {
Console.WriteLine("double par1 {0}, double par2 {1}", par1, par2);
}
/// < summary >
/// Перегруженный метод OLoad с двумя параметрами типа
/// int и float
///
///
///
private void OLoad(int par1, float par2) {
Console.WriteLine("int par1 {0}, float par2 {1}", par1, par2);
}

Все эти методы устроены достаточно просто. Они сообщают информацию о типе и значении переданных аргументов.

Вот тестирующая процедура, вызывающая методOLoad с разным числом и типами аргументов:

/// < summary >
/// Вызов перегруженного метода OLoad . В зависимости от
/// типа и числа аргументов вызывается один из методов группы.
///
public void OLoadTest() {
OLoad(x);
OLoad(ux);
OLoad(y);
OLoad(dy);
// OLoad(x,ux);
// conflict: (int, float) и (long,long)
OLoad(x, (float) ux);
OLoad(y, dy);
OLoad (x , dy );
}

Отметим, что один из вызовов закомментирован, так как он приводит к конфликту на этапе трансляции. Для устранения конфликта при вызове метода пришлось задать явное преобразование аргумента, что показано в строке, следующей за строкой-комментарием. Результат работы тестаOLoadTest представлен на рис. 13.

Рисунок 13. Вывод на печать результатов теста OLoadTest

Явные преобразования

Как уже говорилось, явные преобразования могут быть опасными из-за потери точности. Поэтому они выполняются по указанию программиста, - на нем лежит вся ответственность за результаты.

Преобразования строкового типа

Важным классом преобразований являются преобразования в строковый тип и наоборот. Преобразования в строковый тип всегда определены, поскольку, все типы являются потомками базового классаObject , а, следовательно, обладают методомToString (). Для встроенных типов определена подходящая реализация этого метода. В частности, для всех подтипов арифметического типа методToString () возвращает в подходящей форме строку, задающую соответствующее значение арифметического типа. Отметим, метод ToString можно вызывать явно, но, если явный вызов не указан, то он будет вызываться неявно, всякий раз, когда по контексту требуется преобразование к строковому типу. Вот соответствующий пример:

/// < summary >
/// Демонстрация преобразования в строку данных различного типа.
///
public void ToStringTest()
{
s = " Владимир Петров ";
s1 = " Возраст : ";
ux = 27;
s = s + s1 + ux.ToString();
s1 = " Зарплата : ";
dy = 2700.50;
s = s + s1 + dy;
WhoIsWho (" s ", s );
}

Результат работы этой процедуры показан на рис. 14.

Рисунок 14. Вывод на печать результатов теста ToStringTest

Преобразования из строкового типа в другие типы, например, в арифметический, должны выполняться явно. Но явных преобразований между арифметикой и строками не существуют. Необходимы другие механизмы, и они в C# имеются. Для этой цели можно использовать соответствующие методы классаConvert библиотеки FCL, встроенного в пространство имен System . Приведем соответствующий пример:

/// < summary >
/// Демонстрация преобразования строки в данные различного типа.
///
public void FromStringTest() {
s = " Введите возраст ";
Console.WriteLine(s);
s1 = Console.ReadLine();
ux = Convert.ToUInt32(s1);
WhoIsWho ("Возраст: ", ux );
s = "Введите зарплату ";
Console.WriteLine(s);
s1 = Console.ReadLine();
dy = Convert.ToDouble(s1);
WhoIsWho(" Зарплата : ", dy);
}

Этот пример демонстрирует ввод с консоли данных разных типов. Данные, читаемые с консоли методомReadLine илиRead , всегда представляют собой строку, которую затем необходимо преобразовать в нужный тип. Для этого вызываются соответствующие методы классаConvert . Естественно, для успеха преобразования строка должна содержать значение в формате, допускающем подобное преобразование. Отметим, например, что при записи значения числа для выделения дробной части должна использоваться запятая, а не точка; в противном случае возникнет ошибка периода выполнения.

На рис. 15 показаны результаты вывода и ввода данных с консоли при работе этой процедуры.

Рисунок 15. Вывод на печать результатов теста FromStringTest



Понравилась статья? Поделиться с друзьями: