Лямбда для чайников .... кто-нибудь, кто-нибудь? думаю, нет - PullRequest
36 голосов
/ 30 января 2010

В своем стремлении понять очень странно выглядящий оператор '=>' я нашел хорошее место для начала , и автор очень лаконичен и ясен:

parameters => expression

У кого-нибудь есть советы по пониманию основ лямбды, чтобы было легче "расшифровывать" более сложные лямбда-выражения?

Например: если мне дают что-то вроде (из ответа, который я получил здесь ):

filenames.SelectMany(f => 
        Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
        .Cast<PluginClassAttribute>()
        .Select(a => a.PluginType)
).ToList();

Как я могу разбить это на более простые части?


ОБНОВЛЕНИЕ: хотел показать мое первое лямбда-выражение. Не смейтесь надо мной, но я сделал это, не копируя чей-то пример ... и это сработало в первый раз:

public ModuleData[] GetStartModules( )
{ return modules.FindAll(start => start.IsBatch == true).ToArray(); }

Ответы [ 10 ]

40 голосов
/ 30 января 2010

Давайте разберем ваш пример кода:

filenames.SelectMany(f => 
        Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
        .Cast<PluginClassAttribute>()
        .Select(a => a.PluginType)
).ToList();

Итак, мы начинаем с string[], называемого filenames. Мы вызываем метод расширения SelectMany для массива, а затем вызываем ToList для результата:

filenames.SelectMany(
   ...
).ToList();

SelectMany принимает делегата в качестве параметра, в этом случае делегат должен принять в качестве входного параметра один параметр типа string и вернуть IEnumerable<T> (где выводится тип T). Вот где лямбды выходят на сцену:

filenames.SelectMany(f => 
        Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
).ToList()

Здесь произойдет следующее: для каждого элемента в массиве filenames будет вызван делегат. f является входным параметром, а все, что находится справа от =>, является телом метода, на которое ссылается делегат. В этом случае Assembly.LoadFrom будет вызываться для имени файла в массиве, передавая его имя файла в метод LoadFrom, используя аргумент f. На AssemblyInstance, который возвращается, будет вызван GetCustomAttributes(typeof(PluginClassAttribute), true), который возвращает массив Attribute экземпляров. Таким образом, компилятор не может сделать вывод, что тип T, упомянутый ранее, является Assembly.

На IEnumerable<Attribute>, который возвращается, будет вызываться Cast<PluginClassAttribute>(), возвращая IEnumerable<PluginClassAttribute>.

Итак, теперь у нас есть IEnumerable<PluginClassAttribute>, и мы вызываем Select. Метод Select похож на SelectMany, но возвращает один экземпляр типа T (который выводится компилятором) вместо IEnumerable<T>. Настройка идентична; для каждого элемента в IEnumerable<PluginClassAttribute> он вызовет определенный делегат, передав в него текущее значение элемента:

.Select(a => a.PluginType)

Опять же, a - это входной параметр, a.PluginType - это тело метода. Таким образом, для каждого экземпляра PluginClassAttribute в списке он будет возвращать значение свойства PluginType (я предполагаю, что это свойство имеет тип Type).

Резюме
Если мы склеим эти кусочки вместе:

// process all strings in the filenames array
filenames.SelectMany(f => 
        // get all Attributes of the type PluginClassAttribute from the assembly
        // with the given file name
        Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
        // cast the returned instances to PluginClassAttribute
        .Cast<PluginClassAttribute>()
        // return the PluginType property from each PluginClassAttribute instance
        .Select(a => a.PluginType)
).ToList();

Лямбда против делегатов
Давайте закончим это, сравнив лямбды с делегатами. Возьмите следующий список:

List<string> strings = new List<string> { "one", "two", "three" };

Скажем, мы хотим отфильтровать те, которые начинаются с буквы "t":

var result = strings.Where(s => s.StartsWith("t"));

Это самый распространенный подход; установить его с помощью лямбда-выражения. Но есть альтернативы:

Func<string,bool> func = delegate(string s) { return s.StartsWith("t");};
result = strings.Where(func);

Это по сути то же самое: сначала мы создаем делегат типа Func<string, bool> (это означает, что он принимает string в качестве входного параметра и возвращает bool). Затем мы передаем этот делегат в качестве параметра методу Where. Это то, что компилятор сделал для нас за кулисами в первом примере (strings.Where(s => s.StartsWith("t"));).

Третий вариант - просто передать делегат неанонимному методу:

private bool StringsStartingWithT(string s)
{
    return s.StartsWith("t");
}

// somewhere else in the code:
result = strings.Where(StringsStartingWithT);

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

И если у вас была энергия, прочитанная полностью здесь, хорошо, спасибо за ваше время:)

6 голосов
/ 30 января 2010

Итак, начнем со страшного определения - лямбда - еще один способ определения анонимного метода. Существовал (начиная с C # 2.0, я считаю) способ создания анонимных методов, однако этот синтаксис был очень ... неудобен.

Так что же такое анонимный метод? Это способ определения встроенного метода без имени - следовательно, является анонимным. Это полезно, если у вас есть метод, который принимает делегат, поскольку вы можете передать этот лямбда-выражение / анонимный метод в качестве параметра, если типы совпадают. Возьмите IEnumerable.Select в качестве примера, он определяется следующим образом:

IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);

Если вы обычно используете этот метод, скажем, в List и выбираете каждый элемент дважды (который соединен с собой):

string MyConcat(string str){
    return str + str;
}

...

void myMethod(){
    IEnumerable<string> result = someIEnumerable.Select(MyConcat);
}

Это очень неудобный способ сделать это, особенно если вы хотите выполнить многие из этих операций - как правило, такие методы вы используете только один раз. Определение, которое вы показали (параметры => выражение), очень кратко, и оно попало в точку. Интересная особенность лямбды заключается в том, что вам не нужно выражать тип параметров - , если они могут быть выведены из выражения . То есть. в случае Select - мы знаем , что первый параметр должен иметь тип TSource - потому что определение метода гласит так. Еще больше - если мы вызываем метод так же, как foo.Select (...), тогда возвращаемое значение выражения будет определять TResult.

Интересно также то, что для лямбды с одним утверждением ключевое слово return не нужно - лямбда будет возвращать то, к чему относится это выражение. Однако, если вы используете блок (заключенный в '{' и '}'), вы должны включить ключевое слово return как обычно.

Если вы хотите, все еще на 100% законно определять типы параметров. С этим новым знанием давайте попробуем переписать предыдущий пример:

void myMethod(){
    IEnumerable<string> result = someIEnumerable.Select(s => s + s);
}

Или с указанием явных параметров

void myMethod(){
    IEnumerable<string> result = someIEnumerable.Select((string s) => s + s);
}

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

5 голосов
/ 30 января 2010

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

private int sum5(int n)
{
    return n + 5;
}

эквивалентно лямбде: (n) => n + 5. За исключением того, что вы можете вызывать метод в любом месте вашего класса, и лямбда живет только в объявленной области (вы также можете хранить лямбда-выражения в объектах Action и Func)

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

...methodbody
int acc = 5;
Func<int> addAcc = (n) => n + acc;

У вас есть функция, которая принимает аргумент, как и раньше, но добавленная сумма берется из значения переменной. Лямбда может жить даже после завершения области действия, в которой был определен acc.

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

2 голосов
/ 30 января 2010

Хорошее простое объяснение, предназначенное для разработчиков, знакомых с кодированием, но не имеющих отношения к лямбдам, - это простое видео на TekPub

TekPub - Основные понятия: # 2 лямбды

У вас здесь, конечно, много отзывов, но это еще один хороший источник и простое объяснение.

2 голосов
/ 30 января 2010

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

a => a + 1

создает функцию, которая связывает свободную переменную a в выражении (a + 1) с первым параметром функции и возвращает эту функцию.

Один случай, когда лямбды чрезвычайно полезны, это когда вы используете их для работы со структурами списков. Класс System.Linq.Enumerable предоставляет множество полезных функций, которые позволяют работать с лямбда-выражениями и объектами, реализующими IEnumerable. Например, Enumerable.Where можно использовать для фильтрации списка:

List<string> fruits = new List<string> { 
        "apple", "passionfruit", "banana", "mango", 
        "orange", "blueberry", "grape", "strawberry" };

IEnumerable<string> shortFruits = fruits.Where(fruit => fruit.Length < 6);

foreach (string fruit in shortFruits) {
    Console.WriteLine(fruit);
}

Вывод будет "яблоко, манго, виноград".

Попытайтесь понять, что здесь происходит: выражение fruit => fruit.Length <6 создает функцию, которая возвращает <em>true , если свойство Length параметра меньше 6.

Enumerable.Where зацикливается на List и создает новый List, который содержит только те элементы, для которых предоставленная функция возвращает true. Это избавляет вас от написания кода, который перебирает список, проверяет предикат для каждого элемента и что-то делает.

1 голос
/ 11 сентября 2013

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

Не путайте a => a + 1 как означающее добавить 1 к a и вернуть результат в a. (это, скорее всего, источник путаницы для начинающих. Вместо этого посмотрите на это следующим образом: a - это входной параметр функции (безымянная функция), а a + 1 - оператор (ы) в функции (безымянная функция, созданная «на лету»).

Надеюсь, это поможет:)

0 голосов
/ 30 января 2010

У CodeProject недавно была хорошая вводная статья: Делегаты C #, анонимные методы и лямбда-выражения - O My!

0 голосов
/ 30 января 2010

Мой совет по пониманию основ лямбды - два.

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

Оттуда, я думаю, вам следует просмотреть лекции Эрика Мейера по функциональному программированию , так как они дают отличное введение в функциональное программирование, также с использованием Haskell и переходом на C #.

Как только вы поймете все это, вы должны быть на пути к пониманию лямбд.

0 голосов
/ 30 января 2010

Это просто обозначение C # для записи значения функции. Это не требует присвоения функции имени, поэтому это значение иногда называют анонимной функцией . Другие языки имеют другие обозначения, но они всегда содержат список параметров и тело.

Оригинальная запись, изобретенная Алонзо Черчем для его Лямбда-исчисления в 1930-х годах, использовала греческий символ лямбда в выражении λx.t для представления функции, отсюда и название.

0 голосов
/ 30 января 2010

Лямбда-исчисление распространено во многих языках программирования. Они также называются анонимными функциями в некоторых языках. Хотя разные языки имеют разный синтаксис для лямбды, принцип один и тот же, и их различные части обычно идентичны.

Пожалуй, самый известный из них - анонимные функции Javascript.

lol = function() {laugh()}
# is equivalent to
function lol() {laugh()}

Какая разница? Ну, иногда вы не хотите создавать проблему, просто передавая ее куда-то один раз, а потом никогда.

window.onload = function() {laugh()}
# is way easier than
function lol() {laugh()}
window.onload = lol

Вы можете просмотреть статью в Википедии для получения недостоверной информации или перейти непосредственно к Lambda в программировании в той же статье.

...