Интернет-журнал "Домашняя лаборатория", 2007 №9
Шрифт:
private string message;
//доступ к свойствам
public string Name
{get {return(name);} set {name = value;}}
public double Salary
{get {return(salary);} set {salary = value;}}
public int Id
{get {return(id);} set {id = value;}}
}//class Person
Класс Person устроен обычным способом: у него несколько перегруженных конструкторов, закрытые поля и процедуры-свойства для доступа к ним. Особо обратить внимание прошу на метод класса ToPerson, сигнатура которого совпадает с сигнатурой класса, определенной введенным ранее делегатом MesToPers.
Person man2 = new Person("Владимир");
MesToPers mestopers = new MesToPers(man2.ToPerson);
mestopers("пора работать!");
Обратите внимание, что поскольку метод ToPerson не является статическим методом, то при связывании необходимо передать и объект, вызывающий метод. Более того, переданный объект становится доступным экземпляру делегата. Отсюда сразу же становится ясным, что экземпляры делегата — это не просто указатели на функцию, а более сложно организованные структуры. Они, по крайней мере, содержат пару указателей на метод и на объект, вызвавший метод. Вызываемый метод в своей работе использует как информацию, передаваемую ему через аргументы метода, так и информацию, хранящуюся в полях объекта. В данном примере переданное сообщение "пора работать" присоединится к имени объекта, и результирующая строка будет выдана на печать. В тех случаях, когда метод, связываемый с экземпляром делегата, не использует информацию объекта, этот метод может быть объявлен как статический метод класса. Таким образом, инициализировать экземпляры делегата можно как статическими, так и динамическими методами, связанными с конкретными объектами.
Последние три строки были добавлены в вышеприведенную тестирующую процедуру. Взгляните на результаты ее работы.
Рис. 20.1. Объявление делегатов и создание их экземпляров
Функции высших порядков
Одно из наиболее важных применений делегатов связано с функциями высших порядков. Функцией высшего порядка называется такая функция (метод) класса, у которой один или несколько аргументов принадлежат к функциональному типу. Без этих функций в программировании обойтись довольно трудно. Классическим примером является функция вычисления интеграла, у которой один из аргументов задает подынтегральную функцию. Другим примером может служить функция, сортирующая объекты. Аргументом ее является функция Compare, сравнивающая два объекта. В зависимости оттого, какая функция сравнения будет передана на вход функции сортировки, объекты будут сортироваться по-разному, например, по имени, или по ключу, или по нескольким полям. Вариантов может быть много, и они определяются классом, описывающим сортируемые объекты.
Вычисление интеграла
Давайте более подробно рассмотрим ситуацию с функциями высшего порядка на примере задачи вычисления определенного интеграла с заданной точностью. С этой целью создадим класс, в котором будет описан делегат, определяющий контракт, коему должны удовлетворять подынтегральные функции. В этом же классе определим метод, вычисляющий интеграл. По сути самой задачи этот метод представляет собой функцию высшего порядка. Приведу программный код,
public class HighOrderIntegral
{
//delegate
public delegate double SublntegralFun(double x);
public double Evallntegral(double a, double b, double eps,SublntegralFun sif)
{
int n=4;
double I0=0, I1 = I(a, b, n,sif);
for(n=8; n < Math.Pow(2.0,15.0); n*=2)
{
I0 =I1; I1=I(a,b,n,sif);
if(Math.Abs(I1-10)<eps) break;
}
if(Math.Abs(I1–10)< eps)
Console.WriteLine("Требуемая точность достигнута! "+
" eps = {0}, достигнутая точность ={1}, n= {2}",
eps,Math.Abs(11–10), n);
else
Console.WriteLine("Требуемая точность не достигнута! "+
" eps = {0}, достигнутая точность ={1}, n= {2}",
eps,Math.Abs(I1–I0), n);
return(I1);
}
private double I(double a, double b, int n,
SublntegralFun sif)
{
//Вычисляет частную сумму по методу трапеций
double х = a, sum = sif(x)/2, dx = (b-a)/n;
for (int i= 2; i <= n; i++)
{
x += dx; sum += sif (x);
}
x = b; sum += sif(x)/2;
return(sum*dx);
}
}//class HighOrderIntegral
Прокомментирую этот текст:
• Класс HighOrderIntegral предназначен для работы с функциями. В него вложено описание функционального класса — делегата SubIntegralFun, задающего класс функций с одним аргументом типа double и возвращающих значение этого же типа.
• Метод EvalIntegral — основной метод класса позволяет вычислять определенный интеграл. Этот метод есть функция высшего порядка, поскольку одним из его аргументов является подынтегральная функция, принадлежащая классу SubIntegralFun.
• Для вычисления интеграла применяется классическая схема. Интервал интегрирования разбивается на n частей, и вычисляется частичная сумма по методу трапеций, представляющая приближенное значение интеграла. Затем n удваивается, и вычисляется новая сумма. Если разность двух приближений по модулю меньше заданной точности eps, то вычисление интеграла заканчивается, иначе процесс повторяется в цикле. Цикл завершается либо по достижении заданной точности, либо когда n достигнет некоторого предельного значения (в нашем случае — 215).
• Вычисление частичной суммы интеграла по методу трапеций реализовано закрытой процедурой I.
• Впоследствии класс может быть расширен, и помимо вычисления интеграла он может вычислять и другие характеристики функций.
Чтобы продемонстрировать работу с классом HighOrderIntegral, приведу еще класс Functions, где описано несколько функций, удовлетворяющих контракту, который задан классом SubIntegralFun;
class functions
{
//подынтегральные функции
static public double sif1(double x)
{
int k = 1; int b = 2;
return (double)(k*x +b);
}
static public double sif2(double x)
{
double a = 1.0; double b = 2.0; double c= 3.0;
return (double)(a*x*x +b*x +c);
}
}//class functions
А теперь рассмотрим метод класса клиента, выполняющий создание нужных объектов и тестирующий их работу:
public void TestEvalIntegrals