Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Универсальные коллекции языка С#. Учебное пособие
.pdf
связать с любым методом с совместимой сигнатурой и типом возвращаемого значения. Метод можно вызвать (активировать) с помощью
экземпляра делегата.
Делегаты используются для передачи методов в качестве аргументов в другие методы.
Тип делегата объявляется с помощью ключевого слова delegate.
Ниже приведена общая форма объявления делегата:
delegateвозвращаемый_типимя(список_параметров);
где возвращаемый_тип обозначает тип значения, возвращаемого методами, которые будут вызываться делегатом; имя – конкретное имя делегата; список_параметров – параметры, необходимые для методов,
вызываемых делегатом. Как только будет создан экземпляр делегата,
он может вызывать и ссылаться на методы, возвращаемый тип и параметры которых соответствуют указанным в объявлении делегата.
В
следующем примере показано объявление делегата:
publicdelegateintOperation(intx,inty);
Экземпляр делегата типа Operation может ссылаться на метод,
получающий два значения типа int и возвращающий значение этого
же типа.
Делегату можно назначить любой метод из любого доступного
класса или структуры, соответствующей типу делегата. Этот метод
должен быть статическим методом или методом экземпляра. Такая
гибкость позволяет программно изменять вызовы метода, а также
включать новый
код в существующие классы.
При перегрузке метода его сигнатура не содержит возвращаемого
значения. Однако для делегатов сигнатура метода содержит возвращаемое значение. Другими словами, метод должен иметь тот же возвращаемый тип, что и делегат.
Делегаты имеют следующие свойства.
Делегаты подобны указателям на функции в C++, но являются
полностью объектно-
ориентированными и, в отличие от указателей
C++ на функции-члены, инкапсулируют экземпляр объекта вместе
с методом.
Делегаты допускают передачу методов в качестве параметров.
21

Делегаты можно использовать для определения методов обрат-
ного вызова.
Делегаты можно связывать друг с другом; например, при появ-
лении одного события можно вызывать несколько методов.
Точное соответствие методов типу делегата не требуется.
Для краткой записи встроенных блоков кода введены лямбда-
выражения. В результате компиляции лямбда-выражений (в
опреде-
ленном контексте) получаются типы делегатов.
В следующем примере показано, как определить делегат с именем
myMethodDelegate. Экземпляры этого делегата создаются для метода
экземпляра и статического метода класса myClass. Делегат для метода
экземпляра требует наличия экземпляра класса myClass. Экземпляр
класса myClass сохраняется в переменной с именем obj.
usingSystem;
namespaceExample_2
{
//Определяетсянесколькометодов,накоторыеделегатможетуказывать.
publicclassmyClass
{
//Определяетсяметодэкземпляра.
//Методвозвращаетчислодесятичныхразрядовпредставленияцелого.
publicintmyMethod(intmyInt)
{
intn=Math.Abs(myInt);
intr=0;
while(n>0){n/=10;r++;}
returnr;
}
//
Определяетсястатическийметод.
//Методвозвращаетчислодвоичныхразрядовпредставленияцелого.
publicstaticintmyStatMethod(intmyInt)
{
intn=Math.Abs(myInt);
intr=0;
while(n>0){n/=2;r++;}
returnr;
}
22

}
classProgram
{
//Объявляемделегатдляметода,которыйпринимаетintивозвращаетstring.
publicdelegateintmyMethodDelegate(intmyInt);//Делегат.
staticvoidMain(string[]args)
{
//Создаетсяпоодномуделегатудлякаждогометода.Дляметодаэкземпляра,
//экземпляр(obj)долженбытьиспользован.Дляstaticметода,используйте
//имякласса.
myClassobj=newmyClass();
myMethodDelegatemyD1=newmyMethodDelegate(obj.myMethod);
myMethodDelegatemyD2=newmyMethodDelegate(myClass.myStatMethod);
myMethodDelegatemyD3=obj.myMethod;
myMethodDelegatemyD4=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}");
}
}
Результатработыпримера:
MOO12
MOO15
MOO23
MOO24
GOO12
GOO8
GOO24
ДЕЛЕГАТ 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
и суммирует его положительные значения с текущей суммой. Начальное значение текущей суммы – ноль.
classProgram
{
staticvoidMain(string[]args)
{
//Инициируемделегат(delegate)ссылкойнаметодExtractNumbers.
Func<int,int,int>extractMethod=ExtractNumbers;
int[]v={1,2,4,3,‐3,8,‐5};
//ИспользуемобъектделегатадлявызоваметодаExtractNumbers
//иотображаемрезультат.
ints=v.Aggregate(0,extractMethod);
Console.WriteLine($"суммаположительныхчисел={s}");
}
private
{
if(next>0)returnsum+=next;
elsereturnsum;
}
}
Результатработыпримера:
суммаположительныхчисел=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>, как
показано во втором вызове метода.
staticvoidMain(string[]args)
{
Predicate<string>pred=x=>x.EndsWith("ов");
string[]names=
{"Свичко","Петров","Дробот","Сидоров","Мишин","Федоров"};
//Формируеммассивфамилий,оканчивающихсяна"ов"
//ивыводимнаконсоль.
//Используемделегат.
foreach(varnameinArray.FindAll(names,pred))
Console.WriteLine($"{name}");
//Используемлямдавыражениенепосредственно.
Console.WriteLine();
foreach(varnameinArray.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

Deldel=delegate(stringname)
{Console.WriteLine($"Уведомление,полученноедля:{name}");};
del("Михаила");
//Инициируемделегат(delegate)анонимнымметодом.
Func<int,int,int>extractMethod=delegate(intsum,intnext)
{
if(next%2==0)returnsum+=next;
elsereturnsum;
};
int[]v={
//Используемобъектделегатадлявызоваанонимногометода
//иотображаемрезультат.
ints=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
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
