ASP.NET MVC 4

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;
}

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

или RSS канал: Что новенького на smarly.net