В языке С и С++ в программах широко используют указатели. И указатели на объекты данных, и указатели на функции. Это повышает универсальность программы, позволяет решать самые сложные задачи, например: моделировать работу любых динамических структур данных.
Другое дело — язык язык С#. В нём не принято напрямую работать с указателями. Для повышения безопасности программ в этом языке созданы классы, позволяющие моделировать работу указателей. Так, для работы с динамическими структурами данных в С# используют коллекции. А вот вместо указателей на функции разработчики .NET придумали понятие делегата.
Делегат — это особый вид класса. Создав объект делегата, можно инкапсулировать в него указатель на нужный метод. Инкапсуляция в делегат указателя на метод делает такой указатель безопасным, так как управляемый объект-делегат контролируется системой.
Более того. В делегате можно инкапсулировать не один указатель, а несколько. В этом случае получается множественный делегат. Вызов объекта множественного делегата приводит к последовательному вызову всех инкапсулированных в него методов.
Рассмотрим основные действия при работе с делегатами.
1)Объявление делегата
Прежде чем создать объект любого собственного класса, требуется объявить этот класс. В случае с делегатами это делается так:
delegate тип_возвращаемого_значения тип_делегата (список_формальных_параметров);
где
delegate — ключевое слово, означающее, что в данном предложении даётся определения класса-делегата. Перед этим словом, естественно, может быть указан тип доступа, например: public;
тип_возвращаемого_значения — это тип результата, который должна возвращать делегируемая функция;
тип_делегата — это имя типа делегата, т.е. название класса-делегата;
список_формальных_параметров — список формальных параметров делегируемой функции, который записывается по общим правилам как для любого метода.
Если делегат — это класс, то возникает законный вопрос: а где же определение этого класса, т.е. тело класса? В формальной записи, приведённой выше, есть только заголовок этого класса, заканчивающийся точкой с запятой, но тело отсутствует.
Ответ простой: программисту не нужно вообще в тексте своей программы определять тело делегата! Компилятор сам создаёт реализацию делегата, используя описание делегата. Это очень удобно для пользователя, и было, по-видимому, не слишком сложно для разработчиков платформы .NET, ведь действия с указателями на функции однотипны.
2)Создание ссылки на делегат данного типа
Выполняется следующим образом:
тип_делегата ссылка_на_делегат;
где
тип_делегата — это имя типа делегата;
ссылка_на_делегат — ссылка на будущий объект-делегат.
3.Создание объекта-делегата заданного типа
И здесь всё достаточно просто, но есть два варианта реализации:
а)создание объекта-делегата, инкапсулирующего статическую функцию:
ссылка_на_делегат = new тип_делегата(имя_класса.имя_статической_функции);
б)создание объекта-делегата, инкапсулирующего обычную функцию:
ссылка_на_делегат = new тип_делегата(ссылка_на_объект.имя_обычной_функции);
Внимание! Описание функций, которые мы инкапсулируем в делегат, должно строго соответствовать тем требованиям, которые были указаны при объявлении делегата, т.е. тип результата и список параметров у функций, указанный в обоих случаях (статическая или обычная), обязательно должен соответствовать типу результата и списку параметров типа делегата.
Понятно, что шаги 2 и 3 можно совместить.
4.Применение объекта-делегата
В простейшем случае оно заключается в вызове этого объекта с необходимыми фактическими параметрами, т.е. точно так же, как вызывается метод:
ссылка_на_делегат(фактические_параметры);
Другие способах применения делегатов рассмотрим позже, а пока разберём простейший пример.
Пример 1. Рассмотрим работу делегатов с обычными и статическими методами, а также создание множественного делегата.
Текст программы:
using System;
namespace Prim_Delegate_01
{
public delegate void Del(string s);
class Program
{
static void f_stat(string s)
{
Console.WriteLine("Статическая: {0}",s);
}
void f(string s)
{
Console.WriteLine("Обычная: {0}",s);
}
public static void Main(string[] args)
{
Console.WriteLine("Использование делегатов\n");
Program p = new Program();
Del d1 = new Del(p.f);
Del d2 = new Del(Program.f_stat);
d1("обычная");
d2("статическая");
d1+=new Del(Program.f_stat);
d1("обычная и статическая");
d1-=d2;
d1("обычная");
Console.Write("\nPress any key to continue . . . ");
Console.ReadKey(true);
}
}
}
Результаты работы программы:
Использование делегатов
Обычная: обычная
Статическая: статическая
Обычная: обычная и статическая
Статическая: обычная и статическая
Обычная: обычная
Press any key to continue . . .
Пример 2. Предположим, что необходимо
написать программу для вычисления определённого интеграла
,
например, по методу трапеций. Реализацию численного метода вычисления
интеграла и вычисление подынтегральной функции логично оформить в
виде отдельных подпрограмм (в терминах языка C# —
в виде методов). Для повышения универсальности было бы хорошо
передавать имя подынтегральной функции в функцию, реализующую метод
интегрирования — в Integral(),
чтобы там использовать некоторое формальное имя (хотя бы f()).
Для этого будем использовать делегат.
Текст программы:
using System;
namespace Prim_delegate_Integral
{
public delegate double del(double x);
class C1
{
public static double f(double x)
{
return x*x;
}
}
class Program
{
public static double Integral(del f, double a, double b, int n)
{
double x, s=(f(a) + f(b)) / 2 , h = (b - a) / n;
for(int i = 1; i < n; i++)
{
x = a + i * h;
s += f(x);
}
s *= h;
return s;
}
public static void Main(string[] args)
{
Console.WriteLine("Передача имени метода в подпрограмму:\n");
del d = new del(C1.f);
double a=0, b=1, s;
int n=100;
s=Integral(d,a,b,n);
Console.WriteLine("s={0}",s);
Console.Write("\nPress any key to continue . . . ");
Console.ReadKey(true);
}
}
}
Результаты работы программы:
Передача имени метода в подпрограмму:
s=0,33335
Press any key to continue . . .
|
|
|