Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

lec

.pdf
Скачиваний:
41
Добавлен:
24.03.2015
Размер:
3.43 Mб
Скачать

ИНТЕРФЕЙСЫ

1. Определение и реализация интерфейса

Интерфейс определяет набор методов, которые должны быть реализованы классом, наследующим этот интерфейс. Сам интерфейс не реализует методы.

Интерфейсы объявляются с помощью ключевого слова interface. Вот как выглядит упрощенная форма объявления интерфейса:

interface имя

{

тип_возврата имя_метода_1 (список_параметров); тип_возврата имя_метода_2 (список_параметров);

. . . . . . . .

тип_возврата имя_метода_N (список_параметров);

}

Интерфейсы синтаксически подобны абстрактным классам.

Однако в интерфейсе ни один метод не может включать тело, т.е. интерфейс, в принципе, не предусматривает какой бы то ни было реализации.

Он определяет, что должно быть сделано, но не уточняет, как.

Какой-либо интерфейс может унаследовать и реализовать любое количество классов. И каждый по-своему. При этом один класс может реализовать любое число разных интерфейсов.

Для реализации интерфейса класс должен обеспечить тела (реализацию) методов, описанных в интерфейсе. Каждый класс может определить собственную реализацию. Таким образом, два класса могут реализовать один и тот же интерфейс различными способами, но все классы поддерживают одинаковый набор методов.

Поскольку интерфейс для всех объектов одинаков, это позволяет программе, “осведомленной” о наличии интерфейса, использовать объекты любого класса.

Рассмотрим пример интерфейса для класса, который генерирует ряд чисел.

public interface ISeries

 

{

 

int getNext ();

// Возвращает следующее число ряда.

void reset ();

// Выполняет перезапуск.

void setStart (int x);

// Устанавливает начальное значение.

}

 

Этот интерфейс имеет имя ISeries. Хотя префикс "I" необязателен, многие программисты его используют, чтобы отличать интерфейсы от классов. Интерфейс ISeries объявлен открытым, поэтому он может быть реализован любым классом в любой программе.

221

Помимо сигнатур методов интерфейсы могут объявлять сигнатуры свойств, индексаторов и событий.

Так как в интерфейсе методы не содержат реализации, то:

нельзя создать экземпляр интерфейса;

интерфейсы не могут иметь полей, так как это подразумевает некоторую внутреннюю реализацию;

они не могут определять конструкторы, деструкторы или операторные методы;

кроме того, ни один член интерфейса не может быть объявлен статическим;

все методы в классе-интерфейсе по умолчанию являются открытыми и виртуальными, однако при их реализации писать override не нужно.

Реализация интерфейсов

Формат записи класса, который реализует интерфейс:

class имя_класса : имя_интерфейса

{

// тело класса

}

Если класс реализует интерфейс, он должен это сделать в полном объеме, т.е. реализация интерфейса не может быть выполнена частично.

Классы могут реализовать несколько интерфейсов. В этом случае имена интерфейсов отделяются запятыми.

Класс может наследовать базовый класс и реализовать один или несколько интерфейсов. В этом случае список интерфейсов должен возглавлять имя базового класса.

Рассмотрим пример реализации интерфейса ISeries, объявление которого было приведено выше. Здесь создается класс с именем ByTwos, генерирующий ряд чисел, в котором каждое следующее число больше предыдущего на два.

class ByTwos : ISeries

{

int start; int val;

public ByTwos()

{

start = 0; val = 0;

}

public int getNext()

{

val += 2; return val;

}

222

public void reset()

{

val = start;

}

public void setStart(int x)

{

start = x; val = start;

}

// Здесь могут быть дополнительные члены

}

Рассмотрим пример, демонстрирующий использование интерфейса, реализованного классом ByTwos.

using System;

class SeriesDemo

{

public static void Main()

{

ByTwos ob = new ByTwos();

if (ob is ISeries)

Console.WriteLine("Объект реализует интерфейс ISeries"); else throw new Exception ("Объект НЕ реализует ISeries");

for (int i = 0; i < 5; i++)

Console.WriteLine("Следующее значение = " + ob.getNext());

Console.WriteLine("\nПереход в исходное состояние."); ob.reset();

for (int i = 0; i < 5; i++)

Console.WriteLine("Следующее значение = " + ob.getNext());

Console.WriteLine("\nНачинаем с числа 100."); ob.setStart(100);

for (int i = 0; i < 5; i++)

Console.WriteLine("Следующее значение = " + ob.getNext());

}

}

}

Рассмотрим пример другой реализации интерфейса. Класс Primes генерирует ряд простых чисел. Обратите внимание на то, что его способ реализации интерфейса ISeries в корне отличается от используемого классом ByTwos.

223

class Primes : ISeries

{

int start; int val;

public Primes()

{

start = 2; val = 2;

}

public int getNext()

{

int i, j;

bool isprime;

val++;

for (i = val; i < 1000000; i++)

{

isprime = true;

for (j = 2; j < (i / j + 1); j++)

{

if ((i % j) == 0)

{

isprime = false; break;

}

}

if (isprime)

{

val = i; break;

}

}

return val;

}

public void reset()

{

val = start;

}

public void setStart(int x)

{

start = x; val = start;

}

}

Здесь важно понимать, что, хотя классы Primes и ByTwos генерируют разные ряды чисел, оба они реализуют один и тот же интерфейс ISeries.

224

Использование интерфейсных ссылок

Ссылочная переменная интерфейсного типа может ссылаться на любой объект, который реализует ее интерфейс.

В этом случае интерфейс выступает в роли базового класса, а его наследники являются производными классами. Поэтому в случае наследования интерфейса работает тот же механизм, что и для классов.

Важно понимать, что интерфейсная ссылочная переменная "осведомлена" только о методах, объявленных "под сенью" ключевого слова interface. Следовательно, интерфейсную ссылочную переменную нельзя использовать для доступа к другим переменным или методам, которые может определить объект, реализующий этот интерфейс.

Интерфейсные свойства

Формат объявления: тип имя

{

get;

set;

}

Свойства, предназначенные только для чтения или только для записи, содержат только getили set-элемент, соответственно.

Интерфейсные индексаторы

Формат объявления: тип_элементa this [int индекс]

{

get;

set;

}

Индексаторы, предназначенные только для чтения или только для записи, содержат только getили setэлемент, соответственно.

Наследование интерфейсами интерфейсов

Один интерфейс может унаследовать "богатство" другого. Синтаксис этого механизма аналогичен синтаксису, используемому для наследования классов.

Если класс реализует интерфейс, который наследует другой интерфейс, этот класс должен обеспечить способы реализации для всех членов, определенных внутри цепочки наследования интерфейсов.

Пример:

 

public interface IA { . . .

}

public interface IВ : IA {

. . . }

class MyClass : IВ { . . .

} // Класс должен реализовать члены IA и

IB

225

В производном интерфейсе можно объявить член, который скрывает член, определенный в базовом интерфейсе (член д.б. помечен как new).

Явная реализация членов интерфейса

При реализации члена интерфейса можно квалифицировать его имя с использованием имени интерфейса (явная реализация).

В этом случае такой член будет недоступен вне класса.

interface IA

{

int Method (int x);

}

// Явная реализация интерфейсного метода class Class : IA

{

int IA.Method(int x)

{

return x / 3;

}

}

Причина: класс может реализовать два интерфейса, которые объявляют методы с одинаковыми именами и типами. Полная квалификация имен позволяет избежать неопределенности.

Для чего нужны интерфейсы

Интерфейс не несет в себе никакой функциональности, а объявленные в интерфейсе методы можно реализовать в классе и без существования интерфейса. Почему же он бывает необходим?

Интерфейс может быть базовым классом для нескольких производных для реализации динамического связывания (вызова перегруженных методов).

Если Вы решили реализовать класс с интерфейсом, который будет использовать какая-либо программа (клиент), то компилятор не позволит Вам сделать ошибку в количестве методов, которые необходимо реализовать, сигнатуре каждого метода интерфейса и возвращаемом им значении.

Программа, использующая класс, в котором должен быть реализован интерфейс, может удостовериться в этом, прежде чем вызывать его интерфейсные методы, с помощью рефлексии или операторов as или is:

if (!(ob is IA))

throw new Exception ("Объект НЕ реализует интерфейс IA"); Ряд средств языка во время выполнения программы так же осуществ-

ляют проверку наличия интерфейса.

Например, оператор foreach опрашивает объект, на предмет того,

реализует ли он интерфейс System.Collection.IEnumerable.

226

Если Вы подготовили объект с этим интерфейсом, то оператор foreach будет успешно использовать Ваш метод GetEnumerator(). Этот метод возвратит объект с методом MoveNext() и свойством Current для доступа к очередному элементу коллекции.

Если окажется, что объект не реализует требуемый интерфейс, foreach сгенерирует исключение.

2.Интерфейсы коллекций

ВС# под коллекцией понимается группа объектов. Пространство имен System. Collections содержит множество интерфейсов и классов, которые определяют и реализуют коллекции различных типов. Все эти коллекции разработаны на основе набора четко определенных интерфейсов.

Ряд встроенных реализаций интерфейсов в таких коллекциях как

ArrayList, Hashtable, Stack и Queue, вы можете использовать "как есть".

У каждого программиста также есть возможность реализовать собственную коллекцию, но в большинстве случаев достаточно встроенных.

Среда .NET Framework поддерживает три основных типа коллекций:

общего назначения,

специализированные,

ориентированные на побитовую организацию данных.

Коллекции общего назначения реализуют ряд основных структур данных, включая динамический массив, стек и очередь. Сюда также относятся словари, предназначенные для хранения пар ключ/значение. Коллекции общего назначения работают с данными типа object, поэтому их можно использовать для хранения данных любого типа.

Мощь коллекций состоит в том, что они могут хранить не только встроенные типы, но и объекты любого типа, включая объекты классов, создаваемых программистами.

Коллекции специального назначения ориентированы на обработку данных конкретного типа или на обработку уникальным способом. Например, существуют специализированные коллекции, предназначенные только для обработки строк или однонаправленного списка.

Классы коллекций, ориентированных на побитовую организацию данных, служат для хранения групп битов. Коллекции этой категории поддерживают такой набор операций, который не характерен для коллекций других типов. Например, в известной многим биториентированной коллекции BitArray определены такие побитовые операции, как И и исключающее ИЛИ.

227

Таблица. Интерфейсы коллекций

Интерфейс

Описание

 

 

 

 

Определяет

метод

GetEnumerator(),

IEnumerable

который поддерживает

перечислитель

 

для любого класса коллекции

 

 

Содержит методы, которые позволяют

IEnumerator

поэлементно получать содержимое кол-

 

лекции

 

 

 

ICollection

Определяет

элементы,

которые

 

должны иметь все коллекции

 

 

Определяет коллекцию, к которой

IList

можно получить доступ посредством ин-

 

дексатора

 

 

 

 

 

 

 

 

IDictionary

Определяет коллекцию, которая со-

 

стоит из пар ключ/значение

 

 

Определяет перечислитель для кол-

IDictionaryEnumerator

лекции, которая реализует интерфейс

 

IDictionary

 

 

 

 

Определяет метод compare(), кото-

IComparer

рый выполняет сравнение объектов,

 

хранимых в коллекции

 

 

 

 

 

 

 

IHashCodeProvider

Определяет хеш-функцию

 

 

 

 

 

 

Интерфейсы IEnumerable, IEnumerator и IDictionaryEnumerator

Основополагающим для всех коллекций является реализация перечислителя (нумератора), который поддерживается интерфейсами

IEnumerator и IEnumerable.

Перечислитель обеспечивает стандартизованный способ поэлементного доступа к содержимому коллекции. Поскольку каждая коллекция должна реализовать интерфейс IEnumerable, к элементам любого класса коллекции можно получить доступ с помощью методов, определенных в интерфейсе IEnumerator.

Следовательно, после внесения небольших изменений код, который позволяет циклически опрашивать коллекцию одного типа, можно успешно использовать для циклического опроса коллекции другого типа (например в цикле foreach).

Все коллекции C# реализуют перечислитель.

interface IEnumerable

 

 

 

 

 

GetEnumerator();

 

{

 

 

 

IEnumerator GetEnumerator(

 

);

 

 

 

 

}

 

 

 

 

 

interface IEnumerator

 

 

Перечислитель 1:

{

 

 

 

Current

object Current { get };

 

 

MoveNext()

bool

MoveNext();

 

 

 

 

Reset()

void

Reset();

 

 

 

 

 

 

}

 

 

 

 

 

228

Метод GetEnumerator возвращает объект-перечислитель для коллекции.

Исключение. Для коллекций, в которых хранятся пары ключ/значение (т.е. словари), метод GetEnumerator() возвращает объект типа IDictionaryEnumerator, а не типа IEnumerator.

Класс IDictionaryEnumerator является производным от класса IEnumerator и распространяет свои функциональные возможности перечислителя на область словарей.

Почему перечислитель является объектом?

Ответ: у одной коллекции может быть несколько перечислителей.

КОЛЛЕКЦИЯ

Перечислитель 2

Перечислитель 1:

Current

MoveNext()

Reset()

рис.1

Пример. На основе структуры Vector создать коллекцию. Коллекция должна перечислять трехмерные координаты вектора (X, Y, Z).

using System;

using System.Collections; using System.Text;

class MainEntryPoint

{

static void Main(string[] args)

{

Vector Vect1 = new Vector(1.0, 2.0, 5.0); foreach (double Next in Vect1)

Console.Write(" " + Next); Console.ReadLine();

}

}

struct Vector : IEnumerable

229

{

private double x, y, z;

public Vector(double x, double y, double z)

{

this.x = x; this.y = y; this.z = z;

}

public IEnumerator GetEnumerator()

{

return new VectorEnumerator(this);

}

private class VectorEnumerator : IEnumerator

{

Vector theVector; int location;

public VectorEnumerator(Vector Vect)

{

theVector = Vect; location = -1;

}

public bool MoveNext()

{

++location;

return (location > 2) ? false : true;

}

public object Current

{

get

{

switch (location)

{

case 0:

return theVector.x; case 1:

return theVector.y; case 2:

return theVector.z; default:

throw new IndexOutOfRangeException(

"Вышли за границу вектора: " + location);

}

}

}

public void Reset()

{

location = -1;

}

}

}

230

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]