ASP.NET MVC 4
Адам Фриман
Использование лямбда-выражений
Мы можем использовать делегат, чтобы сделать наш метод FilterByCategory
более общим. Способ, которым вызванный для каждого объекта Product
делегат будет фильтровать объекты, как нам нужно, показан в листинге 4-19. Здесь представлен расширенный метод Filter
, который мы добавили в класс MyExtensionMethods
.
Листинг 4-19: Использование делегата в расширенном методе
using System;
using System.Collections.Generic;
namespace LanguageFeatures.Models
{
public static class MyExtensionMethods
{
public static decimal TotalPrices(this IEnumerable<Product> productEnum)
{
decimal total = 0;
foreach (Product prod in productEnum)
{
total += prod.Price;
}
return total;
}
public static IEnumerable<Product> FilterByCategory(
this IEnumerable<Product> productEnum, string categoryParam)
{
foreach (Product prod in productEnum)
{
if (prod.Category == categoryParam)
{
yield return prod;
}
}
}
public static IEnumerable<Product> Filter(
this IEnumerable<Product> productEnum, Func<Product, bool> selectorParam)
{
foreach (Product prod in productEnum)
{
if (selectorParam(prod))
{
yield return prod;
}
}
}
}
}
Мы использовали Func
в качестве фильтрующего параметра, это обозначает, что нам не нужно определять делегат в качестве типа. Делегат принимает параметр Product
и возвращает значение bool
, которое будет true
, если этот объект Product
должны быть включен в результат. Другая часть этого технического приема немного громоздкая, как показано в листинге 4-20. Здесь представлены изменения, внесенные в расширенный метод UseFilterExtensionMethod
.
Листинг 4-20: Использование фильтрующего расширенного метода с Func
...
public ViewResult UseFilterExtensionMethod() {
// создаем и заполняем ShoppingCart
IEnumerable<Product> products = new ShoppingCart {
Products = new List<Product> {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
}
};
Func<Product, bool> categoryFilter = delegate(Product prod) {
return prod.Category == "Soccer";
};
decimal total = 0;
foreach (Product prod in products.Filter(categoryFilter)) {
total += prod.Price;
}
return View("Result", (object)String.Format("Total: {0}", total));
}
...
Мы продвинулись вперед в том смысле, что теперь мы можем фильтровать объекты Product
, используя любые условия, указанные в делегате, но мы должны определять Func
для каждого вида желаемого фильтра, а это далеко от идеала. Менее громоздкой альтернативой является использование лямбда-выражения, которое представляет собой сокращенный формат для выражения тела метода в делегате. Мы можем использовать его для замены определения нашего делегата, как показано в листинге 4-21.
Листинг 4-21: Использование лямбда-выражения для замены определения делегата
...
public ViewResult UseFilterExtensionMethod() {
// создаем и заполняем ShoppingCart
IEnumerable<Product> products = new ShoppingCart {
Products = new List<Product> {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
}
};
Func<Product, bool> categoryFilter = prod => prod.Category == "Soccer";
decimal total = 0;
foreach (Product prod in products.Filter(categoryFilter)) {
total += prod.Price;
}
return View("Result", (object)String.Format("Total: {0}", total));
}
...
Лямбда-выражение выделено жирным шрифтом. Параметр выражается без указания типа, который будет определен автоматически. Символы =>
произносятся как "переходит" и связывают параметр с результатом лямбда-выражения. В нашем примере параметр объекта Product
, названный prod
, переходит к результату bool
, который будет верным, если prod
параметр Category
будет равен Soccer
. Мы можем еще ужать синтаксис, если полностью откажемся от Func
, как показано в листинге 4-22.
Листинг 4-22: Лямбда-выражение без Func
...
public ViewResult UseFilterExtensionMethod() {
IEnumerable<Product> products = new ShoppingCart {
Products = new List<Product> {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
}
};
decimal total = 0;
foreach (Product prod in products.Filter(prod => prod.Category == "Soccer")) {
total += prod.Price;
}
return View("Result", (object)String.Format("Total: {0}", total));
}
...
В этом примере мы передали лямбда-выражение в качестве параметра методу Filter
. Это хороший и естественный способ выражения фильтра, который мы хотим применить. Мы можем объединить несколько фильтров, расширяя результирующую часть лямбда-выражения, как показано в листинге 4-23.
Листинг 4-23: Расширение фильтрации, представленное лямбда-выражением
...
public ViewResult UseFilterExtensionMethod() {
IEnumerable<Product> products = new ShoppingCart {
Products = new List<Product> {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
}
};
decimal total = 0;
foreach (Product prod in products.Filter(prod => prod.Category == "Soccer" || prod.Price > 20))
{
total += prod.Price;
}
return View("Result", (object)String.Format("Total: {0}", total));
}
...
Это лямбда-выражение будет соответствовать объектам Product
, которые находятся в категории Soccer
или чье свойство Price
больше 20.
Другие формы лямбда-выражений
Нам не обязательно нужно выражать логику делегатов в лямбда-выражении. Мы можем просто вызвать метод, вот так:
prod => EvaluateProduct(prod)
Если нам нужно лямбда-выражение для делегата, который имеет несколько параметров, мы должны заключить параметры в круглые скобки, например, вот так:
(prod, count) => prod.Price > 20 && count > 0
И, наконец, если нам в лямбда-выражении нужна логика, требующая более одного выражения, мы реализуем ее, используя фигурные скобки ({}
), и завершаем данный кусок кода при помощи return
, вот так:
(prod, count) => {
//...несколько выражений кода
return result;
}
Вам не обязательно нужно использовать лямбда-выражения в коде, но они являются аккуратным способом выражения сложных функций, которые становятся простыми в понимании и вполне читабельными. Мы их очень любим, и вы увидите, что в этой книге они часто будут использоваться.