Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык C# и основы платформы .NET.docx
Скачиваний:
36
Добавлен:
11.05.2015
Размер:
178.68 Кб
Скачать

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'));