Российский Государственный Технологический Университет им. К.Э. Циолковского
Опубликован: 02.03.2007 | Доступ: свободный | Студентов: 5677 / 802 | Оценка: 3.96 / 3.45 | Длительность: 27:04:00
ISBN: 978-5-9556-0086-4
Лекция 7:

Наследование и полиморфизм

< Лекция 6 || Лекция 7: 123456 || Лекция 8 >

Операции 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 и посмотреть, КАКАЯ ссылка ЗА КАКОЕ место этот самый объект удерживает.

< Лекция 6 || Лекция 7: 123456 || Лекция 8 >
kewezok kewezok
kewezok kewezok
Елена Шляхт
Елена Шляхт
Объясните плиз в чем отличие а++ от ++а
Почему результат разный?
int a=0, b=0;
Console.WriteLine(a++); //0
Console.WriteLine(++b); //1
a++;
++b;
Console.WriteLine(a); //2
Console.WriteLine(b); //2