Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSBasicCourse2ndedPodbelsky / CSBasicCourse2ndedPodbelsky.rtf
Скачиваний:
27
Добавлен:
22.03.2016
Размер:
11.9 Mб
Скачать

17.4. Делегаты и обратные вызовы

В программировании достаточно часто возникает необходимость создавать

фрагменты кода (например, классы, подпрограммы, функции), которые можно было

бы каким-либо образом настраивать при конкретных применениях. Наиболее

интересна и важна настройка, позволяющая изменять алгоритм выполнения кода.

Классическим примером удобства и полезности такой настройки служат функции

qsort() и bsearch() из стандартной библиотеки. Первая из них выполняет сортировку

элементов массива, вторая позволяет осуществлять бинарный поиск нужного

значения в упорядоченном массиве. Применение каждой из названных функций

требует, чтобы программист-пользователь "сообщил" при вызове, что он понимает

под порядком, в котором должны разместиться сортируемые элементы, или что

означает равенство значений элемента массива и эталона поиска. Указанны

е

критерии сортировки и поиска оформляются в виде вспомогательных функций.

Сведения об этих функциях (адреса этих функций) передаются функциям qsort() и

bsearch() в качестве аргументов вместе с тем массивом, который нужно обработать.

Тем самым в программе пользователя появляется возможность, по разному

программируя вспомогательные функции, достаточно произвольно задавать правила

сортировки и поиска, то есть изменять поведение библиотечных функций qsort() и

bsearch().

О концепции построения функций (методов), вызывающих при исполнении

вспомогательные функции, определённые (позже) в точке вызова (например, в коде

пользователя), говорят, используя термин "обратный вызов

"обратный вызов"

" (callback). В примере с функциями qsort() и bsearch() функции обратного вызова

"обратный вызов:функции обратного вызова"

параметризуют библиотечные

функции.

Еще один пример применения функций обратного вызова – процедура вывода

на экран дисплея графика функции f(x), математическое описание которой заранее

не известно. При обращении к такой процедуре могут быть заданы (например)

пределы изменения аргумента xmin, xmax и ссылка на программную реализацию

конкретной функции f(x).

Третий пример – процедура вычисления определенного интеграла функции

f(x). Так как существует несколько методов численного интегрирования, то при

использовании процедуры можно с помощью параметров задать: программную

реализацию метода интегрирования, реализацию подинтегральной функции f(x),

пределы интегрирования и требуемую точность вычислений. В данном случае

процедура вычисления определенного интеграла параметризуется двумя функциями

обратного вызова.

Итак, обратным вызовом называют обращение из исполняемой функции к

другой функции, которая зачастую определяется не на этапе компиляции, а в

процессе выполнения программы. В языках Си и Си++ для реализации обратны

х

вызовов в качестве параметра в функцию передаётся указатель на ту функцию, к

которой нужно обращаться при исполнении программы. Мы не рассматриваем

механизма указателей, но заметим, что указатель на функцию это адрес участка

основной памяти, в котором размещён код функции. Опасность и ненадёжность

обращения к функции по её адресу состоит в том, что зная только адрес,

невозможно проверить правильность обращения. Ведь нужно убедиться, что

количество аргументов и их типы соответствуют параметрам функции, что верен

тип возвращаемого значения и т.д.

Возможность указанных проверок в языке C# обеспечивают делегаты.

При разработке средств обратного вызова в языке C# было решено

обеспечить программистов не только возможностью контролировать сигнатуру

методов, но и отличать методы классов от методов объектов. Каждый делегат (о

синтаксисе чуть позже) включает в качестве полей ссылку на объект, для которого

нужно вызвать метод, и имя конкретного метода. Если ссылка на объект равна null,

то имя метода воспринимается как имя статического метода.

Как мы уже говорили, делегат это тип, экземпляры которого представляют

ссылки на методы с конкретной спецификацией параметров и фиксированным

типом возвращаемого значения. Делегаты дают возможность рассматривать методы

как

сущности ,

ссылки

на

которые

можно

присваивать

переменным

(соответствующего типа) и передавать в качестве параметров.

Покажем на простом примере, как ссылка на экземпляр делегата может

использоваться в качестве параметра для организации обратных вызовов.

Определим класс, поля которого задают предельные значения xmin, xmax

отрезка числовой оси и количество n равноотстоящих точек на этом отрезке. В

классе определим метод, выводящий на экран значения функции f(x) в точках

числовой оси, определенных значениями xmin, xmax, n. Конкретная функция f(x),

передается методу с помощью параметра-делегата. Определения делегата-типа и

класса (программа 17_04.cs)

:

public delegate double Proc(double x); // делегат-тип

public class Series

{

int n;

double xmi, xma;

public Series(int ni, double xn, double xk) // конструктор

{ n = ni; xmi = xn; xma = xk; }

public void display(Proc fun)

{

Console.WriteLine("Proc: {0}, xmi={1:f2}, xma={2:f2}, n={3}.",

fun.Method, xmi, xma, n);

for (double x = xmi; x <= xma; x += (xma - xmi) / (n - 1))

Console.Write("{0:f2}\t", fun(x));

}

}

Делегат Proc определен как внешний тип в глобальном пространстве имен.

Там же определен класс Series. Для нас интересен его метод с заголовком

public void display(Proc fun)

Параметр метода – ссылка на экземпляр делегата класса Proc. В теле метода

display() выводится заголовок метода, который будет представлять аргумент-

делегат, и n значений адресованной экземпляром делегата функции. Значения

выводятся в цикле, параметром которого служит аргумент функции.

Имея такие определения типа-делегата и класса, можно вводить разные

методы, соответствующие делегату Proc, и разные объекты класса Series. Затем для

объекта класса Series можно вызвать метод display(), передать ему в качестве

аргумента имя метода, представляющего нужную математическую функцию, и

получить ее заголовок и набор значений. Указанные действия выполняет

следующий фрагмент программы:

class Program

{

static double mySin(double x)

{ return Math.Sin(x); }

static double myLine(double x)

{ return x * 5; }

static void Main()

{

Series sequence = new Series(7, 0.0, 1.0);

sequence.display(mySin);

Console.WriteLine();

sequence.display(myLine);

Console.WriteLine();

}

}

В классе Program определены два статических метода, соответствующих

делегату Proc. В методе Main() определен именуемый ссылкой sequence объект

класса Series. Поля этого объекта задают набор точек оси аргумента, в которых

будут вычисляться значения функции при обращении к методу display().

Результат выполнения программы:

Proc: Double mySin(Double), xmi=0,00, xma=1,00, n=7.

0,00 0,17 0,33 0,48 0,62 0,74 0,84

Proc: Double myLine(Double), xmi=0,00, xma=1,00, n=7.

0,00 0,83 1,67 2,50 3,33 4,17 5,00

Обратите внимание, что в данном примере нет явного создания экземпляра

делегата Proc. Нет явного определения и ссылок на экземпляры делегатов.

При обращении к методу display() вместо параметра fun подставляется имя

конкретного метода. Все дальнейшие действия выполняются неявно – создаётся

экземпляр делегата Proc и ссылка на него используется в теле метода.

Соседние файлы в папке CSBasicCourse2ndedPodbelsky