Довольно часто прихожане на нашем форуме просят помочь им в написании кода. Как правило первым моментальным ответом на это следует однострочный код с использованием 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 и методы расширения.
Спасибо за внимание!