Автор работы: Пользователь скрыл имя, 28 Марта 2012 в 11:49, курс лекций
Лекции по дисциплине "Программирование"
Лекция 1. Состав языка Типы данных Переменные и операции
Лекция 2. Линейные программы
Лекция 3. Простейшие операторы. Операторы ветвления
Лекция 4. Операторы цикла и передачи управления
Лекция 5. Обработка исключительных ситуаций
Лекция 6. Классы: основные понятия Описание класса
Лекция 7. Параметры методов
Лекция 8. Конструкторы и свойства
Лекция 9. . Массивы
Лекция 10. Символы и строки
Лекция 11 Дополнительные возможности методов. Индексаторы
Лекция 12. Операции класса. Деструкторы
Лекция 13. Наследование классов
Лекция 14. Интерфейсы
Лекция 15. Стандартные интерфейсы .NET
Лекция 16. Структуры и перечисления
Лекция 17. Делегаты
Лекция 18. События
Синтаксис интерфейса аналогичен синтаксису класса:
[ атрибуты ] [ спецификаторы ] interface имя_интерфейса [ : предки ]
тело_интерфейса [ ; ]
Для интерфейса могут быть указаны спецификаторы new, public, protected, internal и private. Спецификатор new применяется для вложенных интерфейсов и имеет такой же смысл, как и соответствующий модификатор метода класса. Остальные спецификаторы управляют видимостью интерфейса. По умолчанию интерфейс доступен только из сборки, в которой он описан (internal).
Интерфейс может наследовать свойства нескольких интерфейсов, в этом случае предки перечисляются через запятую. Тело интерфейса составляют абстрактные методы, шаблоны свойств и индексаторов, а также события.
В качестве примера рассмотрим интерфейс IAction, определяющий базовое поведение персонажей компьютерной игры, встречавшихся в предыдущих главах. Допустим, что любой персонаж должен уметь выводить себя на экран, атаковать и красиво умирать:
interface IAction
{
void Draw();
int Attack(int a);
void Die();
int Power { get; }
}
В интерфейсе IAction заданы заголовки трех методов и шаблон свойства Power, доступного только для чтения. Если бы требовалось обеспечить возможность установки свойства, в шаблоне следовало указать ключевое слово set, например:
int Power { get; set; }
Отличия интерфейса от абстрактного класса:
элементы интерфейса по умолчанию имеют спецификатор доступа public и не могут иметь спецификаторов, заданных явным образом;
интерфейс не может содержать полей и обычных методов — все элементы интерфейса должны быть абстрактными;
класс, в списке предков которого задается интерфейс, должен определять все его элементы, в то время как потомок абстрактного класса может не переопределять часть абстрактных методов предка (в этом случае производный класс также будет абстрактным);
класс может иметь в списке предков несколько интерфейсов, при этом он должен определять все их методы.
В списке предков класса сначала указывается его базовый класс, если он есть, а затем через запятую интерфейсы, которые реализует этот класс. Например, реализация интерфейса 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();
Существует второй способ реализации интерфейса в классе: явное указание имени интерфейса перед реализуемым элементом. Спецификаторы доступа при этом не указываются. К таким элементам можно обращаться в программе только через объект типа интерфейса, например:
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();
Таким образом, при явном задании имени реализуемого интерфейса соответствующий метод не входит в интерфейс класса. Это позволяет упростить его в том случае, если какие-то элементы интерфейса не требуются конечному пользователю класса.
При работе с объектом через объект типа интерфейса бывает необходимо убедиться, что объект поддерживает данный интерфейс. Проверка выполняется с помощью бинарной операции is. Эта операция определяет, совместим ли текущий тип объекта, находящегося слева от ключевого слова is, с типом, заданным справа.
Результат операции равен true, если объект можно преобразовать к заданному типу, и false в противном случае. Операция обычно используется в следующем контексте:
if ( объект is тип )
{
// выполнить преобразование "объекта" к "типу"
// выполнить действия с преобразованным объектом
}
Допустим, мы оформили какие-то действия с объектами в виде метода с параметром типа object. Прежде чем использовать этот параметр внутри метода для обращения к методам, описанным в производных классах, требуется выполнить преобразование к производному классу. Для безопасного преобразования следует проверить, возможно ли оно, например так:
static void 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();
}
Обе рассмотренные операции применяются как к интерфейсам, так и к классам.
Интерфейс может не иметь или иметь сколько угодно интерфейсов-предков, в последнем случае он наследует все элементы всех своих базовых интерфейсов, начиная с самого верхнего уровня.
Как и в обычной иерархии классов, базовые интерфейсы определяют общее поведение, а их потомки конкретизируют и дополняют его. В интерфейсе-потомке можно также указать элементы, переопределяющие унаследованные элементы с такой же сигнатурой. В этом случае перед элементом указывается ключевое слово new, как и в аналогичной ситуации в классах. С помощью этого слова соответствующий элемент базового интерфейса скрывается. Класс, реализующий интерфейс, должен определять все его элементы, в том числе унаследованные.
Интерфейс, на собственные или унаследованные элементы которого имеется явная ссылка, должен быть указан в списке предков класса.
Класс наследует все методы своего предка, в том числе те, которые реализовывали интерфейсы. Он может переопределить эти методы с помощью спецификатора new, но обращаться к ним можно будет только через объект класса. Если использовать для обращения ссылку на интерфейс, вызывается не переопределенная версия:
interface IBase
{
void A();
}
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 A();
}
class Base : IBase
{
void IBase.A() { ... } // не используется в Derived
}
class Derived : Base, IBase
{
public void A() { ... }
}
Если класс наследует от класса и интерфейса, которые содержат методы с одинаковыми сигнатурами, унаследованный метод класса воспринимается как реализация интерфейса. Вообще при реализации интерфейса учитывается наличие «подходящих» методов в классе независимо от их происхождения. Это могут быть методы, описанные в текущем или базовом классе, реализующие интерфейс явным или неявным образом.
Лекция 15. Стандартные интерфейсы .NET
В библиотеке классов .NET определено множество стандартных интерфейсов, задающих желаемое поведение объектов. Например, интерфейс IComparable задает метод сравнения объектов на больше-меньше, что позволяет выполнять их сортировку. Реализация интерфейсов IEnumerable и IEnumerator дает возможность просматривать содержимое объекта с помощью конструкции foreach, а реализация интерфейса ICloneable — клонировать объекты.
Стандартные интерфейсы поддерживаются многими стандартными классами библиотеки. Например, работа с массивами с помощью цикла foreach возможна именно потому, что тип Array реализует интерфейсы IEnumerable и IEnumerator. Можно создавать и собственные классы, поддерживающие стандартные интерфейсы, что позволит использовать объекты этих классов стандартными способами.
Интерфейс IComparable определен в пространстве имен System. Он содержит всего один метод CompareTo, возвращающий результат сравнения двух объектов — текущего и переданного ему в качестве параметра:
interface IComparable
{
int CompareTo( object obj )
}
Метод должен возвращать:
0, если текущий объект и параметр равны;
отрицательное число, если текущий объект меньше параметра;
положительное число, если текущий объект больше параметра.
Реализуем интерфейс IComparable в знакомом нам классе Monster. В качестве критерия сравнения объектов выберем поле health. В листинге 17.1 приведена программа, сортирующая массив монстров по возрастанию величины, характеризующей их здоровье.
Листинг 17.1. Пример реализации интерфейса IComparable
using System;
namespace ConsoleApplication1
{
class Monster : IComparable
{
public Monster( int health, int ammo, string name )
{
this.health = health;
this.ammo = ammo;
this.name = name;
}
virtual public void Passport()
{
Console.WriteLine( "Monster {0} \t health = {1} ammo = {2}",
name, health, ammo );
}
public int CompareTo( object obj ) // реализация интерфейса
{
Monster temp = (Monster) obj;
if ( this.health > temp.health ) return 1;
if ( this.health < temp.health ) return -1;
return 0;
}
string name;
int health, ammo;
}
class Class1
{ static void Main()
{
const int n = 3;
Monster[] stado = new Monster[n];
stado[0] = new Monster( 50, 50, "Вася" );
stado[1] = new Monster( 80, 80, "Петя" );
stado[2] = new Monster( 40, 10, "Маша" );
Array.Sort( stado ); // сортировка стала возможной
foreach ( Monster elem in stado ) elem.Passport();
}
}
}
Результат работы программы:
Monster Маша health = 40 ammo = 10
Monster Вася health = 50 ammo = 50
Monster Петя health = 80 ammo = 80
Во многих алгоритмах требуется выполнять сортировку объектов по различным критериям. В C# для этого используется интерфейс IComparer, который мы рассмотрим далее.
Интерфейс IComparer определен в пространстве имен System.Collections. Он содержит один метод CompareTo, возвращающий результат сравнения двух объектов, переданных ему в качестве параметров:
interface IComparer
{
int Compare ( object ob1, object ob2 )
}
Принцип применения этого интерфейса состоит в там, что для каждого критерия сортировки объектов описывается небольшой вспомогательный класс, реализующий этот интерфейс. Объект этого класса передается в стандартный метод сортировки массива в качестве второго аргумента.
Пример сортировки массива объектов из предыдущего листинга по именам (свойство Name, класс SortByName) и количеству вооружений (свойство Ammo, класс SortByAmmo) приведен в листинге 17.2.
Листинг 17.2. Сортировка по двум критериям
using System;
using System.Collections;
namespace ConsoleApplication1
{
class Monster
{
public Monster( int health, int ammo, string name )
{
this.health = health;
this.ammo = ammo;
this.name = name;
}
public int Ammo
{
get { return ammo; }
set
{
if (value > 0) ammo = value;
else ammo = 0;
}
}
public string Name
{
get { return name; }
}
virtual public void Passport()
{
Console.WriteLine( "Monster {0} \t health = {1} ammo = {2}",