Наследование и полиморфизм
Операции is и as
При преобразованиях ссылочных типов используются операции преобразования is и as. Вот код, иллюстрирующий особенности применения операций is и as:
using System;
namespace derivation01
{
// Базовый класс...
class X
{//_____________________________.
public int f1(int key)
{
Console.WriteLine("X.f1");
return key;
}
}//_____________________________.
// Производный...
class Y:X
{//_____________________________.
new public int f1(int key)
{
Console.WriteLine("Y.f1");
base.f1(key);
return key;
}
public int yf1(int key)
{
Console.WriteLine("Y.yf1");
return key;
}
}//_____________________________.
// Производный...
class Z:X
{//_____________________________.
public int zf1(int key)
{
Console.WriteLine("Z.zf1");
return key;
}
}//_____________________________.
class Class1
{
static void Main(string[] args)
{
int i;
// Всего лишь ссылки на объекты...
X x;
Y y;
Z z;
Random rnd = new Random();
// И вот такой тестовый пример позволяет выявить особенности применения
// операций is и as.
for (i = 0; i < 10; i++)
{
// Ссылка на объект базового класса случайным образом
// настраивается на объекты производного класса.
if (rnd.Next(0,2) == 1)
x = new Y();
else
x = new Z();
// Вызов метода f1 не вызывает проблем.
// Метод базового класса имеется у каждого объекта.
x.f1(0);
// А вот вызвать метод, объявленный в производном классе
// (с использованием операции явного приведения типа),
// удается не всегда. Метод yf1 был объявлен лишь в классе Y.
// Ниже операция is принимает значение true лишь в том случае,
// если ссылка на объект базового класса была настроена на объект
// класса Y.
if (x is Y)
{
((Y)x).yf1 (0); // И только в этом случае можно вызвать метод,
// объявленный в Y.
}
else
{
((Z)x).zf1 (1); // А в противном случае попытка вызова yf1
// привела бы к катастрофе.
try
{
((Y)x).yf1 (0);
}
catch (Exception ex)
{
Console.WriteLine("–1–" + ex.ToString());
}
}
// А теперь объект, адресуемый ссылкой на базовый класс, надо попытаться
// ПРАВИЛЬНО переадресовать на ссылку соответствующего типа. И это тоже
// удается сделать не всегда. Явное приведение может вызвать исключение.
try
{
z = (Z)x;
}
catch (Exception ex)
{
Console.WriteLine("–2–" + ex.ToString());
}
try
{
y = (Y)x;
}
catch (Exception ex)
{
Console.WriteLine("–3–" + ex.ToString());
}
// Здесь помогает операция as.
// В случае невозможности переадресации соответствующая ссылка оказывается
// установленной в null. А эту проверку выполнить проще...
if (rnd.Next(0,2) == 1)
{
z = x as Z;
if (z != null) z.zf1(2);
else Console.WriteLine("?????");
}
else
{
y = x as Y;
if (y != null) y.yf1(3);
else Console.WriteLine("!!!!!");
}
}
}
}
}
Листинг
7.5.
Boxing и Unboxing. Приведение к типу object
Любой тип .NET строится на основе базового типа (наследует) object. Это означает, что:
- методы базового типа object доступны к выполнению любым (производным) типом,
- любой объект (независимо от типа) может быть преобразован к типу object и обратно. Деятельность по приведению объекта к типу object называется Boxing. Обратное преобразование называют Unboxing.
Примеры вызова методов базового класса:
int i = 125; i.ToString(); i.Equals(100);
Примеры преобразований:
int i = 125; // Объявление и инициализация переменной типа int. object o = i; // Неявное приведение к типу object в результате присвоения. object q = (object)i; // Явное приведение к типу object. int x = (int)o; // Пример Unboxing'а.
И еще пример кода на ту же тему:
using System;
namespace Boxing02
{
class TestClass
{
public int t;
public string str;
// Конструктор класса принимает параметры в упакованном виде.
public TestClass(object oT, object oStr)
{
t = (int)oT; // unboxing посредством явного преобразования.
str = oStr as string; // unboxing в исполнении операции as.
}
}
class Program
{
static void Main(string[] args)
{
TestClass tc;
// При создании объекта пакуем параметры.
tc = new TestClass((object)0, (object)(((int)0).ToString()));
Console.WriteLine("tc.t == {0}, tc.str == {1}", tc.t, tc.str);
}
}
}Виртуальные функции. Принцип полиморфизма
Основа реализации принципа полиморфизма – наследование. Ссылка на объект базового класса, настроенная на объект производного, может обеспечить выполнение методов ПРОИЗВОДНОГО класса, которые НЕ БЫЛИ ОБЪЯВЛЕНЫ В БАЗОВОМ КЛАССЕ. При реализации принципа полиморфизма происходят вещи, которые не укладываются в ранее описанную схему.
Одноименные функции с одинаковой сигнатурой могут объявляться как в базовом, так и в производном классах. Между этими функциями может быть установлено отношение замещения:
функция ПРОИЗВОДНОГО класса ЗАМЕЩАЕТ функцию БАЗОВОГО класса.
Основное условие отношения замещения заключается в том, что замещаемая функция базового класса должна при этом дополнительно специфицироваться спецификатором virtual.
Отношение замещения между функциями базового и производного класса устанавливается, если соответствующая (одноименная функция с соответствующей сигнатурой) функция производного класса специфицируется дополнительным спецификатором override.
Введем следующие обозначения и построим схему наследования.
| public void F(){ } | 0 | |
| public virtual void F() { } | 0 | |
| new public virtual void F() { } | 1..N | |
| new public void F() { } | 1..N | |
| public override void F() { } | 1..N |
Схема возможных вариантов объявления методов в иерархии наследования трех уровней:
Наконец, замечательный пример:
using System;
namespace Implementing
{
class A
{
public virtual void F() { Console.WriteLine("A");}
}
class B:A
{
public override void F() { Console.WriteLine("B"); }
}
class C:B
{
new public virtual void F() { Console.WriteLine("C"); }
}
class D:C
{
public override void F() { Console.WriteLine("D"); }
}
class Starter
{
static void Main(string[] args)
{
D d = new D();
C c = d;
B b = c;
A a = b;
d.F(); /* D */
c.F(); /* D */
b.F(); /* B */
a.F(); /* B */
}
}
}
Листинг
7.6.
в котором становится ВСЕ понятно, если ПОДРОБНО нарисовать схемы классов, структуру объекта — представителя класса D и посмотреть, КАКАЯ ссылка ЗА КАКОЕ место этот самый объект удерживает.






