Хотя в приведенной программе относительно легко выяснить, что результирующий набор может быть интерпретирован как перечисление объектов string (например, IEnumerable), тот факт, что подмножество на самом деле имеет тип OrderedEnumerable, не настолько ясен.

Поскольку результирующие наборы LINQ могут быть представлены с применением порядочного количества типов из разнообразных пространств имен LINQ, было бы утомительно определять подходящий тип для хранения результирующего набора. Причина в том, что во многих случаях лежащий в основе тип не очевиден и даже напрямую не доступен в коде (и как вы увидите, в ряде ситуаций тип генерируется на этапе компиляции).

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

static void QueryOverInts()

{

  int[] numbers = {10, 20, 30, 40, 1, 2, 3, 8};

  // Вывести только элементы меньше 10.

  IEnumerable subset = from i in numbers where i < 10 select i;

  foreach (int i in subset)

  {

    Console.WriteLine("Item: {0}", i);

  }

  ReflectOverQueryResults(subset);

}

В рассматриваемом случае переменная subset имеет совершенно другой внутренний тип. На этот раз тип, реализующий интерфейс IEnumerable, представляет собой низкоуровневый класс по имени WhereArrayIterator:

Item: 1

Item: 2

Item: 3

Item: 8

***** Info about your query *****

resultSet is of type: WhereArrayIterator`1

resultSet location: System.Linq

Учитывая, что точный тип запроса LINQ не вполне очевиден, в первых примерах результаты запросов были представлены как переменная IEnumerable, где Т — тип данных в возвращенной последовательности (string, int и т.д.). Тем не менее, ситуация по-прежнему довольно запутана. Чтобы еще больше все усложнить, стоит упомянуть, что поскольку интерфейс IEnumerable расширяет необобщенный IEnumerable, получать результат запроса LINQ допускается и так:

System.Collections.IEnumerable subset =

  from i in numbers

  where i < 10

  select i;

К счастью, неявная типизация при работе с запросами LINQ значительно проясняет картину:

static void QueryOverInts()

{

  int[] numbers = {10, 20, 30, 40, 1, 2, 3, 8};

  // Здесь используется неявная типизация...

  var subset = from i in numbers where i < 10 select i;

  // ...и здесь тоже.

  foreach (var i in subset)

  {

    Console.WriteLine("Item: {0} ", i);

  }

  ReflectOverQueryResults(subset);

}

В качестве эмпирического правила: при захвате результатов запроса LINQ всегда необходимо использовать неявную типизацию. Однако помните, что (в большинстве случаев) действительное возвращаемое значение имеет тип, реализующий интерфейс IEnumerable.

Какой точно тип кроется за ним (OrderedEnumerable, WhereArrayIterator и т.п.), к делу не относится, и определять его вовсе не обязательно. Как было показано в предыдущем примере кода, для прохода по извлеченным данным можно просто применить ключевое слово var внутри конструкции foreach.

<p id="AutBody_Root491">LINQ и расширяющие методы</p>

Несмотря на то что в текущем примере совершенно не требуется напрямую писать какие-то расширяющие методы, на самом деле они благополучно используются на заднем плане. Выражения запросов LINQ могут применяться для прохода по содержимому контейнеров данных, которые реализуют обобщенный интерфейс IEnumerable. Тем не менее, класс System.Array (используемый для представления массива строк и массива целых чисел) не реализует этот контракт:

// Похоже, что тип System.Array не реализует

// корректную инфраструктуру для выражений запросов!

public abstract class Array : ICloneable, IList,

  IStructuralComparable, IStructuralEquatable

{

  ...

}

Хотя класс System.Array не реализует напрямую интерфейс IEnumerable, он косвенно получает необходимую функциональность данного типа (а также многие другие члены, связанные с LINQ) через статический тип класса System.Linq.Enumerable.

Перейти на страницу:

Похожие книги