У списку предків класу спочатку вказується його базовий клас, якщо він є, а потім через кому - інтерфейси, які реалізує цей клас. Таким чином, в С# підтримується одиночне спадкоємство для класів і множинне - для інтерфейсів. Це дозволяє додати похідному класу властивості декількох базових інтерфейсів, реалізовуючи їх на свій розсуд.
Наприклад, реалізація інтерфейсу IAction в класі Monster показана на лістингу 9.1:
Лістинг 9.1. Реалізація інтерфейсу
using System;
namespace Iterface
{
interface IAction
{
void Draw();
int Attack(int a);
void Die();
void Passport();
int Power { get; set;}
}
class Monster : IAction
{
public Monster (int health, int ammo, string name)
{
this.health = health;
this.ammo = ammo;
this.name = name;
}
public void Draw()
{
Console.WriteLine( "Тут був " + name );
}
public int Attack(int ammo_)
{
ammo -= ammo_;
if (ammo > 0) Console.WriteLine("Ба-бах!");
else ammo = 0;
return ammo;
}
public void Die()
{
Console.WriteLine("Monster " + name + " RIP");
health = 0;
}
public int Power
{
get
{
return ammo * health;
}
set
{
if (value > 0)
ammo = health = value;
}
}
public void Passport()
{
Console.WriteLine("Monster {0} \t health = {1} ammo = {2}",
name, health, ammo);
}
string name;
int health, ammo;
}
class Class1
{
public static void Act(IAction A)
{
A.Passport();
}
static void Main()
{
Monster X = new Monster( 80, 80, "Вася" ); // 1 Об'єкт класу
X.Draw();
IAction Y = new Monster( 80, 80, "Миша" ); // 2 Об'єкт типу інтерфейсу
Y.Draw();
Console.WriteLine("Power - " + Y.Power);
Y.Power = 100;
Y.Attack(-500);
Y.Draw();
Y.Passport();
Act(Y);
}
}
}
Природно, що сигнатури методів в інтерфейсі і реалізації повинні повністю збігатися. Для елементів інтерфейсу, що реалізовуються, в класі слід указувати специфікатор public. До цих елементів можна звертатися двома способами: через об'єкт класу і через об'єкт типу відповідного інтерфейсу. Це показано відповідно в операторах 1 і 2.
Результат роботи програми:
Тут був Вася
Тут був Міша
Power - 6400
Ба – бах!
Тут був Міша
Monster Міша health = 100 ammo = 600
Monster Міша health = 100 ammo = 600
Існує другий спосіб реалізації інтерфейсу в класі: явна вказівка імені інтерфейсу перед елементом, що реалізовується. Специфікатори доступу при цьому не указуються. До таких елементів можна звертатися в програмі тільки через об'єкт типу інтерфейсу, наприклад:
class Monster : IAction
{
int IAction.Power
{
get
{
return ammo * health;
}
}
void IAction.Draw()
{
Console.WriteLine( " Тут був " + name );
}
…
}
…
IAction Actor = new Monster(10,10,"Maшa");
Actor.Draw(); // звернення через об'єкт типу інтерфейсу
// Monster Vasia = new Monster(50, 50, "Вася" );
// Vasia.Draw(); помилка!
Таким чином, при явному завданні імені інтерфейсу, що реалізовується, відповідний метод не входить в інтерфейс класу. Це дозволяє спростити його в тому випадку, якщо якісь елементи інтерфейсу не потрібні кінцевому користувачеві класу.
Крім того, явне завдання імені інтерфейсу, що реалізовується, перед ім'ям методу дозволяє уникнути конфліктів при множинному спадкоємстві, якщо елементи з однаковими іменами або сигнатурою зустрічаються більш ніж в одному інтерфейсі. Хай, наприклад, клас Monster підтримує два інтерфейси: один для управління об'єктами, а інший для тестування:
interface ITest
{
void Draw();
}
interface IAction
{
void Draw();
int Attack( int a );
void Die();
int Power { get; }
}
class Monster : IAction, ITest
{
void ITest.Draw()
{
Console.Writel_ine("Testing " + name );
}
void IAction.Draw()
{
Console.WriteLine( " Тут був " + name );
}
…
}
Обидва інтерфейси містять метод Draw з однією і тією ж сигнатурою. Розрізняти їх допомагає явна вказівка імені інтерфейсу. Звертатися до цих методів можна, використовуючи операцію приведення типу, наприклад:
Monster Vasia = new MONSTER( 50, 50, "Вася" );
((Itest)Vasia).Draw(); // результат: Тут був Вася
((Iaction)Vasia).Draw(); // результат: Testing Вася
Втім, якщо від таких методів не потрібна різна поведінка, можна реалізувати метод першим способом (із специфікатором public), компілятор не заперечує
:
class Monster: IAction. ITest
{
public void Draw()
{
Console.WriteLine( " Тут був " + name ):
}
…
}
До методу Draw, описаного таким чином, можна звертатися будь-яким способом: через об'єкт класу Monster, через інтерфейс IAction або ITest.
Конфлікт виникає в тому випадку, якщо компілятор не може визначити з контексту звернення до елементу, елемент якого саме з інтерфейсів, що реалізовуються, потрібно викликати. При цьому завжди допомагає явне завдання імені інтерфейсу.