Слово
[<спецификатор доступа>] delegate <тип результата > <имя класса> (<список аргументов>);
Этим объявлением класса задается функциональный тип - множество функций с заданной сигнатурой, у которых аргументы определяются списком, заданным в объявлении
Спецификатор доступа может быть, как обычно, опущен. Где следует размещать объявление
Так же, как и интерфейсы C#, object, а у object, например, int.
Начнем примеры этой лекции с объявления трех
namespace Delegates
{
//объявление классов - делегатов
delegate void Proc(ref int x);
delegate void MesToPers(string s);
class OwnDel
{
public delegate int Fun1(int x);
int Plus1( int x){return(x+100);}//Plus1
int Minus1(int x){return(x-100);}//Minus1
void Plus(ref int x){x+= 100;}
void Minus(ref int x){x-=100;}
//поля класса
public Proc p1;
public Fun1 f1;
char sign;
//конструктор
public OwnDel(char sign)
{
this.sign = sign;
if (sign == '+')
{p1 = new Proc(Plus);f1 = new Fun1(Plus1);}
else
{p1 = new Proc(Minus);f1 = new Fun1(Minus1);}
}
}//class OwnDel
Прокомментирую этот текст.
Proc, MesToPers, Fun1. Каждый из них описывает множество функций фиксированной сигнатуры.OwnDel описаны четыре метода: Plus, Minus, Plus1, Minus1, сигнатуры которых соответствуют сигнатурам, задаваемых классами Proc и Fun1.p1 и f1 класса OwnDel являются экземплярами классов Proc и Fun1.p1 и f1 связываются с конкретными методами Plus или Minus, Plus1 или Minus1. Связывание с той или иной функцией в данном случае определяется значением поля sign.Заметьте,
Приведу теперь процедуру, тестирующую работу созданного класса:
public void TestOwnDel()
{
int account = 1000, account1=0;
OwnDel oda = new OwnDel('+');
Console.WriteLine("account = {0}, account1 = {1}",
account, account1);
oda.p1(ref account); account1=oda.f1(account);
Console.WriteLine("account = {0}, account1 = {1}",
account, account1);
}
Клиент класса OwnDel создает экземпляр класса, передавая конструктору знак той операции, которую он хотел бы выполнить над своими счетами - account и account1. Вызов p1 и f1, связанных к моменту вызова с
В нашем примере объявление Person, слегка изменив его определение:
class Person
{
//конструкторы
public Person(){name =""; id=0; salary=0.0;}
public Person(string name){this.name = name;}
public Person (string name, int id, double salary)
{this.name = name; this.id=id; this.salary = salary;}
public Person (Person pers)
{this.name = pers.name; this.id = pers.id;
this.salary = pers.salary;}
//методы
public void ToPerson(string mes)
{
this.message = mes;
Console.WriteLine("{0}, {1}",name, message);
}
//свойства
private string name;
private int id;
private double salary;
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 man1 = new Person("Владимир");
MesToPers mestopers = new MesToPers(man1.ToPerson);
mestopers("пора работать!");
Обратите внимание, что поскольку метод ToPerson не является статическим методом, то при связывании необходимо передать и объект, вызывающий метод. Более того, переданный объект становится доступным
Последние три строки были добавлены в вышеприведенную тестирующую процедуру. Взгляните на результаты ее работы.
(рис 20.1) Объявление делегатов и создание их экземпляровОдно из наиболее важных применений Compare, сравнивающая два объекта. В зависимости от того, какая функция сравнения будет передана на вход функции сортировки, объекты будут сортироваться по-разному, например, по имени, или по ключу, или по нескольким полям. Вариантов может быть много, и они определяются классом, описывающим сортируемые объекты.
Давайте более подробно рассмотрим ситуацию с
public class HighOrderIntegral
{
//delegate
public delegate double SubIntegralFun(double x);
public double EvalIntegral(double a, double b,
double eps,SubIntegralFun 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-I0)<eps) break;
}
if(Math.Abs(I1-I0)< eps)
Console.WriteLine("Требуемая точность достигнута! "+
" eps = {0}, достигнутая точность ={1}, n= {2}",
eps,Math.Abs(I1-I0),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,
SubIntegralFun sif)
{
//Вычисляет частную сумму по методу трапеций
double x = 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.eps, то вычисление интеграла заканчивается, иначе процесс повторяется в цикле. Цикл завершается либо по достижении заданной точности, либо когда n достигнет некоторого предельного значения (в нашем случае - 215).I.Чтобы продемонстрировать работу с классом HighOrderIntegral, приведу еще класс Functions, где описано несколько функций, удовлетворяющих SubIntegralFun:
class Functions
{
//подынтегральные функции
public static double sif1(double x)
{
int k = 1; int b = 2;
return (double)(k*x +b);
}
public static 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()
{
double myint1=0.0;
HighOrderIntegral.SubIntegralFun hoisif1 =
new HighOrderIntegral.SubIntegralFun(Functions.sif1);
HighOrderIntegral hoi = new HighOrderIntegral();
myint1 = hoi.EvalIntegral(2,3,0.1e-5,hoisif1);
Console.WriteLine("myintegral1 = {0}",myint1);
HighOrderIntegral.SubIntegralFun hoisif2 =
new HighOrderIntegral.SubIntegralFun(Functions.sif2);
myint1= hoi.EvalIntegral(2,3,0.1e-5,hoisif2);
Console.WriteLine("myintegral2 = {0}",myint1);
}//EvalIntegrals
Здесь создаются два HighOrderIntegral, вызывающий метод вычисления интеграла. Результаты работы показаны на 20.2.
(рис 20.2) Вычисление интеграла с использованием функций высших порядков
(рис 20.3) Построение системы методом "раскрутки"Успех языка С и операционной системы Unix во многом объясняется тем, что в свое время они были созданы
При построении систем
Пусть F - G функционального типа. Тогда функцию G, задающую параметр (а иногда и саму функцию F ), называют Q вызывает функцию внутреннего F, то предварительно во внешнем G, которая и будет передана F. Таким образом, функция Q внешнего F внутреннего G внешнего
Наш пример с вычислением интеграла хорошо демонстрирует HighOrderIntegral - это внутренний EvalIntegral, требующая задания EvalIntegral вызывается из внешнего callback функции из класса Functions.
Многие из функций операционной системы Windows, входящие в состав Win API 32, требуют при своем вызове задания callback - функций. Примером может служить работа с объектом операционной системы Timer. Конструктор этого объекта является callback - функцию, вызываемую для обработки событий, которые поступают от таймера.
Пример работы с таймером приводить сейчас не буду, ограничусь лишь сообщением синтаксиса объявления конструктора объекта Timer:
public Timer(TimerCallback callback,object state, int dueTime, int period);
Первым параметром конструктора является callback, которая принадлежит функциональному классу TimerCallback, заданному
public delegate void TimerCallback(object state);
Сегодня многие программные системы проектируются и разрабатываются не в функциональном, а в объектно-ориентированном стиле. Такая система представляет собой одно или несколько семейств интерфейсов и классов, связанных отношением наследования. Классы-потомки наследуют методы своих родителей, могут их переопределять и добавлять новые методы. Переопределив метод родителя, потомки без труда могут вызывать как собственный метод, так и метод родителя; все незакрытые методы родителя им известны и доступны. Но может ли родитель вызывать методы, определенные потомком, учитывая, что в момент создания родительского метода потомок не только не создан, но еще, скорее всего, и не спроектирован? Тем не менее, ответ на этот вопрос положителен. Достигается такая возможность опять-таки благодаря
О полиморфизме говорилось достаточно много в предыдущих лекциях. Тем не менее, позволю напомнить суть дела. Родитель может объявить свой метод виртуальным, в этом случае в Q вызывает виртуальный метод F, то, благодаря позднему связыванию, реализуется полиморфизм и реально будет вызван не метод родителя F, а метод F, который реализован потомком, вызвавшим родительский метод Q. Ситуация в точности напоминает раскрутку и вызов обратных функций. Родительский метод Q находится во внутреннем F из внешнего F в данном случае задается не
Идея примера такова. Вначале построим родительский класс, метод которого будет вычислять интеграл от некоторой подынтегральной функции, заданной виртуальным методом класса. Далее построим класс-потомок, наследующий родительский метод вычисления интеграла и переопределяющий виртуальный метод, в котором потомок задаст собственную подынтегральную функцию. При такой технологии, всякий раз, когда нужно вычислить интеграл, нужно создать класс-потомок, в котором переопределяется виртуальный метод. Приведу пример кода, следующего этой схеме:
class FIntegral
{
//базовый класс, в котором определен метод вычисления
//интеграла и виртуальный метод, задающий базовую
//подынтегральную функцию
public double EvaluateIntegral(double a, double b,
double eps)
{
int n=4;
double I0=0, I1 = I( a, b, n);
for( n=8; n < Math.Pow(2.0,15.0); n*=2)
{
I0 =I1; I1=I(a,b,n);
if(Math.Abs(I1-I0)<eps)break;
}
if(Math.Abs(I1-I0)< eps)
Console.WriteLine("Требуемая точность достигнута! "+
" eps = {0}, достигнутая точность ={1}, n= {2}",
eps,Math.Abs(I1-I0),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)
{
//Вычисляет частную сумму по методу трапеций
double x = 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);
}
protected virtual double sif(double x)
{return(1.0);}
}//FIntegral
Этот код большей частью знаком. В отличие от класса HighOrderIntegral, здесь нет EvaluateIntegral нет параметра функционального типа. Вместо этого тут же в классе определен защищенный виртуальный метод, задающий конкретную подынтегральную функцию. В качестве таковой выбрана самая простая функция, тождественно равная единице.
Для вычисления интеграла от реальной функции единственное, что теперь нужно сделать - это задать класс-потомок, переопределяющий виртуальный метод. Вот пример такого класса:
class FIntegralSon:FIntegral
{
protected override double sif(double x)
{
double a = 1.0; double b = 2.0; double c= 3.0;
return (double)(a*x*x +b*x +c);
}
}//FIntegralSon
Принципиально задача решена. Осталось только написать фрагмент кода, запускающий вычисления. Он оформлен в виде следующей процедуры:
public void TestPolymorphIntegral()
{
FIntegral integral1 = new FIntegral();
FIntegralSon integral2 = new FIntegralSon();
double res1 = integral1.EvaluateIntegral(2.0,3.0,0.1e-5);
double res2 = integral2.EvaluateIntegral(2.0,3.0,0.1e-5);
Console.WriteLine("Father = {0}, Son = {1}", res1,res2);
}//PolymorphIntegral
Взгляните на результаты вычислений.
(рис 20.4) Вычисление интеграла, использующее полиморфизм
В наших примерах рассматривалась ситуация, при которой в некотором классе объявлялись функции, удовлетворяющие
Рассмотрим пример, демонстрирующий и поясняющий эту возможность при работе с
Person с полями: имя, идентификационный номер, зарплата. В этом классе определим различные реализации функции Compare, позволяющие сравнивать два объекта по имени, по номеру, по зарплате, по нескольким полям. Самое интересное, ради чего и строится данный пример: для каждой реализации Compare будет построена процедура-свойство, которая задает реализацию Persons ;Persons будет играть роль Person.В этом классе будут определены операции над объектами. Среди операций нас, прежде всего, будет интересовать сортировка объектов, реализованная в виде Person. Persons.
Теперь, когда задача ясна, приступим к ее реализации. Класс Person уже появлялся в наших примерах, поэтому он просто дополнен до нужной функциональности. Добавим методы сравнения двух объектов Person:
//методы сравнения
private static int CompareName(Person obj1, Person obj2)
{
return(string.Compare(obj1.name,obj2.name));
}
private static int CompareId(Person obj1, Person obj2)
{
if( obj1.id > obj2.id) return(1);
else return(-1);
}
private static int CompareSalary(Person obj1, Person obj2)
{
if( obj1.salary > obj2.salary) return(1);
else if(obj1.salary < obj2.salary)return(-1);
else return(0);
}
private static int CompareSalaryName(Person obj1, Person obj2)
{
if( obj1.salary > obj2.salary) return(1);
else if(obj1.salary < obj2.salary)return(-1);
else return(string.Compare(obj1.name,obj2.name));
}
Заметьте, методы закрыты и, следовательно, недоступны извне. Их четыре, но могло бы быть и больше, при возрастании сложности объекта растет число таких методов. Все методы имеют одну и ту же сигнатуру и удовлетворяют Person.
//делегаты как свойства
public static Persons.CompareItems SortByName
{
get {return(new Persons.CompareItems(CompareName));}
}
public static Persons.CompareItems SortById
{
get {return(new Persons.CompareItems(CompareId));}
}
public static Persons.CompareItems SortBySalary
{
get {return(new Persons.CompareItems(CompareSalary));}
}
public static Persons.CompareItems SortBySalaryName
{
get {return(new Persons.CompareItems(CompareSalaryName));}
}
Всякий раз, когда будет запрошено, например, свойство SortByName класса Person, будет возвращен объект функционального класса Persons.CompareItems, задающий ссылку на метод CompareName класса Person. Объект будет создаваться динамически в момент запроса.
Класс Person полностью определен, и теперь давайте перейдем к определению Person. Начну с определения свойств класса Persons:
class Persons
{ //контейнер объектов Person
//делегат
public delegate int CompareItems(Person obj1, Person obj2);
private int freeItem = 0;
const int n = 100;
private Person[]persons = new Person[n];
}
В классе определен функциональный класс - CompareItems, задающий
freeItem - указатель на первый свободный элемент массива. Сам массив является закрытым свойством, и доступ к нему осуществляется благодаря индексатору:
//индексатор
public Person this[int num]
{
get { return(persons[num-1]); }
set { persons[num-1] = value; }
}
Добавим классический для контейнеров набор методов - добавление нового элемента, загрузка элементов из базы данных и печать элементов:
public void AddPerson(Person pers)
{
if(freeItem < n)
{
Person p = new Person(pers);
persons[freeItem++]= p;
}
else Console.WriteLine("Не могу добавить Person");
}
public void LoadPersons()
{
//реально загрузка должна идти из базы данных
AddPerson(new Person("Соколов",123, 750.0));
AddPerson(new Person("Синицын",128, 850.0));
AddPerson(new Person("Воробьев",223, 750.0));
AddPerson(new Person("Орлов",129, 800.0));
AddPerson(new Person("Соколов",133, 1750.0));
AddPerson(new Person("Орлов",119, 750.0));
}//LoadPersons
public void PrintPersons()
{
for(int i =0; i<freeItem; i++)
{
Console.WriteLine("{0,10} {1,5} {2,5}",
persons[i].Name, persons[i].Id, persons[i].Salary);
}
}//PrintPersons
Конечно, метод LoadPerson в реальной жизни устроен по-другому, но в нашем примере он свою задачу выполняет. А теперь определим метод сортировки записей с функциональным параметром, задающим тот или иной способ сравнения элементов:
//сортировка
public void SimpleSortPerson(CompareItems compare)
{
Person temp = new Person();
for(int i = 1; i<freeItem;i++)
for(int j = freeItem -1; j>=i; j--)
if (compare(persons[j],persons[j-1])==-1)
{
temp = persons[j-1];
persons[j-1]=persons[j];
persons[j] = temp;
}
}//SimpleSortObject
}//Persons
Единственный аргумент метода SimpleSortPerson принадлежит классу CompareItems, заданному
public void TestSortPersons()
{
Persons persons = new Persons();
persons.LoadPersons();
Console.WriteLine (" Сортировка по имени: ");
persons.SimpleSortPerson(Person.SortByName);
persons.PrintPersons();
Console.WriteLine (" Сортировка по идентификатору: ");
persons.SimpleSortPerson(Person.SortById);
persons.PrintPersons();
Console.WriteLine (" Сортировка по зарплате: ");
persons.SimpleSortPerson(Person.SortBySalary);
persons.PrintPersons();
Console.WriteLine (" Сортировка по зарплате и имени: ");
persons.SimpleSortPerson(Person.SortBySalaryName);
persons.PrintPersons();
}//SortPersons
Результаты работы сортировки данных изображены на рис. 20.5.
(рис 20.5) Сортировка данных
Давайте просуммируем то, что уже известно о функциональном типе данных. Ключевое слово delegate позволяет задать определение функционального типа (класса), фиксирующее
Вместе с тем, объявление функционального типа не укладывается в синтаксис, привычный для C#. Хотелось бы писать, как принято:
Delegate FType = new Delegate(<определение типа>)
Но так объявлять переменные этого класса нельзя, и стоит понять, почему. Есть ли вообще
public delegate int FType(int X);
переменная FType принадлежит
public abstract class Delegate: ICloneable, ISerializable
Для абстрактных классов реализация не определена, и это означает, что нельзя создавать экземпляры класса. delegate, которое косвенно позволяет работать с
Трудно, кажется, придумать, что можно делать с
В чем суть
Понятно, что, если есть операция присоединения
Рассмотрим основные Combine и Remove. Первый из них присоединяет
Combine(del1, del2) Remove(del1, del2)
Аргументы del1 и del2 должны быть одного функционального класса. При добавлении del2 в список, в котором del2 уже присутствует, будет добавлен второй экземпляр. При попытке удаления del2 из списка, в котором del2 нет, Remove благополучно завершит работу, не выдавая сообщения об ошибке.
del1 и del2. Обычное использование этих методов имеет вид:
del1 = (<type>) Combine(del1, del2); del1 = (<type>) Remove(del1, del2);
Метод GetInvocationList является динамическим методом класса - он возвращает foreach, поочередно получая элементы списка. Чуть позже появится пример, поясняющий необходимость подобной работы со списком.
Два Method и Target полезны для получения подробных сведений о Method возвращает объект класса MethodInfo из пространства имен Reflection. Свойство Target возвращает информацию об объекте, вызвавшем
У Object, есть еще несколько методов, но мы на них останавливаться не будем, они используются не столь часто.
Наряду с методами, над Combine и Remove. Выше написанные присваивания объекту del1 с помощью этих операций могут быть переписаны в виде:
del1 +=del2; del1 -=del2;
Как видите, запись становится проще, исчезает необходимость в задании явного приведения к типу. Ограничения на del1 и del2, естественно, остаются те же, что и для методов Combine и Remove.
Рассмотрим следующую ситуацию. Пусть есть городские службы: милиция, скорая помощь, пожарные. Каждая из служб по-своему реагируют на события, происходящие в городе. Построим примитивную модель жизни города, в которой случаются события и сообщения о них посылаются службам. В последующей лекции эта модель будет развита. Сейчас она носит формальный характер, демонстрируя, главным образом, работу с
Начнем с построения класса с именем Combination, где, следуя уже описанной технологии, введем get. Три MesToPers, размещенным в пространстве имен проекта. Вот программный код, в котором описаны функции, задающие действия служб:
class Combination
{
private static void policeman(string mes)
{
//анализ сообщения
if(mes =="Пожар!")
Console.WriteLine(mes + " Милиция ищет виновных!");
else
Console.WriteLine(mes +" Милиция здесь!");
}
private static void ambulanceman(string mes)
{
if(mes =="Пожар!")
Console.WriteLine(mes + " Скорая спасает пострадавших!");
else
Console.WriteLine(mes + " Скорая помощь здесь!");
}
private static void fireman(string mes)
{
if(mes =="Пожар!")
Console.WriteLine(mes + " Пожарные тушат пожар!");
else
Console.WriteLine( mes + " Пожарные здесь!");
}
}
Как видите, все три функции имеют не только одинаковую сигнатуру, но и устроены одинаково. Они анализируют приходящее к ним сообщение, переданное через параметр mes, а затем, в зависимости от результата, выполняют ту или иную работу, которая в данном случае сводится к выдаче соответствующего сообщения. Сами функции закрыты, и мы сейчас организуем к ним доступ:
public static MesToPers Policeman
{
get {return (new MesToPers(policeman));}
}
public static MesToPers Fireman
{
get {return (new MesToPers(fireman));}
}
public static MesToPers Ambulanceman
{
get {return (new MesToPers(ambulanceman));}
}
Три статических открытых свойства - Policeman, Fireman, Ambulanceman - динамически создают экземпляры класса MesToPers, связанные с соответствующими закрытыми функциями класса.
Службы у нас есть, покажем, как с ними можно работать. С этой целью добавим в класс Testing, где проводятся различные эксперименты, следующую процедуру:
public void TestSomeServices()
{
MesToPers Comb;
Comb = (MesToPers)Delegate.Combine(Combination.Ambulanceman,
Combination.Policeman);
Comb = (MesToPers)Delegate.Combine(Comb,Combination.Fireman);
Comb("Пожар!");
Вначале объявляется без инициализации Comb, которой в следующем операторе присваивается ссылка на Combine, чей Ambulanceman и Policeman. Затем к Comb присоединяется новый кандидат Fireman. При вызове Comb ему передается сообщение "Пожар!". В результате вызова Comb поочередно запускаются все три экземпляра входящие в список, каждому из которых передается сообщение.
Давайте теперь начнем поочередно отключать Comb с новыми сообщениями:
Comb = (MesToPers)Delegate.Remove(Comb,Combination.Policeman);
//Такое возможно: попытка отключить не существующий элемент
Comb = (MesToPers)Delegate.Remove(Comb,Combination.Policeman);
Comb("Через 30 минут!");
Comb = (MesToPers)Delegate.Remove(Comb,Combination.Ambulanceman);
Comb("Через час!");
Comb = (MesToPers)Delegate.Remove(Comb,Combination.Fireman);
//Comb("Через два часа!"); // Comb не определен
В этом фрагменте поочередно отключаются разные службы - милиция, скорая помощь, пожарные, и каждый раз вызывается Comb. После последнего отключения, когда Comb приводит к ошибке, потому оператор вызова закомментирован.
Покажем теперь, что ту же работу можно выполнить, используя не методы, а операции:
//операции + и -
Comb = Combination.Ambulanceman;
Console.WriteLine( Comb.Method.Name);
Comb+= Combination.Fireman;
Comb+= Combination.Policeman;
Comb("День города!");
Comb -= Combination.Ambulanceman;
Comb -= Combination.Fireman;
Comb("На следующий день!");
}//TestSomeServices
Обратите внимание, здесь демонстрируется вызов свойства Method, возвращающее объект, свойство Name которого выводится на печать. Результаты, порожденные работой этой процедуры, изображены на рис. 20.6.
(рис 20.6) Службы города
Как быть, если в GetInvocationList и в цикле поочередно вызывать
Добавим в класс Combination "плохого" кандидата, который пытается делить на ноль:
//метод, вызывающий исключительную ситуацию
public static void BadService(string mes)
{
int i =7, j=5, k=0;
Console.WriteLine("Bad Service: Zero Divide");
j=i/k;
}
Создадим процедуру, в которой в
public void TestBadJob()
{
MesToPers Comb;
Comb = (MesToPers)Delegate.Combine(Combination.Ambulanceman,
Combination.Policeman);
Comb = (MesToPers)Delegate.Combine(Comb,
new MesToPers(Combination.BadService));
Comb = (MesToPers)Delegate.Combine(Comb,Combination.Fireman);
foreach(MesToPers currentJob in Comb.GetInvocationList())
{
try
{
currentJob("Пожар!");
}
catch(Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(currentJob.Method.Name);
}
}
}//BadJob
Поясню, как будет работать эта процедура при ее вызове. Вначале две службы нормально отработают, но при вызове третьей службы возникнет исключительная ситуация "деление на ноль". Универсальный обработчик Exception перехватит эту ситуацию и напечатает как свойство Message объекта e, так и имя метода, вызвавшего исключительную ситуацию, используя свойство Method объекта, вызвавшего ситуацию. После завершения работы блока обработчика ситуации выполнение программы продолжится, выполнится следующий шаг цикла, и служба пожарных благополучно выполнит свою работу. Вот результаты вывода:
(рис 20.7) "Плохая служба"Разговор о
Слово
[<спецификатор доступа>] delegate <тип результата > <имя класса> (<список аргументов>);
Этим объявлением класса задается функциональный тип - множество функций с заданной сигнатурой, у которых аргументы определяются списком, заданным в объявлении
Спецификатор доступа может быть, как обычно, опущен. Где следует размещать объявление
Так же, как и интерфейсы C#, object, а у object, например, int.
Начнем примеры этой лекции с объявления трех
namespace Delegates
{
//объявление классов - делегатов
delegate void Proc(ref int x);
delegate void MesToPers(string s);
class OwnDel
{
public delegate int Fun1(int x);
int Plus1( int x){return(x+100);}//Plus1
int Minus1(int x){return(x-100);}//Minus1
void Plus(ref int x){x+= 100;}
void Minus(ref int x){x-=100;}
//поля класса
public Proc p1;
public Fun1 f1;
char sign;
//конструктор
public OwnDel(char sign)
{
this.sign = sign;
if (sign == '+')
{p1 = new Proc(Plus);f1 = new Fun1(Plus1);}
else
{p1 = new Proc(Minus);f1 = new Fun1(Minus1);}
}
}//class OwnDel
Прокомментирую этот текст.
Proc, MesToPers, Fun1. Каждый из них описывает множество функций фиксированной сигнатуры.OwnDel описаны четыре метода: Plus, Minus, Plus1, Minus1, сигнатуры которых соответствуют сигнатурам, задаваемых классами Proc и Fun1.p1 и f1 класса OwnDel являются экземплярами классов Proc и Fun1.p1 и f1 связываются с конкретными методами Plus или Minus, Plus1 или Minus1. Связывание с той или иной функцией в данном случае определяется значением поля sign.Заметьте,
Приведу теперь процедуру, тестирующую работу созданного класса:
public void TestOwnDel()
{
int account = 1000, account1=0;
OwnDel oda = new OwnDel('+');
Console.WriteLine("account = {0}, account1 = {1}",
account, account1);
oda.p1(ref account); account1=oda.f1(account);
Console.WriteLine("account = {0}, account1 = {1}",
account, account1);
}
Клиент класса OwnDel создает экземпляр класса, передавая конструктору знак той операции, которую он хотел бы выполнить над своими счетами - account и account1. Вызов p1 и f1, связанных к моменту вызова с
В нашем примере объявление Person, слегка изменив его определение:
class Person
{
//конструкторы
public Person(){name =""; id=0; salary=0.0;}
public Person(string name){this.name = name;}
public Person (string name, int id, double salary)
{this.name = name; this.id=id; this.salary = salary;}
public Person (Person pers)
{this.name = pers.name; this.id = pers.id;
this.salary = pers.salary;}
//методы
public void ToPerson(string mes)
{
this.message = mes;
Console.WriteLine("{0}, {1}",name, message);
}
//свойства
private string name;
private int id;
private double salary;
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 man1 = new Person("Владимир");
MesToPers mestopers = new MesToPers(man1.ToPerson);
mestopers("пора работать!");
Обратите внимание, что поскольку метод ToPerson не является статическим методом, то при связывании необходимо передать и объект, вызывающий метод. Более того, переданный объект становится доступным
Последние три строки были добавлены в вышеприведенную тестирующую процедуру. Взгляните на результаты ее работы.
(рис 20.1) Объявление делегатов и создание их экземпляровОдно из наиболее важных применений Compare, сравнивающая два объекта. В зависимости от того, какая функция сравнения будет передана на вход функции сортировки, объекты будут сортироваться по-разному, например, по имени, или по ключу, или по нескольким полям. Вариантов может быть много, и они определяются классом, описывающим сортируемые объекты.
Давайте более подробно рассмотрим ситуацию с
public class HighOrderIntegral
{
//delegate
public delegate double SubIntegralFun(double x);
public double EvalIntegral(double a, double b,
double eps,SubIntegralFun 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-I0)<eps) break;
}
if(Math.Abs(I1-I0)< eps)
Console.WriteLine("Требуемая точность достигнута! "+
" eps = {0}, достигнутая точность ={1}, n= {2}",
eps,Math.Abs(I1-I0),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,
SubIntegralFun sif)
{
//Вычисляет частную сумму по методу трапеций
double x = 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.eps, то вычисление интеграла заканчивается, иначе процесс повторяется в цикле. Цикл завершается либо по достижении заданной точности, либо когда n достигнет некоторого предельного значения (в нашем случае - 215).I.Чтобы продемонстрировать работу с классом HighOrderIntegral, приведу еще класс Functions, где описано несколько функций, удовлетворяющих SubIntegralFun:
class Functions
{
//подынтегральные функции
public static double sif1(double x)
{
int k = 1; int b = 2;
return (double)(k*x +b);
}
public static 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()
{
double myint1=0.0;
HighOrderIntegral.SubIntegralFun hoisif1 =
new HighOrderIntegral.SubIntegralFun(Functions.sif1);
HighOrderIntegral hoi = new HighOrderIntegral();
myint1 = hoi.EvalIntegral(2,3,0.1e-5,hoisif1);
Console.WriteLine("myintegral1 = {0}",myint1);
HighOrderIntegral.SubIntegralFun hoisif2 =
new HighOrderIntegral.SubIntegralFun(Functions.sif2);
myint1= hoi.EvalIntegral(2,3,0.1e-5,hoisif2);
Console.WriteLine("myintegral2 = {0}",myint1);
}//EvalIntegrals
Здесь создаются два HighOrderIntegral, вызывающий метод вычисления интеграла. Результаты работы показаны на 20.2.
(рис 20.2) Вычисление интеграла с использованием функций высших порядков
(рис 20.3) Построение системы методом "раскрутки"Успех языка С и операционной системы Unix во многом объясняется тем, что в свое время они были созданы
При построении систем
Пусть F - G функционального типа. Тогда функцию G, задающую параметр (а иногда и саму функцию F ), называют Q вызывает функцию внутреннего F, то предварительно во внешнем G, которая и будет передана F. Таким образом, функция Q внешнего F внутреннего G внешнего
Наш пример с вычислением интеграла хорошо демонстрирует HighOrderIntegral - это внутренний EvalIntegral, требующая задания EvalIntegral вызывается из внешнего callback функции из класса Functions.
Многие из функций операционной системы Windows, входящие в состав Win API 32, требуют при своем вызове задания callback - функций. Примером может служить работа с объектом операционной системы Timer. Конструктор этого объекта является callback - функцию, вызываемую для обработки событий, которые поступают от таймера.
Пример работы с таймером приводить сейчас не буду, ограничусь лишь сообщением синтаксиса объявления конструктора объекта Timer:
public Timer(TimerCallback callback,object state, int dueTime, int period);
Первым параметром конструктора является callback, которая принадлежит функциональному классу TimerCallback, заданному
public delegate void TimerCallback(object state);
Сегодня многие программные системы проектируются и разрабатываются не в функциональном, а в объектно-ориентированном стиле. Такая система представляет собой одно или несколько семейств интерфейсов и классов, связанных отношением наследования. Классы-потомки наследуют методы своих родителей, могут их переопределять и добавлять новые методы. Переопределив метод родителя, потомки без труда могут вызывать как собственный метод, так и метод родителя; все незакрытые методы родителя им известны и доступны. Но может ли родитель вызывать методы, определенные потомком, учитывая, что в момент создания родительского метода потомок не только не создан, но еще, скорее всего, и не спроектирован? Тем не менее, ответ на этот вопрос положителен. Достигается такая возможность опять-таки благодаря
О полиморфизме говорилось достаточно много в предыдущих лекциях. Тем не менее, позволю напомнить суть дела. Родитель может объявить свой метод виртуальным, в этом случае в Q вызывает виртуальный метод F, то, благодаря позднему связыванию, реализуется полиморфизм и реально будет вызван не метод родителя F, а метод F, который реализован потомком, вызвавшим родительский метод Q. Ситуация в точности напоминает раскрутку и вызов обратных функций. Родительский метод Q находится во внутреннем F из внешнего F в данном случае задается не
Идея примера такова. Вначале построим родительский класс, метод которого будет вычислять интеграл от некоторой подынтегральной функции, заданной виртуальным методом класса. Далее построим класс-потомок, наследующий родительский метод вычисления интеграла и переопределяющий виртуальный метод, в котором потомок задаст собственную подынтегральную функцию. При такой технологии, всякий раз, когда нужно вычислить интеграл, нужно создать класс-потомок, в котором переопределяется виртуальный метод. Приведу пример кода, следующего этой схеме:
class FIntegral
{
//базовый класс, в котором определен метод вычисления
//интеграла и виртуальный метод, задающий базовую
//подынтегральную функцию
public double EvaluateIntegral(double a, double b,
double eps)
{
int n=4;
double I0=0, I1 = I( a, b, n);
for( n=8; n < Math.Pow(2.0,15.0); n*=2)
{
I0 =I1; I1=I(a,b,n);
if(Math.Abs(I1-I0)<eps)break;
}
if(Math.Abs(I1-I0)< eps)
Console.WriteLine("Требуемая точность достигнута! "+
" eps = {0}, достигнутая точность ={1}, n= {2}",
eps,Math.Abs(I1-I0),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)
{
//Вычисляет частную сумму по методу трапеций
double x = 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);
}
protected virtual double sif(double x)
{return(1.0);}
}//FIntegral
Этот код большей частью знаком. В отличие от класса HighOrderIntegral, здесь нет EvaluateIntegral нет параметра функционального типа. Вместо этого тут же в классе определен защищенный виртуальный метод, задающий конкретную подынтегральную функцию. В качестве таковой выбрана самая простая функция, тождественно равная единице.
Для вычисления интеграла от реальной функции единственное, что теперь нужно сделать - это задать класс-потомок, переопределяющий виртуальный метод. Вот пример такого класса:
class FIntegralSon:FIntegral
{
protected override double sif(double x)
{
double a = 1.0; double b = 2.0; double c= 3.0;
return (double)(a*x*x +b*x +c);
}
}//FIntegralSon
Принципиально задача решена. Осталось только написать фрагмент кода, запускающий вычисления. Он оформлен в виде следующей процедуры:
public void TestPolymorphIntegral()
{
FIntegral integral1 = new FIntegral();
FIntegralSon integral2 = new FIntegralSon();
double res1 = integral1.EvaluateIntegral(2.0,3.0,0.1e-5);
double res2 = integral2.EvaluateIntegral(2.0,3.0,0.1e-5);
Console.WriteLine("Father = {0}, Son = {1}", res1,res2);
}//PolymorphIntegral
Взгляните на результаты вычислений.
(рис 20.4) Вычисление интеграла, использующее полиморфизм
В наших примерах рассматривалась ситуация, при которой в некотором классе объявлялись функции, удовлетворяющие
Рассмотрим пример, демонстрирующий и поясняющий эту возможность при работе с
Person с полями: имя, идентификационный номер, зарплата. В этом классе определим различные реализации функции Compare, позволяющие сравнивать два объекта по имени, по номеру, по зарплате, по нескольким полям. Самое интересное, ради чего и строится данный пример: для каждой реализации Compare будет построена процедура-свойство, которая задает реализацию Persons ;Persons будет играть роль Person.В этом классе будут определены операции над объектами. Среди операций нас, прежде всего, будет интересовать сортировка объектов, реализованная в виде Person. Persons.
Теперь, когда задача ясна, приступим к ее реализации. Класс Person уже появлялся в наших примерах, поэтому он просто дополнен до нужной функциональности. Добавим методы сравнения двух объектов Person:
//методы сравнения
private static int CompareName(Person obj1, Person obj2)
{
return(string.Compare(obj1.name,obj2.name));
}
private static int CompareId(Person obj1, Person obj2)
{
if( obj1.id > obj2.id) return(1);
else return(-1);
}
private static int CompareSalary(Person obj1, Person obj2)
{
if( obj1.salary > obj2.salary) return(1);
else if(obj1.salary < obj2.salary)return(-1);
else return(0);
}
private static int CompareSalaryName(Person obj1, Person obj2)
{
if( obj1.salary > obj2.salary) return(1);
else if(obj1.salary < obj2.salary)return(-1);
else return(string.Compare(obj1.name,obj2.name));
}
Заметьте, методы закрыты и, следовательно, недоступны извне. Их четыре, но могло бы быть и больше, при возрастании сложности объекта растет число таких методов. Все методы имеют одну и ту же сигнатуру и удовлетворяют Person.
//делегаты как свойства
public static Persons.CompareItems SortByName
{
get {return(new Persons.CompareItems(CompareName));}
}
public static Persons.CompareItems SortById
{
get {return(new Persons.CompareItems(CompareId));}
}
public static Persons.CompareItems SortBySalary
{
get {return(new Persons.CompareItems(CompareSalary));}
}
public static Persons.CompareItems SortBySalaryName
{
get {return(new Persons.CompareItems(CompareSalaryName));}
}
Всякий раз, когда будет запрошено, например, свойство SortByName класса Person, будет возвращен объект функционального класса Persons.CompareItems, задающий ссылку на метод CompareName класса Person. Объект будет создаваться динамически в момент запроса.
Класс Person полностью определен, и теперь давайте перейдем к определению Person. Начну с определения свойств класса Persons:
class Persons
{ //контейнер объектов Person
//делегат
public delegate int CompareItems(Person obj1, Person obj2);
private int freeItem = 0;
const int n = 100;
private Person[]persons = new Person[n];
}
В классе определен функциональный класс - CompareItems, задающий
freeItem - указатель на первый свободный элемент массива. Сам массив является закрытым свойством, и доступ к нему осуществляется благодаря индексатору:
//индексатор
public Person this[int num]
{
get { return(persons[num-1]); }
set { persons[num-1] = value; }
}
Добавим классический для контейнеров набор методов - добавление нового элемента, загрузка элементов из базы данных и печать элементов:
public void AddPerson(Person pers)
{
if(freeItem < n)
{
Person p = new Person(pers);
persons[freeItem++]= p;
}
else Console.WriteLine("Не могу добавить Person");
}
public void LoadPersons()
{
//реально загрузка должна идти из базы данных
AddPerson(new Person("Соколов",123, 750.0));
AddPerson(new Person("Синицын",128, 850.0));
AddPerson(new Person("Воробьев",223, 750.0));
AddPerson(new Person("Орлов",129, 800.0));
AddPerson(new Person("Соколов",133, 1750.0));
AddPerson(new Person("Орлов",119, 750.0));
}//LoadPersons
public void PrintPersons()
{
for(int i =0; i<freeItem; i++)
{
Console.WriteLine("{0,10} {1,5} {2,5}",
persons[i].Name, persons[i].Id, persons[i].Salary);
}
}//PrintPersons
Конечно, метод LoadPerson в реальной жизни устроен по-другому, но в нашем примере он свою задачу выполняет. А теперь определим метод сортировки записей с функциональным параметром, задающим тот или иной способ сравнения элементов:
//сортировка
public void SimpleSortPerson(CompareItems compare)
{
Person temp = new Person();
for(int i = 1; i<freeItem;i++)
for(int j = freeItem -1; j>=i; j--)
if (compare(persons[j],persons[j-1])==-1)
{
temp = persons[j-1];
persons[j-1]=persons[j];
persons[j] = temp;
}
}//SimpleSortObject
}//Persons
Единственный аргумент метода SimpleSortPerson принадлежит классу CompareItems, заданному
public void TestSortPersons()
{
Persons persons = new Persons();
persons.LoadPersons();
Console.WriteLine (" Сортировка по имени: ");
persons.SimpleSortPerson(Person.SortByName);
persons.PrintPersons();
Console.WriteLine (" Сортировка по идентификатору: ");
persons.SimpleSortPerson(Person.SortById);
persons.PrintPersons();
Console.WriteLine (" Сортировка по зарплате: ");
persons.SimpleSortPerson(Person.SortBySalary);
persons.PrintPersons();
Console.WriteLine (" Сортировка по зарплате и имени: ");
persons.SimpleSortPerson(Person.SortBySalaryName);
persons.PrintPersons();
}//SortPersons
Результаты работы сортировки данных изображены на рис. 20.5.
(рис 20.5) Сортировка данных
Давайте просуммируем то, что уже известно о функциональном типе данных. Ключевое слово delegate позволяет задать определение функционального типа (класса), фиксирующее
Вместе с тем, объявление функционального типа не укладывается в синтаксис, привычный для C#. Хотелось бы писать, как принято:
Delegate FType = new Delegate(<определение типа>)
Но так объявлять переменные этого класса нельзя, и стоит понять, почему. Есть ли вообще
public delegate int FType(int X);
переменная FType принадлежит
public abstract class Delegate: ICloneable, ISerializable
Для абстрактных классов реализация не определена, и это означает, что нельзя создавать экземпляры класса. delegate, которое косвенно позволяет работать с
Трудно, кажется, придумать, что можно делать с
В чем суть
Понятно, что, если есть операция присоединения
Рассмотрим основные Combine и Remove. Первый из них присоединяет
Combine(del1, del2) Remove(del1, del2)
Аргументы del1 и del2 должны быть одного функционального класса. При добавлении del2 в список, в котором del2 уже присутствует, будет добавлен второй экземпляр. При попытке удаления del2 из списка, в котором del2 нет, Remove благополучно завершит работу, не выдавая сообщения об ошибке.
del1 и del2. Обычное использование этих методов имеет вид:
del1 = (<type>) Combine(del1, del2); del1 = (<type>) Remove(del1, del2);
Метод GetInvocationList является динамическим методом класса - он возвращает foreach, поочередно получая элементы списка. Чуть позже появится пример, поясняющий необходимость подобной работы со списком.
Два Method и Target полезны для получения подробных сведений о Method возвращает объект класса MethodInfo из пространства имен Reflection. Свойство Target возвращает информацию об объекте, вызвавшем
У Object, есть еще несколько методов, но мы на них останавливаться не будем, они используются не столь часто.
Наряду с методами, над Combine и Remove. Выше написанные присваивания объекту del1 с помощью этих операций могут быть переписаны в виде:
del1 +=del2; del1 -=del2;
Как видите, запись становится проще, исчезает необходимость в задании явного приведения к типу. Ограничения на del1 и del2, естественно, остаются те же, что и для методов Combine и Remove.
Рассмотрим следующую ситуацию. Пусть есть городские службы: милиция, скорая помощь, пожарные. Каждая из служб по-своему реагируют на события, происходящие в городе. Построим примитивную модель жизни города, в которой случаются события и сообщения о них посылаются службам. В последующей лекции эта модель будет развита. Сейчас она носит формальный характер, демонстрируя, главным образом, работу с
Начнем с построения класса с именем Combination, где, следуя уже описанной технологии, введем get. Три MesToPers, размещенным в пространстве имен проекта. Вот программный код, в котором описаны функции, задающие действия служб:
class Combination
{
private static void policeman(string mes)
{
//анализ сообщения
if(mes =="Пожар!")
Console.WriteLine(mes + " Милиция ищет виновных!");
else
Console.WriteLine(mes +" Милиция здесь!");
}
private static void ambulanceman(string mes)
{
if(mes =="Пожар!")
Console.WriteLine(mes + " Скорая спасает пострадавших!");
else
Console.WriteLine(mes + " Скорая помощь здесь!");
}
private static void fireman(string mes)
{
if(mes =="Пожар!")
Console.WriteLine(mes + " Пожарные тушат пожар!");
else
Console.WriteLine( mes + " Пожарные здесь!");
}
}
Как видите, все три функции имеют не только одинаковую сигнатуру, но и устроены одинаково. Они анализируют приходящее к ним сообщение, переданное через параметр mes, а затем, в зависимости от результата, выполняют ту или иную работу, которая в данном случае сводится к выдаче соответствующего сообщения. Сами функции закрыты, и мы сейчас организуем к ним доступ:
public static MesToPers Policeman
{
get {return (new MesToPers(policeman));}
}
public static MesToPers Fireman
{
get {return (new MesToPers(fireman));}
}
public static MesToPers Ambulanceman
{
get {return (new MesToPers(ambulanceman));}
}
Три статических открытых свойства - Policeman, Fireman, Ambulanceman - динамически создают экземпляры класса MesToPers, связанные с соответствующими закрытыми функциями класса.
Службы у нас есть, покажем, как с ними можно работать. С этой целью добавим в класс Testing, где проводятся различные эксперименты, следующую процедуру:
public void TestSomeServices()
{
MesToPers Comb;
Comb = (MesToPers)Delegate.Combine(Combination.Ambulanceman,
Combination.Policeman);
Comb = (MesToPers)Delegate.Combine(Comb,Combination.Fireman);
Comb("Пожар!");
Вначале объявляется без инициализации Comb, которой в следующем операторе присваивается ссылка на Combine, чей Ambulanceman и Policeman. Затем к Comb присоединяется новый кандидат Fireman. При вызове Comb ему передается сообщение "Пожар!". В результате вызова Comb поочередно запускаются все три экземпляра входящие в список, каждому из которых передается сообщение.
Давайте теперь начнем поочередно отключать Comb с новыми сообщениями:
Comb = (MesToPers)Delegate.Remove(Comb,Combination.Policeman);
//Такое возможно: попытка отключить не существующий элемент
Comb = (MesToPers)Delegate.Remove(Comb,Combination.Policeman);
Comb("Через 30 минут!");
Comb = (MesToPers)Delegate.Remove(Comb,Combination.Ambulanceman);
Comb("Через час!");
Comb = (MesToPers)Delegate.Remove(Comb,Combination.Fireman);
//Comb("Через два часа!"); // Comb не определен
В этом фрагменте поочередно отключаются разные службы - милиция, скорая помощь, пожарные, и каждый раз вызывается Comb. После последнего отключения, когда Comb приводит к ошибке, потому оператор вызова закомментирован.
Покажем теперь, что ту же работу можно выполнить, используя не методы, а операции:
//операции + и -
Comb = Combination.Ambulanceman;
Console.WriteLine( Comb.Method.Name);
Comb+= Combination.Fireman;
Comb+= Combination.Policeman;
Comb("День города!");
Comb -= Combination.Ambulanceman;
Comb -= Combination.Fireman;
Comb("На следующий день!");
}//TestSomeServices
Обратите внимание, здесь демонстрируется вызов свойства Method, возвращающее объект, свойство Name которого выводится на печать. Результаты, порожденные работой этой процедуры, изображены на рис. 20.6.
(рис 20.6) Службы города
Как быть, если в GetInvocationList и в цикле поочередно вызывать
Добавим в класс Combination "плохого" кандидата, который пытается делить на ноль:
//метод, вызывающий исключительную ситуацию
public static void BadService(string mes)
{
int i =7, j=5, k=0;
Console.WriteLine("Bad Service: Zero Divide");
j=i/k;
}
Создадим процедуру, в которой в
public void TestBadJob()
{
MesToPers Comb;
Comb = (MesToPers)Delegate.Combine(Combination.Ambulanceman,
Combination.Policeman);
Comb = (MesToPers)Delegate.Combine(Comb,
new MesToPers(Combination.BadService));
Comb = (MesToPers)Delegate.Combine(Comb,Combination.Fireman);
foreach(MesToPers currentJob in Comb.GetInvocationList())
{
try
{
currentJob("Пожар!");
}
catch(Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(currentJob.Method.Name);
}
}
}//BadJob
Поясню, как будет работать эта процедура при ее вызове. Вначале две службы нормально отработают, но при вызове третьей службы возникнет исключительная ситуация "деление на ноль". Универсальный обработчик Exception перехватит эту ситуацию и напечатает как свойство Message объекта e, так и имя метода, вызвавшего исключительную ситуацию, используя свойство Method объекта, вызвавшего ситуацию. После завершения работы блока обработчика ситуации выполнение программы продолжится, выполнится следующий шаг цикла, и служба пожарных благополучно выполнит свою работу. Вот результаты вывода:
(рис 20.7) "Плохая служба"Разговор о
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.