Зачем использовать .AsEnumerable () вместо приведения к IEnumerable <T>? - PullRequest
62 голосов
/ 06 января 2010

Один из методов расширения на IEnumerable<T> - .AsEnumerable(). Этот метод преобразует перечисляемый объект, для которого он был вызван, в экземпляр IEnumerable<T>. Однако, поскольку объект должен реализовать IEnumerable<T> для применения к этому методу расширения, преобразование в IEnumerable<T> является простым вопросом приведения к IEnumerable<T>. У меня вопрос, почему этот метод вообще существует?

Пример:

List<string> strings = new List<string>() { "test", "test2", "test3" };
IEnumerable<string> stringsEnum1 = strings.AsEnumerable();
IEnumerable<string> stringsEnum2 = (IEnumerable<string>)strings;

В приведенном выше примере stringsEnum1 и stringsEnum2 эквивалентны. Какой смысл в методе расширения?

Редактировать : Как следствие, почему метод .AsQueryable() при приведении к IQueryable<T> эквивалентен?

Ответы [ 7 ]

81 голосов
/ 06 января 2010

Читаемость является главной проблемой здесь. Считайте, что

Table.AsEnumerable().Where(somePredicate)

гораздо лучше читается, чем

((IEnumerable<TableObject>)Table).Where(somePredicate).

Или представьте, что хотите выполнить часть запроса на SQL Server, а остальную часть в памяти:

Table.Where(somePredicate)
     .Select(someProjection)
     .AsEnumerable()
     .SomethingElse()

против

((IEnumerable<SomeProjectionType>)Table.Where(somePredicate)
                                       .Select(someProjection))
                                       .SomethingElse()

Теперь о том, почему такой метод полезен вообще, вспомним пример Table в LINQ to SQL DataContext. Поскольку Table является IQueryable, он реализует IEnumerable. Когда вы вызываете метод Where для такого Table и перечисляете результаты, выполняется код, который в конечном итоге приводит к выполнению оператора SQL на SQL Server. AsEnumerable говорит: нет, я не хочу использовать поставщик LINQ to SQL для выполнения Where, я хочу использовать реализацию LINQ to Objects Where.

Таким образом, перечисление более

Table.Where(somePredicate)

вызывает выполнение запроса на SQL Server, а перечисление более

Table.AsEnumerable().Where(somePredicate)

переносит таблицу, представленную Table, в память и выполняет функцию Where в памяти (а не на SQL Server!)

В этом смысл AsEnumerable: позволить вам скрыть конкретную реализацию IEnumerable методов и вместо этого использовать стандартную реализацию.

15 голосов
/ 06 января 2010

Я подумал о причине, кроме читабельности, хотя и связанной с реализацией запросов: использование Linq to Objects для анонимных типов, возвращаемых через другого поставщика Linq. Вы не можете привести к анонимному типу (или к коллекции анонимных типов), но вы можете использовать .AsEnumerable(), чтобы выполнить приведение за вас.

Пример:

// Get an IQueryable of anonymous types.
var query = from p in db.PeopleTable /* Assume Linq to SQL */
            select new { Name = p.Name, Age = p.Age };

// Execute the query and pull the results into an IEnumerable of anonymous types
var enum = query.AsEnumerable();

// Use Linq to Objects methods to further refine.
var refined = from p in enum
              select new
              {
                  Name = GetPrettyName(p.Name),
                  DOB = CalculateDOB(p.Age, DateTime.Now)
              };

Очевидно, что причина в том, что мы хотим использовать что-то вроде Linq to SQL, чтобы преобразовать некоторые записи в анонимный тип, а затем выполнить некоторую пользовательскую логику (что было бы невозможно через Linq to SQL), используя Linq to Objects on на стороне клиента.

Приведение к IEnumerable<_anon> невозможно, поэтому .AsEnumerable() - единственный путь.

Спасибо всем, кто ответил, чтобы помочь мне собрать это воедино. =)

4 голосов
/ 06 января 2017

Пока я читаю книгу C# 6.0 in a Nutshell. Ниже приведен пример AsEnumerable в книге.


Цель состоит в том, чтобы привести последовательность IQueryable<T> к IEnumerable<T>, заставляя последующие операторы запроса связываться с операторами Enumerable вместо операторов Queryable. Это заставляет остальную часть запроса выполнить локально .

В качестве иллюстрации предположим, что у нас была таблица MedicalArticles в SQL Server, и мы хотели использовать LINQ to SQL или EF для получения всех статей по гриппу, аннотация которых содержала менее 100 слов. Для последнего предиката нам нужно регулярное выражение:

Regex wordCounter = new Regex (@"\b(\w|[-'])+\b");

var query = dataContext.MedicalArticles
            .Where (article => article.Topic == "influenza" &&
            wordCounter.Matches (article.Abstract).Count < 100);

Проблема в том, что SQL Server не поддерживает регулярные выражения, поэтому поставщики LINQ-to-db будут выдавать исключение, жалуясь на то, что запрос не может быть переведен в SQL. Мы можем решить эту проблему, выполнив запрос в два этапа: сначала получим все статьи по гриппу с помощью запроса LINQ to SQL, а затем отфильтруем локально по рефератам менее 100 слов:

Regex wordCounter = new Regex (@"\b(\w|[-'])+\b");

IEnumerable<MedicalArticle> sqlQuery = dataContext.MedicalArticles
    .Where (article => article.Topic == "influenza");

IEnumerable<MedicalArticle> localQuery = sqlQuery
    .Where (article => wordCounter.Matches (article.Abstract).Count < 100);

С помощью AsEnumerable мы можем сделать то же самое в одном запросе:

var query = dataContext.MedicalArticles
      .Where (article => article.Topic == "influenza")
      .AsEnumerable()
      .Where (article => wordCounter.Matches (article.Abstract).Count < 100);

Альтернативой вызову AsEnumerable является вызов ToArray или ToList. Преимущество AsEnumerable заключается в том, что он не вызывает немедленное выполнение запроса и не создает никакой структуры хранения.

3 голосов
/ 24 мая 2011

Если в объекте есть метод с тем же именем, что и у метода расширения Linq, он скрывает метод расширения. Использование AsEnumerable позволяет получить расширение.

Это кажется новым в SP1.

Вчера у меня была строка кода, которая извлекала идентификаторы членов из таблицы данных: -

var lMmIds = new List<int>(
    lDmMember.DataTable.Select(R => R.MmId)
);

, который работал очень хорошо, пока я не установил SP1. Теперь это не сработает, если не прочитать

var lMmIds = new List<int>(
    lDmMember.DataTable.AsEnumerable().Select(R => (int)((dsMtables.tbMMemberRow)R).MmId)
);

Редактировать: я нашел реальную причину

Это так, что вы можете использовать как удаленные методы (например, WHERE в операторе SQL), так и локальные методы в одном и том же операторе linq. Без использования AsEnumerable (т.е. просто приведения) он заставит генератор запросов попытаться создать дерево выражений для удаленного выполнения, содержащее локальный метод. Помещение AsEnumerable в запрос приведет к тому, что остальная часть этого запроса будет выполняться локально по результатам удаленного запроса.

С https://msdn.microsoft.com/en-us/library/bb335435(v=vs.110).aspx

Тип таблицы, представляющий таблицу базы данных, может иметь метод Where, который принимает аргумент предиката в качестве дерева выражений и преобразует дерево в SQL для удаленного выполнения. Если удаленное выполнение нежелательно, например, потому что предикат вызывает локальный метод, метод AsEnumerable можно использовать для скрытия пользовательских методов и вместо этого сделать стандартные операторы запросов доступными.

3 голосов
/ 14 сентября 2010

Анонимные типы являются основной причиной предоставления таких методов расширения. (вы не можете использовать анонимные типы в параметрах обобщений) Но вызов метода может использовать вывод типа, что позволяет вам не указывать тип в общих параметрах.

3 голосов
/ 06 января 2010

Это просто лучший и самый короткий способ приведения в IEnumerable. Если вы посмотрите на него в Reflector, вы увидите, что он ничего не делает, кроме как возвращает объект как IEnumerable.

Из MSDN:

AsEnumerable (Of TSource) (IEnumerable (Of TSource)) метод не имеет никакого эффекта, кроме как изменить тип источника во время компиляции от типа, который реализует От IEnumerable (Of T) к IEnumerable (Of T) сам по себе.

2 голосов
/ 06 января 2010

Как вы говорите, если тип уже реализует IEnumerable<T>, то на самом деле нет никакой функциональной разницы между приведением к интерфейсу или вызовом метода AsEnumerable.

Мое предположение, и это только предположение, заключается в том, что вызов AsEnumerable улучшает читабельность и сохраняет свободную подпись других методов расширения LINQ:

var query = ((IEnumerable<YourType>)yourCollection).Select(x => x.YourProperty);

// vs

var query = yourCollection.AsEnumerable().Select(x => x.YourProperty);

Это также позволяет типам, которые не реализуют IEnumerable<T> - , например, DataTable - иметь собственную версию расширения AsEnumerable. Это позволяет вам продолжать использовать один и тот же шаблон в запросах к этим типам - даже если это другой метод AsEnumerable, который вы вызываете - без необходимости беспокоиться о том, действительно ли тип реализует IEnumerable<T>.

...