Поскольку делегат является классом, его можно передавать в методы в качестве параметра. Таким образом обеспечивается функциональная параметризация: в метод можно передавать не только различные данные, но и различные функции их обработки. Функциональная параметризация применяется для создания универсальных методов и обеспечения возможности обратного вызова. В качестве простейшего примера универсального метода можно привести метод вывода таблицы значений функции, в который передается диапазон значений аргумента, шаг его изменения и вид вычисляемой функции. Этот пример приводится далее.
Обратный вызов (callback) представляет собой вызов функции, передаваемой в другую функцию в качестве параметра. Рассмотрим рис. 10.1. Допустим, в библиотеке описана функция А, параметром которой является имя другой функции. В вызывающем коде описывается функция с требуемой сигнатурой (В) и передается в функцию А. Выполнение функции А приводит к вызову В, то есть управление передается из библиотечной функции обратно в вызывающий код.
|
|
Механизм обратного вызова широко используется в программировании. Например, он реализуется во многих стандартных функциях Windows. Пример передачи делегата в качестве параметра приведен в листинге 10.3. Программа выводит таблицу значений функции на заданном интервале с шагом, равным единице.
Листинг 10.3. Передача делегата через список параметров
using System;
namespace ConsoleApplication1
{
public delegate double Fun(double x); // объявление делегата
class Class1
{
public static void Table(Fun F, double x, double b)
{
Console.WriteLine(" X Y ");
while (x <= b)
{
Console.WriteLine("| {0.8:0.000} | {1.8:0.000} |", x, F(x));
x += 1;
}
Console.WriteLine("--------------------");
}
public static double Simple(double x)
{
return 1;
}
static void Main()
{
Console.WriteLine(" Таблица функции Sin ");
Table(new Fun(Math.Sin), -2, 2);
Console.WriteLine(" Таблица функции Simple ");
Table(new Fun(Simple), 0, 3);
}
}
}
Результат работы программы:
В среде Visual Studio 2005, использующей версию 2.0 языка С#, можно применять упрощенный синтаксис для делегатов. Первое упрощение заключается в том, что в большинстве случаев явным образом создавать экземпляр делегата не требуется, поскольку он создается автоматически по контексту. Второе упрощение заключается в возможности создания так называемых анонимных методов — фрагментов кода, описываемых непосредственно в том месте, где используется делегат. В листинге 10.4 использованы оба упрощения для реализации тех же действий, что и листинге 10.3.
Листинг 10.4. Передача делегата через список параметров (версия 2.0)
using System;
namespace ConsoleApplication1
{
public delegate double Fun(double x); // объявление делегата
class Class1
{
public static void Table(Fun F, double x, double b)
{
Console.WriteLine(" X Y ");
|
|
while (x <= b)
{
Console.WriteLine("| {0,8:0.000} | {1,8:0.000} |", x, F(x));
x += 1;
}
Console.WriteLine(" ");
}
static void Main()
{
Console.WriteLine(" Таблица функции Sin ");
Table(Math.Sin, -2, 2); // упрощение 1
Console.WriteLine(" Таблица функции Simple ");
Table(delegate(double x) { return 1; }, 0, 3); // упрощение 2
}
}
}
В первом случае экземпляр делегата, соответствующего функции Sin, создается автоматически1.
В результате в 2005 году язык С# в этой части вплотную приблизился к синтаксису старого доброго Паскаля, в котором передача функций в качестве параметров была реализована еще в 1992 году, если не раньше.
Чтобы это могло произойти, список параметров и тип возвращаемого значения функции должны быть совместимы с делегатом. Во втором случае не требуется оформлять простой фрагмент кода в виде отдельной функции Simple, как это было сделано в предыдущем листинге, — код функции оформляется как анонимный метод и встраивается прямо в место передачи. Альтернативой использованию делегатов в качестве параметров являются виртуальные методы. Универсальный метод вывода таблицы значений функции можно реализовать с помощью абстрактного базового класса, содержащего два метода: метод вывода таблицы и абстрактный метод, задающий вид вычисляемой функции. Для вывода таблицы конкретной функции необходимо создать производный класс, переопределяющий этот абстрактный метод. Реализация метода вывода таблицы с помощью наследования и виртуальных методов приведена в листинге 10.5.
Листинг 10.5. Альтернатива параметрам-делегатам
using System;
namespace ConsoleApplication1
{
abstract class TableFun
{
public abstract double F(double x);
public void Table(double x, double b)
{
Console.WriteLine(" X Y ");
while (x <= b)
{
Console.WriteLine("| {0.8:0.000} | {1.8:0.000} |", х, F(x));
х += 1;
}
Console.WriteLine(" ----------------------");
}
}
class SimpleFun: TableFun
{
public override double F(double x)
{
return 1;
}
}
class SinFun: TableFun
{
public override double F(double x)
{
return Math.Sin(x);
}
}
class Class1
{
static void Main()
{
TableFun a = new SinFun();
Console.WriteLine(" Таблица функции Sin ");
a.Table(-2, 2);
a = new SimpleFun();
Console.WriteLine(" Таблица функции Simple ");
a.Table(0, 3);
}
}
}
Результат работы этой программы такой же, как и предыдущей, но, на мой взгляд, в данном случае применение делегатов предпочтительнее.