Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Универсальные коллекции языка С#. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
связать с любым методом с совместимой сигнатурой и типом возвра­щаемого значения. Метод можно вызвать (активировать) с помощью экземпляра делегата.
Делегаты используются для передачи методов в качестве аргумен­тов в другие методы.
Тип делегата объявляется с помощью ключевого слова delegate. Ниже приведена общая форма объявления делегата:
delegateвозвращаемый_типимя(список_параметров);
где возвращаемый_тип обозначает тип значения, возвращаемого мето­дами, которые будут вызываться делегатом; имя – конкретное имя де­легата; список_параметров – параметры, необходимые для методов, вызываемых делегатом. Как только будет создан экземпляр делегата, он может вызывать и ссылаться на методы, возвращаемый тип и пара­метры которых соответствуют указанным в объявлении делегата.
В
следующем примере показано объявление делегата:
publicdelegateintOperation(intx,inty);
Экземпляр делегата типа Operation может ссылаться на метод, получающий два значения типа int и возвращающий значение этого же типа.
Делегату можно назначить любой метод из любого доступного класса или структуры, соответствующей типу делегата. Этот метод должен быть статическим методом или методом экземпляра. Такая гибкость позволяет программно изменять вызовы метода, а также включать новый
код в существующие классы.
При перегрузке метода его сигнатура не содержит возвращаемого значения. Однако для делегатов сигнатура метода содержит возвраща­емое значение. Другими словами, метод должен иметь тот же возвра­щаемый тип, что и делегат.
Делегаты имеют следующие свойства.
Делегаты подобны указателям на функции в C++, но являются полностью объектно-
ориентированными и, в отличие от указателей C++ на функции-члены, инкапсулируют экземпляр объекта вместе с методом.
Делегаты допускают передачу методов в качестве параметров.
21
Делегаты можно использовать для определения методов обрат-
ного вызова.
Делегаты можно связывать друг с другом; например, при появ-
лении одного события можно вызывать несколько методов.
Точное соответствие методов типу делегата не требуется.  Для краткой записи встроенных блоков кода введены лямбда-
выражения. В результате компиляции лямбда-выражений (в
опреде-
ленном контексте) получаются типы делегатов.
В следующем примере показано, как определить делегат с именем myMethodDelegate. Экземпляры этого делегата создаются для метода экземпляра и статического метода класса myClass. Делегат для метода экземпляра требует наличия экземпляра класса myClass. Экземпляр класса myClass сохраняется в переменной с именем obj.
usingSystem; 
namespaceExample_2 {
//Определяетсянесколькометодов,накоторыеделегатможетуказывать. publicclassmyClass {  //Определяетсяметодэкземпляра. //Методвозвращаетчислодесятичныхразрядовпредставленияцелого. publicintmyMethod(intmyInt) { intn=Math.Abs(myInt); intr=0; while(n>0){n/=10;r++;} returnr; }  //
Определяетсястатическийметод. //Методвозвращаетчислодвоичныхразрядовпредставленияцелого. publicstaticintmyStatMethod(intmyInt) { intn=Math.Abs(myInt); intr=0; while(n>0){n/=2;r++;} returnr; }
22
} classProgram { //Объявляемделегатдляметода,которыйпринимаетintивозвращаетstring. publicdelegateintmyMethodDelegate(intmyInt);//Делегат. staticvoidMain(string[]args) { //Создаетсяпоодномуделегатудлякаждогометода.Дляметодаэкземпляра, //экземпляр(obj)долженбытьиспользован.Дляstaticметода,используйте //имякласса. myClassobj=newmyClass(); myMethodDelegatemyD1=newmyMethodDelegate(obj.myMethod); myMethodDelegatemyD2=newmyMethodDelegate(myClass.myStatMethod); myMethodDelegatemyD3=obj.myMethod; myMethodDelegatemyD4=myClass.myStatMethod;  //Вызываемделегаты. Console.WriteLine($"{100}‐{myD1(100)}десятичныхразрядов"); Console.WriteLine($"{‐100}‐{myD2(‐100)}двоичныхразрядов"); Console.WriteLine($"{100}‐{myD3(100)}десятичныхразрядов"); Console.WriteLine($"{‐100}‐{myD4(‐100)}двоичныхразрядов"); } } } Результатработыпримера: 100‐3десятичныхразрядов ‐100‐7двоичныхразрядов 100‐3десятичныхразрядов ‐100‐7двоичныхразрядов
Делегаты могут быть универсальными. В коде, который ссылается на универсальный делегат, можно указать аргумент типа для создания закрытого сконструированного типа. Это будет аналогично созданию экземпляра универсального класса или вызову универсального метода, как показано в следующем примере:
publicdelegatevoidDel<T>(Titem);//Универсальныйтипделегата. publicstaticvoidNotify(inti){}//Метод. //ЭкземплярделегатассылаетсянаметодNotify Del<int>m1=newDel<int>(Notify); Del<int>m2=Notify;//Упрощенныйсинтаксис.
23
ДЕЛЕГАТ ACTION
В C# имеется предопределенный набор универсальных классов де­легатов Action<T> и Func<T>. Обобщенный делегат Action<T> предна­значен для ссылки на метод, возвращающий значение void. Этот класс делегата существует в различных вариантах, так что ему можно пере­давать до 16 разных типов параметров.
Класс Action без обобщенного параметра предназначен для вызова метода, который не имеет параметров и не
publicdelegatevoidAction();
возвращает значений.
Класс Action<in Т> предназначен для вызова метода, который при­нимает один параметр и не возвращает значения.
publicdelegatevoidAction<inT>(Tobj);
Action<in T1, in Т2> – для вызова метода с двумя параметрами и Action<in T1, in Т2, in ТЗ, in Т4, in Т5, in Т6, in Т7, in Т8> – для вызо­ва метода с восемью параметрами.
В приведенном ниже примере демонстрируется применение уни­версального делегата класса Action<Т> для выполнения над элемента­ми массива int[] v действий, описанных в методе Moo и Goo. Метод
Moo вызывается в методе Do, а метод
Goo в методе ForEach класса
Array и применяется ко всем элементам массива.
classProgram { staticvoidMain(string[]args) { //ОбъявляемуниверсальныйделегатextractMethклассаAction<int> //исвязываемегосметодомMoo. Action<int>extractMeth=Moo;  int[]v={9,12,8,15,23,5,24}; //ИспользуемобъектклассаActionдлявызоваметодаMoo Do(v,extractMeth);  //ИспользуемобъектклассаActionдлявызоваметодаGoo Array.ForEach(v,Goo); }  staticvoidDo(int[]v,Action<int>f)
24
{ foreach(vareinv)f(e); }  staticvoidMoo(inta) { if(a>10&a<30)Console.WriteLine($"MOO{a}"); }  staticvoidGoo(inta) { if(a%2==0)Console.WriteLine($"GOO{a}"); } }  Результатработыпримера:
MOO12 MOO15 MOO23 MOO24 GOO12 GOO8 GOO24
ДЕЛЕГАТ FUNC
Делегаты классов Func<> подобно делегатам классов Action<> определены в разных вариантах для передачи до 16 типов параметров и типа возвращаемого значения. Делегат класса Func<out TResult > позволяет вызывать метод, который не имеет параметров и возвращает значение типа, указанного в параметре TResult.
publicdelegateTResultFunc<outTResult>();
Func<in T, out TResult> – тип делегата для вызова метода с одним параметром T, который возвращает значение типа, указанного в пара­метре TResult.
publicdelegateTResultFunc<inT,outTResult>(Targ);
Делегат класса Func<in T1, in T2, in ТЗ, in T4, out TResult> – тип делегата для вызова метода с четырьмя параметрами.
25
В приведенном ниже примере демонстрируется применение уни­версального делегата extractMethod класса Func<T1, T2, TResult> для нахождения суммы элементов массива int[] v, удовлетворяющих условию, описанному в методе ExtractNumbers. Метод ExtractNumbers вызывается в методе Aggregate для каждого элемента массива int[] v и суммирует его положительные значения с текущей суммой. Началь­ное значение текущей суммы – ноль.
classProgram { staticvoidMain(string[]args) { //Инициируемделегат(delegate)ссылкойнаметодExtractNumbers. Func<int,int,int>extractMethod=ExtractNumbers; int[]v={1,2,4,3,‐3,8,‐5}; //ИспользуемобъектделегатадлявызоваметодаExtractNumbers //иотображаемрезультат. ints=v.Aggregate(0,extractMethod); Console.WriteLine($"суммаположительныхчисел={s}"); } private { if(next>0)returnsum+=next; elsereturnsum; } }  Результатработыпримера: суммаположительныхчисел=18
staticintExtractNumbers(intsum,intnext)
ДЕЛЕГАТ PREDICATE
Это класс, объекты этого класса представляют метод, определяю­щий набор критериев и соответствие переданного в метод объекта этим критериям. Его тип определен следующим образом:
publicdelegateboolPredicate<inT>(Tobj);
Здесь T – тип сравниваемого объекта obj (входной параметр), прове­ряемый на соответствие критериям, заданным в методе, который будет представлять делегат. Метод, представляемый делегатом
26
Predicate, возвращает значение типа Boolean. Он возвращает значение true, если объект obj удовлетворяет критериям, заданным в методе, который представляет этот делегат; в противном случае – значение false.
В следующем примере кода делегат типа Predicate<T> с методом Array.FindAll используется для формирования строкового массива фа-
милий names, которые оканчиваются на «ов» и вывода его элементов на консоль. В примере
явно определяется делегат типа Predicate<T> с именем pred и связывается с лямбда-выражением, которое возвраща­ет true, если строка, переданная в него, оканчивается на «ов». Обратите внимание, что в метод FindAll можно сразу передать лямбда­выражение вместо явного определения делегата типа Predicate<T>, как показано во втором вызове метода.
staticvoidMain(string[]args) { Predicate<string>pred=x=>x.EndsWith("ов"); string[]names= {"Свичко","Петров","Дробот","Сидоров","Мишин","Федоров"}; //Формируеммассивфамилий,оканчивающихсяна"ов" //ивыводимнаконсоль. //Используемделегат. foreach(varnameinArray.FindAll(names,pred)) Console.WriteLine($"{name}"); //Используемлямдавыражениенепосредственно. Console.WriteLine();  foreach(varnameinArray.FindAll(names,x=>x.EndsWith("ов"))) Console.WriteLine($"{name}"); } Результатработыпримера: Петров Сидоров Федоров  Петров Сидоров Федоров 
27
АНОНИМНЫЕ МЕТОДЫ
Метод, на который ссылается делегат, нередко используется только для вызова посредством делегата, но непосредственно он не вызывает­ся вообще. В таких случаях можно воспользоваться анонимной функ­цией, чтобы не создавать отдельный метод. Анонимная функция представляет собой безымянный кодовый блок, передаваемый кон­структору делегата. Преимущество анонимной функции состоит в том
,
что ее использование избавляет от необходимости объявлять отдель­ный метод, единственное назначение которого состоит в том, чтобы передать его делегату.
В C# предусмотрены две разновидности анонимных функций: ано­нимные методы и лямбда-выражения.
Лямбда-выражение совершенствует принцип действия анонимного метода и в настоящее время считается предпочтительным для создания анонимной функции. Но
анонимные методы широко применяются в существующем коде C# и поэтому по-прежнему являются важной составной частью C#. Поскольку анонимные методы предшествовали появлению лямбда-выражений, представление о них позволяет лучше понять особенности лямбда-выражений. Кроме того, анонимные мето­ды могут быть использованы в целом ряде случаев, где применение лямбда-выражений невозможно.
Анонимный метод – один
из способов создания безымянного блока кода, связанного с конкретным экземпляром делегата. Для создания анонимного метода достаточно указать кодовый блок после ключевого слова delegate.
В приведенном ниже примере демонстрируется применение деле­гата класса Del и универсального делегата extractMethod класса Func<T1,T2, TResult>, которые связываются с анонимным методом. Метод ExtractNumbers вызывается в методе Aggregate для каждого элемента массива int[] v и суммирует
его четные значения с текущей
суммой. Начальное значение текущей суммы – ноль.
classProgram { delegatevoidDel(stringstr); staticvoidMain(string[]args) { //ИнстанцируемделегатDelиспользуяанонимныйметод.
28
Deldel=delegate(stringname) {Console.WriteLine($"Уведомление,полученноедля:{name}");}; del("Михаила");  //Инициируемделегат(delegate)анонимнымметодом. Func<int,int,int>extractMethod=delegate(intsum,intnext) { if(next%2==0)returnsum+=next; elsereturnsum; };  int[]v={ //Используемобъектделегатадлявызоваанонимногометода //иотображаемрезультат. ints=v.Aggregate(0,extractMethod); Console.WriteLine($"суммачетныхчисел={s}"); } }  Результатработыпримера: Уведомление,полученноедля:Михаила суммачетныхчисел=14
1,2,4,3,‐3,8,‐5};
ЛЯМБДЫ
Начиная с C# 3.0 доступен новый синтаксис для назначения реали­зации кода делегатам, называемый лямбда-выражениями (lambda expression). Лямбда-выражения можно использовать везде, где есть параметр типа делегата. Синтаксис лямбда-выражений проще синтак­сиса анонимных методов.
Во всех лямбда-выражениях применяется лямбда-оператор (=>), который разделяет лямбда-выражение на две части. В левой его
части указывается входной параметр (или несколько параметров), а в правой части – тело лямбда-выражения. Оператор (=>) надо понимать как «переходит» или «становится».
Лямбда-выражение может быть преобразовано в тип делегата, ко­торый определяется типами параметров и возвращаемым значением лямбда-выражения. Если лямбда-выражение не возвращает значение, оно может быть преобразовано в
один из типов делегата Action; в про-
тивном случае его можно преобразовать в один из типов делегатов
Func или Predicate.
29
В C# поддерживаются две разновидности лямбда-выражений в за­висимости от тела самого лямбда-выражения. Если тело лямбда­выражения состоит из одного выражения, то это лямбда-выражение. В этом случае тело выражения не заключается в фигурные скобки. Ес­ли тело лямбда-выражения состоит из блока операторов, заключенных в фигурные скобки, то это
– лямбда-оператор. При этом лямбда опера­тор может содержать целый ряд операторов, в том числе циклы, вызо­вы методов и условные операторы if.
ЛЯМБДА-ВЫРАЖЕНИЯ
В лямбда-выражении часть, находящаяся справа от оператора (=>), воздействует на параметр (или ряд параметров), указываемый слева. Возвращаемым результатом вычисления такого выражения является результат выполнения выражения. Ниже приведена общая форма лямбда-выражения, принимающего единственный параметр:
параметр=>выражение
Если же требуется указать несколько параметров, то используется следующая форма:
(список_параметров)=>выражение
Таким образом, когда требуется указать два параметра или более, их следует заключить в скобки. Если же выражение не требует пара­метров, то следует использовать пустые скобки.
Для использования лямбда-выражения необходимо сначала объ­явить тип делегата, совместимый с лямбда-выражением, а затем экзем­пляру делегата присвоить лямбда-выражение. Лямбда-выражение бу
-
дет вычислено при обращении к экземпляру делегата. Результатом его вычисления становится возвращаемое значение.
В приведенном ниже примере демонстрируется применение лябда­выражения для инициализации универсального делегата d2 класса Func<int,int> и передачи лямбда-выражения в качестве параметра­делегата в функцию Average. Функция Average, используя d2, вычис­ляет среднее суммы квадратов элементов массива int[] v, и, используя лямбда-выражение, вычисляет
среднее суммы произведений элементов
30
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]