Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Пособие КНЕУ.doc
Скачиваний:
24
Добавлен:
07.03.2016
Размер:
3.9 Mб
Скачать

9.3. Робота з об'єктами через інтерфейси. Операції is і as

При роботі з об'єктом через об'єкт типу інтерфейсу буває необхідно переконатися, що об'єкт підтримує даний інтерфейс. Перевірка виконується за допомогою бінарної операції is. Ця операція визначає, чи сумісний поточний тип об'єкту, що знаходиться зліва від ключового слова is, з типом, заданим праворуч.

Результат операції рівний true, якщо об'єкт можна перетворити до заданого типу, і false – в іншому випадку. Операція зазвичай використовується в наступному контексті:

if ( об’єкт is тип )

{

// виконати перетворення "об'єкту" до "типу"

// виконати дії з перетвореним об'єктом

}

Допустимо, ми оформили якісь дії з об'єктами у вигляді методу з параметром типу object. Перш ніж використовувати цей параметр усередині методу для звернення до методів, описаних в похідних класах, потрібно виконати перетворення до похідного класу. Для безпечного перетворення слід перевірити, чи можливо воно, наприклад так:

static vod Act(object A)

{

if ( A is IAction )

{

IAction Actor = (IAction) A;

Actor.Draw();

}

}

У метод Act можна передавати будь-які об'єкти, але на екран будуть виведені тільки ті, які підтримують інтерфейс IAction.

Недоліком використання операції is є те, що перетворення фактично виконується двічі: при перевірці і при власне перетворенні. Ефективнішою є інша операція - as. Вона виконує перетворення до заданого типу, а якщо це неможливо, формує результат null, наприклад:

static void Act(object A)

{

IAction Actor = A as IAction;

if (Actor != null )Actor.Draw();

}

Обидві розглянуті операції застосовуються як до інтерфейсів, так і до класів.

9.4. Інтерфейси і спадкоємство

Інтерфейс може не мати або мати скільки завгодно інтерфейсів-предків, в останньому випадку він успадковує всі елементи всіх своїх базових інтерфейсів, починаючи з самого верхнього рівня. Базові інтерфейси мають бути доступні як і їх нащадки. Як і в звичайній ієрархії класів, базові інтерфейси визначають загальну поведінку, а їх нащадки конкретизують і доповнюють його. У інтерфейсі-нащадку можна також вказати елементи, які змінюють успадковані елементи з такою ж сигнатурою. В цьому випадку перед елементом указується ключове слово new, як і в аналогічній ситуації в класах. За допомогою цього слова відповідний елемент базового інтерфейсу приховується. Приклад:

interface IBase

{

void F(int i );

}

interface Ileft : IBase

{

new void F( int i ); // перевизначення методу F

{

interface Iright : IBase

{

void G();

}

interface IDerived : ILeft, IRight {}

class A

{

void Test( IDerived d ) {

d.F(1); // Викликається ILeft.F

((IBase)d). F(1): // Викликається IBase.F

((ILeft )d). F(1); //' Викликається ILeft. F

((IRight)d).F(1); // Викликається IBase.F

}

}

Метод F з інтерфейсу IBase прихований інтерфейсом ILeft, не дивлячись на те що в ланцюжку IDerived - IRight - IBase він не перевизначався.

Клас, що реалізовує інтерфейс, повинен визначати всі його елементи, зокрема успадковані. Якщо при цьому явно указується ім'я інтерфейсу, воно повинне посилатися на той інтерфейс, в якому був описаний відповідний елемент, наприклад:

class А : IRight

{

IRight.G() { ... }

IBase.F( int i ) { ... } // IRight.F( int i ) - не можна

}

Інтерфейс, на власні або успадковані елементи якого є явне посилання, має бути вказаний в списку предків класу, наприклад:

class В : А

{

// IRight.G() { ... } не можна!

}

class С : A, IRight

{

IRight.G() { ... } // можна

IBase.F( int i ) { ... } // можна

}

Клас успадковує всі методи свого предка, зокрема ті, які реалізовували інтерфейси. Він може перевизначити ці методи за допомогою специфікатора new, але звертатися до них можна буде тільки через об'єкт класу. Якщо використовувати для звернення посилання на інтерфейс, викликається не перевизначена версія:

interface IBase

{

void А();

}

class Base : IBase

{

public void A() { ... }

}

class Derived: Base

{

new public void A() { ... }

}

...

Derived d = new Derived ();

d.A(); // викликається Derived.A();

IBase id = d;

id.A(); // викликається Base.A();

Проте якщо інтерфейс реалізується за допомогою віртуального методу класу, після його перевизначення в нащадку будь-який варіант звернення (через клас або через інтерфейс) приведе до одного і того ж результату:

interface IBase

{

void А();

}

class Base : IBase

{

public virtual void A() { ... }

}

class Derived: Base

{

Public override void A() { ... }

}

...

Derived d = new Derived ();

d.A(); // викликається Derived.A();

IBase id = d;

id.A(); // викликається Base.A();

Метод інтерфейсу, реалізований явною вказівкою імені, оголошувати віртуальним забороняється. При необхідності перевизначити в нащадках його поведінку користуються наступним прийомом: з цього методу викликається інший, захищений метод, який оголошується віртуальним. У приведеному далі прикладі метод А інтерфейсу IВase реалізується за допомогою захищеного віртуального методу А, який можна перевизначати в нащадках класу Base:

interface IBase

{

void A();

}

class Base : IBase

{

void IBase.A() { A_(); }

protected virtual void A_() { ... }

}

class Derived: Base

{

protected override void A() { ... }

}

Існує можливість повторно реалізувати інтерфейс, вказавши його ім'я в списку предків класу разом з класом-предком, що вже реалізував цей інтерфейс. При цьому реалізація перевизначених методів базового класу до уваги не береться:

interface IBase

{

void А();

}

class Base : IBase

{

void IBase.A() { ... } // не використовується в Derived

}

class Derived : Base, IBase

{

public void A() { ... }

}

Якщо клас успадковує від класу і інтерфейсу, які містять методи з однаковими сигнатурами, успадкований метод класу сприймається як реалізація інтерфейсу, наприклад:

interface Interfacel

{

void F( ):

}

class Class1

{

public void F() { ... }

public void G{} { ... }

}

class Class2 : Class1, Interface1

{

new public void G( ) { ... }

}

Тут клас Class2 успадковує від класу Class1 метод F. Інтерфейс Interface1 також містить метод F. Компілятор не видає помилку, тому що клас Class2 містить метод, відповідний для реалізації інтерфейсу.

Взагалі при реалізації інтерфейсу враховується наявність “відповідних” методів в класі незалежно від їх походження. Це можуть бути методи (описані в поточному або базовому класі) які реалізують інтерфейс явним або неявним чином.