Потеря и восстановление информации о типе

Если присвоить объект производного класса (Rectangle) переменной базового класса (Shape), как ниже:

Shape myShape = new Rectangle();

часть информации об объекте Rectangle будет утрачена. Теперь нельзя четко определить, относится ли myShape к типу Rectangle, или Triangle. Чтобы убедится в этом, добавим в класс Circle новую переменную. Даже если известно, что myShape содержит Circle, попытка обратиться к свой­ству Radius с помощью опера­тора:

myShape.Radius = 20.4; //Неверно

приведет к ошибке, поскольку в момент компиляции неизвестно, указывает ли myShape на объект типа Rectangle, Triangle или Circle.

Для примера создадим объект аналогичного класса:

Circle myCircle = new Circle();

myCircle.Radius = 5;

Console.WriteLine(myCircle.Radius);

В данном случае код будет верен, т.к. тип переменной экземпляра Circle myCircle совпадает с классом Circle.

В случае, когда мы вызываем метод следующим образом:

myShape = new Triangle();

myShape.DrawYouself();

Пока вызываются только те элементы класса Rectangle, которые определены и в Shape, ограничение в доступе не проявляется.

Упакуем созданные объекты в некий массив. Напоминаю, что массив должен содержать элементы одного типа, в данном случае типа Shape. Фрагмент кода имеет вид:

Shape[] ShapeCollection = new Shape[3];

ShapeCollection[0] = new Triangle();

ShapeCollection[1] = new Rectangle();

ShapeCollection[2] = new Circle();

for (int i = 0; i < ShapeCollection.Length; i++)

{

ShapeCollection[i].DrawYouself();

}

В программе черчения, например, реаль­ное содержимое элементов массива ShapeCollection типа Shape не требовалось, поскольку вы­зывался только метод DrawYotirself (посредством динамического связывания).

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

Стандартное обращение к переменной Radius не дает результата, поскольку элементы ShapeCollection принадлежат типу Shape и не поддер­живают обращения к Radius. Если бы можно было четко определить, что в элементе хра­нится объект типа Circle и затем преобразовать переменную к этому типу, можно было бы и обратиться к свойству Radius.

К счастью, С# следит за тем, на какой объект (и какого типа) указывает переменная в каждый момент времени. (Иначе не реализовать механизм динамического связыва­ния.) Для доступа к этой информации в программе применяются операции is и as, пред­ставленные далее.

Операции is и as

Операция is позволяет проверить, является ли переменная (например, myShape) ука­зателем на объект определенного типа (например, Circle). Результат ее — логичес­кое значение true или false. Чтобы проверить, содержит ли myShape объект Circle, используется логическое выражение:

myShape is Circle,

которое возвращает true, если myShape указывает на Circle, и false — в противном случае.

Если в myShape хранится Circle, эту переменную можно преобразовать к типу Circle и обратиться к свойству Radius. Для этого применяется операция приведения типа.

Оператор присваивания

Shape myShape = new Circle();

Потеря и восстановление информации о типе - student2.ru Потеря и восстановление информации о типе - student2.ru показывает, что объект класса Circle можно присвоить переменной myShape типа Shape . Это возможно, поскольку класс Circle — производный от класса Shape, т. е. все эле­менты класса, доступные в Shape, доступны и в Circle. Shape в иерархии выше, чем Circle, и такое присваивание требует восходящего приведения типа.

Потеря и восстановление информации о типе - student2.ru

ыо не указано явно. В отличие с

Движение в противоположном направлении, с приведением переменной к типу, рас­положенному в иерархии ниже, называется нисходящим приведением типа. Нисходящее приведение создает некоторые трудности. Например, нельзя использовать оператор вида:

Circle myCircle = myShape;

поскольку myShape может также содержать Rectangleили Triangle,а эти классы могут и не иметь элементов, к которым можно обращаться через Circle.Тем не менее, если известно, что myShape фактически указывает на объект Circle,можно воспользовать­ся явным приведением типа:

Circle myCircle = (Circle)myShape;

Отсюда следует правило. Если объект класса А приводится к классу В, причем А — потомок В, это называется восходящим приведением. При этом не требуется явной операции приведения типа. Если же А — предок В, это называется нисходящим приведением. Нисходящее приведе­ние всегда требует явной операции (<Tип>).

Используя операцию is(позволяющую узнать, указывает ли переменная на объект определенного типа) и операцию нисходящего приведения типа, можно решить проблему с Circle и вычислить общую площадь объектов этого типа в массиве ShapeCollection.

double square = 0;

for (int i = 0; i < ShapeCollection.Length; i++)

{

if (ShapeCollection[i] is Circle)

{

myCircle = (Circle)ShapeCollection[i];

myCircle.Radius = 1;

square = square + 3.14 * myCircle.Radius * myCircle.Radius;

}

}

Console.WriteLine(square);

Тип элемента ShapeCollection проверяется не только операцией is, но и операцией приведе­ния типа:

myCircle = (Circle)ShapeCollection[i];

Хотя в этом и нет необходимости (проверка производится автома­тически при каждом приведении). Это сопровождается излишними затратами вычисли­тельных ресурсов, избежать которых позволяет операция as, рассматриваемая далее.

Операция as предназначена специально для нисходящего приведения. Она объеди­няет операцию is проверки типа, оператор if и операцию приведения в одном действии, как показано в представленном ниже фрагменте:

Операция as между ShapeCollection[i] и указывает, что элемент ShapeCollection[i] нужно привести к типу Circle. Если это возможно (когда ShapeCollection[i] содержит объект класса Circle), которая присваивается переменной myCircle. Если же ShapeCollection[i] не содержит объект Circle, то myCircle присваивается значение null, возвращаемое операцией as. Таким образом, до обращения к свойству Radius в myCircle достаточно проверить, что переменная не равна null.

double square1 = 0;

for (int i = 0; i < ShapeCollection.Length; i++)

{

myCircle = ShapeCollection[i] as Circle;

if (myCircle!=null)

{

myCircle.Radius = 1;

square1 = square1 + 3.14 * myCircle.Radius * myCircle.Radius;

}

}

Console.WriteLine(square1);

Полный текст программы имеет вид:

public abstract class Shape

{

public abstract void DrawYouself();

}

public class Triangle : Shape

{

public int Height = 0;

public override void DrawYouself()

{

Console.WriteLine(" * ");

Console.WriteLine(" * * ");

Console.WriteLine(" * * ");

Console.WriteLine(" *______* ");

}

}

public class Circle : Shape

{

public int Radius = 0;

public override void DrawYouself()

{

Console.WriteLine(" *** ");

Console.WriteLine(" * * ");

Console.WriteLine(" * * ");

Console.WriteLine(" * * ");

Console.WriteLine(" *** ");

}

}

public class Rectangle : Shape

{

public override void DrawYouself()

{

Console.WriteLine(" ******* ");

Console.WriteLine(" * * ");

Console.WriteLine(" * * ");

Console.WriteLine(" * * ");

Console.WriteLine(" ******* ");

}

}

class Program

{

static void Main(string[] args)

{

Circle myCircle;

Shape[] ShapeCollection = new Shape[4];

ShapeCollection[0] = new Triangle();

ShapeCollection[1] = new Circle();

ShapeCollection[2] = new Rectangle();

ShapeCollection[3] = new Circle();

double square = 0;

for (int i = 0; i < ShapeCollection.Length; i++)

{

if (ShapeCollection[i] is Circle)

{

myCircle = (Circle)ShapeCollection[i];

myCircle.Radius = 1;

square = square + 3.14 * myCircle.Radius * myCircle.Radius;

}

}

Console.WriteLine(square);

double square1 = 0;

for (int i = 0; i < ShapeCollection.Length; i++)

{

myCircle = ShapeCollection[i] as Circle;

if (myCircle!=null)

{

myCircle.Radius = 1;

square1 = square1 + 3.14 * myCircle.Radius * myCircle.Radius;

}

}

Console.WriteLine(square1);

Console.ReadLine();

}

}

Файловый ввод-вывод

Термин «ввод», обозначает процесс поступления в программу данных (чисел, сим­волов) из внешнего источника (клавиатуры, файла, удаленного компьютера). "Вывод" — это процесс поступления данных из программы в окружающую информационную среду (на экран, в файл, на удаленный компьютер).

Для чего применяются файлы? Мы подробно останавливались на использовании методов Console.ReadLine и Console.WriteLine для ввода данных с клавиатуры и вывода их на экран. Однако, данные, введенные с клавиатуры и отображенные на экране, до­ступны лишь временно — при завершении исполнения программы все данные стира­ются. Файлы позволяют сохранять данные после завершения работы программы и, ес­тественно, после выключения компьютера. Содержимое файла можно использовать повторно из одной и той же (или разных) программ. Применение файлов позволяет удобно работать со значительными объемам информации.

Наши рекомендации