Читать в оригинале

<< Предыдущая Оглавление Следующая >>


2.2.4. Примеры преобразований встроенных типов

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

Testing: using System;
namespace TypesProject
{
public class Testing {
/// <summary>
/// набор скалярных полей разного типа.
/// </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.
/// </summary>
/// <param name="name"> Имя второго аргумента</param>
/// <param name="any"> Допустим аргумент любого типа</param>
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
/// с одним или двумя аргументами арифметического типа.
/// Если фактический аргумент один, то будет вызван один из
/// методов, наиболее близко подходящий по типу аргумента.
/// При вызове метода с двумя аргументами возможен
/// конфликт выбора подходящего метода, приводящий
/// к ошибке периода компиляции.
/// </summary>
private void OLoad(float par) {
     Console.WriteLine("float value {0}", par);
}
/// <summary>
/// Перегруженный метод OLoad с одним параметром типа long
/// </summary>
/// <param name="par"></param>
private void OLoad(long par) {
     Console.WriteLine("long value {0}", par);
}
/// <summary>
/// Перегруженный метод OnLoad с одним параметром типа ulong
/// </summary>
/// <param name="par"></param>
private void OLoad(ulong par) {
     Console.WriteLine("ulong value {0}", par);
}
/// <summary>
/// Перегруженный метод OLoad с одним параметром типа double
/// </summary>
/// <param name="par"></param>
private void OnLoad(double par) {
     Console.WriteLine("double value {0}", par);
}
/// <summary>
/// Перегруженный метод OLoad с двумя параметрами типа long и long
/// </summary>
/// <param name="par1"></param>
/// <param name="par2"></param>
private void OLoad(long par1, long par2) {
     Console.WriteLine("long par1 {0}, long par2 {1}", par1, par2);
}
/// <summary>
/// Перегруженный метод OLoad с двумя параметрами типа
/// double и double
/// </summary>
/// <param name="par1"></param>
/// <param name="par2"></param>
private void OLoad(double par1, double par2) {
     Console.WriteLine("double par1 {0}, double par2 {1}", par1, par2);
}
/// <summary>
/// Перегруженный метод OLoad с двумя параметрами типа
/// int и float
/// </summary>
/// <param name="par1"></param>
/// <param name="par2"></param>
private void OLoad(int par1, float par2) {
     Console.WriteLine("int par1 {0}, float par2 {1}", par1, par2);
}

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

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

/// <summary>
/// Вызов перегруженного метода OLoad. В зависимости от
/// типа и числа аргументов вызывается один из методов группы.
/// </summary>
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>
/// Демонстрация преобразования в строку данных различного типа.
/// </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>
/// Демонстрация преобразования строки в данные различного типа.
/// </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

 



<< Предыдущая Оглавление Следующая >>