Автор Тема: Лямбда выражения  (Прочитано 1610 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Апрель 28, 2015, 05:32:58 am
Прочитано 1610 раз

Mimi Neko

  • Администратор
  • Старожил форума

  • Оффлайн
  • *****

  • 2454
  • Репутация:
    153
    • Просмотр профиля
Лямбда выражения в C#, и их использование
(перепечатка статьи)

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

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

Итак, сформулируем задачу: у нас есть класс MyClass,

C#
   
public class MyClass {
        public int IntegerValue { get; set; }
        public string StringValue { get; set; }
}

коллекция его экземпляров:

C#
private List<MyClass> myCollection = new List<MyClass>() {
            new MyClass(){ IntegerValue=0, StringValue="qwe" },
            new MyClass(){ IntegerValue=1, StringValue="wer" },
            new MyClass(){ IntegerValue=2, StringValue="ert" },
            new MyClass(){ IntegerValue=3, StringValue="rty" },
            new MyClass(){ IntegerValue=4, StringValue="tyu" },
            new MyClass(){ IntegerValue=5, StringValue="yui" },
            new MyClass(){ IntegerValue=6, StringValue="uio" },
            new MyClass(){ IntegerValue=7, StringValue="iop" },
            new MyClass(){ IntegerValue=8, StringValue="opq" },
            new MyClass(){ IntegerValue=9, StringValue="pqw" },
};

и нам требуется выбрать из неё элементы, у которых IntegerValue меньше 5

Программист не задумываясь сразу напишет строку:

C#
var found = myCollection.FindAll(mc => mc.IntegerValue < 5);
с левой частью все ещё более-менее понятно: var на этапе компиляции просто превратится в List<MyClass> (так как метод FindAll имеет именно такой возвращаемый тип), а вот с правой возникают сложности. Естественно Вы можете сказать, что такую выборку можно сделать и в простом цикле,

C#
            List<MyClass> found = new List<MyClass>();
            for (int i = 0; i < myCollection.Count; i++)
                if (myCollection[i].IntegerValue < 5)
                    found.Add(myCollection[i]);
но как видно из примера, это займет уже в четыре раза большее количество строк, а самое главное - потеряется универсальность кода. Тем более ситуация усложнится при манипуляциях с большими коллекциями, где выборку надо делать по множеству критериев.

рассмотрим же метод FindAll с поверхности: он требует на вход некий Predicate<T> match, который возвращает значение типа bool (в нашем случае - true, если IntegerValue меньше 5)

внутри же этот метод выглядит следующим образом:

C#
        public List<T> FindAll(Predicate<T> match) {
            if( match == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
 
            List<T> list = new List<T>();
            for(int i = 0 ; i < _size; i++) {
                if(match(_items[i])) {
                    list.Add(_items[i]);
                }
            }
            return list;
        }

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

так что же значила эта строка?
mc => mc.IntegerValue < 5
или, что то же самое
(mc) => { return mc.IntegerValue < 5; }
Правильно - очень заковыристый способ создания делегата предиката!

Обойтись без этой конструкции можно добавив в код следующий метод:
private static bool IntegerValueLessThanFive(MyClass mc) {
            if (mc.IntegerValue < 5) return true;
            return false;
}

И производить поиск следующим способом:
Predicate<MyClass> predicate = new Predicate<MyClass>(IntegerValueLessThanFive);
List<MyClass> found = myCollection.FindAll(predicate);

либо так:
List<MyClass> found = myCollection.FindAll(IntegerValueLessThanFive);
так как обертка предиката будет создана автоматически на этапе компиляции

К сожалению, приведенный выше код занимает намного больше места, явно объявлен метод для простейшей выборки. Зато он сработает в самой старой спецификации C# - да, раньше только так и писали.

С выходом C# 2.0 в языке появились так называемые анонимные делегаты. Поясню: метод IntegerValueLessThanFive имеет свое имя но в действительности будет использован только в FindAll, следовательно, зачем ему вообще имя - ссылки достаточно? Тип Predicate<T> в системе объявлен следующим образом:
public delegate bool Predicate<T>(T value);
Не по теме:

Для тех кто не знает, делегат - ссылка на функцию.


И чтобы не пользоваться конструкцией
public static Predicate<MyClass> IntegerValueLessThanFivePredicate = new Predicate<MyClass>(IntegerValueLessThanFive);
или создавать предикат в коде (до поиска) появилась возможность объявления тел методов, как анонимных делегатов:
List<MyClass> found = myCollection.FindAll(delegate(MyClass mc) { return mc.IntegerValue < 5; });
Так намного компактнее, правда?

В C# 3.0 вместе с LINQ пришли лямбда-выражения.
Для математики лямбда-исчисление - огромный раздел, но для прикладного программирования лямбда-выражения - не более чем простой способ объявления анонимных делегатов:
Predicate<MyClass> p1 = (mc) => { return mc.IntegerValue < 5; };
// вместо
Predicate<MyClass> p2 = delegate(MyClass mc) { return mc.IntegerValue < 5; };
Обратите внимание, что в первом случае у mc отсутствует тип (его можно дописать так "(MyClass mc)=>...") - компилятор сам разрешает такие деликатные ситуации, а нам не требуется выписывать лишние символы.

Конечно свежеиспеченный предикат можно использовать и как обычный метод:
Predicate<MyClass> pred = (MyClass mc) => { return mc.IntegerValue < 5; };
 bool b = pred.Invoke(myCollection[0]);

С помощью лямбда-выражений можно к примеру объявлять поведение элементов управления в одном методе:
            Action<object, EventArgs> changeText = (object sender, EventArgs e) => {
                this.Text = (sender as Control).Text;
            };
 
            button1.Click += new EventHandler(changeText);
            button2.Click += new EventHandler(changeText);

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

Таким образом, не надо бояться таких аккуратных записей, теперь Вы знаете, как это работает
            List<MyClass> found = myCollection.FindAll(mc => {
                mc.IntegerValue < 5 && mc.StringValue.Contains("a");
            });

Рекомендую статью HIMen , где рассказано про "нововведения" C# 3.0 , такие как LINQ и методы расширения.

Спасибо за внимание!

источник



Октябрь 08, 2018, 23:24:39 pm
Ответ #2

Mimi Neko

  • Администратор
  • Старожил форума

  • Оффлайн
  • *****

  • 2454
  • Репутация:
    153
    • Просмотр профиля

Делегаты и Лямбда выражения в C# .Net — Шпаргалка или коротко о главном
https://habr.com/post/329886/