Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
веб.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
933.89 Кб
Скачать

2 Делегати

 

Делегати призначені для ситуацій, коли потрібно передати метод іншому методу. Щоб побачити, що це значить, розглянемо наступний рядок коду:

 

int i = int.Parse("99");

 

Передаючи дані методу в якості параметра, ви не особливо замислюєтеся про це. З цієї причини ідея передачі іншого методу замість даних може здатися дещо дивною. Однак бувають випадки, коли є метод, який робить щось, але замість того, щоб виконувати операцію над даними, йому може знадобитися звернення до іншого методу. Щоб ще більше ускладнити ситуацію, скажімо, що під час компіляції не відомо, який саме метод потрібно викликати. Ця інформація доступна тільки під час виконання, а тому повинна бути передана першому методу у вигляді параметра. Все це може виглядати трохи заплутаним, тому пояснимо на ряді прикладів.

Запуск потоків і завдань. У С# існує можливість повідомити компілятору, що потрібно запустити деяку нову послідовність виконання паралельно тій, що працює в даний момент. Така послідовність називається потоком (thread), а його запуск здійснюється за допомогою методу Start() одного з базових класів - System.Threading.Thread. Якщо ви повідомляєте комп'ютера про необхідність запуску нової послідовності виконання, то також повинні повідомити йому, звідки саме її слід почати, тобто виклик якого методу повинен її запускати. Іншими словами, метод Thread.Start() повинен отримати параметр, який вказує метод, що підлягає виклику потоком.

Узагальнені бібліотечні класи. Багато бібліотек містять код для виконання різноманітних стандартних завдань. Зазвичай такі бібліотеки можуть бути самодостатніми - в тому сенсі, що при їх написанні ви точно знаєте, як слід вирішувати завдання. Однак іноді задача може включати підзадачу, про яку знає тільки індивідуальний клієнтський код, що використовує цю бібліотеку. Наприклад, припустимо, що потрібно написати клас, який приймає масив об'єктів і сортує їх за зростанням. Частиною процесу сортування повинно бути повторюване порівняння двох об'єктів з масиву, щоб визначити, який з них потрібно розташувати першим. Якщо ви хочете забезпечити своєму класу можливість порівнювати будь-які об'єкти, то він не може знати заздалегідь, як виконувати такі порівняння. Тільки клієнтський код, що використовує клас, може повідомити йому, як слід виконувати порівняння конкретних об'єктів, масив яких необхідно відсортувати. Клієнтський код повинен передати вашого класу подробиці щодо того, який метод викликати для виконання порівняння.

Події. Головна ідея тут полягає в тому, що часто доводиться мати справу з кодом, який повинен бути проінформований про виникнення якихось подій. У програмуванні графічного інтерфейсу користувача багато подібних ситуацій. Коли подія відбувається, виконуюча середу повинна знати, який метод необхідно викликати. Це робиться передачею методу, обробляє події, параметра-делегата.

 

2.1 Оголошення делегатів в С#

Коли ви збираєтеся використовувати клас у С#, то робите це в два етапи. Першим ділом, ви повинні визначити клас, тобто повідомити компілятору, з яких даних і методів він складається. Потім (якщо тільки не використовуються одні лише статичні методи), створюється об'єкт цього класу. З делегатами те ж саме. Спочатку дається визначення делегата, який потрібно буде використовувати. У випадку з делегатами їх визначення означає повідомлення компілятору, якого роду метод буде представляти делегат. Потім потрібно створити один або більше примірників цього делегата. При цьому "за лаштунками" компілятор створює клас, що представляє делегата.

Синтаксис оголошення делегатів виглядає наступним чином:

 

delegate void IntMethodlnvoker(uint x);

 

В даному випадку визначений делегат по імені IntMethodlnvoker і зазначено, що кожен примірник цього делегата може містити посилання на метод, який приймає один параметр типу uint і повертає тип void. Ключовим моментом для розуміння делегатів є те, що вони надзвичайно безпечні щодо типів. Коли ви визначаєте делегат, то повідомляєте йому повну сигнатуру методу, який він може представляти.

Припустимо, що потрібно визначити делегат по імені TwoLongsOp, який повинен представляти функцію, приймаючу два параметри типу long і повертаючу double.

Це можна зробити наступним чином:

 

delegate double TwoLongsOp(long first, long second);

 

Щоб визначити делегат, який представляє метод, що не приймає параметрів і повертає string, можна записати і так:

 

delegate string GetAString();

 

Синтаксис подібний визначенню методу за винятком того, що за таким визначенням не слідує тіло методу, а саме визначення випереджається префіксом delegate. Оскільки, по суті, при такому оголошенні визначається новий клас, оголошення делегата можна помістити в будь-яке місце, де може знаходитися оголошення класу - тобто усередині якогось класу, або поза будь-якого класу, або в просторі імен об'єкта вищого рівня. В залежності від того, якою повинна бути область видимості делегата, до нього можна застосовувати будь-який звичайний модифікатор доступу - publicprivate,protected і т.д.

 

public delegate string GetAString ();

 

Після того, як делегат визначений, можна створювати його екземпляри - з тим, щоб зберігати подробиці певного методу.

 

 

 

2.2 Використання делегатів в С#

У наступному фрагменті коду демонструється використання делегата. Це просто подовжений спосіб викликуToString() для int:

 

private delegate string GetAString();

static void Main()

{

int x = 40;

GetAString firstStringMethod = new GetAString(x.ToString);

Console.WriteLine("Строка равна " + firstStringMethod());

// З firstStringMethod, ініціалізованим х.ToString (),

// приведено оператор, еквівалентний наступному

// Console.WriteLine("Строка равна " + х.ToString ());

 

У цьому коді створюється екземпляр делегата типу GetAString і ініціалізується таким чином, що посилається на методToString() цілочисельний змінної х. Делегати в С# завжди мають конструктор з одним параметром - методом, на який має посилатися делегат. Сигнатура цього методу повинна збігатися з сигнатурою, оголошеної делегатом. Тобто в даному випадку ви отримаєте помилку компіляції, якщо спробуєте ініціалізувати змінну firstStringMethod будь-яким методом, який не буде повертати string або ж буде приймати параметри. Зверніть увагу, що оскільки int.ToString() - метод примірника (а не статичний), то для правильної ініціалізації делегата потрібно вказати екземпляр (х) разом з ім'ям методу.

У наступному рядку коду делегат використовується для відображення стрічки. У будь-якому коді вказівка ​​імені примірника делегата з подальшими дужками, що містять будь-які параметри, дає в точності той же ефект, як і виклик методу, поміщеного в оболонку делегата. Таким чином, в попередньому фрагменті коду оператор Console.WriteLine()повністю еквівалентне тому, що представлений в закоментованому рядку.

 

 

 

2.3 Делегати Action<T> і Func<T>

Замість визначення нового типу делегата з кожним типом параметра і повернення можна використовувати делегатиAction<T> і Func<T>. Узагальнений делегат Action<T> призначений для посилання на метод, який повертає void. Цей клас делегата існує в різних варіантах, так що йому можна передавати до 16 різних типів параметрів.

Клас Action без узагальненого параметра призначений для виклику методів без параметрів, Action<in Т> - для виклику методу з одним параметром, Action<in Tl, in T2> - для виклику методу з двома параметрами і Action<in Tl, in T2, in T3, in T4, in T5, in T6, in T7, in T8> - для виклику методу з вісьмома параметрами.

Делегати Func<T> можуть використовуватися аналогічним чином. Func<T> дозволяє викликати методи з типом повернення. Подібно Action<T>Func<T> визначений в різних варіантах для передачі до 16 типів параметрів і типу повернення.

Func<out Tresult> - тип делегата для виклику методу з типом повернення, але без параметрів, Func<int Tl, out TResult> - для методу з одним параметром, a Func<in Tl, in T2, in T3, in T4, out TResult> - для методу з чотирма параметрами.

 

2.4 Анонімні методи

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

Синтаксис визначення делегата з анонімним методом не змінюється. Відмінність проявляється при створенні екземпляра цього делегата. У наступному дуже простому консольному додатку показано, як працює делегат з анонімним методом.

 

using System;

namespace Wrox.ProCSharp.Delegates

{

  class Program

  {

    static void Main()

    {

string mid = ", середня частина,";

Func<string, string> anonDel = delegate(string param)

{

param += mid;

param += " а це додано до стрічки.";

return param;

};

Console.WriteLine(anonDel("Початок стрічки"));

    }

  }

}

 

Делегат Func<string, string> приймає єдиний рядковий параметр і повертає рядок. anonDel - змінна цього типу делегата. Замість присвоювання цієї змінної імені відомого методу використовується простий блок коду, попереджений ключовим словом delegate, за яким слідує рядковий параметр.

Як бачите, в блоці коду використовується стрічкова змінна рівня методу mid, яка визначена поза анонімним методом і додається до переданого параметру. Потім код повертає стрічкове значення. Коли викликається делегат, йому передається параметр-рядок і повертається стрічка для виводу на консолі.

Вигода від анонімних методів пов'язана зі скороченням коду, який потрібно писати. Визначати метод, який повинен використовуватися делегатом, не потрібно. Ця перевага особливо наочно проявляється при визначенні делегата для події. Це дозволяє знизити складність коду, особливо у разі визначення декількох подій. З анонімними методами код не виконується швидше. Компілятор все одно визначає метод і призначає йому автоматично згенероване ім'я, яке вам знати не обов'язково.

При використанні анонімних методів необхідно дотримуватися низки правил. Усередині анонімного методу не можна застосовувати керуючі оператори (breakgoto або continue) для переходу до коду, що знаходиться за межами тіла анонімного методу. Вірно також і зворотне: передача управління ззовні в тіло анонімного методу не допускається.

У тілі анонімного методу не може використовуватися небезпечний код. До того ж параметри ref і out, які застосовуються поза анонімного методу, в його тілі не доступні. Всі інші змінні, оголошені поза анонімного методу, можуть ним використовуватися.

Якщо необхідно написати деяку функціональність більше одного разу, то в цьому випадку краще не використовувати анонімні методи. Переважно одного разу написати іменований метод і посилатися на нього необхідну кількість разів.

Починаючи з версії С# 3.0, замість анонімних методів можна використовувати лямбда-вирази.

 

2.5 Лямбда-вирази

Починаючи з С# 3.0, доступний новий синтаксис для призначення реалізації коду делегатам, названий лямбда-виразами (lambda expression). Лямбда-вирази можуть використовуватися скрізь, де є параметр типу делегата. Нижче показаний попередній приклад, в якому застосовувалися анонімні методи, адаптований для використання лямбда-виразу.

Синтаксис лямбда-виразів простіший синтаксису анонімних методів. У випадку, якщо підлягаючий виклику метод має параметри, а ці параметри непотрібні, синтаксис анонімних методів простіше, оскільки в цьому випадку вказувати параметри не потрібно.

 

 

 

 

 

using System;

namespace Wrox.ProCSharp.Delegates

{

  class Program

  {

    static void Main()

    {

string mid = ", середня частина,";

Func<stnng, string> lambda = param = >

{

param += mid;

param += " а це додано до стрічки.));

return param;

};

Console .WnteLine (lambda ("Початок стрічки") ) ;

    }

  }

}

 

У лівій частині лямбда-операції => перераховані параметри, необхідні анонімному методу. Права частина лямбда-вирази описує реалізацію методу, що присвоюється змінної lambda.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]