355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Герберт Шилдт » C# 4.0: полное руководство » Текст книги (страница 52)
C# 4.0: полное руководство
  • Текст добавлен: 6 апреля 2017, 04:00

Текст книги "C# 4.0: полное руководство"


Автор книги: Герберт Шилдт



сообщить о нарушении

Текущая страница: 52 (всего у книги 83 страниц)

Создание группового объединения

Как пояснялось ранее, оператор into можно использовать вместе с оператором join для создания группового объединения, образующего последовательность, в которой каждый результат состоит из элементов данных из первой последовательности и группы всех совпадающих элементов из второй последовательности. Примеры группового объединения не приводились выше потому, что в этом объединении нередко применяется анонимный тип. Но теперь, когда представлены анонимные типы, можно обратиться к простому примеру группового объединения.

В приведенном ниже примере программы групповое объединение используется для составления списка, в котором различные транспортные средства (автомашины, суда и самолеты) организованы по общим для них категориям транспорта: наземного, морского, воздушного и речного. В этой программе сначала создается класс Transport, связывающий вид транспорта с его классификацией. Затем в методе Main() формируются две входные последовательности. Первая из них представляет собой массив символьных строк, содержащих названия общих категорий транспорта: наземного, морского, воздушного и речного, а вторая – массив объектов типа Transport, инкапсулирующих различные транспортные средства. Полученное в итоге групповое объединение используется для составления списка транспортных средств, организованных по соответствующим категориям.

// Продемонстрировать применение простого группового объединения.

using System;

using System.Linq;

// Этот класс связывает наименование вида транспорта,

// например поезда, с общей классификацией транспорта:

// наземного, морского, воздушного или речного,

class Transport {

  public string Name { get; set; }

  public string How { get; set; }

  public Transport(string n, string h) {

    Name = n;

    How = h;

  }

}

class GroupJoinDemo {

  static void Main() {

    // Массив классификации видов транспорта,

    string[] travelTypes = {

      «Воздушный»,

      «Морской»,

      «Наземный»,

      «Речной»,

    };

    // Массив видов транспорта.

    Transport[] transports = {

      new Transport(«велосипед», «Наземный»),

      new Transport («аэростат», «Воздушный»),

      new Transport(«лодка», «Речной»),

      new Transport(«самолет», «Воздушный»),

      new Transport(«каноэ», «Речной»),

      new Transport(«биплан», «Воздушный»),

      new Transport(«автомашина», «Наземный»),

      new Transport(«судно», «Морской»),

      new Transport(«поезд», «Наземный»)

    };

    // Сформировать запрос, в котором групповое

    // объединение используется для составления списка

    // видов транспорта по соответствующим категориям,

    var byHow = from how in travelTypes

             join trans in transports

               on how equals trans.How

             into lst

             select new { How = how, Tlist = lst };

    // Выполнить запрос и вывести его результаты,

    foreach(var t in byHow) {

      Console.WriteLine(«К категории <{0} транспорт> относится:», t.How);

      foreach(var m in t.Tlist)

        Console.WriteLine(" " + m.Name);

      Console.WriteLine();

    }

  }

}

Ниже приведен результат выполнения этой программы.

К категории <Воздушный транспорт> относится:

 аэростат

 самолет

 биплан

К категории <Морской транспорт> относится:

 судно

К категории <Наземный транспорт> относится:

 велосипед

 автомашина

 поезд

К категории <Речной транспорт>  относится:

 лодка

 каноэ

Главной частью данной программы, безусловно, является следующий запрос.

var byHow = from how in travelTypes

     join trans in transports

       on how equals trans.How

     into 1st

     select new { How = how, Tlist = 1st };

Этот запрос формируется следующим образом. В операторе from используется переменная диапазона how для охвата всего массива travelTypes. Напомним, что массив travelTypes содержит названия общих категорий транспорта: воздушного, наземного, морского и речного. Каждый вид транспорта объединяется в операторе join со своей категорией. Например, велосипед, автомашина и поезд объединяются с наземным транспортом. Но благодаря оператору into для каждой категории транспорта в операторе join составляется список видов транспорта, относящихся к данной категории. Этот список сохраняется в переменной lst. И наконец, оператор select возвращает объект анонимного типа, инкапсулирующий каждое значение переменной how (категории транспорта) вместе со списком видов транспорта. Именно поэтому для вывода результатов запроса требуются два цикла foreach.

foreach(var t in byHow) {

  Console.WriteLine(«К категории <{0} транспорт> относится:», t.How);

  foreach(var m in t.Tlist)

    Console.WriteLine(" " + m.Name);

  Console.WriteLine();

}

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


Методы запроса

Синтаксис запроса, описанный в предыдущих разделах, применяется при формировании большинства запросов в С#. Он удобен, эффективен и компактен, хотя и не является единственным способом формирования запросов. Другой способ состоит в использовании методов запроса, которые могут вызываться для любого перечислимого объекта, например массива.


Основные методы запроса

Методы запроса определяются в классе System.Linq.Enumerable и реализуются в виде методов расширения функций обобщенной формы интерфейса IEnumerable. (Методы запроса определяются также в классе System.Linq.Queryable, расширяющем функции обобщенной формы интерфейса IQueryable, но этот интерфейс в настоящей главе не рассматривается.) Метод расширения дополняет функции другого класса, но без наследования. Поддержка методов расширения была внедрена в версию C# 3.0 и более подробно рассматривается далее в этой главе. А до тех пор достаточно сказать, что методы запроса могут вызываться только для тех объектов, которые реализуют интерфейс IEnumerable.

В классе Enumerable предоставляется немало методов запроса, но основными считаются те методы, которые соответствуют описанным ранее операторам запроса. Эти методы перечислены ниже вместе с соответствующими операторами запроса. Следует, однако, иметь в виду, что эти методы имеют также перегружаемые формы, а здесь они представлены лишь в самой простой своей форме. Но именно эта их форма используется чаще всего.

Оператор запроса  Эквивалентный метод запроса

select             Select(selector)

where              Where(predicate)

orderby           OrderBy(keySelector) или

                  OrderByDescending(keySelector)

join              Join(inner, outerKeySelector,

                    innerKeySelector, resultSelector)

group              GroupBy(keySelector)

За исключением метода Join(), остальные методы запроса принимают единственный аргумент, который представляет собой объект некоторой разновидности обобщенного типа Func. Это тип встроенного делегата, объявляемый следующим образом:

delegate TResult Func(Т arg)

где TResult обозначает тип результата, который дает делегат, а Т – тип элемента. В методах запроса аргументы selector, predicate или keySelector определяют действие, которое предпринимает метод запроса. Например, в методе Where() аргумент predicate определяет порядок отбора данных в запросе. Каждый метод запроса возвращает перечислимый объект. Поэтому результат выполнения одного метода запроса можно использовать для вызова другого, соединяя эти методы в цепочку.

Метод Join() принимает четыре аргумента. Первый аргумент (inner) представляет собой ссылку на вторую объединяемую последовательность, а первой является последовательность, для которой вызывается метод Join(). Селектор ключа для первой последовательности передается в качестве аргумента outerKeySelector, а селектор ключа для второй последовательности – в качестве аргумента innerKeySelector. Результат объединения обозначается как аргумент resultSelector. Аргумент outerKeySelector имеет тип Func, аргумент innerKeySelector – тип Func, тогда как аргумент resultSelector – тип Func, где TOuter – тип элемента из вызывающей последовательности; Tinner – тип элемента из передаваемой последовательности; TResult – тип элемента из объединяемой в итоге последовательности, возвращаемой в виде перечислимого объекта.

Аргумент метода запроса представляет собой метод, совместимый с указываемой формой делегата Funс, но он не обязательно должен быть явно объявляемым методом. На самом деле вместо него чаще всего используется лямбда-выражение. Как пояснялось в главе 15, лямбда-выражение обеспечивает более простой, но эффективный способ определения того, что, по существу, является анонимным методом, а компилятор C# автоматически преобразует лямбда-выражение в форму, которая может быть передана в качестве параметра делегату Funс. Благодаря тому что лямбда-выражения обеспечивают более простой и рациональный способ программирования, они используются во всех примерах, представленных далее в этом разделе.


Формирование запросов с помощью методов запроса

Используя методы запроса одновременно с лямбда-выражениями, можно формировать запросы, вообще не пользуясь синтаксисом, предусмотренным в C# для запросов. Вместо этого достаточно вызвать соответствующие методы запроса. Обратимся сначала к простому примеру. Он представляет собой вариант первого примера программы из этой главы, переделанный с целью продемонстрировать применение методов запроса Where() и Select() вместо соответствующих операторов.

// Использовать методы запроса для формирования простого запроса.

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

using System;

using System.Linq;

class SimpQuery {

  static void Main() {

    int[] nums = { 1, -2, 3, 0, -4, 5 };

    // Использовать методы Where() и Select() для

    // формирования простого запроса.

    var posNums = nums.Where(n => n > 0).Select(r => r);

    Console.Write("Положительные значения из массива nums: ");

    // Выполнить запрос и вывести его результаты,

    foreach(int i in posNums) Console.Write(i + " ") ;

    Console.WriteLine();

  }

}

Эта версия программы дает такой же результат, как и исходная.

Положительные значения из массива nums: 1 3 5

Обратите особое внимание в данной программе на следующую строку кода.

var posNums = nums.Where(n => n > 0).Select(r => r);

В этой строке кода формируется запрос, сохраняемый в переменной posNums. По этому запросу, в свою очередь, формируется последовательность положительных значений, извлекаемых из массива nums. Для этой цели служит метод Where(), отбирающий запрашиваемые значения, а также метод Select(), избирательно формирующий из этих значений окончательный результат. Метод Where() может быть вызван для массива nums, поскольку во всех массивах реализуется интерфейс IEnumerable, поддерживающий методы расширения запроса.

Формально метод Select() в рассматриваемом здесь примере не нужен, поскольку это простой запрос. Ведь последовательность, возвращаемая методом Where(), уже содержит конечный результат. Но окончательный выбор можно сделать и по более сложному критерию, как это было показано ранее на примерах использования синтаксиса запросов. Так, по приведенному ниже запросу из массива nums возвращаются положительные значения, увеличенные на порядок величины.

var posNums = nums.Where(n => n > 0) .Select (r => r * 10);

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

var posNums = nums.Where(n => n > 0).OrderByDescending(j => j);

где выражение j => j обозначает, что упорядочение зависит от входного параметра, который является элементом данных из последовательности, получаемой из метода Where().

В приведенном ниже примере демонстрируется применение метода запроса GroupBy(). Это измененный вариант представленного ранее примера.

// Продемонстрировать применение метода запроса GroupBy().

// Это переработанный вариант примера, представленного ранее

// для демонстрации синтаксиса запросов.

using System;

using System.Linq;

class GroupByDemo {

  static void Main() {

    string[] websites = {

      «hsNameA.com», «hsNameB.net», «hsNameC.net»,

      «hsNameD.com», «hsNameE.org», «hsNameF.org»,

      «hsNameG.tv», «hsNameH.net», «hsNamel.tv»

    };

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

    // веб-сайтов по имени домена самого верхнего уровня.

    var webAddrs = websites.Where(w => w.LastIndexOf('.') != 1).

          GroupBy(x => x.Substring(x.LastIndexOf(".", x.Length)));

    // Выполнить запрос и вывести его результаты, foreach(var sites in webAddrs) {

    foreach (var sites in webAddrs) {

      Console.WriteLine("Веб-сайты, сгруппированные " +

           "по имени домена " + sites.Key);

      foreach (var site in sites)

        Console.WriteLine(" " + site);

      Console.WriteLine();

    }

  }

}

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

Рассмотрим другой пример. Но сначала приведем еще раз запрос из представленного ранее примера применения оператора join.

var inStockList = from item in items

  join entry in statusList

    on item.ItemNumber equals entry.ItemNumber

  select new Temp(item.Name, entry.InStock);

По этому запросу формируется последовательность, состоящая из объектов, инкапсулирующих наименование товара и состояние его запасов на складе. Вся эта информация получается путем объединения двух источников данных: items и statusList. Ниже приведен переделанный вариант данного запроса, в котором вместо синтаксиса, предусмотренного в C# для запросов, используется метод запроса Join().

// Использовать метод запроса Join() для составления списка

// наименований товаров и состояния их запасов на складе,

var inStockList = items.Join(statusList,

                             kl => kl.ItemNumber,

                             k2 => k2.ItemNumber,

                        (kl, k2) => new Temp(kl.Name, k2.InStock) );

В данном варианте именованный класс Temp используется для хранения результирующего объекта, но вместо него можно воспользоваться анонимным типом. Такой вариант запроса приведен ниже.

var inStockList = items.Join(statusList,

                            kl => kl.ItemNumber,

                            k2 => k2.ItemNumber,

                        (kl, k2) => new { kl.Name, k2.InStock} );


Синтаксис запросов и методы запроса

Как пояснялось в предыдущем разделе, запросы в C# можно формировать двумя способами, используя синтаксис запросов или методы запроса. Любопытно, что оба способа связаны друг с другом более тесно, чем кажется, глядя на исходный код программы. Дело в том, что синтаксис запросов компилируется в вызовы методов запроса. Поэтому код

where х < 10

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

Where(х => х < 10)

Таким образом, оба способа формирования запросов в конечном итоге сходятся на одном и том же.

Но если оба способа оказываются в конечном счете равноценными, то какой из них лучше для программирования на С#? В целом, рекомендуется чаще пользоваться синтаксисом запросов, поскольку он полностью интегрирован в язык С#, поддерживается соответствующими ключевыми словами и синтаксическим конструкциями.


Дополнительные методы расширения, связанные с запросами

Помимо методов, соответствующих операторам запроса, поддерживаемым в С#, име-ется ряд других методов расширения, связанных с запросами и зачастую оказывающих помощь в формировании запросов. Эти методы предоставляются в среде .NET Framework и определены для интерфейса IEnumerable в классе Enumerable. Ниже приведены наиболее часто используемые методы расширения, связанные с запросами. Многие из них могут перегружаться, поэтому они представлены лишь в самой общей форме.

Метод             Описание

All(predicate)    Возвращает логическое значение true,

                 если все элементы в последовательности

                 удовлетворяют условию, задаваемому параметром

                 predicate

Any(predicate)   Возвращает логическое значение true, если

                 любой элемент в последовательности удовлетворяет

                 условию, задаваемому параметром predicate

Average()        Возвращает среднее всех значений в числовой

                 последовательности

Contains(value)  Возвращает логическое значение true, если

                 в последовательности содержится указанный объект

Count()          Возвращает длину последовательности, т.е.

                 количество составляющих ее элементов

First()          Возвращает первый элемент в последовательности

Last()           Возвращает последний элемент в последовательности

Max()            Возвращает максимальное значение в

                 последовательности

Min()             Возвращает минимальное значение в

                 последовательности

Sum()            Возвращает сумму значений в числовой

                 последовательности

Метод Count() уже демонстрировался ранее в этой главе. А в следующей программе демонстрируются остальные методы расширения, связанные с запросами.

// Использовать ряд методов расширения, определенных в классе Enumerable.

using System;

using System.Linq;

class ExtMethods {

  static void Main() {

    int[] nums = { 3, 1, 2, 5, 4 };

    Console.WriteLine("Минимальное значение равно " + nums.Min());

    Console.WriteLine("Максимальное значение равно " + nums.Max());

    Console.WriteLine("Первое значение равно " + nums.First());

    Console.WriteLine("Последнее значение равно " + nums.Last());

    Console.WriteLine("Суммарное значение равно " + nums.Sum());

    Console.WriteLine("Среднее значение равно " + nums.Average());

    if(nums.All(n => n > 0))

      Console.WriteLine(«Все значения больше нуля.»);

    if(nums.Any(n => (n % 2) == 0))

      Console.WriteLine(«По крайней мере одно значение является четным.»);

    if(nums.Contains(3))

      Console.WriteLine(«Массив содержит значение 3.»);

  }

}

Вот к какому результату приводит выполнение этой программы.

Минимальное значение равно 1

Максимальное значение равно 5

Первое значение равно 3

Последнее значение равно 4

Суммарное значение равно 15

Среднее значение равно 3

Все значения больше нуля.

По крайней мере одно значение является четным

Массив содержит значение 3.

Методы расширения, связанные с запросами, можно также использовать в самом запросе, основываясь на синтаксисе запросов, предусмотренном в С#. И в действительности это делается очень часто. Например, метод Average() используется в приведенной ниже программе для получения последовательности, состоящей только из тех значений, которые оказываются меньше среднего всех значений в массиве.

// Использовать метод Average() вместе с синтаксисом запросов.

using System;

using System.Linq;

class ExtMethods2 {

  static void Main() {

    int[] nums = { 1, 2, 4, 8, 6, 9, 10, 3, 6, 7 };

    var ItAvg = from n in nums

        let x = nums.Average()

        where n < x

        select n;

    Console.WriteLine("Среднее значение равно " + nums.Average());

    Console.Write("Значения меньше среднего: ");

    // Выполнить запрос и вывести его результаты,

    foreach(int i in ItAvg) Console.Write(i + " ");

    Console.WriteLine();

  }

}

При выполнении этой программы получается следующий результат.

Среднее значение равно 5.6

Значения меньше среднего: 1 2 4 3

Обратите особое внимание в этой программе на следующий код запроса.

var ItAvg = from n in nums

    let x = nums.Average()

    where n < x

    select n;

Как видите, переменной x в операторе let присваивается среднее всех значений в массиве nums. Это значение получается в результате вызова метода Average() для массива nums.


    Ваша оценка произведения:

Популярные книги за неделю