Форум программистов, компьютерный форум, киберфорум
Наши страницы
C# .NET
Войти
Регистрация
Восстановить пароль
 
Рейтинг: Рейтинг темы: голосов - 345, средняя оценка - 4.96
Mikant
1280 / 952 / 127
Регистрация: 08.12.2009
Сообщений: 1,299
#1

Лямбда - выражения - C#

14.02.2010, 16:10. Просмотров 50805. Ответов 0
Метки нет (Все метки)

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

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

Итак, сформулируем задачу: у нас есть класс MyClass,
C#
1
2
3
4
    public class MyClass {
        public int IntegerValue { get; set; }
        public string StringValue { get; set; }
    }
коллекция его экземпляров:
C#
1
2
3
4
5
6
7
8
9
10
11
12
        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#
1
var found = myCollection.FindAll(mc => mc.IntegerValue < 5);
с левой частью все ещё более-менее понятно: var на этапе компиляции просто превратится в List<MyClass> (так как метод FindAll имеет именно такой возвращаемый тип), а вот с правой возникают сложности. Естественно Вы можете сказать, что такую выборку можно сделать и в простом цикле,
C#
1
2
3
4
            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#
1
2
3
4
5
6
7
8
9
10
11
12
13
        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;
        }
таким же образом как я описал выше создается новая коллекция, затем в цикле проверяется условие предиката и, если оно выполняется, в список добавляется новый элемент.

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

Обойтись без этой конструкции можно добавив в код следующий метод:
C#
1
2
3
4
        private static bool IntegerValueLessThanFive(MyClass mc) {
            if (mc.IntegerValue < 5) return true;
            return false;
        }
И производить поиск следующим способом:
C#
1
2
Predicate<MyClass> predicate = new Predicate<MyClass>(IntegerValueLessThanFive);
List<MyClass> found = myCollection.FindAll(predicate);
либо так:
C#
1
List<MyClass> found = myCollection.FindAll(IntegerValueLessThanFive);
так как обертка предиката будет создана автоматически на этапе компиляции

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

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

Не по теме:

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



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

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

Конечно свежеиспеченный предикат можно использовать и как обычный метод:
C#
1
2
Predicate<MyClass> pred = (MyClass mc) => { return mc.IntegerValue < 5; };
 bool b = pred.Invoke(myCollection[0]);
С помощью лямбда-выражений можно к примеру объявлять поведение элементов управления в одном методе:
C#
1
2
3
4
5
6
            Action<object, EventArgs> changeText = (object sender, EventArgs e) => {
                this.Text = (sender as Control).Text;
            };
 
            button1.Click += new EventHandler(changeText);
            button2.Click += new EventHandler(changeText);
Их малые размеры позволяют создавать гибкую систему методов внутри других методов и строить длинные деревья выражений.

Таким образом, не надо бояться таких аккуратных записей, теперь Вы знаете, как это работает
C#
1
2
3
            List<MyClass> found = myCollection.FindAll(mc => {
                mc.IntegerValue < 5 && mc.StringValue.Contains("a");
            });
Рекомендую статью HIMen , где рассказано про "нововведения" C# 3.0 , такие как LINQ и методы расширения.

Спасибо за внимание!
124
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
14.02.2010, 16:10
Я подобрал для вас темы с готовыми решениями и ответами на вопрос Лямбда - выражения (C#):

Лямбда-выражения
Собственно,какие у нее плюсы перед анонимными методами,кроме конечно того,что...

Упрощение кода Лямбда-выражения и цыкл
Здравствуйте, подскажите пожалуйста можно ли как-то упростить данный код...

Подскажите по лямбда выражениям
Тут http://msdn.microsoft.com/ru-ru/library/bb397687(v=VS.90).aspx написанно...

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

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

Лямбда Выражение как простой вызов функции.
Здравствуйте, подскажите несведующему, чтобы использовать лямбда выражения...

0
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
14.02.2010, 16:10
Привет! Вот еще темы с решениями:

Лямбда выражение и LINQ. Последовательность без повторений
Как сделать что бы результирующая последовательность имела не повторяющие числа...

Лямбда обработчик события. Нужно вернуть значение вовне обработчика, во внешний код
Я хочу сделать как-то так: Uri GetUriBlaBlaBla() { var wb = new...

Лямбда выражение в качестве параметра метода. Как получить свойство с которым производится операция?
Добрый день! Нужно в методе принимать лямбду: ...

Не удается преобразовать лямбда выражение к типу "Delegate"
Всем привет! Декомпилировал рабочую программу и почему то после открытия...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
1
Закрытая тема Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru