Similar presentations:
Дополнительные возможности классов. Лекция #4
1. Язык С#
Дополнительные возможности классовЛекция #4
2. Создание пользовательского индексатора
Оператор [ ]3. Оператор индекса [ ]
// Объявляем массив целых чиселint [] myInts = {10,9,100,432,9874}
// Применяем оператор индекса для обращения к элементу
for (int j=0; j<myInts.Length; j++)
Console.WriteLine("Index {0} = {1}",j, myInts[j]);
3
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);
}
}
}
4
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; }
}
}
5
6. Свойство Length
// Используем System.Array.Length? Нельзя!Console.WriteLine("Cars in stock: {0}", carLot.Length);
// Если очень хочется, то придется создать его самостоятельно
public class Cars
{
…
public int Length()
{
// Код для получения информации о количестве элементов в массиве
}
}
6
7. Перегрузка операторов
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!"
8
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;
}
}
9
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;
}
10
11. CLS не поддерживает перегрузку операторов
// Кроме перегруженного оператора, в этом определении класса// предусмотрен обычный метод с теми же возможностями
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);
}
}
11
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); }
}
12
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!");
}
13
14. Перегрузка операторов сравнения
// Применяем перегруженный оператор < для объектов класса Carpublic class CarApp
{
public static int Main(string[] args)
{
// Создаем массив объектов класса Car
Car[] myAutos = new Car[5];
myAutos[0]
myAutos[1]
myAutos[2]
myAutos[3]
myAutos[4]
=
=
=
=
=
new
new
new
new
new
Car(123, "Rusty");
Car(6, "Mary");
Car(6, "Viper");
Car(13, "NoName");
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;
}
}
14
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); }
}
15
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);
}
17
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; }
}
18
19. Пример делегата
////
//
//
Делегат – это класс, инкапсулирующий указатель
на функцию. В нашем случае этой функцией должен
стать какой-то метод, принимающий в качестве
параметра объект класса Car и ничего не возвращающий
public delegate void CarDelegate(Car c);
19
20. Делегаты как вложенные типы
// Помещаем определение делегата внутрь определения// класса
public class Car : Object
{ …
public delegate void CarDelegate(Car c);
…
}
20
21. Члены System.MulticastDelegate
MethodTarget
Combine()
GetInvocationList()
Remove()
Свойство возвращает имя метода, на который
указывает делегат
Если делегат указывает на метод – член класса,
то Target – имя класса, если NULL, то делегат
указывает на статический метод
Статический метод для создания делегата,
указывающего сразу на несколько функций
Возвращает массив типов Delegate, каждый из
которых представляет собой запись во внутреннем
списке указателей на функции делегата
Статический метод удаляет делегат из списка
указателей на функцию
21
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);
}
}
22
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;
}
}
23
24. Анализ работы делегата
foreach (car c in the theCars) proc(c); // proc(c) => CarApp.WashCar(c)24
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;
}
25
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 );
26
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());
…
27
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...");
}
}
28
29. Делегаты, указывающие на обычные функции
// Делегаты будутpublic static int
{
// Создаем
Garage g =
указывать на обычные методы класса ServiceDept
Main(string[] args)
гараж
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;
}
29
30. События
events31. События
// Этот класс Car будет посылать пользователю сообщения о своем состоянииpublic class Car
{
...
// Переменная для хранения информации о состоянии машины
private bool dead;
// Делегат. Он нужен, чтобы вызвать функцию или функции
// при возникновении события
public delegate void EngineHandler (string msg);
// Два события
public static event EngineHandler Exploded;
public static event EngineHandler AboutToBlow;
...
}
31
32. Вызов события
• Указать имя события• И все необходимые параметры, которые
требует соответствующий делегат
32
33. Вызов события
// Вызываем нужное событие в заивисимости от состояния объекта Carpublic 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);
}
}
33
34. Как работают события
Любое событие – набор двух скрытых методов:• add_Exploded()
• remove_Exploded()
и статического класса
• Exploded: private static class
34
35. Прием событий
• Подключение к прослушиванию событияClassName.EventName +=
new ObjectVariable.DelegateName(functionToCall)
• Прекращаем прослушивание
ClassName.EventName -=
new ObjectVariable.DelegateName(functionToCall)
35
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;
}
…
36
37. Пример реализации реакции на событие
// Приемник OnBlowUppublic 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);
}
}
37
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);
...
}
38
39. Несколько приемников событий
// Первый приемник события Explodedpublic 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);
}
}
39
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);
}
}
40
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;
}
}
41
42. Реализация обработки событий с использованием интерфейсов
callback interfaces43. Интерфейс для работы с событиями
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);
}
}
43
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);
}
...
}
44
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);
}
}
45
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;
}
}
46