Потеря и восстановление информации о типе
Если присвоить объект производного класса (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();
показывает, что объект класса Circle можно присвоить переменной myShape типа Shape . Это возможно, поскольку класс Circle — производный от класса Shape, т. е. все элементы класса, доступные в Shape, доступны и в Circle. Shape в иерархии выше, чем Circle, и такое присваивание требует восходящего приведения типа.
ыо не указано явно. В отличие с
Движение в противоположном направлении, с приведением переменной к типу, расположенному в иерархии ниже, называется нисходящим приведением типа. Нисходящее приведение создает некоторые трудности. Например, нельзя использовать оператор вида:
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 для ввода данных с клавиатуры и вывода их на экран. Однако, данные, введенные с клавиатуры и отображенные на экране, доступны лишь временно — при завершении исполнения программы все данные стираются. Файлы позволяют сохранять данные после завершения работы программы и, естественно, после выключения компьютера. Содержимое файла можно использовать повторно из одной и той же (или разных) программ. Применение файлов позволяет удобно работать со значительными объемам информации.