- •А. А. Волосевич
- •Содержание
- •1. Общая характеристика платформы .Net
- •1.1. Инфраструктура платформы .Net
- •1.2. Версии платформы .Net
- •2. Общие концепции синтаксиса языка c#
- •3. Система типов clr и языка c#
- •4. Идентификаторы, ключевые слова и литералы
- •5. Выражения и операции
- •1. Первичные операции
- •2. Унарные операции
- •6. Операторы
- •6.1. Операторы объявления
- •6.2. Операторы выражений
- •6.3. Операторы перехода
- •6.4. Операторы выбора
- •6.5. Операторы циклов
- •6.6. Прочие операторы
- •7. Начальные сведения о массивах
- •8. Классы
- •8.1. Допустимые элементы класса
- •8.2. Модификаторы доступа для элементов и типов
- •8.3. Разделяемые классы
- •8.4. Использование класса
- •9. Методы
- •9.1. Описание метода
- •9.2. Вызов метода
- •9.3. Разделяемые методы
- •10. Свойства и индексаторы
- •11. Статические элементы и методы расширения
- •11.1. Статические элементы
- •11.2. Статические классы
- •11.3. Методы расширения
- •12. Конструкторы и инициализация объектов
- •13. Наследование классов
- •14. КлассSystem.Objectи иерархия типов
- •15. Структуры
- •16. Перечисления
- •17. Интерфейсы
- •18. Универсальные шаблоны
- •18.1. Универсальные классы и структуры
- •18.2. Ограничения на параметры универсальных типов
- •18.3. Универсальные методы
- •18.4. Ковариантность и контравариантность
- •19. Использование универсальных шаблонов
- •19.1. Кортежи
- •19.2. Типы, допускающие значение null
- •19.3. Прочие примеры универсальных шаблонов
- •20. Делегаты
- •21. Анонимные методы и лямбда-выражения
- •22. События
- •23. Перегрузка операций
- •24. Анонимные типы
- •25. Пространства имён
- •26. Генерация и обработка исключительных ситуаций
- •27. Директивы препроцессора
- •28. Документирование исходного кода
- •Литература
21. Анонимные методы и лямбда-выражения
Назначение анонимных методов(anonymous methods) заключается в том, чтобы сократить объём кода, который должен писать разработчик при использовании делегатов. Если рассмотреть примеры предыдущего параграфа, то очевидно, что даже для объектов-делегатов, содержащих минимум действий, необходимо создавать метод (а, возможно, и отдельный класс) и инкапсулировать этот метод в делегате. При применении анонимных методов формируется безымянный блок кода, который назначается объекту-делегату.
Синтаксис объявления анонимного метода включает ключевое слово delegateи список формальных параметров. Ковариантность и контравариантность делегатов работает и в случае применения анонимных методов. Дополнительным правилом является возможность описать анонимный метод без параметров, если параметры не используются в теле метода, а делегат не имеетout-параметров.
Модифицируем фрагмент кода из предыдущего параграфа, используя анонимный метод:
int[] a = {1, 2, 3};
Transformer<int,int> t =delegate(intx) {returnx * x; };
a.Transform(t);
Лямбда-выраженияилямбда-операторы– это альтернативный синтаксис записи анонимных методов. Начнём с рассмотрения лямбда-операторов. Пусть имеется анонимный метод:
Func<int, bool> f = delegate(int x)
{
inty = x - 100;
returny > 0;
};
При использовании лямбда-операторов список параметров отделяется от тела оператора символами =>, а ключевое словоdelegateне указывается:
Func<int,bool> f = (intx) => {inty = x - 100;returny > 0; };
Более того, так как тип параметра лямбда-оператора уже фактически указан слева при объявлении f, то его можно не указывать справа. В случае одного параметра можно также опустить обрамляющие его скобки1:
Func<int,bool> f = x => {inty = x - 100;returny > 0; };
Когда лямбда-оператор содержит в своём теле единственный оператор return, он может быть записан в компактной формелямбда-выражения:
Func<int,bool> f_2 = x => x > 0;
Заметим, что для переменной, в которую помещается лямбда-оператор или лямбда-выражение, требуется явно указывать тип – varиспользовать нельзя.
Рассмотрим пример, демонстрирующий применение лямбда-выражений. Используем метод Transform()из предыдущего параграфа, чтобы получить из числового массива массив булевых значений, а из него – массив строк. Обратите внимание на компактность получаемого кода.
int[] a = {1, 2, 3};
string[] s = a.Transform(x => x > 2).Transform(y => y.ToString());
// s содержит "False", "False", "True"
Важно понимать, что анонимные методы и лямбда-операторы трансформируются в обычные методы некого специального, генерируемого компилятором класса.
// код, который написал пользователь
public class MainClass
{
publicstaticvoidMain()
{
System.Func<int,bool> f = x => x > 0;
var result = f(1);
}
}
// приблизительный листинг кода, созданного компилятором
publicclassMainClass
{
publicstaticvoidMain()
{
varcgc =newCompilerGeneratedClass();
System.Func<int,bool> f = cgc.CompilerGeneratedMethod;
varresult = f(1);
}
privatesealedclassCompilerGeneratedClass
{
publicboolCompilerGeneratedMethod(intx)
{
return x > 0;
}
}
}
Анонимные методы и лямбда-операторы способны захватывать внешний контекст вычисления. Если при описании тела анонимного метода применялась внешняя переменная, вызов метода будет использовать текущеезначение переменной. Захват внешнего контекста иначе называютзамыканием(closure).
int[] a = {1, 2, 3};
int external = 0; // внешняя переменная
Transformer<int,int> t = x => x + external;// замыкание
external = 10; // изменили переменную после описания t
int[] b = a.Transform(t); // прибавляет 10 к каждому элементу
Технически замыкание реализуется следующим образом. В класс, генерируемый компилятором для поддержки анонимных методов и лямбда-операторов, помещаются открытые поля, соответствующие захваченным внешним переменным. Работа с переменными заменяется работой с этими полями:
// код, который написал пользователь
publicclassMainClass
{
publicstaticvoidMain()
{
intexternal = 0;
System.Func<int,bool> f = x => x > external;
external = 10;
var result = f(1);
}
}
// приблизительный листинг кода, созданного компилятором
publicclassMainClass
{
publicstaticvoidMain()
{
varcgc =newCompilerGeneratedClass();
cgc.external = 0;
System.Func<int,bool> f = cgc.CompilerGeneratedMethod;
cgc.external = 10;
varresult = f(1);
}
privatesealedclassCompilerGeneratedClass
{
publicintexternal;
publicboolCompilerGeneratedMethod(intx)
{
return x > external;
}
}
}
Эффектный приём использования замыканий – функции, реализующие мемоизацию. Мемоизация(memoization)– это кэширование результатов вычислений. В следующем примере описан метод расширения для строк, который возвращает функцию подсчёта встречаемости символа в строке.
publicstaticFunc<char,int> FrequencyFunc(thisstringtext)
{
int[] freq =newint[char.MaxValue];
foreach(charcintext)
{
freq[c]++;
}
returnch => freq[ch];
}
// использование частотной функции
varf ="There is no spoon".FrequencyFunc();
Console.WriteLine(f('o'));