Студопедия.Орг Главная | Случайная страница | Контакты | Мы поможем в написании вашей работы!  
 

Анонимные методы и лямбда-выражения



Назначение анонимных методов (anonymous methods) заключается в том, чтобы сократить объём кода, который должен писать разработчик при использовании делегатов. Если рассмотреть примеры предыдущего параграфа, то очевидно, что даже для объектов-делегатов, содержащих минимум действий, необходимо создавать метод (а, возможно, и отдельный класс) и инкапсулировать этот метод в делегате. При применении анонимных методов формируется безымянный блок кода, который назначается объекту-делегату.

Синтаксис объявления анонимного метода включает ключевое слово delegate и список формальных параметров. Ковариантность и контравариантность делегатов работает и в случае применения анонимных методов. Дополнительным правилом является возможность описать анонимный метод без параметров, если параметры не используются в теле метода, а делегат не имеет out-параметров.

Модифицируем фрагмент кода из предыдущего параграфа, используя анонимные методы:

int[] a = {1, 2, 3};

Transformer<int, int> t = delegate(int x) { return x * x; };

a.Transform(t);

Лямбда-выражения и лямбда-операторы – это альтернативный синтаксис записи анонимных методов. Начнём с рассмотрения лямбда-операторов. Пусть имеется анонимный метод:

Func<int, bool> f = delegate(int x)

{

int y = x - 100;

return y > 0;

};

При использовании лямбда-операторов список параметров отделяется от тела оператора символами =>, а ключевое слово delegate не указывается:

Func<int, bool> f = (int x) => { int y = x - 100; return y > 0; };

Более того, так как мы уже фактически указали тип аргумента лямбда-оператора слева при объявлении f, то его можно не указывать справа. В случае если у нас один аргумент, можно опустить обрамляющие его скобки[17]:

Func<int, bool> f = x => { int y = x - 100; return y > 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"

Анонимные методы и лямбда-операторы способны захватывать внешний контекст вычисления. Если при описании тела анонимного метода применялась внешняя переменная, вызов метода будет использовать текущее значение переменной. Захват внешнего контекста иначе называют замыканием (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 к каждому элементу

Эффектный приём использования замыканий – функции, реализующие мемоизацию. Мемоизация (memoization)– это кэширование результатов вычислений. В следующем примере описан метод расширения для строк, который возвращает функцию подсчёта встречаемости символа в строке.

public static Func<char, int> FrequencyFunc(this string text)

{

int[] freq = new int[char.MaxValue];

foreach (char c in text)

{

freq[c]++;

}

return ch => freq[ch];

}

// использование частотной функции

var f = "There is no spoon".FrequencyFunc();

Console.WriteLine(f('o'));

События

События – способ описания связи одного объекта с другими по действиям. Работу с событиями можно условно разделить на три этапа:

– объявление события (publishing);

– регистрация получателя события (subscribing);

– генерация события (raising).

Событие можно объявить в пределах класса, структуры или интерфейса. Базовый синтаксис объявления события следующий:

модификаторы event тип-делегата имя-события;

Ключевое слово event указывает на объявление события. При объявлении события требуется указать делегат, описывающий метод обработки события. Обычно этот делегат имеет тип возвращаемого значения void.

Фактически, события являются полями типа делегатов. При объявлении события компилятор добавляет в класс или структуру private-поле с именем имя-события и типом тип-делегата. Кроме этого, для обслуживания события компилятор создаёт два метода add_Name() и remove_Name(), где Name – имя события. Эти методы содержат код, добавляющий и удаляющий обработчик события в цепочку группового делегата, связанного с событием.

Если программиста по каким-либо причинам не устраивает автоматическая генерация методов add_Name() и remove_Name(), он может описать собственную реализацию данных методов. Для этого при объявлении события указывается блок, содержащий секции add и remove:

модификаторы event тип-делегата имя-события

{

add { операторы }

remove { операторы }

};

В блоке добавления и удаления обработчиков обычно размещается код, добавляющий или удаляющий метод в цепочку группового делегата. Поле-делегат в этом случае должно быть явно объявлено в классе.

Для генерации события в требуемом месте кода помещается вызов в формате имя-события(фактические-аргументы). Предварительно обычно проверяют, назначен ли обработчик события. Генерация события может происходить только в том же классе, в котором событие объявлено[18].

Приведём пример класса, содержащего объявление и генерацию события. Данный класс будет включать метод с целым параметром, устанавливающий значение поля класса. Если значение параметра отрицательно, генерируется событие, определённое в классе:

public delegate void Handler(int val);

public class ExampleClass

{

private int _field;

public int Field

{

get

{

return _field;

}

set

{

_field = value;

if (value < 0)

{

// проверка нужна, чтобы предотвратить генерацию

// исключительной ситуации, если нет обработчика

if (NegativeValueSet!= null)

{

NegativeValueSet(value);

}

}

}

}

public event Handler NegativeValueSet;

}

Рассмотрим этап регистрации получателя события. Чтобы отреагировать на событие, его надо ассоциировать с обработчиком события. Обработчиком может быть метод, приводимый к типу события (делегату). В качестве обработчика может выступать анонимный метод или лямбда-оператор. Назначение и удаление обработчиков события выполняется при помощи операторов += и -=.

Используем класс ExampleClass и продемонстрируем назначение и удаление обработчиков событий:

public class MainClass

{

public static void Reaction(int i)

{

Console.WriteLine("Negative value = {0}", i);

}

public static void Main()

{

var c = new ExampleClass();

c.Field = -10; // нет обработчиков, нет реакции на событие

// назначаем обработчик

c.NegativeValueSet += Reaction;

c.Field = -20; // вывод: "Negative value = -20"

// назначаем ещё один обработчик в виде лямбда-выражения

c.NegativeValueSet += i => Console.WriteLine(i);

c.Field = -30; // вывод: "Negative value = -30" и "-30"

// удаляем первый обработчик

c.NegativeValueSet -= Reaction;

}

}

Платформа.NET предлагает средства стандартизации работы с событиями. В частности, для типов событий зарезервированы следующие делегаты:

public delegate void EventHandler(object sender, EventArgs e);

public delegate void EventHandler<T>(object sender, T e)

where T: EventArgs;

Как видим, данные делегаты предполагают, что первым параметром будет выступать объект, в котором событие было сгенерировано. Второй параметр используется для передачи информации события. Это либо класс EventArgs, либо наследник этого класса с необходимыми полями.

Сама генерация события обычно выносится в отдельный виртуальный метод класса. В этом методе проверяется, был ли установлен обработчик события. Также можно создать копию события перед обработкой (это актуально в многопоточных приложениях)

Внесём изменения в код класса ExampleClass, чтобы работа с событиями соответствовала стандартам:

public class MyEventArgs: EventArgs

{

public int NewValue { get; private set; }

public MyEventArgs(int newValue)

{

NewValue = newValue;

}

}

public class ExampleClass

{

private int _field;

public int Field

{

get { return _field; }

set

{

_field = value;

if (value < 0)

{

OnNegativeValueSet(new MyEventArgs(value));

}

}

}

protected virtual void OnNegativeValueSet(MyEventArgs e)

{

EventHandler<MyEventArgs> local = NegativeValueSet;

if (local!= null)

{

local(this, e);

}

}

public event EventHandler<MyEventArgs> NegativeValueSet;

}

Создадим несколько полезных классов для упрощения работы с событиями. Очень часто для передачи информации события достаточно класса с единственным свойством. В этом случае можно использовать универсальный класс EventArgs<T>.

public class EventArgs<T>: EventArgs

{

public T EventInfo { get; private set; }

public EventArgs(T eventInfo)

{

EventInfo = eventInfo;

}

}

Вот пример использования EventArgs<T> при описании события:

public event EventHandler<EventArgs<int>> NegativeValueSet;

Поместим в класс EventHelper два метода расширения, упрощающих генерацию событий:

public static class EventHelper

{

public static void Raise<T>(

this EventHandler<EventArgs<T>> handler,

EventArgs<T> args, object sender = null)

{

var local = handler;

if (local!= null)

{

local(sender, args);

}

}

public static void Raise(this EventHandler handler,

EventArgs args, object sender = null)

{

var local = handler;

if (local!= null)

{

local(sender, args);

}

}

}

Вот пример использования метода расширения из класса EventHelper:

protected virtual void OnNegativeValueSet(EventArgs<int> e)

{

NegativeValueSet.Raise(e, this);

}





Дата публикования: 2015-11-01; Прочитано: 371 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!



studopedia.org - Студопедия.Орг - 2014-2024 год. Студопедия не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования (0.019 с)...