Делегаты. Анонимные функции. Лямбда

Download Report

Transcript Делегаты. Анонимные функции. Лямбда

Делегаты
Анонимные функции
Лямбда - выражения
Делегат –это объект, который может ссылаться на
метод.
При создании делегата в итоге получается объект,
содержащий ссылку на метод. Метод можно
вызывать по этой ссылке.
Или: делегат позволяет вызывать метод, на который он
ссылается.
Делегат — это безопасный в отношении типов объект,
указывающий на другой метод (или список методов)
приложения, который может быть вызван позднее.
Объект делегата поддерживает три важных фрагмента
информации:
• адрес метода, на котором он вызывается;
• аргументы (если есть) этого метода;
• возвращаемое значение (если есть) этого метода.
Общая форма объявления делегата:
delegate возвращаемый_тип имя (список_параметров);
возвращаемый_тип - тип значения,
возвращаемого методами, которые будут
вызываться делегатом;
имя - конкретное имя делегата;
список_параметров - параметры, необходимые
для методов, вызываемых делегатом.
Как только будет создан экземпляр делегата, он
может вызывать и ссылаться на те методы,
возвращаемый тип и параметры которых
соответствуют указанным в объявлении
делегата.
Делегат может служить для вызова
любого метода с соответствующей
сигнатурой и возвращаемым типом.
Вызываемый метод может быть методом
экземпляра, связанным с отдельным
объектом, или статическим методом,
связанным с конкретным классом.
Возвращаемый тип и сигнатура метода
должны быть согласованы с теми,
которые указаны в объявлении
делегата.
Пусть необходимо в некоторый метод в качестве параметра
передавать другой метод. Объявление типа-делегата:
delegate void MyDelegate();
Будущий метод не принимает параметров. Любой метод,
который может быть передан в делегат, не возвращает
значения и не принимает ни одного параметра.
Создание объекта типа-делегата обычным способом,
используя конструктор:
MyDelegate myDelegate=new MyDelegate(MyMethod);
В конструктор передали имя целевого метода. Не указываем
всю сигнатуру метода, а лишь его имя. Данный делегат и
так знает все о сигнатуре передаваемого ему метода.
Нельзя передать в делегат метод, сигнатура которого не
соответствует заданной при объявлении типа-делегата.
Для запуска целевого метода - вызвать объект делегата:
myDelegate();
namespace ConsoleApplication1
{ //создание типа-делегата
delegate void MyDelegate();
class Program
{ static void Main(string[] args)
{ //создание экземпляра делегата с передачей имен
и метода
MyDelegate myDelegate = new MyDelegate(MyMethod);
//вызов метода делегата
myDelegate();
}
static void MyMethod()
{ Console.WriteLine ("Делегированный метод
выполняется");
Console.ReadKey();
}
}
}
Пример 2 работы делегата с методами иной сигнатуры:
namespace ConsoleApplication1
{ //создание типа-делегата
delegate string MyDelegate(int i);
class Program
{ static void Main(string[] args)
{//создание экземпляра делегата с передачей имени
метода
MyDelegate myDelegate = new MyDelegate(MyMethod);
//вызов метода делегата
string result = myDelegate(5);
Console.WriteLine(result);
Console.ReadKey();
}
static string MyMethod(int p)
{ return p.ToString(); }
}
}
Результаты работы программы - 5
Объявили тип-делегат, который может вызывать
методы, принимающие один параметр типа
int и возвращающие тип string. Если метод
принимает несколько параметров, то в
делегате они указываются через запятую.
Вызов целевого метода через делегат:
string result = myDelegate(5);
Указали значение входного параметра для
целевого метода и получили результат
работы метода.
Мощь делегатов раскрывается при
использовании событий, где способность
группового вызова методов (нескольких по
очереди) дает большую гибкость в
функциональности приложения.
Пример 3.
using System;
namespace ConsoleApplication1{ // Создадим делегат
delegate int IntOperation (int i, int j);
class Program { // Организуем ряд методов
static int Sum(int x, int y)
{ return x + y;}
static int Prz(int x, int y)
{ return x * y;}
static int Del(int x, int y)
{return x / y;}
static void Main()
{ // Сконструируем делегат
IntOperation op1 = new IntOperation(Sum);
int result = op1(5, 10);
Console.WriteLine("Сумма: " + result);
// Изменим ссылку на метод
op1 = new IntOperation(Prz);
result = op1(5, 10);
Console.WriteLine("Произведение: " + result);
Console.ReadLine();
} }}
Вывод из примера: в тот момент, когда
происходит обращение к экземпляру делегата
IntOperation, вызывается метод, на который он
ссылается. Следовательно, вызов метода
разрешается во время выполнения, а не в
процессе компиляции.
Групповое преобразование делегируемых
методов
В С# 2.0 - средство, упрощающее синтаксис
присваивания метода делегату.
Это - групповое преобразование методов,
позволяющее присвоить имя метода делегату,
не прибегая к оператору new или явному вызову
конструктора делегата.
Синтаксис группового преобразования методов
упрощен по сравнению с прежним подходом к
делегированию.
Пример 4.
using System;namespace ConsoleApplication1
{ // Создадим делегат
delegate int IntOperation (int i, int j);
class Program
{ // Организуем ряд методов
static int Sum(int x, int y)
{ return x + y;}
static int Prz(int x, int y)
{ return x * y;}
static int Del(int x, int y)
{ return x / y;}
static void Main()
{ // Присваиваем имя метода обычным способом
IntOperation op1 = new IntOperation(Sum);
// Используем групповое преобразование
op1 = Sum;
int result = op1(5, 10);
Console.WriteLine("Сумма: " + result);
// Изменим ссылку на метод
op1 = Prz;
result = op1(5, 10);
Console.WriteLine("Произведение: " + result);
Console.ReadLine();
} }}
Применение методов экземпляра в качестве
делегатов
В примере 4 использовались статические методы, но
делегат может ссылаться и на методы экземпляра,
хотя для этого требуется ссылка на объект.
Рассмотрим измененный вариант примера 4, в котором
операции с числами инкапсулируются в классе
MyClass.
В данном случае может быть также использован
синтаксис группового преобразования методов.
Результат выполнения такой, как и в примере 4, но
делегат обращается к методам по ссылке на
статические методы класса MyClass.
Пример 5.
using System;
namespace ConsoleApplication1
{ // Создадим делегат
delegate int IntOperation (int i, int j);
public class MyClass
{ public static int Sum(int x, int y)
{ return x + y; }
public static int Prz(int x, int y)
{return x * y; }
public static int Del(int x, int y)
{return x / y; } }
class Program {
static void Main()
{ // Инициализируем делегат
IntOperation op1 = MyClass.Sum;
int result = op1(5, 10);
Console.WriteLine("Сумма: " + result);
op1 = MyClass.Prz;
result = op1(5, 10);
Console.WriteLine("Произведение: " + result);
Console.ReadLine();
} }}
Групповая адресация
- это возможность создать список, или цепочку вызовов,
для методов, которые вызываются автоматически
при обращении к делегату.
Для создания цепочки достаточно получить экземпляр
делегата, а затем добавить методы в цепочку с
помощью оператора + или +=.
Для удаления метода из цепочки - оператор - или -=.
Если делегат возвращает значение, то им становится
значение, возвращаемое последним методом в
списке вызовов. Поэтому делегат, в котором
используется групповая адресация, обычно имеет
возвращаемый тип void.
Цепочки вызовов - эффективный механизм, позволяют
определить ряд методов, выполняемых единым
блоком. Улучшается структура кода.
Цепочки вызовов имеют особое значение для
обработки событий.
Пример 6 (распечатка).
Делегаты - гибкие средствами программирования
благодаря двум свойствам:
ковариантности и контравариантности.
Метод, передаваемый делегату, должен иметь такой же
возвращаемый тип и сигнатуру, как и делегат. Но в
отношении производных типов это правило
оказывается не таким строгим благодаря
ковариантности и контравариантности.
Ковариантность позволяет присвоить делегату метод,
возвращаемым типом которого служит класс,
производный от класса, указываемого в возвращаемом
типе делегата.
Контравариантность позволяет присвоить делегату
метод, типом параметра которого служит класс,
являющийся базовым для класса, указываемого в
объявлении делегата.
Ковариантность и контравариантность делегатов
применяются при реализации событий.
Пример 7 (распечатка)
Свойства делегатов:
• Делегаты похожи на указатели функций в C++, но
являются типобезопасными.
• Делегаты допускают передачу методов в качестве
параметров.
• Делегаты можно использовать для задания функций
обратного вызова.
• Делегаты можно связывать друг с другом (несколько
методов можно вызвать по одному событию).
• Точное соответствие методов сигнатуры делегата не
требуется.
Анонимные методы
Метод, на который ссылается делегат, часто
используется только для этой цели.
Основание для существования метода - то, что он
может быть вызван посредством делегата, но
сам он не вызывается вообще. В таких случаях
можно использовать анонимную функцию, чтобы
не создавать отдельный метод.
Анонимная функция - безымянный кодовый блок,
передаваемый конструктору делегата.
Преимущество анонимной функции - ее простота.
Благодаря ей не надо объявлять отдельный метод,
единственное назначение которого в том, что он
передается делегату.
Начиная с С# 3.0 – 2 разновидности анонимных
функций:
- анонимные методы
- лямбда-выражения
Анонимные методы были внедрены в С# 2.0, а
лямбда-выражения — в версии 3.0.
Лямбда-выражение совершенствует принцип
действия анонимного метода и более
предпочтительно для создания анонимной
функции. Но анонимные методы широко
применяются в коде С# и являются важной
составной частью С#.
Анонимные методы могут быть использованы в
случаях, где применение лямбда-выражений
оказывается невозможным.
Метод анонимный, то есть не имеет названия.
Пример анонимного метода, который выведет все
директории указанного каталога на ПК:
// используем анонимный метод
Anonim anonim = delegate{
DirectoryInfo dir = new DirectoryInfo("D:\\");
foreach (DirectoryInfo d in dir.GetDirectories())
Console.WriteLine(d.Name); };
anonim(); // запускаем
Блок безымянный. Просто конкретному делегату указали
данный блок.
Пример 8 использования анонимных методов с
параметрами. Определим делегат:
// сигнатура делегата
delegate void Anonim2(int start, int finish);
// теперь аноним. метод с параметрами
Anonim2 anonim2 = delegate(int a, int b) {
for (int i = a; i <= b; i++)
Console.Write("Бегун сейчас на {0} километре." +
" До финиша осталось {1}\n", i, b-i);
};
anonim2(1, 10);
После слова delegate в скобках
указываются параметры.
Можно использовать анонимные
методы, которые возвращают
значения.
Для этого надо изменить в сигнатуре
делегата тип void на другой тип,
например, int, а в методе
использовать слово return для
возврата значения из метода
Анонимный метод - один из способов
создания безымянного блока кода,
связанного с конкретным экземпляром
делегата.
Для создания анонимного метода
достаточно указать кодовый блок после
ключевого слова delegate.
delegate void Anonim();
// сигнатура делегата
Пример 9
using System;using System.Collections.Generic;
namespace ConsoleApplication1{
delegate int Sum(int number);
class Program {
static Sum SomeVar()
{
int result = 0;
// Вызов анонимного метода
Sum del = delegate (int number)
{
for (int i = 0; i <= number; i++)
result += i;
return result;
};
return del;
}
static void Main()
{
Sum del1 = SomeVar();
for (int i = 1; i <= 5; i++) {
Console.WriteLine("Cумма {0} равна: {1}",i,del1(i)); }
Console.ReadLine();
} }}
Локальная переменная, в область действия которой входит
анонимный метод - внешняя переменная. Такие переменные
доступны для использования в анонимном методе. В этом
случае внешняя переменная считается захваченной.
Захваченная переменная существует до тех пор, пока
захвативший ее делегат не будет собран в "мусор". Поэтому
если локальная переменная, которая обычно прекращает
свое существование после выхода из кодового блока,
используется в анонимном методе, то она продолжает
существовать пока не будет уничтожен делегат,
ссылающийся на этот метод.
Захват локальной переменной может привести к неожиданным
результатам, как в примере 9, где локальная переменная
result не обнуляется после каждого вызова анонимного
метода. В результате получается необычный результат
суммы чисел.
Лямбда выражения (lambda expression)
- более лучшая замена анонимным методам, основываются
на новом синтаксисе.
Лямбда - выражения могут использоваться везде, где есть
параметр типа делегата.
В лямбда выражениях должен быть лямбда оператор “=>”.
Он разделяет выражение на две части.
• В левой части – входной параметр (несколько параметров);
• В правой части – тело метода.
Оператор => описывается словами: "переходит" или
"становится".
2 разновидности лямбда-выражений:
Если тело лямбда-выражения состоит из одного выражения, то
- одиночное лямбда-выражение. Тело выражения не
заключается в фигурные скобки.
Если тело лямбда-выражения состоит из блока операторов в
фигурных скобках, то - блочное лямбда-выражение. Оно
может содержать ряд операторов (в том числе циклы,
вызовы методов и условные операторы if).
В одиночном лямбда-выражении часть, находящаяся
справа от оператора =>, воздействует на параметр (ряд
параметров), указываемый слева.
Возвращаемым результатом вычисления выражения
является результат выполнения лямбда-оператора.
Общая форма одиночного лямбда-выражения,
принимающего единственный параметр:
параметр => выражение
Если требуется указать несколько параметров, то
используется форма:
(список_параметров) => выражение
Когда требуется указать два параметра или более, их
следует заключить в скобки. Если выражение не требует
параметров, то следует использовать пустые скобки.
Использование лямбда выражения на 3 этапа:
•
Определения делегата, совместимого с лямбдавыражением.
•
Создание экземпляра делегата, которому
присваивается лямбда-выражение.
•
Само использование выражения, которое
происходит при обращение к делегату.
Одиночные лямбда-выражения:
•
Этап 1: delegate int Pow(int change);
•
Этап 2: Pow pow = change => change * change;
•
•
Этап 3:
Console.WriteLine(pow(10)); // результат будет 100
Пример 8а про бегуна с использованием
лямбда-выражения.
// сигнатура делегата совместимого с лямбдавыражением.
delegate int LambdaDelegate(int step);
LambdaDelegate lambdaDel = a => ++a;
int step = 1;
int finish = 10;
while (step <= finish)
{
Console.Write("Бегун сейчас на {0} километре." + "
До финиша осталось {1}\n", step, finish - step);
step = lambdaDel(step);
}
Результат будет тот же, что при использование
анонимных методов. Типы параметров не
указываются. Компилятор сам определяет,
какие типы имеют параметры, исходя из
делегата и его типа, который возвращается.
Можно и явно указать тип:
LambdaDelegate lambdaDel = (int a) => ++a;
!!! Выражение “++a”.
Если переписать: “a++”
– будет бесконечный цикл!
Пример 10 (распечатка)
Блочные лямбда-выражения
Создание блочного лямбда - выражения - взять его в
фигурные скобки.
Преимущества использования – возможность
управлять операторами if, for и т.д.
Все также как при написание простых методов.
Этап 1:
delegate int Pow(int change);
Этап 2:
Pow lambdaDel = change =>
{
if (change != 0)
return change * change;
return 0;
};
Этап 3:
Console.WriteLine(pow(10)); // ризультат будет 100
Модифицируем пример 10, добавив капчу в форму
регистрации (распечатка пример 10а)