В списке предков класса сначала указывается его базовый класс, если он есть, а затем через запятую — интерфейсы, которые реализует этот класс. Таким образом, в С# поддерживается одиночное наследование для классов и множественное — для интерфейсов. Это позволяет придать производному классу свойства нескольких базовых интерфейсов, реализуя их по своему усмотрению. Например, реализация интерфейса IAction в классе Monster может выглядеть следующим образом:
using System;
namespace ConsoleApplication1
{
interface IAction
{
void Draw();
int Attack(int a);
void Die();
int Power { get; }
}
class Monster: IAction
{
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;
}
}
//...
}
}
Естественно, что сигнатуры методов в интерфейсе и реализации должны полностью совпадать. Для реализуемых элементов интерфейса в классе следует указывать спецификатор public. К этим элементам можно обращаться как через объект класса, так и через объект типа соответствующего интерфейса. Этот объект должен содержать ссылку на класс, поддерживающий интерфейс. Естественно, что объекты типа интерфейса, так же как и объекты абстрактных классов, создавать нельзя.
|
|
Monster Vasia = new Monster(50, 50, "Вася"); // объект класса Monster
Vasia.Draw(); // результат: Здесь был Вася
IAction Actor = new Monster(10, 10, "Маша"); // объект типа интерфейса
Actor.Draw(); // результат: Здесь был Маша
Удобство второго способа проявляется при присваивании объектам типа IAction ссылок на объекты различных классов, поддерживающих этот интерфейс. Например, легко себе представить метод с параметром типа интерфейса. На место этого параметра можно передавать любой объект, реализующий интерфейс:
static void Act(IAction A)
{
A.Draw();
}
static void Main()
{
Monster Vasia = new Monster(50, 50, "Вася");
Act(Vasia);
//...
}
Существует второй способ реализации интерфейса в классе: явное указание имени интерфейса перед реализуемым элементом. Спецификаторы доступа при этом не указываются. К таким элементам можно обращаться в программе только через объект типа интерфейса, например:
class Monster: IAction
{
int IAction.Power
{
get
{
return ammo * health;
}
void IAction.Draw()
{
Console.WriteLine("Здесь был " + name);
}
//...
}
//...
IAction Actor = new Monster(10, 10, "Маша");
Actor.Draw(); // обращение через объект типа интерфейса
// Monster Vasia = new Monster(50, 50, "Вася");
// Vasia.Draw(); // ошибка!Таким образом, при явном задании имени реализуемого интерфейса соответствующий метод не входит в интерфейс класса. Это позволяет упростить его в том случае, если какие-то элементы интерфейса не требуются конечному пользователю класса.
|
|
Кроме того, явное задание имени реализуемого интерфейса перед именем метода позволяет избежать конфликтов при множественном наследовании, если элементы с одинаковыми именами или сигнатурой встречаются более чем в одном интерфейсе1. Пусть, например, класс 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.WriteLine("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. Конфликт возникает в том случае, если компилятор не может определить из контекста обращения к элементу, элемент какого именно из реализуемых интерфейсов требуется вызвать. При этом всегда помогает явное задание имени интерфейса.