Слайд 1Язык С#
Дополнительные возможности классов
Лекция #4
Слайд 2Создание пользовательского индексатора
Оператор [ ]
Слайд 3Оператор индекса [ ]
// Объявляем массив целых чисел
int [] myInts
= {10,9,100,432,9874}
// Применяем оператор индекса для обращения к элементу
for (int
j=0; j Console.WriteLine("Index {0} = {1}",j, myInts[j]);
Слайд 4Использование индексатора
// Индексатор позволяет обращаться к элементам контейнерного класса //
при помощи того же синтаксиса, что и к элементам обычного
массива
public class CarApp
{
public static void Main()
{
// Считаем, что в классе Cars уже реализован метод индексатора
Cars carLot = new Cars();
// Создаем несколько объектов Car и сразу добавляем их в carLot
carLot[0] = new Car("FeeFee", 200, 0);
carLot[1] = new Car("Clunker", 90, 0);
carLot[2] = new Car("Zippy", 30, 0);
// Выводим информацию о каждом внутреннем элементе в контейнере
// на консоль:
for(int i = 0; i < 3; i++)
{
Console.WriteLine("Car number {0}:", i);
Console.WriteLine("Name: {0}", carLot[i].PetName);
Console.WriteLine("Max speed: {0}', carLot[i].MaxSpeed);
}
}
}
Слайд 5Создание индексатора
// Добавляем в существующее определение класса индексатор
public class Cars:
IEnumerator, IEnumerable
{
...
// Вернемся к основам и будем использовать в качестве
контейнера для
// объектов Car обычный массив. Естественно, мы вправе, если так
// больше нравится, работать с ArrayList
private Car[] carArray;
public Cars()
{ carArray = new Car[10];
}
// Индексатор позволяет обратиться к объекту Car по его порядковому
// номеру в наборе (числовому индексу)
public Car this[int pos]
{ get // Метод для доступа к элементу в массиве
{ if(pos < 0 || pos > 10)
throw new IndexOutOfRangeException("Out of range!");
else
return (carArray[pos]);
}
// Метод для добавления новых объектов в массив
set { carArray[pos] = value; }
}
}
Слайд 6Свойство Length
// Используем System.Array.Length? Нельзя!
Console.WriteLine("Cars in stock: {0}", carLot.Length);
// Если
очень хочется, то придется создать его самостоятельно
public class Cars
{
…
public int Length()
{
// Код для получения информации о количестве элементов в массиве
}
}
Слайд 8Использование операции + с встроенными типами
// Оператор сложения
int a =
100;
Int b = 240;
Int c = a+b; // c ==
340
// Складываем два строковых значения
string S1 = "Hello";
string S2 = " world!";
string S2 = s1+s2; // s3 == "Hello world!"
Слайд 9Создание перегруженных операторов
// Класс Point с перегруженными операторами
public class Point
{ private
int x, y;
public Point(){};
public Point(int xPos, int yPos){ x =
xPos; y = yPos; )
// Перегружаем оператор сложения
public static Point operator + (Point p1, Point p2)
{ Point newPoint = new Point(p1.x + p2.x, p1.y + p2.y);
return newPoint;
}
// ...и вычитания
public static Point operator - (Point p1, Point p2)
{ // Вычисляем значение новой координаты x
int newX = p1.x - p2.x;
if(newX < 0) throw new ArgumentOutOfRangeException();
// Вычисляем значение новой координаты y
int newY = p1.y - p2.y;
if(newY < 0) throw new ArgumentOutOfRangeException();
return new Point(newX, newY);
}
public override string ToString()
{ return "X pos: " + this.x + " Y pos: " + this.y;
}
}
Слайд 10Использование перегруженных операторов
// «Складываем» и «вычитаем» точки
public static int
Main(string[] args)
{
// Задаем две точки
Point ptOne = new Point(100, 100);
Point
ptTwo = new Point(40, 40);
// «Складываем» две точки, чтобы получить третью
Point bigPoint = ptOne + ptTwo;
Console.WriteLine("Here is the big point: {0}",
bigPoint.ToString());
// «Вычитаем» одну точку из другой, чтобы получить третью
Point minorPoint = bigPoint - ptOne;
Console.WriteLine("Just a minor point: {0}",
minorPoint.ToString());
return 0;
}
Слайд 11CLS не поддерживает перегрузку операторов
// Кроме перегруженного оператора, в этом
определении класса
// предусмотрен обычный метод с теми же возможностями
public class Point
{
...
// Метод AddPoints работает точно так же, как перегруженный оператор сложения
public static Point AddPoints (Point p1, Point p2)
{
return new Point(p1.x + p2.x, p1.y + p2.y);
}
// ...а метод SubtractPoints() — как перегруженный оператор вычитания
public static Point SSubtractPoints (point p1, Point p2)
{
// Вычисляем значение новой координаты x
int newX = p1.x - p2.x;
if(newX < 0)
throw new ArgumentOutOfRangeException();
// Вычисляем значение новой координаты y
int newY = p1.y - p2.y;
if(newY < 0)
throw new ArgumentOutOfRangeException();
return new Point(newX, newY);
}
}
Слайд 12Перегрузка операторов равенства
// Новое воплощение класса Point снабжено перегруженными операторами
//
равенства = = и !=, которые всегда(!) перегружаются в паре
public
class Point
{
public int x, y;
public Point(){}
public Point(int xPos, int yPos){x = xPos; y = yPos;}
...
public override bool Equals(object o)
{
if( ((Point)o).x == this.x && ((Point)o).y == this.y)
return true;
else
return false;
}
public override int GetHashCode()
{ return this.ToString().GetHashCode(); }
// А вот и сама перегрузка операторов равенства
public static bool operator ==(Point p1, Point p2)
{ return p1.Equals(p2); }
public static bool operator !=(Point p1, Point p2)
{ return !p1.Equals(p2); }
}
Слайд 13Перегрузка операторов равенства
// Применяем перегруженные операторы равенства
public static int Main(string[]
args)
{
...
if (ptOne == ptTwo) // Две точки совпадают?
Console.WriteLine("Same values!");
else
Console.WriteLine("Nope, different values.");
if
(ptOne != ptTwo) // Это разные точки?
Console.WriteLine("These are not equal.");
else
Console.WriteLine("Same values!");
}
Слайд 14Перегрузка операторов сравнения
// Применяем перегруженный оператор < для объектов класса
Car
public class CarApp
{
public static int Main(string[] args)
{
// Создаем массив
объектов класса Car
Car[] myAutos = new Car[5];
myAutos[0] = new Car(123, "Rusty");
myAutos[1] = new Car(6, "Mary");
myAutos[2] = new Car(6, "Viper");
myAutos[3] = new Car(13, "NoName");
myAutos[4] = new Car(6, "Chucky");
// Что меньше — Rusty или Chucky?
if (myAutos[0] < myAutos[4])
Console.WriteLine("Rusty is less than Chucky!");
else
Console.WriteLine("Chucky is less than Rusty!");
return 0;
}
}
Слайд 15Перегрузка операторов сравнения
// Класс Car с перегруженными операторами сравнения
public class
Car : IComparable
{
...
public int CompareTo (object o)
{
Car temp =
(Car) o;
if (this.CarID > temp.CarID) return 1;
if (this.CarID < temp.CarID) return -1;
else return 0;
}
public static bool operator < (Car c1, Car c2)
{ IComparable itfComp = (IComparable)c1; return (itfComp.CompareTo(c2) < 0); }
public static bool operator > (Car c1, Car c2)
{ IComparable itfComp = (IComparable)c1; return (itfComp.CompareTo(c2) > 0); }
public static bool operator <= (Car c1, Car c2)
{ IComparable itfComp = (IComparable)c1; return (itfComp.CompareTo(c2) <= 0); }
public static bool operator >= (Car c1, Car c2)
{ IComparable itfComp = (IComparable)c1; return (itfComp.CompareTo(c2) >= 0); }
}
Слайд 16Делегаты
callback function
указатели на функцию
Слайд 17Делегат – объект
public delegate void PlayGame(object Toy, int volume);
// компилятор
создаст следующие команды
public house PlayGame: System.MulticastDelegate
{
PlayGame(object target, int ptr);
// Синхронный метод Invoke()
public void virtual Invoke(object Toy, int volume);
// Асинхронная версия того же самого вызова
public virtual IAsyncResult BeginInvoke
(object Toy, int volume, AsyncCallback cb, object o);
public virtual void EndInvoke(IAsyncResult result);
}
Слайд 18Модификация класса Car
public class Car
{
...
// Новые переменные!
private bool
isDirty; // Испачкан ли наш автомобиль?
private bool shouldRotate; // Нужна
ли замена шин?
// Конструктор с новыми параметрами
public car(string name, int max, int curr, bool dirty, bool rotate)
{
...
isDirty = dirty; shouldRotate = rotate;
}
// Свойство для isDirty
public bool Dirty
{ get { return isDirty; }
set ( isDirty = value; }
}
// Свойство для shouldRotate
public bool Rotate
{ get { return shouldRotate; }
set { shouldRotate = value; }
}
Слайд 19Пример делегата
// Делегат – это класс, инкапсулирующий указатель
// на
функцию. В нашем случае этой функцией должен
// стать какой-то
метод, принимающий в качестве
// параметра объект класса Car и ничего не возвращающий
public delegate void CarDelegate(Car c);
Слайд 20Делегаты как вложенные типы
// Помещаем определение делегата внутрь определения
// класса
public
class Car : Object
{ …
public delegate void CarDelegate(Car c);
…
}
Слайд 22Применение делегатов
// В классе Garage предусмотрен метод, принимающий CarDelegate в
качестве параметра
public class Garage
{ ArrayList theCars = new
ArrayList(); // Набор машин в гараже
public Garage() // Создаем объекты машин в гараже
{ // Применяем новый вариант конструктора
theCars.Add(new car("Viper", 100, 0, true, false));
theCars.Add(new car("Fred", 100, 0, false, false));
theCars.Add(new car("BillyBob", 100, 0, false, true));
theCars.Add(new car("Bart", 100, 0, true, true));
theCars.Add(new car("Stan", 100, 0, false, true));
}
// Этот метод принимает Car.CarDelegate в качестве параметра. Таким образом,
// можно считать, что proc — это эквивалент указтеля на функцию
public void ProcessCars(Car.CarDelegate proc)
{
// Интересно, а куда мы передаем наш вызов?
Console.WriteLine("***** Calling: {0} *****", proc.Method.ToString());
// Еще одна проверка: вызываемый метод является статическим или обычным?
if (proc.Target != null) Console.WriteLine("->Target: {0}", proc.Target.ToString());
else Console.WriteLine("->Target is a static method");
// Для чего это все затевалось: при помощи делегата вызываем метод
// и передаем ему все объекты Car
foreach (car c in the theCars) proc(c);
}
}
Слайд 23Применение делегатов
// Гараж передает право выполнить всю работу этим статическим
функциям —
// наверное, у него нет хороших механиков...
public class
CarApp
{
// Первый метод, на который будет указывать делегат
public static void WashCar (Car c)
{ if (c.Dirty) Console.WriteLine("Cleaning a car");
else Console.WriteLine("This car is already clean...");
}
// Второй метод для делегата
public static void RotateTires (Car c)
{ if (c.Rotate) Console.WriteLine("Tires have been rotated");
else Console.WriteLine("Don't need to be rotated...");
}
public static int Main (string[] args)
{
Garage g = new Garage(); // Создаем объект Garage
g.ProcessCars(new Car.CarDelegate(WashCar)); // Моем все грязные машины
g.ProcessCars(new Car.CarDelegate(RotateTires)); // Меняем шины
return 0;
}
}
Слайд 24Анализ работы делегата
foreach (car c in the theCars) proc(c); //
proc(c) => CarApp.WashCar(c)
Слайд 25Многоадресность
// Добавляем во внутренний список указателей делегата сразу
// два
указателя на функции:
public static int Main(string[] args)
{
//
Создаем объект Garage
Garage g = new Garage();
// Создаем два новых делегата
Car.CarDelegate wash = new Car.CarDelegate(WashCar);
Car.CarDelegate rotate = new Car.CarDelegate(RotateTires);
// Чтобы объединить два указателя на функции в многоадресном
// делегате, используется перегруженный оператор сложения (+).
// В результате создается новый делегат, который содержит
// указатели на обе функции
g.ProcessCars(wash + rotate);
return 0;
}
Слайд 26Многоадресность
// Оператор + - это более удобный вариант статического метода
//
Delegate.Combine()
g.ProcessCars( (Car.CarDelegate) Delegate.Combine(wash,rotate) );
// Можно сохранить комбинированный делегат в отдельной
переменной
// Создаем два новых делегата
Car.CarDelegate wash = new Car.CarDelegate(WashCar);
Car.CarDelegate rotate = new Car.CarDelegate(RotateTires);
// Объединяем их в новый делегат
MulticastDelegate d = wash + rotate;
// Передаем комбинированный делегат методу ProcessCars()
g.ProcessCars( (Car.CarDelegate) d );
Слайд 27Многоадресность
// Статический метод Remove() возвращает новый делегат с удаленной
// записью
в таблице указателей на функции
Delegate washOnly = MulticastDelegate.Remove(d,rotate);
g.ProcessCars ( (CarDelegate)
washOnly);
// С помощью GetInvocationList можно вывести на консоль
// все указатели функции, хранящиеся во внутренней таблице
public void ProcessCars(Car.CarDelegate proc)
{
// Куда мы передаем вызов
Foreach (Delegate d in proc.GetInvocationList())
Console.WriteLine("***** Calling: {0} *****",d.Method.ToString());
…
Слайд 28Делегаты, указывающие на обычные функции
// Статические функции перестали быть статическими
и переместились
// во вспомогательный класс
public class ServiceDept
{
//
Уже не статическая!
public void WashCar (Car c)
{
if (c.Dirty) Console.WriteLine("Cleaning a car");
else Console.WriteLine("This car is already clean...");
}
// То же самое
public void RotateTires (Car c)
{
if (c.Rotate) Console.WriteLine("Tires have been rotated");
else Console.WriteLine("Don't need to be rotated...");
}
}
Слайд 29Делегаты, указывающие на обычные функции
// Делегаты будут указывать на обычные
методы класса ServiceDept
public static int Main(string[] args)
{
// Создаем гараж
Garage
g = new Garage();
// Создаем отдел обслуживания
ServiceDept sd = new ServiceDept();
// Гараж делегирует работу отделу обслуживания
Car.CarDelegate wash = new Car.CarDelegate(sd.WashCar);
Car.CarDelegate rotate = new Car.CarDelegate(sd.RotateTires);
MulticastDelegate d = wash + rotate;
// Обращаемся в гараж с просьбой сделать эту работу
g.ProcessCars((Car.CarDelegate)d);
return 0;
}
Слайд 31События
// Этот класс Car будет посылать пользователю сообщения о своем
состоянии
public class Car
{
...
// Переменная для хранения информации
о состоянии машины
private bool dead;
// Делегат. Он нужен, чтобы вызвать функцию или функции
// при возникновении события
public delegate void EngineHandler (string msg);
// Два события
public static event EngineHandler Exploded;
public static event EngineHandler AboutToBlow;
...
}
Слайд 32Вызов события
Указать имя события
И все необходимые параметры, которые требует соответствующий
делегат
Слайд 33Вызов события
// Вызываем нужное событие в заивисимости от состояния объекта
Car
public void SpeedUp(int delta)
{
// Если автомобиль уже вышел
из строя, генерируем событие Exploded
if (dead)
{ if (Exploded != null)
Exploded ("Sorry, this car is dead...");
}
else
{ currSpeed += delta;
// Приближаемся к опасной черте? Генерируем событие AboutToBlow
if (10 = = maxSpeed - currSpeed)
if (AboutToBlow != null)
AboutToBlow ("Careful, approaching terminal speed!");
// Все нормально! Работаем как обычно
if (crrSpeed >= maxSpeed) dead = true;
else Console.WriteLine("\tCurrSpeed = {0]", currSpeed);
}
}
Слайд 34Как работают события
Любое событие – набор двух скрытых методов:
add_Exploded()
remove_Exploded()
и статического
класса
Exploded: private static class
Слайд 35Прием событий
Подключение к прослушиванию события
ObjectVariable.EventName +=
new ObjectVariable.DelegateName(functionToCall)
Прекращаем прослушивание
ObjectVariable.EventName -=
new ObjectVariable.DelegateName(functionToCall)
Слайд 36Пример реализации реакции на событие
public class CarApp
{
public static int Main(string[]
args)
{
Car c1 = new Car("SlugBug", 100, 10);
// Устанавливаем приемники событий
Car.Exploded
+= new Car.EngineHandler(OnBlowUp);
Car.AboutToBlow += new Car.EngineHandler(OnAboutToBlow);
// Разгоняем машину (при этом будут инициированы события)
for (int i = 0; i < 10; i++) c1.SpeedUp(20);
// Отключаем приемники событий
Car.Exploded -= new Car.EngineHandler(OnBlowUp);
Car.AboutToBlow -= new Car.EngineHandler(OnAboutToBlow);
// Теперь реакции на события нет!
for (int i = 0; i < 10; i++) c1.SpeedUp(20);
return 0;
}
…
Слайд 37Пример реализации реакции на событие
// Приемник OnBlowUp
public static void OnBlowUp
(string s)
{
Console.WriteLine("Message from car: {0}", s);
}
// Приемник OnAboutToBlow
public static void
OnAboutToBlow (string s)
{
Console.WriteLine("Message from car: {0}", s);
}
}
Слайд 38Несколько приемников событий
public class CarApp
{
public static int Main(string[] args)
{
// Создаем
объект класса Car как обычно
Car c 1 = new Car("SlugBug",
100, 10);
// Устанавливаем приемники событий:
Car.Exploded += new Car.EngineHandler(OnBlowUp);
Car.Exploded += new Car.EngineHandler(OnBlowUp2);
Car.AboutToBlow += new Car.EngineHandler(OnAboutToBlow);
// Разгоняем машину (при этом будут инициированы события)
for(int i = 0; i < 10; i++)
c1.SpeedUp(20);
// Отключаем приемники событий
Car.Exploded -= new Car.EngineHandler(OnBlowUp);
Car.Exploded -= new Car.EngineHandler(OnBlowUp2);
Car.AboutToBlow -= new Car.EngineHandler(OnAboutToBlow);
...
}
Слайд 39Несколько приемников событий
// Первый приемник события Exploded
public static void OnBlowUp
(string s)
{
Console.WriteLine("Message from car: {0}", s);
}
// Второй приемник события Exploded
public
static void OnBlowUp2 (string s)
{
Console.WriteLine("-> AGAIN I say: {0}", s);
}
// Приемник для события AboutToBlow
public static void OnAboutToBlow(string s)
{
Console.WriteLine("Message from car: {0}", s);
}
}
Слайд 40Объекты как приемники событий
// Служебный класс для приемников событий
public class
CarEventSink
{
// Приемник OnBlowUp для события Exploded
public void OnBlowUp(string s)
{
Console.WriteLine("Message from
car: {0}", s);
}
// Приемник OnBlowUp2 для того же события
public void OnBlowUp2(string s)
{
Console.WriteLine(""->AGAIN I say: {0}", s);
}
// Приемник OnAboutToBlow для события AboutToBlow
public void OnAboutToBlow(string s)
{
Console.WriteLine("Message from car: {0}", s);
}
}
Слайд 41Объекты как приемники событий
// Обратите внимание на создание объекта CarEventSink
и его использование
public class CarApp
{
public static int Main(string[] args)
{
Car c1
= new Car("SlugBug", 100, 10);
// Создаем объект с приемниками
CarEventSink sink = new CarEventSink();
// Устанавливаем приемники
Car.Exploded += new Car.EngineHandler(sink.OnBlowUp);
Car.Exploded += new Car.EngineHandler(sink.OnBlowUp2);
Car.AboutToBlow += new Car.EngineHandler(sink.OnAboutToBlow);
for(int i = 0; i < 10; i++) c1.SpeedUp(20);
// Отключаем приемники событий
Car.Exploded -= new Car.EngineHandler(sink.OnBlowUp);
Car.Exploded -= new Car.EngineHandler(sink.OnBlowUp2);
Car.AboutToBlow -= new Car.EngineHandler(sink.OnAboutToBlow);
return 0;
}
}
Слайд 42Реализация обработки событий с использованием интерфейсов
callback interfaces
Слайд 43Интерфейс для работы с событиями
public interface IEngineEvents
{
void AboutToBlow (string msg);
void
Exploded (string msg);
}
Класс с методами-приемниками событий
public class CarEventSink : IEngineEvents
{
public
void AboutToBlow (string msg)
{ Console.WriteLine(name + " reporting: " + msg);
}
public void Exploded (string msg)
{ Console.WriteLine(name + " reporting: " + msg);
}
}
Слайд 44Использование ArrayList
// Этот вариант класса Car не использует ни делегатов
C#, ни событий
// в их обычном понимании
public class
Car
{
// Хранилище для подключенных приемников
ArrayList itfConnections = new ArrayList();
// Метод для подключения приемников
public void Advise (IEngineEvents itfClientImpl)
{
itfConnections.Add (itfClientImpl);
}
// Метод для отключения приемников
public void Unadvise (IEngineEvents itfClientImpl)
{
itfConnections.Remove (itfClientImpl);
}
...
}
Слайд 45Протокол обработки событий, основанный на интерфейсах:
class Car
{
...
public void SpeedUp(int
delta)
{
// Если машина развалилась, посылаем сообщение о
событии Exploded
// каждому приемнику для этого события
if (dead)
foreach (IEngineEvents e in itfConnections)
e.Exploded ("Sorry, this car is dead...");
else
currSpeed += delta;
// Если автомобиль только близок к худшему исходу, работаем
// с другим событием
if (10 = = maxSpeed - currSpeed)
foreach (IEngineEvents e in itfConnections)
e.AboutToBlow ("Careful buddy! Gonna blow!");
// А у нас все в порядке
if (currSpeed >= maxSpeed)
dead = true;
else
Console.WriteLine("\tCurrSpeed = {0}", currSpeed);
}
}
Слайд 46Использование интерфейса обратного вызова
// Создаем объект класса Car и реагируем
на его события
public class CarApp
{
public static int Main(string[] args)
{
Car c1 = new car("SlugBug", 100, 10);
// Создаем объект с методами-приемниками
CarEventSink sink = new CarEventSink();
// Передаем объекту класса Car ссылку на объект с методами-приемниками
c1.Advise(sink);
// Разгоняем автомобиль, чтобы наступили события
for (int i=0; i<10; i++)
c1.SpeedUp(20);
// Отключаем приемник
c1.Unadvise(sink);
return 0;
}
}