Главная » C# » Использование    лямбда-выражений в электронной таблице в Visual C# (Sharp)

0

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

BaseType[,] CellState;

Func<IWorksheet<BaseType>, int, int, BaseType>[,] Cells; Func<IWorksheet<BaseType>, int, int, BaseType>[] ColCells;

Член  данных  CellState содержит состояние  ячейки листа.  Его тип — BaseType, т. е. тип, каким был объявлен тип BaseType. Члены данных cells и ColCells обвлены как лямбда-выражения с тремя параметрами и возвращаемым значением.

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

delegate string WhatAmI();

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

WhatAmI[] animals = new WhatAmI[2]; string animal;

animal = "cow"; animals[0] = delegate О {

return animal;

} ;

animal = "horse";

animals[1] = delegate*) { animal = "(" + animal + ")"; return animal;

} ;

Console. WriteLine ("Animal 1(" + animals [0]O + ") 2(" + animals [1]0 + ")");

В данном примере создается массив делегатов whatAmi, каждому из которых будет присвоен анонимный метод. В первом случае это метод, возвращающий значение переменной animal. А во втором — это буфер, где значение переменной animal заключено в кавычки. В каждом случае выполняется присваивание нового экземяра анонимного метода.

Какие, вы думаете, будут выведены значения для animal 1 и animal 2? Казалось бы, должны быть cow и horse, соответственно, не так ли? Но вот каким будет детвительный вывод:

Animal 1(horse) 2((horse))

Как видим, действительный вывод отличается от того, который мы ожидали. Это демонстрирует, что анонимные методы, или лямбда-выражения, не сохраняют стояние. Такое  поведение является желательным, т. к. при исполнении элементов массива animals[0] и animals[1] они будут ссылаться на последнюю версию пеменной animal.

Но ситуация существенно усложняется, если один анонимный метод модифицирт переменную, объявленную вне его области видимости. Чтобы продемонстрирать, что делегат в самом деле не имеет состояния, модифицируем оператор Console.WriteLineО, чтобы первым ИСПОЛНЯЛСЯ метод animals[l], который модифицирует состояние animal, и  посмотрим,  какой  эффект  это  будет  иметь на вывод:

Animal 2((horse)) 1((horse))

Мы видим, что т. к. с переменной animal были выполнены манипуляции, то первая реализация анонимного метода видит модифицированную версию.

Но сказать, что анонимные делегаты и лямбда-выражения совсем не имеют состояния, будет не вполне правильно,  т. к.  можно  создать  анонимный  делегат или лямбда-выражения, не имеющие состояния. Для примера рассмотрим следующую реализацию анонимного делегата WhatAmi, который инкапсулирован в контексте фабрики:

static class Builder {

public static WhatAmi BuildWhatAml(string animal) { return delegate() {

return animal;

} ;

}

}

На этот раз анонимный делегат создается в контексте метода BuiidwhatAmio, где тип animal передается в качестве параметра. Код массива, модифицированный для использования фабрики, выглядит таким образом:

WhatAmI[] animals = new WhatAmI[2]; string animal;

animal = "cow";

animals[0] = Builder.BuildWhatAmI(animal); animal = "horse";

animals[l] = Builder.BuildWhatAmI(animal);

Console.WriteLine("Animal 2(" + animals[l]() +

") 1(" + animals[0]() + ")");

Это такой же код, как и в предыдущей версии, за исключением вызова класса Builder. Чтобы больше не держать вас в напряженном ожидании, покажем вывод этого кода:

Animal 2(horse) l(cow)

Можно видеть, что когда анонимный делегат или лямбда-выражение используется в контексте другого метода и возвращается вызывающему коду, то они приобретт состояние. Таким образом, мы имеем два разных вида поведения, хотя вызающий код одинаковый в обоих случаях.

Хотя некоторые могут думать, что такое поведение является неправильным, с тои зрения функционального программирования оно верно. В главе 16 будет рамотрено, как из этого поведения можно извлечь пользу.

Возвратимся к примеру электронной таблицы. Некоторые фрагменты его исходно кода будут неработоспособными по причине только что описанного поведения. А именно следующий фрагмент кода:

for (int row = 0; row < items.Length; row++) {

AssignCellCalculation(row, 1, (worksheet, cellRow, cellCol") =>

{

return worksheet.GetCellState(row, 0) – worksheet.Calculate(items.Length, 0);

}

) ;

}

А вот с этим фрагментом никаких проблем нет:

static class Builder {

static Func<IWorksheet<double>, int, int, double> BuildSubtractFromAverage(int row, double!] items) {

return (worksheet, cellRow, cellCol) => {

return worksheet.GetCellState(row, 0) – worksheet.Calculate(items.Length,  0);

} ;

}

for (int row = 0; row < items.Length; row++) { AssignCellCalculation(row, 1,

Builder.BuildSubtractFromAverage(row,  items));

}

Разница между этими двумя фрагментами кода заключается в том, что в одном иользуется лямбда-выражение, объявленное в контексте метода, а во втором — нет. Но главное различие заключается в реализации лямбда-выражения или анонимного делегата. Как указано в главе 9, в более ранних версиях языка С# необходимо было применять метод класса. Так вот, это требование остается в силе и с настоящими версиями языка. В данном случае компилятор С# автоматически создал класс, сержащий методы-делегаты. В случае анонимного метода whatAmi фактически герируется следующий код:

[CompilerGenerated) // Код, сгенерированный компилятором private sealed class ос DisplayClass2

{

// Поля

public string animal;

// Методы

public <>c DisplayClass2(); public string <Variationl>b 0();

} public string <Variationl>b  1();

Сгенерированный класс имеет член данных animal, который в действительности является переменной, объявленной в контексте метода. Также в нем имеются два анонимных метода — ь     о ()  и ь    1 ().

А подо всем этим код присваивает методы класса <>с DispiayClass2 элементам массива animals. Поэтому, когда каждая реализация метода манипулирует перенной animal, она видит ту же самую переменную или член данных.

Так почему же одна версия анонимного метода сохраняет состояние, в то время как другая  нет?  Ответ  на этот  вопрос  связан  с  реализацией  анонимного  метода.  При объявлении каждого анонимного метода в контексте метода создается класс и его экземпляр. Поэтому если несколько анонимных методов используется в контексте метода, то все они разделяют один и тот же экземпляр класса. А когда вызывается метод,  создающий  анонимный  метод, то экземпляр  класса создается  при  каждом вызове метода. Таким образом, метод Builder.BuiidwhatAmi()  создает экземпляр класса с анонимным методом, которой возвращается вызывающему коду. Во всем

13 Зак. 555

этом может сбивать с толку то, что анонимный метод получается созданием экзеляра класса, хотя новый идентификатор и не используется.

ПРИМЕЧАНИЕ

Кром е введения обобщени й .NET, виртуальная машина .NET не очень сильно  измилась,   но  язык  С#  подвергся  довольно  значительным  модификациям.  Цель ю  многих из  этих  изменений  является   упрощение   распространенных  методов   программировия.  Это  означает,  что  сгенерированный  код  будет  вести  себя  подобно  коду,  который ва м  пришлось бы  писать самостоятельно,  если  бы  данная  возможность отсутствовала.

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

  • Комментарии