Jest wiele do powiedzenia na ten temat. Pozwól mi skupić się na AsEnumerablei AsQueryablei wzmianka ToList()po drodze.
Co robią te metody?
AsEnumerablei odpowiednio AsQueryableobsadzić lub przekonwertować na IEnumerablelub IQueryable. Mówię obsadę lub konwersję bez podania przyczyny:
Gdy obiekt źródłowy już implementuje interfejs docelowy, sam obiekt źródłowy jest zwracany, ale rzutowany na interfejs docelowy. Innymi słowy: typ nie jest zmieniany, ale typem kompilacji jest.
Gdy obiekt źródłowy nie implementuje interfejsu docelowego, obiekt źródłowy jest konwertowany na obiekt, który implementuje interfejs docelowy. Tak więc zarówno typ, jak i typ kompilacji są zmieniane.
Pokażę to na kilku przykładach. Mam tę małą metodę, która zgłasza typ czasu kompilacji i rzeczywisty typ obiektu ( dzięki uprzejmości Jona Skeeta ):
void ReportTypeProperties<T>(T obj)
{
Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}
Spróbujmy dowolnego linq-to-sql Table<T>, który implementuje IQueryable:
ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());
Wynik:
Compile-time type: Table`1
Actual type: Table`1
Compile-time type: IEnumerable`1
Actual type: Table`1
Compile-time type: IQueryable`1
Actual type: Table`1
Widzisz, że sama klasa tabeli jest zawsze zwracana, ale jej reprezentacja zmienia się.
Teraz obiekt, który implementuje IEnumerable, a nie IQueryable:
var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());
Wyniki:
Compile-time type: Int32[]
Actual type: Int32[]
Compile-time type: IEnumerable`1
Actual type: Int32[]
Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1
Tu jest. AsQueryable()przekształcił tablicę w obiekt EnumerableQuery, który „reprezentuje IEnumerable<T>kolekcję jako IQueryable<T>źródło danych”. (MSDN).
Jakie jest zastosowanie?
AsEnumerablejest często używany do przełączania z dowolnej IQueryableimplementacji na LINQ na obiekty (L2O), głównie dlatego, że ta pierwsza nie obsługuje funkcji, które ma L2O. Aby uzyskać więcej informacji, zobacz Jaki jest wpływ AsEnumerable () na jednostkę LINQ? .
Na przykład w zapytaniu Entity Framework możemy użyć tylko ograniczonej liczby metod. Więc jeśli na przykład musimy użyć jednej z naszych własnych metod w zapytaniu, zwykle napisalibyśmy coś takiego
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList - który zamienia an IEnumerable<T>na a List<T>- jest również często używany do tego celu. Zaletą używania AsEnumerablevs. ToListjest to, że AsEnumerablenie wykonuje zapytania. AsEnumerablezachowuje odroczone wykonanie i nie tworzy często bezużytecznej listy pośredniej.
Z drugiej strony, gdy pożądane jest wymuszone wykonanie zapytania LINQ, ToListmożna to zrobić.
AsQueryablemoże służyć do tego, aby kolekcja wymienna akceptowała wyrażenia w instrukcjach LINQ. Zobacz więcej szczegółów: Czy naprawdę potrzebuję używać AsQueryable () w kolekcji? .
Uwaga na temat nadużywania substancji!
AsEnumerabledziała jak narkotyk. Jest to szybka poprawka, ale kosztuje i nie rozwiązuje podstawowego problemu.
W wielu odpowiedziach na przepełnienie stosu widzę, że ludzie starają AsEnumerablesię naprawić prawie każdy problem z nieobsługiwanymi metodami w wyrażeniach LINQ. Ale cena nie zawsze jest jasna. Na przykład, jeśli to zrobisz:
context.MyLongWideTable // A table with many records and columns
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate })
... wszystko jest starannie przetłumaczone na instrukcję SQL, która filtruje ( Where) i projekty ( Select). Oznacza to, że zarówno długość, jak i szerokość zestawu wyników SQL są odpowiednio zmniejszone.
Załóżmy teraz, że użytkownicy chcą widzieć tylko datę CreateDate. W Entity Framework szybko odkryjesz, że ...
.Select(x => new { x.Name, x.CreateDate.Date })
... nie jest obsługiwany (w momencie pisania). Ach, na szczęście jest AsEnumerablepoprawka:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate.Date })
Pewnie, pewnie działa. Ale wciąga całą tabelę do pamięci, a następnie stosuje filtr i projekcje. Cóż, większość ludzi jest wystarczająco inteligentna, aby zrobić Wherepierwsze:
context.MyLongWideTable
.Where(x => x.Type == "type").AsEnumerable()
.Select(x => new { x.Name, x.CreateDate.Date })
Ale nadal wszystkie kolumny są pobierane jako pierwsze, a projekcja odbywa się w pamięci.
Prawdziwa poprawka to:
context.MyLongWideTable
.Where(x => x.Type == "type")
.Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })
(Ale to wymaga trochę więcej wiedzy ...)
Czego NIE robią te metody?
Przywróć możliwości IQueryable
Teraz ważne zastrzeżenie. Kiedy to zrobisz
context.Observations.AsEnumerable()
.AsQueryable()
skończysz z obiektem źródłowym reprezentowanym jako IQueryable. (Ponieważ obie metody tylko rzutują i nie konwertują).
Ale kiedy to zrobisz
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
jaki będzie wynik?
SelectProdukuje WhereSelectEnumerableIterator. Jest to wewnętrzna klasa .Net, która implementuje IEnumerable, a nieIQueryable . Tak więc nastąpiła konwersja na inny typ i kolejne AsQueryablenigdy nie mogą już zwrócić oryginalnego źródła.
Konsekwencją tego jest, że używanie AsQueryableto nie sposób magicznie wstrzyknąć dostawca zapytania z jego specyficznymi cechami w produkt przeliczalny. Załóżmy, że tak
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
Miejsce, w którym warunek nigdy nie zostanie przetłumaczony na SQL. AsEnumerable()następnie instrukcje LINQ definitywnie przerywają połączenie z dostawcą zapytań w ramach encji.
Celowo pokazuję ten przykład, ponieważ widziałem tutaj pytania, w których ludzie na przykład próbują „wprowadzić” Includefunkcje do kolekcji, dzwoniąc AsQueryable. Kompiluje się i działa, ale nic nie robi, ponieważ obiekt bazowy nie ma Includejuż implementacji.
Wykonać
Zarówno AsQueryablei AsEnumerablenie wykonują (lub enumerate ) obiekt źródłowy. Zmieniają tylko swój typ lub reprezentację. Oba dotyczyły interfejsów IQueryablei IEnumerablesą niczym innym jak „wyliczeniem, które się czeka”. Nie są one wykonywane, dopóki nie zostaną do tego zmuszone, na przykład, jak wspomniano powyżej, przez telefon ToList().
Oznacza to, że Wykonywanie IEnumerableuzyskać dzwoniąc AsEnumerablena IQueryableobiekcie, wykona instrumentu bazowego IQueryable. Kolejne wykonanie IEnumerabletestamentu ponownie wykona IQueryable. Co może być bardzo drogie.
Konkretne implementacje
Do tej pory było to tylko o Queryable.AsQueryablei Enumerable.AsEnumerablemetod rozszerzenie. Ale oczywiście każdy może pisać metody instancji lub metody rozszerzeń o tych samych nazwach (i funkcjach).
W rzeczywistości powszechnym przykładem konkretnej AsEnumerablemetody rozszerzenia jest DataTableExtensions.AsEnumerable. DataTablenie implementuje IQueryablelub IEnumerable, więc standardowe metody rozszerzenia nie mają zastosowania.