Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
25
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

public class HomeController : Controller

{

public string Index()

{

return "Navigate to a URL to show an example";

}

// ...другие метода действия опущены для краткости...

public ViewResult UseExtensionEnumerable()

{

IEnumerable<Product> products = new ShoppingCart

{

Products = new List<Product> {

new Product {Name = "Kayak", Price = 275M},

new Product {Name = "Lifejacket", Price = 48.95M}, new Product {Name = "Soccer ball", Price = 19.50M}, new Product {Name = "Corner flag", Price = 34.95M}

}

};

// создание и заполнение массива объектов Product Product[] productArray = {

new Product {Name = "Kayak", Price = 275M},

new Product {Name = "Lifejacket", Price = 48.95M}, new Product {Name = "Soccer ball", Price = 19.50M}, new Product {Name = "Corner flag", Price = 34.95M}

}; // получение общей стоимости продуктов в корзине

decimal cartTotal = products.TotalPrices(); decimal arrayTotal = productArray.TotalPrices(); return View("Result",

(object)String.Format("Cart Total: {0}, Array Total: {1}", cartTotal, arrayTotal));

}

}

}

Примечание

Способ, которым C# массивы реализуют интерфейс IEnumerable<T>, немного необычен. Вы не найдете его в списке реализуемых интерфейсов в документации MSDN. Эта поддержка обрабатывается компилятором, поэтому данный код для более ранних версий C# по-прежнему компилируется. Странно, но это так. Мы могли бы использовать другой класс коллекции в этом примере, но мы хотели показать наши знания о темных углах спецификации C#. Также странно, но это так.

Если вы запустите проект и нацелитесь на метод действия, вы увидите следующие результаты, которые показывают, что мы получим тот же результат от расширенного метода, независимо от того, как собраны объекты Product:

Cart Total: 378.40, Array Total: 378.40

Создание фильтрующих методов расширения

Последнее, что мы хотим рассказать вам о методах расширения, это то, что они могут быть использованы для фильтрации объектов коллекции. Расширенный метод, который работает с IEnumerable<T> и который также возвращает IEnumerable<T>, может использовать ключевое слово yield, чтобы применить критерии выбора элементов в исходных данных для получения сокращенного набора результатов. В листинге 4-17 показан такой метод, который мы добавили в класс MyExtensionMethods.

81

Листинг 4-17: Фильтрующий расширенный метод

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;

}

}

}

}

}

Этот метод расширения, названный FilterByCategory, принимает дополнительный параметр, который позволяет нам вводить условия фильтрации, когда мы вызываем метод. Те объекты Product, свойство Category которых соответствует параметру, возвращаются в результате использования IEnumerable<Product>, а те, у которых не соответствует – отбрасываются. В листинге 4-18 показано, как используется этот метод.

Листинг 4-18: Использование фильтрующего расширенного метода

using LanguageFeatures.Models; using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

namespace LanguageFeatures.Controllers

{

public class HomeController : Controller

{

public string Index()

{

return "Navigate to a URL to show an example";

}

// ... другие методы действия опущены для краткости ...

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}

}

82

};

decimal total = 0;

foreach (Product prod in products.FilterByCategory("Soccer"))

{

total += prod.Price;

}

return View("Result", (object)String.Format("Total: {0}", total));

}

}

}

Когда мы вызываем метод FilterByCategory для ShoppingCart, возвращаются только те объекты Product, которые находятся в категории Soccer. Если вы запустите проект и будете использовать метод действия UseFilterExtensionMethod, вы увидите следующий результат, который является общей стоимостью единиц продукции в категории Soccer:

Total: 54.45

Использование лямбда-выражений

Мы можем использовать делегат, чтобы сделать наш метод 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;

}

83

}

}

}

}

Мы использовали 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));

}

...

84

Лямбда-выражение выделено жирным шрифтом. Параметр выражается без указания типа, который будет определен автоматически. Символы => произносятся как "переходит" и связывают параметр с результатом лямбда-выражения. В нашем примере параметр объекта 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.

85

Другие формы лямбда-выражений

Нам не обязательно нужно выражать логику делегатов в лямбда-выражении. Мы можем просто вызвать метод, вот так:

prod => EvaluateProduct(prod)

Если нам нужно лямбда-выражение для делегата, который имеет несколько параметров, мы должны заключить параметры в круглые скобки, например, вот так:

(prod, count) => prod.Price > 20 && count > 0

И, наконец, если нам в лямбда-выражении нужна логика, требующая более одного выражения, мы реализуем ее, используя фигурные скобки ({}), и завершаем данный кусок кода при помощи return , вот так:

(prod, count) => { //...несколько выражений кода return result;

}

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

Автоматическое определение типа

Ключевое слово var в C# позволяет определять локальные переменные без явного указания типа этих переменных, как показано в листинге 4-24. Это называется автоматическим определением типа или неявной типизацией.

Листинг 4-24: Использование автоматического определения типа

..

var myVariable = new Product { Name = "Kayak", Category = "Watersports", Price = 275M };

string name = myVariable.Name; // правильно

int count = myVariable.Count; // ошибка компилятора

...

Нельзя сказать, что myVariable не имеет типа. Это мы просим компилятор, чтобы он обрабатывал ее в коде. По следующим выражениям видно, что компилятор будет работать только с членами выбранного класса, в данном случае с объектами Product.

Использование анонимных типов

Объединив инициализаторы объектов и автоматическое определение типа, мы можем создавать простые объекты для хранения данных без необходимости указания соответствующего класса или структуры. В листинге 4-25 представлен пример.

Листинг 4-25: Создание анонимного типа

...

var myAnonType = new { Name = "MVC",

86

Category = "Pattern" };

...

В этом примере myAnonType – это анонимно типизированный объект. Это не обозначает, что он является динамическим, как например, JavaScript переменные, которые являются динамически типизированными (слабо типизированными). Это просто обозначает, что тип будет автоматически определен компилятором. Строгая типизация по-прежнему соблюдается. Например, вы можете получать и устанавливать только те свойства, которые были определены в инициализаторе.

C# компилятор генерирует класс, основываясь на имени и типе параметров в инициализаторе. Два анонимно типизированных объекта, которые имеют одинаковые имена свойств и типы, будут относиться к одному и тому же автоматически созданному классу. Это обозначает, что мы можем создать массивы анонимно типизированных объектов, как показано в листинге 4-26. Здесь представлен метод действия CreateAnonArray, который мы добавили в контроллер Home.

Листинг 4-26: Создание массива анонимно типизированных объектов

using LanguageFeatures.Models; using System;

using System.Collections.Generic; using System.Linq;

using System.Text; using System.Web; using System.Web.Mvc;

namespace LanguageFeatures.Controllers

{

public class HomeController : Controller

{

public string Index()

{

return "Navigate to a URL to show an example";

}

// ...другие методы действия опущены для краткости ...

public ViewResult CreateAnonArray()

{

var oddsAndEnds = new[] {

new { Name = "MVC", Category = "Pattern"}, new { Name = "Hat", Category = "Clothing"}, new { Name = "Apple", Category = "Fruit"} };

StringBuilder result = new StringBuilder(); foreach (var item in oddsAndEnds)

{

result.Append(item.Name).Append(" ");

}

return View("Result", (object)result.ToString());

}

}

}

Обратите внимание, что при объявлении массива мы используем var. Мы должны это сделать, потому что у нас нет типа, который мы могли бы указать, как в типизированном массиве. И хотя мы не определили класс ни для одного из этих объектов, мы все еще можем перечислить содержимое массива и прочитать значение свойства Name каждого из них. Это важно, потому что без этой возможностимы вообще не смогли бы создавать массивы анонимно типизированных объектов. Или, вернее, мы могли бы создавать такие массивы, но мы не могли бы ничего полезного с ними делать. Если вы запустите этот пример и будете работать с данным методом действия, вы увидите следующий результат:

MVC Hat Apple

87

Использование LINQ

Все возможности, которые мы описывали до сих пор, хорошо работают с LINQ. Мы любим LINQ. Это прекрасное и важное дополнение к .NET. Если вы никогда не использовали LINQ, вы очень многое упустили. LINQ представляет собой SQL-подобный синтаксис для выборки данных в классах. Представьте себе, что у нас есть набор объектов Product, и мы хотим получить три из них с самыми высокими ценами и передать их методу View. Без LINQ мы в итоге получили бы нечто похожее на листинг 4-27, который показывает метод действия FindProducts, добавленный в контроллер Home.

Листинг 4-27: Выборка без LINQ

public ViewResult FindProducts()

{

Product[] products = {

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}

};

//определяем массив для результатов

Product[] foundProducts = new Product[3];

//сортируем содержание массива

Array.Sort(products, (item1, item2) =>

{

return Comparer<decimal>.Default.Compare(item1.Price, item2.Price); });

//получаем три первых элемента массива в качестве результата

Array.Copy(products, foundProducts, 3);

//создаем результат

StringBuilder result = new StringBuilder(); foreach (Product p in foundProducts)

{

result.AppendFormat("Price: {0} ", p.Price);

}

return View("Result", (object)result.ToString());

}

С LINQ мы можем значительно упростить процесс выборки, как показано в листинге 4-28.

Листинг 4-28: Использование LINQ для выборки данных

public ViewResult FindProducts()

{

Product[] products = {

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}

};

var foundProducts = from match in products orderby match.Price descending select new

{

match.Name,

match.Price

};

// создаем результат int count = 0;

StringBuilder result = new StringBuilder(); foreach (var p in foundProducts)

{

88

result.AppendFormat("Price: {0} ", p.Price); if (++count == 3)

{

break;

}

}

return View("Result", (object)result.ToString());

}

}

Это намного аккуратнее. Вы можете увидеть SQL-подобную выборку, выделенную жирным шрифтом. Мы располагаем объекты Product в порядке убывания по свойству Price и используем ключевое слово select, чтобы вернуть анонимный тип, содержащий только свойства Name и Price. Этот стиль LINQ известен как синтаксис запросов, и это тот стиль, который разработчики считают наиболее удобным, когда они начинают использовать LINQ. Такая выборка возвращает один анонимно типизированный объект для каждого Product в массиве, который мы использовали в исходном запросе, поэтому нам нужно поработать с результатами, чтобы получить первые три объекта и вывести их на экран.

Если же мы готовы отказаться от простоты синтаксиса запросов, мы сможем ощутить намного больше мощи LINQ. Альтернативой является синтаксис точечной нотации, или точечная нотация, которая основана на методах расширения. В листинге 4-29 показано, как мы можем использовать этот альтернативный синтаксис для обработки наших объектов Product.

Листинг 4-29: Использование точечной нотации LINQ

public ViewResult FindProducts() { Product[] products = {

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}

};

var foundProducts = products.OrderByDescending(e => e.Price)

.Take(3)

.Select(e => new { e.Name,

e.Price

});

StringBuilder result = new StringBuilder(); foreach (var p in foundProducts) {

result.AppendFormat("Price: {0} ", p.Price);

}

return View("Result", (object)result.ToString());

}

Мы будем первыми, кто признает, что на эту LINQ выборку, выделенную жирным шрифтом, не так приятно смотреть, как на синтаксис запроса. Однако, не для всех LINQ функций имеются соответствующие ключевые слова C#. Как серьезные LINQ программисты мы должны перейти к использованию методов расширения. Каждый из методов расширение LINQ в листинге применяется к IEnumerable<T> и возвращает тоже IEnumerable<T>, что позволяет связывать методы цепочкой, чтобы формировать сложные выборки.

Примечание

Все методы расширения LINQ находятся в пространстве имен System.Linq, которое вы должны вставить в код при помощи ключевого слова using, прежде чем делать выборку. Visual Studio автоматически добавляет пространство имен System.Linq к классам контроллера, но вы можете добавить его вручную в любом другом месте MVC проекта.

89

Метод OrderByDescending сортирует элементы в исходных данных. В этом случае лямбдавыражение возвращает значение, которое мы хотим использовать для сравнения. Метод Take возвращает указанное число элементов с начала цепочки результатов (это то, что мы не могли сделать, используя синтаксис запроса). Метод Select позволяет нам проектировать нужные результаты. В данном случае мы проектируем анонимный объект, который содержит свойства Name и Price. Обратите внимание, что нам даже не нужно указывать имена свойств в анонимном типе. C# сделал вывод, основываясь на свойствах, которые мы вставили в метод Select.

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

работают с IEnumerable<T>.

Таблица 4-1: Некоторые полезные методы расширения LINQ

Метод расширения

All

Any

Contains

Count

First

FirstOrDefault

Last

LastOrDefault

Max, Min

OrderBy,

OrderByDescending

Reverse

Select

SelectMany

Описание

Возвращает true, если все элементы в исходных данных соответствуют утверждению

Возвращает true, если, как минимум, один из элементов в исходных данных соответствуют утверждению

Возвращает true, если исходные данные содержат указанный элемент или значение

Возвращает число элементов в исходных данных

Возвращает первый элемент из исходных данных

Возвращает первый элемент из исходных данных или значение по умолчанию, если элементов нет

Возвращает последний элемент из исходных данных

Возвращает последний элемент из исходных данных или значение по умолчанию, если элементов нет

Возвращает самое большое или самое маленькое значение, указанное лямбда-выражением

Сортирует исходные данные, основываясь на значении, возвращенном лямбда-выражением

Меняет порядок элементов в исходных данных

Проектирует результат выборки

Проектирует каждый элемент данных в последовательность элементов, а затем объединяет все эти результирующие последовательности в одну последовательность

Отложенный

Нет

Нет

Нет

Нет

Нет

Нет

Нет

Нет

Нет

Да

Да

Да

Да

90

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]