Лямбда выражения в 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 и методы расширения.
Спасибо за внимание!
источник