Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка - Основи Програмування C_.doc
Скачиваний:
46
Добавлен:
18.12.2018
Размер:
1.44 Mб
Скачать

Делегати, події та обробники подій

В цьому розділі будуть розглянуті деякі питання, пов’язані з реалізацією реакції програми на події, що можуть виникати під час її роботи, – це так звана модель подій в мові С#. Події (events) найчастіше виникають при розробці програм, орієнтованих на роботу під Windows. Ними можуть бути натискання певних клавіш або кнопок, «кліки» миші, тощо. З технічної точки зору при цьому необхідні засоби, які дозволяють викликати деякий конкретний метод, як реакцію на певну подію, що сталась в системі під час роботи. При цьому, який саме метод має зреагувати на подію, звісно, не відомо в момент компіляції програми. В багатьох мовах програмування, зокрема в С++, цій меті слугують вказівники на функцію, які виступають в ролі параметрів функції-обробника події. Проте використання прямої адресації, пов’язаної із застосуванням вказівників, не завжди бездоганне з точки зору безпеки програми. Мова С# має безпечний засіб реалізації подієорієнтованої моделі програми – це делегати.

1. Делегати (delegate).

Отже, делегати призначені для збереження відомостей про метод, який може бути переданий у метод-обробник події в ролі його параметру. Фактично вони «делегують» деякий метод для виконання іншим методом. З синтаксичної точки зору делегат – це посилальний тип даних (reference type), що визначає повну сигнатуру методу разом із його типом результату. При створенні екземплярів класу ми спочатку визначаємо клас, лише потім створюємо і використовуємо відповідні об’єкти. Так само маємо поступити і з делегатами – спочатку декларуємо тип делегата, потім створюємо і використовуємо його екземпляри, передаючи їх у метод для виконання ним.

Для декларації делегату використовується службове слово delegate, після якого просто вказується результат та сигнатура методу, що відповідає даному типу делегату. Наведемо приклади декларацій делегатів:

// делегат DoSth представляє функції, що приймають 1 рядковий

// параметр та не повертають результат

delegate void DoSth(string x);

// делегат MyFun представляє функції, що приймають 1 дійсний

// параметр і повертає дійсне число

delegate double MyFun(double x);

Делегат насправді являє собою клас, успадкований від класу System.Delegate. Тому декларувати делегат можна як у просторі імен, так і всередині іншого класу.

Для ілюстрації корисності делегатів, розглянемо типовий приклад. Нехай необхідно програмно побудувати графік скалярної функції виду . Для скінченого набору функцій можна написати метод, який повертає результат виклику функції . Програма з методом для обрахунку лише трьох функцій (синусу, косинусу та модуля) наведена нижче:

using System;

namespace Demo

{

class Program

{

static void Main(string[] args)

{

// косинус в точці 1

Console.WriteLine(Method(1, 1));

// синус в точці 1

Console.WriteLine(Method(2, 1));

// модуль в точці 1

Console.WriteLine(Method(3, 1));

}

// Метод обраховує значення функції,

// визначеної номером iFunctionNumber, в точці x

static double Method(int iFunctionNumber, double x)

{

if (iFunctionNumber == 1)

return Math.Cos(x);

if (iFunctionNumber == 2)

return Math.Sin(x);

if (iFunctionNumber == 3)

return Math.Abs(x);

else return 0;

}

}

}

Як можна бачити, даний код є важко піддається подальшому розширенню кількості функцій, тому що всі можливі функції, значення яких можуть в ньому обраховуватись, повинні бути жорстко закодовані (hard coded) в метод Method класу Program. Подібні програми являють собою зразок поганого стилю програмування. Спробуємо покращити код програми, використавши в ролі параметру методу Method() не номер функції, а делегат для цього методу.

using System;

namespace Demo

{

// делегат MyFun представляє функції, що приймають 1 дійсний

// параметр та повертають дійсний результат

delegate double MyFun(double x);

class Program

{

static void Main(string[] args)

{

// косинус в точці 1

Console.WriteLine(Method(Math.Cos, 1));

// синус в точці 1

Console.WriteLine(Method(Math.Sin, 1));

// модуль в точці 1

Console.WriteLine(Method(Math.Abs, 1));

}

// Метод обраховує значення функції,

// що визначається делегатом MyFun, в точці x

static double Method(MyFun f, double x)

{ return f(x); }

}

}

Зауваження

  1. Як можна помітити, тепер код програми значно скоротився.

  2. Даний метод Method() має першим параметром функцію-делегат, значення якої буде повертатись як результат цього методу.

  3. Аргументом для функції з параметром-делегатом може бути довільний метод з іншого класу, причому як статичний, так і метод екземпляру.

  4. В ролі аргументу для параметра-делегата також може виступати метод, безпосередньо написаний в даному коді – так званий вбудований метод.

Вдосконалимо попередню програму, щоб проілюструвати зміст двох останніх зауважень та продемонструємо використання оператора += для делегатів. Оператор += для делегатів дозволяє побудувати послідовність методів-делегатів, які будуть виконані один за одним («ланцюжком») – таким чином буде організована та виконана ціла черга методів. (Так само можна використати і оператор -= для виключення членів черги із ланцюжка обробки). В реальному житті ця ситуація дійсно нагадує чергу, коли всі, хто в ній знаходяться, послідовно виконують одну й ту саму дію:

using System;

namespace Demo

{

// делегат MyFun представляє функції, що приймають 1 дійсний

// параметр та повертають дійсний результат

delegate double MyFun(double x);

class Program

{

static void Main(string[] args)

{

// Визначаємо делегат f, який поки що є нульовим посиланням

MyFun f = null;

// включимо до ланцюжка делегатів статичний метод:

f += Math.Sin;

// включимо до ланцюжка деякий тестовий метод TestFun з

// відповідною сигнатурою:

f += Program.TestFun;

// включимо до ланцюжка вбудований метод – його код

// визначений безпосередньо тут:

f += delegate(double x)

{

return x * x - 1;

};

// запускаємо чергу-ланцюжок в метод Method():

Console.WriteLine(Method(f, 1));

}

// Визначення тестового методу TestFun, сигнатура якого

// відповідає делегату

static double TestFun(double x)

{

return x * x * x;

}

// Метод обраховує значення функції,

// визначеної делегатом f, в точці x

static double Method(MyFun f, double x)

{ return f(x); }

}

}

В результаті виконання цього прикладу на екрані побачимо 0. Це результат виконання інструкції Console.WriteLine (Method(f, 1));. В ній викликається метод Method (f, 1), в якому змінна f одержує по черзі посилання на всі методи із ланцюжка делегатів, а на екрані ми бачимо лише результат звертання до останнього з них – вбудованого методу, який повертає значення .

Зауваження

  1. Оскільки делегат є представником reference-типу, то його значенням за замовченням є null. Таким чином, якщо в метод замість функції буде переданий нульовий вказівник null (у випадку відсутньої ініціалізації делегату), відбудеться виключення NullReferenceException. Тому, коректним підходом до використання екземпляру-делегату є перевірка його значення на нерівність нульовій адресі null.

  2. У випадку побудови ланцюжка з функцій-делегатів, що повертають значення, та виклику методу з аргументом-делегатом, послідовно виконаються всі функції, що входять до ланцюжка, а результатом всього ланцюжка буде результат виконання останньої функції в ланцюжку (last wins).