Анонимные методы и лямбда-выражения
Делегат-тип Cast предназначен для представления методов с параметром типа double, возвращающих значение типа int. В методе Main () объявлены два экземпляра делегата Cast, ассоциированные со ссылками castl и cast2. Для инициализации ссылок вместо явного обращения к конструктору делегата Cast используются анонимные методы, каждый из которых вводится с помощью служебного слова delegate. Анонимный… Читать ещё >
Анонимные методы и лямбда-выражения (реферат, курсовая, диплом, контрольная)
В том месте, где экземпляр делегата должен представлять конкретный метод, можно объявить безымянный метод. Такой метод называют анонимным. Протокол этого метода (спецификация параметров и тип возвращаемого значения) определяет тип делегата. Тело анонимного метода подобно телу соответствующего именованного метода, но имеются некоторые отличия (об отличиях чуть позже).
Формат декларации анонимного метода:
delegate (спецификация_параметров)
{операторы_тела_метода};
Компилятор, «встретив» декларацию анонимного метода, формирует экземпляр делегата, и настраивает его на выполнение операторов тела анонимного метода. Применение анонимных методов рекомендуется в тех случаях, когда метод используется однократно или его вызовы локализованы в одном блоке кода программы.
В следующей программе используются два анонимных метода, ассоциированных со ссылками, имеющими тип делегата (Cast), определенного в глобальном пространстве имен. Методы выполняют округление положительного вещественного числа: один до ближайшего целого и второй до большего целого.
// 17−05.cs — анонимные методы…
using System;
delegate int Cast (double x); // Объявление делегата-типа
class Program.
{
static void Main ().
{
double test = 15.3;
Cast castl = delegate (double z) // ближайшее целое
{ return (int)(z + 0.5); };
Cast cast2 = delegate (double z) 11 большее целое { return ((int)z == z? (int)z: (int)(z + 1)); };
Console.WriteLine («castl (test)={0}, cast2(test)= {1}», castl (test), cast2(test));
Console.WriteLine («castl (44.0)={0}, cast2(44.0)= {1}», castl (44.0), cast2(44.0));
}
}
Результаты выполнения программы:
castl (test)=15, cast2(test)= 16.
castl (44.0)=44, cast2(44.0)= 44.
Делегат-тип Cast предназначен для представления методов с параметром типа double, возвращающих значение типа int. В методе Main () объявлены два экземпляра делегата Cast, ассоциированные со ссылками castl и cast2. Для инициализации ссылок вместо явного обращения к конструктору делегата Cast используются анонимные методы, каждый из которых вводится с помощью служебного слова delegate. Анонимный метод, связанный со ссылкой castl, получив в качестве параметра вещественное значение, возвращает ближайшее к нему целое число. Анонимный метод, представляемый ссылкой cast2, возвращает целое число, не меньшее значения параметра.
Более удобным (с точки зрения краткости и выразительности) средством для кодирования методов, представляемых экземплярами делегатов, являются лямбда-выражения. Лямбда-выражения появились в языке C# позже анонимных методов, но практически полностью их заменяют, так как обладают большими выразительными возможностями и краткостью, нежели анонимные методы.
Общая форма записи лямбда-выражений:
(параметры) => блок операторов
Здесь параметры — это спецификация параметров, соответствующая требованиям к спецификации параметров в типе делегата.
Лексему «=>» называют лямбда-операцией. Именно эта операция является неотъемлемой частью лямбда-выражения.
Вслед за лямбда-операцией размещен блок операторов (тело лямбдавыражения). К этому блоку требования те же, что и к телу метода. Если протокол, декларируемый типом делегата, предусматривает возвращение результата, отличного от void, то для выхода из тела лямбда-выражения используются операторы return с выражениями соответствующего типа. Таким образом, возвращаемое из тела лямбда-выражения значение и типы параметров соответствуют протоколу, определяемому делегатом.
Для формирования экземпляра делегата delegate int Cast (double x); можно записать, например, такое лямбда-выражение:
(double z) => {return (int)(z + 0.5);}.
Возможны следующие варианты упрощения записи лямбда-выражений:
- 1) список параметров можно использовать без указания их типов;
- 2) если параметр один, то круглые скобки можно опустить;
- 3) если в блоке операторов только один оператор, то фигурные скобки можно опустить;
- 4) если в блоке операторов только один оператор и это оператор return выражение, то и служебное слово return (и фигурные скобки) можно опустить. В этом случае тело лямбда-выражения упрощается до отдельного выражения.
С учетом перечисленных сокращений приведенное выше лямбдавыражение принимает такой вид:
z => (int)(z + 0.5).
Обратим внимание, что соответствие между параметрами лямбдавыражения и спецификацией параметров делегата-типа устанавливается по их взаимному расположению (т. е. позиционно, имена параметров не учитываются).
Продемонстрируем применение лямбда-выражения с двумя параметрами, не возвращающего результат в точку вызова.
// 17_о — Pair — Делегаты и лямбда-выражения using System;
public delegate void Pair (string s, int d); // делегат-тип class Program {.
static void MainQ.
{
Pair birthday = (month, date) =>
Console.WriteLine («Month = {0}; Date = {1}», month, date);
Pair person = (name, year) =>
{
DateTime moment = DateTime. Now; int age = moment. Year — year;
Console.WriteLine («Name: {0}; Age: {1}», name, age);
};
person («Charley», 1967);
birthday («December», 22);
} }
Результаты выполнения программы:
Name: Charley; Age: 52 Month = December; Date = 22.
Делегат-тип Pair «настроен» на представления методов с двумя параметрами типов string и int, не возвращающих результат в точку вызова.
Инициализация ссылок birthday и person, имеющих тип делегататипа Pair, выполнена с помощью лямбда-выражений.
В параметрах лямбда-выражения для переменной person задаются имя и год рождения некой персоны. В теле этого лямбда-выражения несколько операторов. Обращением к свойству DateTime.Now.Year определяется числовое значение текущего года и вычисляется возраст персоны в настоящий момент. Затем имя и возраст персоны выводятся на экран.
В теле лямбда-выражения для переменной birthday один оператор. Он выводит число и месяц какого-то события (наверное, это день рождения Чарли).
Анонимные методы и лямбда-выражения удобно применять для «настроек» библиотечных методов, предусматривающих применение обратного вызова. В гл. 9, посвященной методам С#, рассмотрено применение функций обратного вызова в обращениях к библиотечному методу сортировки элементов массива. Там мы использовали имена методов в качестве аргументов метода Array. Sort (). Если обратиться к документации, то увидим, что заголовок этого метода включает параметр с типом делегата. Поэтому замещать этот параметр можно и анонимными методами, и лямбда-выражениями, разместив их объявления непосредственно в обращениях к методу Array.SortQ.
Приведем программу, в которой элементы целочисленного массива упорядочиваются по убыванию значений, а затем сортируются по их четности. Для задания правил упорядочения применим в обращениях к методу Array. SortQ в качестве аргументов лямбда-выражения:
// 1706.cs — лямбда-выражения и Array. Sort () using System; class Program {.
static void Main ().
{
int[ ] ar = { 4, 5, 2, 7, 8, 1, 9, 3 >;
Array.Sort (ar, (int x, int у) => // no убыванию
{
if (x < y) return 1; if (x == y) return 0; return -1;
}
);
foreach (int memb in ar).
Console.Write («{0} «, memb);
Console.WriteLine ();
Array.Sort (ar, (int x, int у) => // no четности
{
if (x % 2 ≠ 0 & у % 2 == 0) return 1; if (x == y) return 0; return -1;
>
);
foreach (int memb in ar).
Console.Write («{0} «, memb);
} }
Результат выполнения программы:
- 98 754 321
- 24 813 579
Так как каждый из методов используется в данной программе только один раз, то нет необходимости объявлять тип делегатов отдельно и определять ссылки с типом делегата. Лямбда-выражения представляют конкретные методы непосредственно в аргументах метода Array. SortQ.
Напомним, что делегат — это тип и поэтому может не только специфицировать параметры методов. В следующем примере делегат определяет тип свойства класса. В решаемой далее задаче нужно объявить делегат-тип для представления методов, вычисляющих значения логических функций двух переменных. Далее нужно определить класс, объект которого заполняет таблицу истинности для функции, представленной в объекте полем, имеющим тип делегата.
Для обращения к полю определить открытое свойство с типом делегата. (При заполнении таблицы истинности объект «не знает» для какой логической функции строится таблица; функция передается объекту в точке обращения к его методу.).
Итак, требуется определить класс со статическим методом для вывода на экран таблицы истинности логической функции двух переменных. При выводе заменять логическое значение ИСТИНА значением 1, а ЛОЖЬ — 0.
Конкретные функции передавать методу с помощью лямбда-выражений, определяемых в точке вызова. Ссылку с типом делегата, представляющую лямбда-выражение, присваивать свойству объекта.
В методе Main () определить два лямбда-выражения, представляющие логические функции, и построить для них таблицы истинности.
// 1707.cs — свойство с типом делегата и анонимные методы
using System;
public delegate bool BoolDel (bool x, bool у); // Делегат-тип.
public class Create.
{ // Класс таблиц истинности
BoolDel specificFun; // поле, определяющее логическую функцию public BoolDel SpecificFun {.
set { specificFun = value; }.
}
public bool[,] defineQ { // Формирование логического массива bool[,] res = new bool[4, 3]; bool bx, by; int k = 0;
for (int i = 0; i <= 1; i++) for (int j = 0; j <= l; j++).
{
bx = (i == 0? false: true); by = (j == 0? false: true); res[k, 0] = bx; res[k, 1] = by;
res[k++, 2] = specificFun (bx, by);
>
return res;
>
}
public class Methods { // Класс с методами
static public void printTabl (bool[,] tabl).
{
Console.WriteLine («A В F»);
for (int i = 0; i < tabl. GetLength (0); i++).
Console.WriteLine («{0} {1} {2}» ,.
tabl[i, 0]? 1: 0, tabl[i, 1]? 1: 0, tabl[i, 2]? 1: 0);
} }
class Program.
{ // Основной класс программы static void Main ().
{
Create create = new CreateQ;
create.SpecificFun = (bool a, bool b) =>
{ return a || b; };
Console.WriteLine («Таблица для (A || B):»);
Methods. printTabl (create, define ()); create. SpecificFun = (bool d, bool e) =>
{ return d && !e; };
Console. 1л1г^е1_1пе («пТаблица для (A &&!B):»);
Methods.printTabl (create.define ());
}
Результаты выполнения программы:
Таблица для (А || В):
А В F 0 0 0 0 11 10 1 111.
Таблица для (А &&!В):
А В F 0 0 0 0 10 10 1 110.
Изучая лямбда-выражения и анонимные методы, следует обратить внимание на возможности применения в них внешних переменных. Как и для локальных методов, внешними считаются параметры и локальные переменные того метода, в котором определен анонимный метод или лямбда-выражение. Особенности использования внешних переменных в лямбда-выражениях, в локальных и анонимных методах совпадают, поэтому рассмотрим только лямбда-выражения.
Внешняя переменная, используемая в лямбда-выражении, называется захваченной переменной. Важно понимать, когда происходит «захват» значения, которое имеет захваченная переменная. Приведем пример:
// 17_Ь — захваченные переменные в лямбда-выражениях using System; class Program {.
delegate void Pusto (); static void Main ().
{
string name = «Tom» ;
Pusto output = () =>
{ Console. WriteLine ($" Hello, {name}!"); }; output (); name = «lack»; output ();
} }
Результаты, выводимые на экран:
Hello, Tom!
Hello, lack!
Делегат-тип Pusto представляет методы без параметров, ничего не возвращающие в точку вызова. Переменная output с типом этого делегата инициализирована с помощью лямбда-выражения, в теле которого используется внешняя переменная name. При обращениях к экземпляру делегата с помощью выражения output () используется то значение захваченной переменной пате, которое она имеет в момент обращения (а не в момент создания экземпляра делегата). Это правило иллюстрируют результаты выполнения программы.
Захваченная лямбда-выражением внешняя переменная продолжает существовать до тех пор, пока лямбда-выражение остается частью экземпляра делегата. Это происходит даже в том случае, когда переменная выходит из обычной области своего существования. Покажем эту особенность на примере:
// 17_с — расширение области существования переменных using System; class Program {.
delegate void Pusto ();
static void Main ().
{
Pusto output;
{
string report = «Сообщение из блока» ;
output = () => Console. WriteLine ($" {report}!");
}
outputQ;
} }
Результаты, выводимые на экран:
Сообщение из блока!
В методе Main () есть вложенный блок. Ссылка output определена вне этого блока, но ассоциирована с лямбда-выражением в теле внутреннего блока. В лямбда-выражение входит локальная переменная report внутреннего блока. Обращение к экземпляру делегата с помощью выражения output () выполнено за пределами внутреннего блока. Но используется значение захваченной переменной report из внутреннего блока, что иллюстрируют результаты выполнения программы.
Так как значение захваченной переменной определяется не в момент декларации лямбда-выражения, а в момент обращения к тому экземпляру делегата, в котором это выражение используется, то существует немного неожиданная ситуация, в том случае, когда лямбда-выражение захватывает параметр цикла.
Рассмотрим следующий фрагмент кода:
Pusto [] take = new Pusto[3];
for (char h = 'A'; h < 'D'; h++).
take[h — 'A'] = () => Console. Write (h + ««);
foreach (Pusto d in take) d ();
Декларирован массив, тип элементов которого определяет делегат Pusto. В параметрическом цикле каждый элемент массива инициализирован лямбда-выражением, которое захватывает переменную h — параметр цикла. После выхода из цикла for в цикле foreach выполняются обращения к элементам массива, каждый из которых выводит значение захваченного параметра. Так как обращения к элементам массива (к экземплярам делегата Pusto) выполняются после завершения параметрического цикла, то параметр цикла имеет то значение 'D', при котором цикл завершен. Результат на экране дисплея:
D D D.
Лямбда-выражения могут изменять захваченные переменные практически так же, как могут изменять операторы блока внешние по отношению к блоку переменные.