- •Тема 1 Колекції Структури даних
- •Неузагальнені колекції
- •Деякі інтерфейси неузагальнених колекцій
- •Деякі класи неузагальнених колекцій
- •Узагальнені колекції
- •Тема 2 Файловий ввід-вивід Організація файлового вводу-виводу
- •Байтовий ввід-вивід у файл
- •Двійковий ввід-вивід у файл
- •Символьний ввід-вивід у файл
- •Організація роботи із файлами даних стандарту xml
- •Простір імен System.Xml
- •Класи XmlNode та XmlLinkedNode
- •Клас XmlDeclaration
- •Класи XmlComment та XmlAttribute
- •Клас XmlElement
- •Клас XmlDocument
- •Приклад
- •Тема 3 Делегати та події Клас delegate
- •Делегати в якості параметрів
- •Анонімні методи та узагальнені делегати System.Action, System.Func
- •Комбіновані делегати та шаблон «спостерігач»
- •Події: створення та обробка
Анонімні методи та узагальнені делегати System.Action, System.Func
Насправді у наведеному прикладі ми використали спрощений синтаксис для делегата, який можна використовувати починаючи із платформи версії 2. Воно полягає в тому, що в окремих випадках створення екземпляра делегата здійснюється автоматично − як, наприклад, автоматично створюється екземпляр делегата, який відповідає функції Sin. Друге спрощення полягає у можливості створення так званих анонімних методів − фрагментів коду, які описуються безпосередньо у тому місці, де використовується делегат. Наприклад:
root = Solver.GetRoot(delegate (double x){return -x*x+4;}, -1, 4, 1e-10, Solver.SecantPoint, out count); Console.WriteLine("root= {0}", root); Console.WriteLine("count= {0}", count); |
Також у розглянутому прикладі можна було обійтися без явного оголошення делегатів ElementaryFunction та GetApproximation. Справа в тому, що починаючи з версії 3.5 у просторі імен System визначено ряд узагальнених делегатів Action та Func, які забезпечують гнучкість делегатів із загальними параметрами. Зокрема, їх можна використовувати для передачі методу в якості параметра без явного оголошення делегата користувачем. Так, наприклад, узагальнений делегат Func<T,TResult> інкапсулює метод з одним параметром, та повертає значення типу, вказаного в параметрі TResult. Використовуючи цей узагальнений делегат ми могли б реалізувати, наприклад, у класі Solver метод MidPoint так:
double MidPoint(Func<double, double> f, double a, double b) { return (a + b) / 2; } |
Комбіновані делегати та шаблон «спостерігач»
Для забезпечення гнучкого, динамічного зв’язку між об’єктами під час виконання програми застосовується наступна стратегія. Об’єкт, який називається джерелом, при зміні свого стану, який може бути цікавий для інших об’єктів, відправляє їм повідомлення. Ці об’єкти називаються спостерігачами. Отримавши повідомлення, спостерігач опитує джерело для того, щоб синхронізувати із ним свій стан. Описана стратегія відома під назвою шаблон «спостерігач». Прикладом такої стратегії може бути зв’язок електронної таблиці із створеними на її основі діаграмами.
Реалізація шаблону «спостерігач» спирається на поняття комбінованих делегатів. Розглянемо його. Як і все у С#, делегати є класами. Базовим класом для всіх делегатів є клас System.MulticastDelegate, який, в свою чергу, є похідним від класу System.Delegate. Від цих класів делегати успадковують можливість збереження посилань відразу на кілька методів. Ці посилання зберігаються у вигляді списку, який називають списком виклику. Комбінованим (груповим) делегатом називається делегат, список виклику якого містить більше одного посилання. При звертанні до групового делегата методи будуть виконуватися по черзі в порядку їх розміщення у списку виклику.
Додати метод у список виклику можна за допомогою двох перевантажень статичного методу Combine(), який з’єднує списки викликів делегатів (метод одержує як параметр масив делегатів), або двох делегатів (метод одержує як параметри два делегати). Також для цих цілей можна використовувати перевантажену операцію «+=». Другий операнд операції повинен бути делегатом або методом, який додається до списку виклику вихідного делегата.
Зворотну операцію – видалення методу зі списку виклику виконують статичний метод Remove() та перевантажена операція «-=». Видаляється останнє входження методу або списку викликів делегата зі списку викликів іншого делегата.
Тепер можемо реалізувати приклад із використанням шаблону «спостерігач». Розглянемо приклад, у якому демонструється схема сповіщення джерелом двох спостерігачів. У якості джерела буде виступати об’єкт класу прямокутних таблиць дійсних чисел. Перший спостерігач буде слідкувати за найбільшим значенням таблиці, а другий − за найменшим.
Реалізація шаблону «спостерігач» проводиться у декілька кроків.
Крок 1. Опис делегата методів класів спостерігачів, які будуть їх відповідною реакцією на зміну стану джерела.
У нашому прикладі зміна стану джерела буде означати зміну значення окремого елемента таблиці. У цьому випадку для взаємодії джерела і спостерігачів визначимо наступний делегат:
// опис делегата методу, що є реакцією на зміну стану джерела delegate void ReactionObserver(object sender, int i, int j); |
Для забезпечення оберненого зв’язку між спостерігачем та джерелом визначений нами делегат містить параметр типу object, через який буде передаватися посилання на джерело, тобто на об’єкт, що відправив повідомлення про зміну свого стану. Через два інші параметри будуть передаватися індекси елемента таблиці, значення якого було змінено.
Крок 2. Забезпечення процесу реєстрації делегатів спостерігачів. З цією метою у класі джерела оголошується список спостерігачів у вигляді екземпляра делегата, визначеного на першому кроці:
// клас джерела class MyTable { ReactionObserver MyObservList; } |
В цей екземпляр будуть заноситись методи спостерігачів, які є їх реакціями на зміну стану джерела. Цей процес називається реєстрацією делегатів. При реєстрації ім’я методу додається до списку. Програмно це реалізовується у вигляді методу класу, як, наприклад, для нашого випадку:
class MyTable { ... public void RegisterOnValueChange(ReactionObserver method) { MyObservList += method; } } |
Крок 3. Сповіщення всіх зареєстрованих спостерігачів про зміну стану програмно зводиться до виклику методу, який забезпечує виклик усіх методів-реакцій спостерігачів, які були зареєстровані на попередньому кроці. Реалізація такого методу для нашого прикладу є наступною:
class MyTable { ... // метод сповіщення всіх зареєстрованих спостерігачів про зміну стану void OnValueChange(int i0, int j0) { if (MyObservList != null) MyObservList(this, i0, j0); } } |
Тепер наведемо кінцеву реалізацію класу джерела.
class MyTable { double[,] table; public MyTable(int n, int m) { table = new double[n,m]; Random rndValue = new Random(); for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) this[i, j] = rndValue.Next(20); }
public double this[int i, int j] { get { return table[i, j]; } set { table[i, j] = value; OnValueChange(i,j); } } public int RowCount { get { return table.GetLength(0); } } public int ColCount { get { return table.GetLength(1); } } public void Display() { Console.WriteLine("["); for (int i = 0; i < table.GetLength(0); i++) { for (int j = 0; j < table.GetLength(1); j++) Console.Write(" {0}", table[i, j]); Console.WriteLine(); } Console.WriteLine("]"); } // реалізація шаблону «спостерігач» ReactionObserver MyObservList; public void RegisterOnValueChange(ReactionObserver method) { MyObservList += method; } void OnValueChange(int i0, int j0) { if (MyObservList != null) MyObservList(this, i0, j0); } } |
У наведеній для нашого прикладу реалізації класу джерела виклик методу сповіщення зареєстрованих спостерігачів про зміну стану джерела здійснюється при зміні елемента через індексатор класу. Відмітимо, що у нашому класі це єдиний спосіб зміни значення елемента у програмі.
Крок 4. Реалізація методів-реакцій у класах спостерігачів є завершальним кроком реалізації шаблону «спостерігач». При цьому слід пам’ятати, що сигнатура методів має відповідати делегату, який був визначений на першому кроці. Для нашого прикладу реалізація класів спостерігачів та відповідних методів реакцій буде такою:
// реалізація класу спостерігача за максимумом class obMaximum { double max; public obMaximum(MyTable tab) { max = tab[0, 0]; for (int i = 0; i < tab.RowCount; i++) for (int j = 0; j < tab.ColCount; j++) if (tab[i, j] > max) max = tab[i, j]; } // метод-реакція на зміну стану джерела public void NewMax(object sender, int i, int j) { double d =(sender as MyTable)[i, j]; if (max < d) { max = d; Console.WriteLine("Новий максимум: {0}", max); } } } // реалізація класу спостерігача за мінімумом class obMinimum { double min; public obMinimum(MyTable tab) { min = tab[0, 0]; for (int i = 0; i < tab.RowCount; i++) for (int j = 0; j < tab.ColCount; j++) if (tab[i, j] < min) min = tab[i, j]; } // метод-реакція на зміну стану джерела public void NewMin(object sender, int i, int j) { double d = (sender as MyTable)[i, j]; if (min > d) { min = d; Console.WriteLine("Новий мiнiмум: {0}", min); } } } |
Зв’язок «джерело-спостерігач» встановлюється під час виконання програми для кожного об’єкта окремо. Наприклад:
// створення об’єкта-джерела MyTable t = new MyTable(3,3); // створення об’єктів-спостерігачів obMaximum extr1 = new obMaximum(t); obMinimum extr2 = new obMinimum(t); // реєстрація спостерігачів на зміну стану джерела t.RegisterOnValueChange(extr1.NewMax); t.RegisterOnValueChange(extr2.NewMin); // зміна стану джерела t[1, 1] = 25; t[0, 0] = -2; |
Якщо спостерігач більше не хоче отримувати повідомлення від джерела, то можна видалити відповідний метод зі списку виклику делегата. Наприклад:
class MyTable { ... // відмова від спостереження за джерелом public void UnRegister(ReactionObserver method) { MyObservList -= method; } } |
Приклад.
// відмова від отримання повідомлень спостерігача за мінімумом t.UnRegister(extr2.NewMin); // зміна стану джерела t[0, 1] = -5; |
