Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00151 005582 14269044 na godz. na dobę w sumie
LINQ to Objects w C# 4.0 - książka
LINQ to Objects w C# 4.0 - książka
Autor: Liczba stron: 288
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-246-3609-9 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> c# - programowanie
Porównaj ceny (książka, ebook, audiobook).

Wygodne operacje na danych!

Współczesne systemy wymagają niezwykłej elastyczności, a przy tym muszą powstawać szybko. Klienci nigdy nie mają wystarczająco dużo czasu, żeby spokojnie poczekać na opracowywane rozwiązanie, a do tego wpadają na różne pomysły - w tym zmiany źródeł danych. LINQ to technologia, która pozwala na łatwe pobieranie danych z różnych źródeł dzięki wykonywaniu zapytań podobnych do zapytań SQL. Brzmi dobrze? Tak samo działa!

Ta książka porusza zagadnienia związane z jedną z gałęzi LINQ - LINQ to Objects. W trakcie lektury nauczysz się pisać podstawowe zapytania LINQ, filtrować wyniki, zwracać zestawy pogrupowanych obiektów oraz pobierać tylko unikalne wyniki. Ponadto dowiesz się, jak wykorzystać złączenia oraz korzystać z wbudowanych operatorów. Jako że jest to wyjątkowe kompendium wiedzy na temat LINQ to Objects, znajdziesz tu również szczegółowe informacje na temat tworzenia rozszerzeń do LINQ oraz opis zagadnień związanych z przetwarzaniem równoległym. Jeżeli zajmujesz się programowaniem w C# i chcesz skorzystać z nowoczesnych technologii, zainteresuj się tą książką!

Włącz najlepsze wzorce LINQ to Objects do Twojego codziennego programowania!

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Tytuł oryginału: LINQ to Objects Using C# 4.0: Using and Extending LINQ to Objects and Parallel LINQ (PLINQ) Tłumaczenie: Łukasz Schmidt ISBN: 978-83-246-3609-9 Authorized translation from the English language edition, entitled: LINQ TO OBJECTS USING C# 4.0: USING AND EXTENDING LINQ TO OBJECTS AND PARALLEL LINQ (PLINQ); ISBN 0321637003; by Troy Magennis, published by Pearson Education, Inc, publishing as Addison Wesley. Copyright © 2010 by Pearson Education, Inc. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education Inc. Polish language edition published by HELION S.A.. Copyright © 2012. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: helion@helion.pl WWW: http://helion.pl (księgarnia internetowa, katalog książek) Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/linobj.zip Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/linobj Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Printed in Poland. • Kup książkę • Poleć książkę • Oceń książkę • Księgarnia internetowa • Lubię to! » Nasza społeczność SPIS TRE¥CI Przedmowa .................................................................................................................................. 9 WstÚp ......................................................................................................................................... 11 Kto powinien przeczytać tę książkę? ............................................................................................................... 12 Streszczenie książki ............................................................................................................................................ 12 Stałe elementy książki ........................................................................................................................................ 14 Pobieranie przykładowych kodów dla tej książki .......................................................................................... 15 Wybór języka ...................................................................................................................................................... 15 Wymagania systemowe ..................................................................................................................................... 15 PodziÚkowania ........................................................................................................................... 17 O autorze ................................................................................................................................... 19 ROZDZIA’ 1. Wprowadzenie do LINQ .................................................................................... 21 Co to jest LINQ? ................................................................................................................................................. 21 Aktualna (niemal) historia LINQ .................................................................................................................... 23 Modyfikacje kodu z LINQ — przykłady „przed” i „po” ............................................................................... 25 Korzyści z użycia LINQ ..................................................................................................................................... 31 Podsumowanie ................................................................................................................................................... 33 Źródła ................................................................................................................................................................... 34 ROZDZIA’ 2. Wprowadzenie do LINQ to Objects .................................................................. 35 Usprawnienia języka C# 3.0 umożliwiające pracę z LINQ .......................................................................... 35 LINQ to Objects — pięciominutowy opis ...................................................................................................... 46 Podsumowanie ................................................................................................................................................... 52 Źródła ................................................................................................................................................................... 53 ROZDZIA’ 3. Pisanie podstawowych zapytañ ......................................................................... 55 Opcje stylów składni zapytań ........................................................................................................................... 55 Jak filtrować wyniki (wyrażenie Where) ........................................................................................................ 62 Jak zmienić typ zwracany (projekcja wybierania) ......................................................................................... 66 Jak zwracać elementy, kiedy wynik jest sekwencją (SelectMany) ............................................................... 70 W jaki sposób otrzymać wartości indeksu wyników .................................................................................... 72 Jak usuwać powielone wyniki? ......................................................................................................................... 73 Jak sortować wyniki ........................................................................................................................................... 73 Podsumowanie ................................................................................................................................................... 81 6 Spis treĂci ROZDZIA’ 4. Grupowanie i ïÈczenie danych ........................................................................... 83 Jak grupować elementy ..................................................................................................................................... 83 Jak łączyć dane z inną sekwencją ..................................................................................................................... 98 Podsumowanie ................................................................................................................................................. 121 ROZDZIA’ 5. Standardowe operatory zapytañ ..................................................................... 123 Operatory wbudowane .................................................................................................................................... 123 Operatory agregacji — praca z liczbami ....................................................................................................... 124 Operatory konwersji — zmiana typów ......................................................................................................... 131 Operatory elementów ...................................................................................................................................... 141 Operator równości — SequenceEqual .......................................................................................................... 149 Operatory generujące — generowanie sekwencji danych ......................................................................... 150 Operatory scalania ........................................................................................................................................... 154 Operatory partycjonowania — pomijanie i pobieranie elementów ......................................................... 155 Operatory kwantyfikacji — All, Any i Contains ......................................................................................... 158 Podsumowanie ................................................................................................................................................. 164 ROZDZIA’ 6. Praca ze zbiorami danych ................................................................................ 165 Wprowadzenie ................................................................................................................................................. 165 Operatory zbiorów w LINQ ........................................................................................................................... 166 Klasa HashSet T ............................................................................................................................................ 175 Podsumowanie ................................................................................................................................................. 181 ROZDZIA’ 7. Tworzenie rozszerzeñ LINQ to Objects ......................................................... 183 Pisanie nowego operatora zapytania ............................................................................................................. 183 Pisanie operatora pojedynczego elementu ................................................................................................... 184 Pisanie operatora sekwencji ........................................................................................................................... 195 Pisanie operatora agregacji ............................................................................................................................. 201 Pisanie operatora grupującego ....................................................................................................................... 206 Podsumowanie ................................................................................................................................................. 215 ROZDZIA’ 8. Nowe funkcje w C# 4.0 ................................................................................... 217 Ewolucja C# ...................................................................................................................................................... 217 Parametry opcjonalne i argumenty nazwane .............................................................................................. 218 Dynamiczne określanie typów ....................................................................................................................... 225 COM-Interop i LINQ ...................................................................................................................................... 232 Podsumowanie ................................................................................................................................................. 240 Źródła ................................................................................................................................................................ 240 Spis treĂci 7 ROZDZIA’ 9. Parallel LINQ to Objects ................................................................................. 241 Źródła programowania równoległego .......................................................................................................... 241 Wielowątkowość a równoległość w kodzie .................................................................................................. 244 Oczekiwania związane z równoległością, przeszkody i bariery ................................................................. 246 Równoległość danych LINQ ........................................................................................................................... 249 Pisanie operatorów Parallel LINQ ................................................................................................................. 265 Podsumowanie ................................................................................................................................................. 275 Źródła ................................................................................................................................................................. 275 Dodatek A Sïowniczek ............................................................................................................ 277 Skorowidz ................................................................................................................................ 281 8 Spis treĂci R O Z D Z I A ’ 3 PISANIE PODSTAWOWYCH ZAPYTA” Cele rozdziaïu: Q Zaprezentowanie opcji składni LINQ. Q Wprowadzenie do pisania podstawowych zapytań. Q Pokazanie, w jaki sposób filtrować, dokonywać projekcji i sortować dane za pomocą zapytań LINQ. Najważniejszym celem tego rozdziału jest przedstawienie podstaw pisania zapytań. W skład tych podstaw wchodzą informacje o opcjach pisania, filtrowaniu danych, sortowaniu i zwracaniu dokładnie takiego zestawu rezultatów, jakiego będziesz potrzebował. Po przeczytaniu tego rozdziału będziesz rozumiał, jak pisać najpopularniejsze elementy za- pytań, a w kolejnym rozdziale rozbudujesz tę wiedzę, poznając bardziej zaawansowane funkcje zapytań służące do grupowania i złączania z innymi źródłami. Opcje stylów skïadni zapytañ Większość z wcześniejszych przykładów w tej książce używała składni wyrażeń zapytań, jednak istnieją dwa style pisania zapytań LINQ. Nie wszystkie operatory są dostępne w składni wyrażeń zapytań wbudowanej w kompilatorze C# i aby użyć pozostałych operato- rów (lub wywoływać własne operatory), konieczne jest użycie składni zapytań w postaci metod rozszerzeń lub — jeśli to konieczne — połączenia obu składni. Będziesz musiał poznać oba style składni zapytań, aby pisać, odczytywać i rozumieć kod napisany za pomocą LINQ. Q Format metody rozszerzenia (zwany także składnią kropkową) — format metody rozszerzenia polega po prostu na zastosowaniu kolejno metod rozszerzeń, z których każda zwraca wynik IEnumerable T , pozwalający kolejnej metodzie „przepłynąć” na poprzednim wyniku itd. (tzw. płynny interfejs). int[] nums = new int[] {0,4,2,6,3,8,3,1}; var result1 = nums.Where(n = n 5).OrderBy (n = n); // lub z podziaáami linii dla wiĊkszej przejrzystoĞci var result2 = nums .Where(n = n 5) .OrderBy (n = n); 56 Rozdziaï 3. Pisanie podstawowych zapytañ Q Format wyrażeń zapytań (preferowany, w szczególności dla złączeń i grup) — mimo że nie wszystkie standardowe operatory są obsługiwane przez składnię wyrażeń zapytań, korzyści w postaci przejrzystego kodu dla obsługiwanych operatorów są bardzo duże. Składnia wyrażeń zapytań jest łatwiejsza od składni metod rozszerzeń, ponieważ upraszcza zapis poprzez usunięcie wyrażeń lambda i wprowadzenie znajomej, zbliżonej do SQL reprezentacji. int[] nums = new int[] {0,4,2,6,3,8,3,1}; var result = (from n in nums where n 5 orderby n select n).Distinct(); Q Zapytania i format składni kropkowej (połączenie dwóch formatów) — ten format łączy składnię wyrażeń zapytań, umieszczoną w nawiasach, za którą następują kolejne operatory zapisane za pomocą składni kropkowej. Tak długo, jak wyrażenie zapytania zwraca IEnumerable T , można za nim umieścić łańcuch metod rozszerzeń. int[] nums = new int[] {0,4,2,6,3,8,3,1}; var result = (from n in nums where n 5 orderby n select n).Distinct(); Której skïadni wyraĝeñ uĝywaÊ? Wybór bÚdzie zaleĝny od osobistych preferencji, jednak celem jest uĝywanie skïadni, która jest najïatwiejsza do odczytania i pomoĝe programistom, którzy mogÈ póěniej pracowaÊ z kodem, zrozumieÊ Twoje intencje. MajÈc to na uwadze, staraj siÚ nie mieszaÊ niepotrzebnie róĝnych skïadni w jednym zapytaniu; wymieszanie stylów powoduje, ĝe zapytanie trudniej odczytaÊ, a czytajÈcy musi liczyÊ nawiasy, aby ustaliÊ, do której czÚĂci zapytania ma zastosowanie skïadnia metody rozszerzenia. JeĂli juĝ ïÈczysz style, umieszczaj je obok siebie, na przykïad uĝywaj wyraĝenia zapytania na poczÈtku w nawiasach, nastÚpnie metod rozszerzeñ na koñcu, dla tych operatorów, dla których sÈ potrzebne (tak jak pokazano we wszystkich przykïadach w tej ksiÈĝce, w których ïÈczenie skïadni byïo konieczne). Ja preferujÚ (byÊ moĝe z tego powodu, ĝe pracowaïem z SQL) uĝycie skïadni wyraĝeñ zapytañ wszÚdzie, gdzie jest to moĝliwe, oraz uĝycie skïadni metod rozszerzeñ, kiedy konieczne jest zastosowanie operatora nieobsïugiwanego przez wyraĝenia zapytañ (np. operatora Distinct), jednak zawsze umieszczam te operatory na koñcu zapytania. Czasem uĝywam wyïÈcznie skïadni metod rozszerzeñ, ale nigdy w przypadku, kiedy zapytanie zawiera operatory Join lub GroupBy. Każda ze składni zapytań ma swoje zalety i wady, które zostaną opisane szczegółowo w kolejnych podrozdziałach. Opcje stylów skïadni zapytañ 57 Skïadnia wyraĝeñ zapytañ Składnia wyrażeń zapytań jest dostępna w wersji języka C# 3.0 i nowszych; sprawia, że zapytania są bardziej czytelne i spójne. Kompilator konwertuje wyrażenie zapytania na składnię metod rozszerzeń w czasie kompilacji, a więc wybór składni opiera się wyłącz- nie na kryterium czytelności. Na rysunku 3.1 pokazano podstawową postać wyrażeń zapytań wbudowanych w C# 3.0. Rysunek 3.1. Podstawowa forma skïadni wyraĝeñ zapytañ. Specyfikacja jÚzyka C# 3.0 opisuje dokïadnie, w jaki sposób ta forma przekïadana jest na metody rozszerzeñ w celu kompilacji Uwaga Fakt, ĝe kolejnoĂÊ sïów kluczowych jest inna niĝ w SQL, nie jest szczÚĂliwy dla znawców SQL. Jednak ta róĝnica wynika z waĝnej przyczyny, która ma uïatwiÊ pracÚ programistom. KolejnoĂÊ From-Where-Select pozwala Ărodowisku programistycznemu (w tym przypadku Visual Studio) zapewniÊ peïne wsparcie Intellisense przy pisaniu zapytania. W chwili, kiedy wpiszesz wyraĝenie from, zostanÈ wyĂwietlone wïaĂciwoĂci danego elementu. To nie byïoby moĝliwe (i nie jest moĝliwe w przypadku narzÚdzi edycyjnych SQL Server), gdyby projektanci C# przyjÚli bardziej znanÈ kolejnoĂÊ sïów kluczowych Select-From-Where. Większość składni wyrażeń zapytań nie wymaga wyjaśniania programistom, którzy mają doświadczenie w pracy z inną składnią zapytań, taką jak SQL. Mimo że kolejność jest inna od tradycyjnych języków zapytań, nazwa każdego słowa kluczowego daje wy- raźną wskazówkę na temat jego działania, z wyjątkiem wyrażeń let i into, które zostaną opisane niżej. Let — tworzenie zmiennej lokalnej Pisanie zapytań może często wymagać mniejszej ilości powielonego kodu, jeśli utworzymy zmienną lokalną, która posłuży do przechowania wyniku obliczenia pomocniczego lub podzapytania. Słowo kluczowe let pozwala przechować wynik wyrażenia (wartość lub podzapytanie) w zakresie zmiennej odpowiadającym pozostałej części pisanego zapytania. Po przypisaniu wartości po raz pierwszy, zmiennej nie można już przypisać innej wartości. 58 Rozdziaï 3. Pisanie podstawowych zapytañ W poniższym kodzie do zmiennej lokalnej o nazwie average przypisywana jest wartość odpowiadająca wartości średniej całej sekwencji źródłowej, obliczanej jedno- krotnie, ale wykorzystywanej w całej projekcji select każdego elementu: var variance = from element in source let average = source.Average() select Math.Pow((element - average), 2); Słowo kluczowe let implementowane jest wyłącznie przez kompilator, który tworzy typ anonimowy zawierający zarówno oryginalną zmienną zakresu (element w poprzed- nim przykładzie), jak i nową zmienną let. Powyższe zapytanie jest mapowane (przekła- dane przez kompilator) bezpośrednio na pokazane niżej zapytanie metody rozszerzenia: var variance = source.Select ( element = new { element = element, average = source.Average () } ) .Select (temp0 = Math.Pow ( ((double)temp0.element - temp0.average) , 2)); Każda dodatkowa zmienna let, która zostanie wprowadzona, spowoduje, że bieżący typ anonimowy zostanie połączony z kolejnym typem anonimowym zawierającym sie- bie i dodatkową zmienną itd. Jednak cała ta „magia” staje się jasna, kiedy piszemy wyra- żenie zapytania. Into — kontynuacja zapytania Słowa kluczowe group, join i select wyrażeń zapytań umożliwiają przechwycenie otrzyma- nej sekwencji w zmiennej lokalnej i użycie jej w pozostałej części zapytania. Słowo kluczowe into pozwala kontynuować zapytanie poprzez użycie wyniku przechowanego w zmiennej lokalnej w każdym miejscu po jej definicji. Najczęstszym sposobem wykorzystania into jest przechwycenie wyniku operacji grupowania, która razem z wbudowanymi funkcjami złączeń zostanie omówiona w roz- dziale 4., „Grupowanie i łączenie danych”. Poniższy przykład jest krótkim wprowa- dzeniem do tych funkcji. Grupuje wszystkie elementy o tej samej wartości i przechowuje wynik w zmiennej groups poprzez użycie słowa kluczowego into (w połączeniu ze sło- wem kluczowym group), zmienna groups może brać udział i jest dostępna w pozostałej części wyrażenia zapytania. var groupings = from element in source group element by element into groups select new { Opcje stylów skïadni zapytañ 59 Key = groups.Key, Count = groups.Count() }; Porównanie opcji skïadni zapytañ Kod w listingu 3.1 używa składni metod rozszerzeń, a w listingu 3.2 składni wyrażeń zapytań, jednak funkcjonalnie są takie same: oba dają ten sam wynik, pokazany w wyj- ściu 3.1. Jasność kodu w składni wyrażenia zapytania bierze się z usunięcia zwrotów wy- rażenia lambda i zastosowania stylu operatorów SQL. Oba style składni są funkcjonalnie identyczne i dla prostych zapytań (takich jak to) korzyść z uzyskania bardziej przejrzy- stego kodu jest minimalna. Listing 3.1. Zapytanie wyszukuje wszystkie kontakty w województwie oznaczonym skrótem WM i sortuje je wedïug nazwiska, a nastÚpnie imienia za pomocÈ skïadni metod rozszerzeñ — zobacz wyjĂcie 3.1 List Contact contacts = Contact.SampleData(); var q = contacts.Where(c = c.State == WM ) .OrderBy(c = c.LastName) .ThenBy(c = c.FirstName); foreach (Contact c in q) Console.WriteLine( {0} {1} , c.FirstName, c.LastName); Listing 3.2. To samo zapytanie, co w listingu 3.1, ale uĝywajÈce skïadni wyraĝeñ zapytañ — zobacz wyjĂcie 3.1 List Contact contacts = Contact.SampleData(); var q = from c in contacts where c.State == WM orderby c.LastName, c.FirstName select c; foreach (Contact c in q) Console.WriteLine( {0} {1} , c.FirstName, c.LastName); WyjĂcie 3.1. Stanisïaw Kowal Cyryl Latos Alfred Wieczorek Tym razem poprzez zastosowanie składni wyrażeń zapytań zamiast składni metod rozszerzeń uzyskujemy znaczne korzyści w postaci czytelności kodu; jest tak, kiedy za- pytanie zawiera funkcje złączeń i (lub) grupowania. Mimo że nie cała funkcjonalność 60 Rozdziaï 3. Pisanie podstawowych zapytañ grupowania i złączania jest bezpośrednio dostępna, kiedy używasz składni wyrażeń za- pytań, to jednak większość pisanych przez Ciebie zapytań nie będzie wymagać tych do- datkowych funkcji. Listing 3.3 pokazuje dość nieczytelną składnię metod rozszerzeń dla Join (nieczytelną w tym znaczeniu, że po szybkim przeczytaniu kodu nie jest jasne, co oznacza każdy z argumentów metody GropuBy). Funkcjonalny odpowiednik korzystają- cy ze składni wyrażeń zapytań dla tego samego zapytania został pokazany w listingu 3.4. Oba zapytania dają ten sam wynik, pokazany w wyjściu 3.2. Listing 3.3. ZïÈczenia stajÈ siÚ szczególnie zïoĝone w skïadni metod rozszerzeñ. To zapytanie zwraca piÚÊ pierwszych szczegóïowych zestawów informacji o poïÈczeniach w kolejnoĂci od najnowszej — zobacz wyjĂcie 3.2 List Contact contacts = Contact.SampleData(); List CallLog callLog = CallLog.SampleData(); var q = callLog.Join(contacts, call = call.Number, contact = contact.Phone, (call, contact) = new { contact.FirstName, contact.LastName, call.When, call.Duration }) .Take(5) .OrderByDescending(call = call.When); foreach (var call in q) Console.WriteLine( {0} - {1} {2} ({3}min) , call.When.ToString( ddMMM HH:m ), call.FirstName, call.LastName, call.Duration); Jeśli to jeszcze nie stało się jasne, ja preferuję użycie składni wyrażeń zapytań za każdym razem, kiedy w zapytaniu wymagane jest wykonanie operacji Join lub GroupBy. Kiedy standardowy operator nie jest obsługiwany przez składnię wyrażeń zapytań (tak jak w przypadku np. metody .Take), umieszczam zapytanie w nawiasach i używam składni metod rozszerzeń od tego punktu, tak jak w listingu 3.4. Listing 3.4. Skïadnia wyraĝeñ zapytañ dla zapytania identycznego z tym pokazanym w listingu 3.3 — zobacz wyjĂcie 3.2 List Contact contacts = Contact.SampleData(); List CallLog callLog = CallLog.SampleData(); var q = (from call in callLog join contact in contacts on call.Number equals contact.Phone Opcje stylów skïadni zapytañ 61 orderby call.When descending select new { contact.FirstName, contact.LastName, call.When, call.Duration }).Take(5); foreach (var call in q) Console.WriteLine( {0} - {1} {2} ({3}min) , call.When.ToString( ddMMM HH:m ), call.FirstName, call.LastName, call.Duration); WyjĂcie 3.2. 07sie 11:15 - Stanisïaw Kowal (4min) 07sie 10:35 - Cezary Zbytek (2min) 07sie 10:5 - Maciej KaraĂ (1min) 07sie 09:23 - Adrian Hawrat (15min) 07sie 08:12 - Bartïomiej Gajewski (2min) Wskazówki dotyczÈce skïadni metod rozszerzeñ Q Na poczÈtku umieszczaj najbardziej ograniczajÈcÈ metodÚ zapytania; dziÚki temu zmniejszysz obciÈĝenie kolejnych operatorów. Q Umieszczaj kaĝdy operator w nowej linii (razem z kropkÈ-ïÈcznikiem). DziÚki temu bÚdziesz mógï wykomentowaÊ poszczególne operatory podczas debugowania. Q Zachowaj spójnoĂÊ — w caïej aplikacji uĝywaj tego samego stylu. Q Aby uïatwiÊ odczytywanie zapytañ, nie bój siÚ dzielenia zapytañ na kilka czÚĂci i stosowania wciÚÊ do zaznaczenia ich hierarchii. Wskazówki dotyczÈce skïadni wyraĝeñ zapytañ Q JeĂli musisz wymieszaÊ metody rozszerzeñ z wyraĝeniami rozszerzeñ, umieĂÊ je na koñcu. Q UmieĂÊ kaĝdÈ czÚĂÊ wyraĝenia zapytania w oddzielnej linii. DziÚki temu bÚdziesz mógï wykomentowaÊ poszczególne wyraĝenia podczas debugowania. 62 Rozdziaï 3. Pisanie podstawowych zapytañ Jak filtrowaÊ wyniki (wyraĝenie Where) Jednym z najważniejszych zadań zapytania LINQ jest zawężenie wyników z większej kolekcji na podstawie określonych kryteriów. Można to osiągnąć za pomocą operatora Where, który testuje każdy element kolekcji źródłowej i zwraca tylko te elementy, które dają prawdziwy wynik sprawdzenia wobec kryteriów w wyrażeniu predykatu. Predykat (ang. predicate) to po prostu wyrażenie, które przyjmuje element tego samego typu, co elementy w kolekcji źródłowej, i zwraca prawdę lub fałsz. Predykat jest przekazywany do wyrażenia Where za pomocą wyrażenia lambda. Metoda rozszerzenia dla operatora Where jest zaskakująco prosta; iteruje przez kolekcję za pomocą instrukcji foreach, testując każdy element i zwracając te, które przechodzą test. Oto kod zbliżony do kodu w bibliotece System.Linq: public delegate TResult Func T1, TResult (T1 arg1); public static IEnumerable T Where T ( this IEnumerable T source, Func T, bool predicate) { foreach (T element in source) { if (predicate(element)) yield return element; } } Operator Where w LINQ to Objects jest z pozoru rozwiązaniem bardzo podstawo- wym, ale jego implementacja jest prosta w wyniku zastosowania potężnej instrukcji yield return, która pojawiła się w platformie .NET 2.0, aby ułatwić budowanie iteratorów kolekcji. Każdy kod implementujący wbudowany wzorzec enumeracji (tak jak wszelkie kolekcje implementujące interfejs IEnumerable) samoistnie obsługuje obiekty proszące o kolejny element w kolekcji — jednocześnie przetwarzany jest kolejny element do zwrotu (obsługiwany np. przez słowo kluczowe foreach). Dowolna kolekcja imple- mentująca wzorzec IEnumerable T (który także implementuje IEnumerable) będzie rozsze- rzana operatorem Where, który będzie zwracał pojedynczy element, kiedy zostanie o to poproszony, pod warunkiem że ten element będzie spełniał warunki wyrażenia predy- katu (zwróci wartość true). Wyrażenia filtrujące predykatów są przekazywane do metody rozszerzenia za pomocą wyrażenia lambda (krótki opis wyrażeń lambda znajdziesz w rozdziale 2., „Wprowadzenie do LINQ to Objects”), jednak jeśli zostanie użyta składnia wyrażenia zapytania, predykat filtrujący przybierze nawet bardziej elegancką formę. Oba style tworzenia wyrażeń pre- dykatów zostaną opisane szczegółowo w kolejnych podrozdziałach. Filtr Where za pomocÈ wyraĝenia lambda W przypadku tworzenia predykatu dla operatora Where predykat przyjmuje element wejściowy tego samego typu, co elementy w kolekcji źródłowej, i zwraca prawdę lub Jak filtrowaÊ wyniki (wyraĝenie Where) 63 fałsz (wartość typu Boolean). Prosty predykat dla wyrażenia Where możesz zobaczyć w poniższym kodzie: string[] animals = new string[] { Koala , Kangur , PajÈk , Wombat , Waran , WÈĝ , Rekin , Pïaszczka , Meduza }; var q = from a in animals where a.StartsWith( W ) a.Length 4 select a; foreach (string s in q) Console.WriteLine(s); W powyższym kodzie każda wartość ciągu znaków z tablicy nazw zwierząt jest prze- kazywana do metody rozszerzenia Where w zmiennej zakresu a. Każdy ciąg znaków w a jest sprawdzany na podstawie warunków funkcji predykatu i tylko te ciągi, które przechodzą test (zwracają prawdę), są zwracane w wynikach zapytania. W tym przykładzie tylko dwa ciągi znaków przechodzą test i są wyświetlane w oknie konsoli, są to: Wombat Waran Kompilator C# konwertuje wyrażenie lambda na standardowe wywołanie metody anonimowej (poniższy kod jest funkcjonalnie taki sam): var q = animals.Where( delegate(string a) { return a.StartsWith( W ) a.Length 4; }); Co to jest opóěnione wykonanie? Wyraĝenie Where rozpocznie testowanie predykatu dopiero wtedy, kiedy ktoĂ (czyli Ty poprzez wyra- ĝenie foreach lub inny standardowy operator zapytania zawierajÈcy wyraĝenie foreach) podejmie próbÚ iterowania przez wyniki; do tego czasu mechanizm iteratora bÚdzie tylko pamiÚtaï, w którym miejscu siÚ znajdowaï, kiedy poproszono go po raz ostatni o element. Jest to tzw. opóěnione wyko- nanie (ang. deferred execution), sprawiajÈce, ĝe wykonanie zapytania jest przewidywalne i moĝesz kontrolowaÊ, kiedy i w jaki sposób zapytanie bÚdzie wykonane. JeĂli potrzebujesz wyników natych- miast, moĝesz wywoïaÊ ToList(), ToArray() lub inny standardowy operator wywoïujÈcy natych- miastowÈ aktualizacjÚ wyników w innej formie; w przeciwnym przypadku ewaluacja zapytania zosta- nie rozpoczÚta dopiero, kiedy rozpoczniesz iterowanie przez jego wynik. Filtr Where za pomocÈ wyraĝeñ zapytañ (sposób preferowany) Składnia wyrażenia zapytania Where pomija wyraźną definicję zmiennej zakresu i ope- rator wyrażenia lambda (= ), dzięki czemu składnia jest bardziej spójna i bardziej zbli- żona do wyrażeń SQL, łatwo zrozumiałych dla wielu programistów. Z tych powodów jest to składnia preferowana. Przepisanie poprzedniego przykładu za pomocą składni wyrażeń zapytań pokazuje te różnice: 64 Rozdziaï 3. Pisanie podstawowych zapytañ string[] animals = new string[] { Koala , Kangur , PajÈk , Wombat , Waran , WÈĝ , Rekin , Pïaszczka , Meduza }; var q = from a in animals where a.StartsWith( W ) a.Length 4 select a; foreach (string s in q) Console.WriteLine(s); Uĝywanie metody zewnÚtrznej w celu ewaluacji Mimo że możesz pisać zapytania zawierające bezpośrednio kod predykatu filtru, to nie musisz tego robić. Jeśli predykat jest długi i ma być wykorzystany w więcej niż jednym wyrażeniu zapytania, możesz rozważyć umieszczenie go we własnej metodzie (dobra praktyka dla wszelkiego powielanego kodu). Poprzedni przykład został przepisany za pomocą zewnętrznej funkcji predykatu, aby zademonstrować tę technikę: string[] animals = new string[] { Koala , Kangur , PajÈk , Wombat , Waran , WÈĝ , Rekin , Pïaszczka , Meduza }; var q = from a in animals where MyPredicate(a) select a; foreach (string s in q) Console.WriteLine(s); public bool MyPredicate(string a) { if (a.StartsWith( W ) a.Length 4) return true; else return false; } Aby lepiej zademonstrować tę technikę w nieco bardziej skomplikowanym scenariuszu, kod w listingu 3.5 tworzy metodę predykatu, zawierającą logikę potrzebną do ustalenia, czy zwierzę „może być niebezpieczne”. Poprzez enkapsulację tej logiki w pojedynczej metodzie nie musimy powielać tego samego kodu w kilku miejscach aplikacji. Listing 3.5. Wyraĝenie Where korzystajÈce z metody zewnÚtrznej — zobacz wyjĂcie 3.3 string[] animals = new string[] { Koala , Kangur , PajÈk , Wombat , Waran , ´ WÈĝ , Rekin , Pïaszczka , Meduza }; var q = from a in animals Jak filtrowaÊ wyniki (wyraĝenie Where) 65 where IsAnimalDeadly(a) select a; foreach (string s in q) Console.WriteLine( {0} moĝe byÊ niebezpieczny(a). , s); public static bool IsAnimalDeadly(string s) { string[] deadly = new string[] { PajÈk , Waran , WÈĝ , Rekin , Pïaszczka , ´ Meduza }; return deadly.Contains(s); } WyjĂcie 3.3. PajÈk moĝe byÊ niebezpieczny(a). Waran moĝe byÊ niebezpieczny(a). WÈĝ moĝe byÊ niebezpieczny(a). Rekin moĝe byÊ niebezpieczny(a). Pïaszczka moĝe byÊ niebezpieczny(a). Meduza moĝe byÊ niebezpieczny(a). Filtrowanie na podstawie pozycji indeksu Standardowe operatory zapytań udostępniają wariant operatora Where uwidaczniający pozycję indeksu każdego przetwarzanego elementu kolekcji. Pozycja indeksu (rozpo- czynająca się od zera) może być przekazana do wyrażenia lambda predykatu poprzez przypisanie nazwy zmiennej jako drugiego argumentu (po zmiennej zakresu elementu). Aby uwidocznić pozycję indeksu, należy użyć wyrażenia lambda, co można zrobić wy- łącznie za pomocą składni metody rozszerzenia. Listing 3.6 pokazuje najprostszy przy- kład użycia — w tym przypadku polegający na zwróceniu pierwszego oraz wszystkich elementów o parzystych pozycjach indeksu w kolekcji źródłowej; wynik pokazano w wyjściu 3.4. Listing 3.6. Pozycja indeksu moĝe zostaÊ uĝyta jako czÚĂÊ predykatu wyraĝenia Where, kiedy uĝywamy wyraĝeñ lambda — zobacz wyjĂcie 3.4 string[] animals = new string[] { Koala , Kangur , PajÈk , Wombat , Waran , WÈĝ , Rekin , Pïaszczka , Meduza }; // pobranie pierwszego, a potem co drugiego zwierzĊcia (indeksy nieparzyste) var q = animals.Where((a, index) = index 2 == 0); foreach (string s in q) Console.WriteLine(s); 66 Rozdziaï 3. Pisanie podstawowych zapytañ WyjĂcie 3.4. Koala PajÈk Waran Rekin Meduza Jak zmieniÊ typ zwracany (projekcja wybierania) Kiedy piszesz zapytania SQL dla systemu baz danych, wskazywanie zestawu kolumn, z których mają zostać zwrócone wyniki, staje się Twoją drugą naturą. Celem jest ograni- czenie zwracanych kolumn tylko do tych, które są niezbędne, aby poprawić wydajność i ograniczyć ruch w sieci (im mniej przesyłanych danych, tym lepiej). Efekt ten osiąga się poprzez wymienianie nazw kolumn po wyrażeniu Select w formacie pokazanym poniżej. W większości przypadków potrzebne kolumny są wskazywane za pomocą następującej składni SQL: Select * from Contacts Select ContactId, FirstName, LastName from Contacts Pierwsze zapytanie zwróci każdą kolumnę (i wiersz) z tabeli Contacts, a drugie tylko trzy wskazane kolumny (dla każdego wiersza), dzięki czemu zaoszczędzona zostanie moc serwera i zasoby sieciowe. Konkluzją jest to, że składnia języka SQL pozwala na użycie odmiennego zestawu wierszy, który może nie odpowiadać istniejącej tabeli, widokowi lub schematowi jako strukturze zwracanych danych. Projekcje wybierania w wyrażeniach zapytań LINQ pozwalają nam osiągnąć to samo. Jeśli w zestawie wyników potrzebujemy tylko kilku wartości właściwości, zostaną zwrócone tylko te właściwości lub pola. Projekcje wybierania w LINQ dają bardziej zróżnicowaną i efektywną kontrolę nad kształtem danych zwracanych z wyrażenia zapytania. Sposoby zwracania wyników przez projekcję wybierania są następujące: Q Jako pojedyncza wartość wyniku lub element. Q W IEnumerable T , gdzie T jest tym samym typem, co elementy źródłowe. Q W IEnumerable T , gdzie T jest dowolnym istniejącym typem skonstruowanym w projekcji wybierania. Q W IEnumerable T , gdzie T jest typem anonimowym skonstruowanym w projekcji wybierania. Q W IEnumberable IGrouping TKey, TElement , który jest kolekcją pogrupowanych obiektów mających wspólny klucz. Każdy styl projekcji ma swoje zastosowania i każdy zostanie opisany w kolejnych podrozdziałach. Jak zmieniÊ typ zwracany (projekcja wybierania) 67 Jak wiele danych powinno byÊ zawartych w projekcji wybierania? Podobnie jak w przypadku wszystkich innych paradygmatów dostÚpu do danych, celem powinno byÊ dÈĝenie do zwracania tak maïej liczby wïaĂciwoĂci, jak to moĝliwe, kiedy definiujemy ksztaït wyniku zapytania. DziÚki temu zmniejsza siÚ zuĝycie pamiÚci i dla wyniku ïatwiej jest pisaÊ kod, poniewaĝ istnieje w nim mniej wïaĂciwoĂci, które naleĝy uwzglÚdniÊ. Zwracanie pojedynczej wartoĂci wyniku lub elementu Niektóre standardowe operatory zapytań zwracają jako wynik pojedynczą wartość lub pojedynczy element z kolekcji źródłowej. Operatory te zostały wymienione w tabeli 3.1. Każdy z nich kończy kaskadowanie wyników do innego zapytania i zamiast tego zwraca pojedynczą wartość wyniku lub element źródłowy. Tabela 3.1. Przykïadowy zestaw operatorów zwracajÈcych wynik bÚdÈcy wartoĂciÈ okreĂlonego typu (opisane w rozdziaïach 5. i 6.) Zwracany typ Operator Numeryczny Boolean Typ T Aggregate, Average, Max, Min, Sum, Count, LongCount All, Any, Contains, SequenceEqual ElementAt, ElementAtOrDefault, First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, DefaultIfEmpty Jako przykład, poniższe proste zapytanie zwraca ostatni element w tablicy liczb cał- kowitych; wyświetla w oknie konsoli liczbę 2: int[] nums = new int[] { 5, 3, 4, 2 }; int last = nums.Last(); ConsoleWriteLine(last); Zwracanie takiego samego typu jak ěródïo — IEnumerable T½ródïa Najbardziej podstawowy rodzaj projekcji zwraca przefiltrowany i posortowany podze- staw elementów oryginalnego źródła. Ta projekcja jest uzyskiwana poprzez wskazanie zmiennej zakresu jako argumentu po słowie kluczowym select. Kod w poniższym przykładzie zwraca IEnumerable Contact , którego typ — Contact jest ustalany na pod- stawie typu elementu w kolekcji źródłowej: List Contact contacts = Contact.SampleData(); IEnumerable Contact q = from c in contacts select c; Bardziej praktyczne zapytanie filtrowałoby wyniki i sortowało je w odpowiedni sposób. Zwracana jest ciągle kolekcja tego samego typu, ale liczba jej elementów i kolejność może być inna (niż kolekcji źródłowej). 68 Rozdziaï 3. Pisanie podstawowych zapytañ List Contact contacts = Contact.SampleData(); IEnumerable Contact q = from c in contacts where c.State == WA orderby c.LastName, c.FirstName ascending select c; Zwracanie typu innego niĝ ěródïo — IEnumerable TDowolny W projekcji za pomocą wyrażenia select może być zwrócony dowolny typ, nie tylko typ źródła. Typem docelowym może być dowolny dostępny typ, który mógłby być „ręcznie” skonstruowany za pomocą instrukcji new w zakresie pisanego kodu. Jeśli tworzony typ posiada konstruktor z parametrami zawierający wszystkie para- metry, jakich potrzebujesz, to wystarczy, że wywołasz ten konstruktor. Jeśli żaden z kon- struktorów nie odpowiada parametrowi potrzebnemu dla tej projekcji, utwórz taki lub rozważ użycie składni C# 3.0 służącej do inicjalizacji typów (opisanej w rozdziale 2.). Korzyść wynikająca z użycia nowego rodzaju inicjalizatora polega na tym, że nie musisz definiować określonego konstruktora za każdym razem, kiedy potrzebna jest nowa sy- gnatura projekcji do obsłużenia zapytania w nowym kształcie. Kod w listingu 3.7 poka- zuje, jak wykonać projekcję IEnumerable ContactName za pomocą obu konstruktorów. Listing 3.7. Projekcja do kolekcji nowego typu — skonstruowanej za pomocÈ okreĂlonego konstruktora lub za pomocÈ skïadni inicjalizacji List Contact contacts = Contact.SampleData(); // uĪycie konstruktora z parametrami IEnumerable ContactName q1 = from c in contacts select new ContactName( c.LastName + , + c.FirstName, (DateTime.Now - c.DateOfBirth).Days / 365); // uĪycie skáadni inicjalizowania typów // uwaga: typ wymaga konstruktora bez parametrów IEnumerable ContactName q2 = from c in contacts select new ContactName { FullName = c.LastName + , + c.FirstName, YearsOfAge = (DateTime.Now - c.DateOfBirth).Days / 365 }; // definicja klasy ContactName public class ContactName { public string FullName { get; set; } public int YearsOfAge { get; set; } Jak zmieniÊ typ zwracany (projekcja wybierania) 69 // konstruktor potrzebny dla przykáadu inicjalizacji obiektów public ContactName() { } // konstruktor potrzebny dla przykáadu projekcji typu public ContactName(string name, int age) { this.FullName = name; this.YearsOfAge = age; } } Uwaga Nie ulegaj pokusie zbyt czÚstego uĝywania skïadni inicjalizatora. Wymaga ona, aby wszystkie wïaĂciwoĂci inicjalizowane za jej pomocÈ mogïy byÊ odczytywane i zapisywane (posiadaÊ instrukcje get i set). JeĂli wïaĂciwoĂÊ ma byÊ tylko do odczytu, nie zamieniaj jej we wïaĂciwoĂÊ do odczytu i zapisu tylko po to, aby skorzystaÊ z tej funkcji. W takim wypadku rozwaĝ zmianÚ tych parametrów konstruktora na opcjonalne za pomocÈ skïadni C# 4.0 dla opcjonalnych parametrów konstruktora, tak jak opisano w rozdziale 8., „Nowe funkcje w C# 4.0”. Zwracanie typu anonimowego — IEnumerable TAnonimowy Typy anonimowe są nową funkcją języka wprowadzoną w C# 3.0; kompilator tworzy taki typ dynamicznie na podstawie wyrażenia inicjalizującego (wyrażenia po prawej stronie pierwszego znaku =). Jak już wyjaśniliśmy szczegółowo w rozdziale 2., nowemu typowi zostaje nadana przez kompilator nazwa niemożliwa do wywołania i bez słowa kluczo- wego var (zmienne lokalne z domyślnie określonym typem) skompilowanie zapytania nie byłoby możliwe. Poniższe zapytanie pokazuje projekcję do kolekcji IEnumerable T , gdzie T jest typem anonimowym: List Contact contacts = Contact.SampleData(); var q = from c in contacts select new { FullName = c.LastName + , + c.FirstName, YearsOfAge = (DateTime.Now - c.DateOfBirth).Days / 365 }; Typ anonimowy utworzony w powyższym przykładzie składa się z dwóch właściwości: FullName i YearsOfAge. Typy anonimowe uwalniają nas od potrzeby pisania i utrzymywania określonej defi- nicji typu dla każdego odmiennego wyniku potrzebnego w kolekcji. Jedyną wadą jest to, że zakres typów tego rodzaju ograniczony jest do metody i nie mogą być one użyte 70 Rozdziaï 3. Pisanie podstawowych zapytañ poza metodą, w której zostały zadeklarowane (chyba że zostaną przekazane jako typ System.Object, ale nie jest to sposób zalecany, ponieważ późniejszy dostęp do właści- wości tego obiektu będzie wymagał użycia refleksji). Zwracanie zestawu pogrupowanych obiektów IEnumerable IGrouping TKlucz, TElement W LINQ to Objects możliwe jest grupowanie wyników mających wspólne wartości źró- dłowe lub dowolną charakterystykę, którą można zrównać za pomocą wyrażenia uży- wającego słowa kluczowego zapytania group by lub metody rozszerzenia GroupBy. Ten temat zostanie omówiony szczegółowo w rozdziale 4. Jak zwracaÊ elementy, kiedy wynik jest sekwencjÈ (SelectMany) Standardowy operator zapytania SelectMany „spłaszcza” wszelkie elementy wyniku IEnumerable T , zwracając każdy element oddzielnie ze źródeł enumerowanych przed przejściem do kolejnego elementu w sekwencji wyniku. W przeciwieństwie do metody rozszerzenia Select, która zatrzyma się na pierwszym poziomie i zwróci sam element IEnumerable T . Listing 3.8 pokazuje, w jaki sposób SelectMany różni się od Select; każda wersja ko- du ma na celu pobranie pojedynczego słowa z zestawu przetwarzanych ciągów znaków. Aby pobrać słowa w opcji 1., wymagana jest podpętla for, ale SelectMany automatycznie wykonuje poditerację pierwotnej kolekcji wyników, tak jak pokazano w opcji 2. Opcja 3. pokazuje, że ten sam rezultat można osiągnąć za pomocą kilku wyrażeń from w wyraże- niu zapytania (które mapuje zapytanie tak, aby użyć operatora SelectMany w tle). Wynik w konsoli pokazano w wyjściu 3.5. Listing 3.8. Select kontra SelectMany — SelectMany „zagïÚbia siÚ” w wynik w postaci IEnumerable i zwraca jego elementy — zobacz wyjĂcie 3.5 string[] sentence = new string[] { Pewien szybki brÈzowy , lis przeskakuje nad , bardzo leniwym psem. }; Console.WriteLine( opcja 1: ); Console.WriteLine( ------ ); // opcja 2: Select zwraca trzy wartoĞci string[] // kaĪda z nich zawiera trzy ciągi znaków. IEnumerable string[] words1 = sentence.Select(w = w.Split( )); // aby pobraü kaĪde ze sáów, musimy uĪyü dwóch pĊtli foreach foreach (string[] segment in words1) foreach (string word in segment) Console.WriteLine(word); Jak zwracaÊ elementy, kiedy wynik jest sekwencjÈ (SelectMany) 71 Console.WriteLine(); Console.WriteLine( opcja 2: ); Console.WriteLine( ------ ); // opcja 2: SelectMany zwraca dziewiĊü ciągów // (iteruje przez wynik Select) IEnumerable string words2 = sentence.SelectMany(segment = segment.Split( )); // za pomocą SelectMany mamy dostĊp do kaĪdego ciągu osobno foreach (var word in words2) Console.WriteLine(word); // opcja 3: identyczna jak opcja 2 powyĪej napisana za pomocą // skáadni wyraĪeĔ zapytaĔ (kilka instrukcji from) IEnumerable string words3 = from segment in sentence from word in segment.Split( ) select word; WyjĂcie 3.5. opcja 1: ------ Pewien szybki brÈzowy lis przeskakuje nad bardzo leniwym psem. opcja 2: ------ Pewien szybki brÈzowy lis przeskakuje nad bardzo leniwym psem. W jaki sposób działa metoda rozszerzenia SelectMany? Tworzy zagnieżdżoną pętlę foreach dla pierwotnego wyniku, zwracając każdy podelement za pomocą instrukcji yield return. Kod zbliżony do kodu SelectMany ma następującą postać: 72 Rozdziaï 3. Pisanie podstawowych zapytañ static IEnumerable S SelectManyIterator T, S ( this IEnumerable T source, Func T, IEnumerable S selector) { foreach (T element in source) { foreach (S subElement in selector(element)) { yield return podElement; } } } W jaki sposób otrzymaÊ wartoĂci indeksu wyników Select i SelectMany udostępniają przeciążenie, które umożliwia odczytanie wartości in- deksów (rozpoczynających się od zera dla każdego zwracanego elementu w projekcji wybierania). Wartość indeksu jest uwidaczniana jako przeciążony parametr selektora wy- rażenia lambda i jest dostępna wyłącznie za pomocą składni metody zapytania. Li- sting 3.9 pokazuje, w jaki sposób uzyskać dostęp do wartości indeksu w projekcji wybie- rania. Jak widać w wyjściu 3.6, ten przykład po prostu dodaje informację o pozycji na liście do każdego ciągu znaków będącego wynikiem wyboru. Listing 3.9. RozpoczynajÈce siÚ od zera wartoĂci indeksu sÈ uwidaczniane przez operatory Select i SelectMany — zobacz wyjĂcie 3.6 List CallLog callLog = CallLog.SampleData(); var q = callLog.GroupBy(g = g.Number) .OrderByDescending(g = g.Count()) .Select((g, index) = new { number = g.Key, rank = index + 1, count = g.Count() }); foreach (var c in q) Console.WriteLine( Pozycja {0} - {1}, dzwoniï(a) {2} razy. , c.rank, c.number, c.count); WyjĂcie 3.6. Pozycja 1 - 885 983 885, dzwoniï(a) 6 razy. Pozycja 2 - 546 607 546, dzwoniï(a) 6 razy. Pozycja 3 - 364 202 364, dzwoniï(a) 4 razy. Pozycja 4 - 603 303 603, dzwoniï(a) 4 razy. Jak usuwaÊ powielone wyniki? 73 Pozycja 5 - 848 553 848, dzwoniï(a) 4 razy. Pozycja 6 - 165 737 165, dzwoniï(a) 2 razy. Pozycja 7 - 278 918 278, dzwoniï(a) 2 razy. Jak usuwaÊ powielone wyniki? Standardowy operator zapytania Distinct powoduje, że zwracane są tylko niepowtarzalne wystąpienia w sekwencji. Ten operator przechowuje wewnętrznie informacje o elementach, które już zwrócił, i pomija drugie i kolejne wystąpienia danego elementu w trakcie zwracania. Operator zostanie omówiony szczegółowo w rozdziale 6., „Praca ze zbiorami danych”, kiedy zajmiemy się jego użyciem w operacjach na zbiorach. Operator Distinct nie jest obsługiwany przez składnię wyrażeń zapytań, a więc często umieszcza się go na końcu zapytania za pomocą składni metody rozszerzenia. Aby zademonstrować jego użycie, poniższy kod usunie powielone ciągi znaków. Wy- nik działania tego kodu w konsoli jest następujący: Piotr Paweï Maria Janina string[] names = new string[] { Piotr , Paweï , Maria , Piotr , Paweï , Maria , Janina }; var q = (from s in names where s.Length 3 select s).Distinct(); foreach (var name in q) Console.WriteLine(name); Jak sortowaÊ wyniki LINQ to Objects posiada rozbudowaną obsługę porządkowania i sortowania wyników. Niezależnie od tego, czy chcesz posortować w porządku rosnącym, malejącym na podstawie różnych wartości właściwości w dowolnej sekwencji, czy też napisać własny algorytm sortowania, funkcje sortowania w LINQ będą w stanie spełnić wszelkie wymagania. Podstawowa skïadnia sortowania Kolekcja wyników otrzymana jako rezultat zapytania może być posortowana w dowolny pożądany sposób, w tym uwzględniający ustawienia regionalne oraz wielkość liter. W przypadku zapytań tworzonych za pomocą składni metod rozszerzeń proces sortowania obsługiwany jest przez standardowe operatory zapytań OrderBy, OrderByDescending, ThenBy i ThenByDescending. Operatory OrderBy i ThenBy sortują w porządku rosnącym 74 Rozdziaï 3. Pisanie podstawowych zapytañ (np. od a do z), a operatory OrderByDescending i ThenByDescending w porządku maleją- cym (np. od z do a). Tylko pierwsza metoda rozszerzenia wykonująca sortowanie może używać operatorów Orderby, każde kolejne wyrażenie sortujące musi używać operatorów ThenBy, których może być zero lub więcej w zależności od tego, ile kontroli nad dalszym sortowaniem chcesz uzyskać, kiedy wiele elementów będzie mieć równoważne pozycje po wykonaniu poprzednich wyrażeń. Poniższe przykłady pokazują sekwencję sortowania na początku według klucza [w], a następnie w kolejności malejącej według klucza [x] i wreszcie w kolejności rosnącej według klucza [y]: [source].OrderBy([w]) .ThenByDescending([x]) .ThenBy([y]); Kiedy używamy składni wyrażeń zapytań, każdy klucz sortowania i opcjonalne słowo kluczowe wskazujące kierunek muszą być oddzielone przecinkami. Jeśli słowa descending lub ascending nie zostaną użyte, LINQ przyjmie kolejność rosnącą (ascending). from [v] in [source] orderBy [w], [x] descending, [y] select [z]; Wynikiem sortowania kolekcji będzie IOrderedEnumerable T , który implementuje IEnumerable T , co umożliwi dalsze kaskadowanie operacji zapytań. Metody rozszerzeń służące do sortowania zostały zaimplementowane za pomocą podstawowego, ale wydajnego algorytmu Quicksort (dokładniejsze wyjaśnienie jego działania — zobacz http://en.wikipedia.org/wiki/Quicksort). Implementacja w LINQ to Objects używa sortowania niestabilnego, co oznacza, że elementy odpowiadające tym samym wartościom klucza mogą nie zachować pozycji względem siebie, jaką miały w kolekcjach źródłowych (ten problem łatwo rozwiązać poprzez skierowanie wyniku do operatora ThenBy lub ThenByDescending). Algorytm jest dość szybki i znajduje za- stosowanie w programowaniu równoległym, co zostało wykorzystane przez Microsoft w Parallel LINQ. Co to jest programowanie równolegïe? Programowanie równolegïe (ang. parallelization) odnosi siÚ do technik poprawiania wydajnoĂci aplikacji poprzez peïne wykorzystanie wielu procesorów i wielu rdzeni w procesorach wykonujÈcych kod. Programowanie równolegïe bÚdzie omówione szczegóïowo w rozdziale 9., „Parallel LINQ to Objects”, w którym zostanie takĝe pokazane, w jaki sposób zapytania LINQ mogÈ w peïni skorzystaÊ z proce- sorów wielordzeniowych i wielu procesorów. Jak sortowaÊ wyniki 75 Odwracanie kolejnoĂci sekwencji wyników (Reverse) Inną metodą rozszerzenia związaną z sortowaniem, której działanie polega na od- wróceniu kolejności całej sekwencji, jest Reverse. Jest ona wywoływana w postaci: [ěródïo].Reverse();. Ważnym zastrzeżeniem, o jakim należy pamiętać podczas uży- wania operatora Reverse, jest to, że operator ten nie testuje równości elementów oraz nie wykonuje sortowania; po prostu zwraca elementy, zaczynając od ostatniego, a koń- cząc na pierwszym. Kolejność zwracania jest dokładnym odwróceniem kolejności, która zostałaby zwrócona z sekwencji wynikowej. Poniższy przykład pokazuje działanie ope- ratora Reverse zwracającego litery T O K w oknie konsoli: string[] letters = new string[] { K , O , T }; var q = letters.Reverse(); foreach (string s in q) Console.Write( + s); Sortowanie ciÈgów znaków bez uwzglÚdniania wielkoĂci liter oraz z uĝyciem ustawieñ regionalnych Każdy standardowy operator zapytania, którego użycie wiąże się z sortowaniem, zawiera przeciążenie umożliwiające wskazanie określonej funkcji komparatora (przy zapisie za pomocą składni metod rozszerzeń). Biblioteka klas .NET zawiera przydatną klasę po- mocniczą StringComparer, która posiada zestaw predefiniowanych, statycznych kompa- ratorów gotowych do użycia. Komparatory te umożliwiają nam zmianę zachowania przy sortowaniu ciągów znaków, sterowanie uwzględnianiem wielkości liter oraz ustawie- niami regionalnymi (ustawieniem języka dla bieżącego wątku). W tabeli 3.2 wymieniono statyczne instancje komparatorów, których można użyć w dowolnym operatorze OrderBy i ThenBy sortującym w porządku rosnącym bądź malejącym (zobacz także podrozdział „Własne komparatory równości dla operatorów zbiorów w LINQ” w rozdziale 6., który poświęcony jest wbudowanym komparatorom ciągów znaków i komparatorom własnym). Tabela 3.2. Wbudowane funkcje StringComparer sïuĝÈce do sterowania z uwzglÚdnieniem wielkoĂci liter oraz sortowaniem z uwzglÚdnieniem ustawieñ regionalnych Komparator CurrentCulture CurrentCultureIgnoreCase InvariantCulture Opis Wykonuje porównanie uwzględniające wielkość liter, używając reguł porównywania słów dla bieżących ustawień regionalnych. Wykonuje porównanie nieuwzględniające wielkości liter, używając reguł porównywania słów dla bieżących ustawień regionalnych. Wykonuje porównanie uwzględniające wielkość liter, używając reguł porównywania słów dla niezmiennych ustawień regionalnych. InvariantCultureIgnoreCase Wykonuje porównanie nieuwzględniające wielkości liter, Ordinal OrdinalIgnoreCase używając reguł porównywania słów dla niezmiennych ustawień regionalnych. Wykonuje uwzględniające wielkość liter porównanie porządkowe. Wykonuje nieuwzględniające wielkości liter porównanie porządkowe. 76 Rozdziaï 3. Pisanie podstawowych zapytañ Listing 3.10 demonstruje składnię i efekt działania wbudowanych komparatorów do- stępnych w .NET Framework. Wynik działania w konsoli pokazano w listingu 3.7, do- myślny wynik uwzględniający wielkość liter można zmienić w nieuwzględniający ich wielkości. Listing 3.10. Sortowanie uwzglÚdniajÈce wielkoĂÊ liter oraz ustawienia regionalne/nieuwzglÚdniajÈce wielkoĂci liter i ustawieñ regionalnych za pomocÈ funkcji StringComparer — zobacz wyjĂcie 3.7 string[] words = new string[] { jAnina , JAnina , janina , Janina }; var cs = words.OrderBy(w = w); var ci = words.OrderBy(w = w, StringComparer.CurrentCultureIgnoreCase); Console.WriteLine( Oryginalna kolejnoĂÊ: ); foreach (string s in words) Console.WriteLine( + s); Console.WriteLine( UwzglÚdniajÈce wielkoĂci liter (domyĂlne): ); foreach (string s in cs) Console.WriteLine( + s); Console.WriteLine( NieuwzglÚdniajÈce wielkoĂci liter: ); foreach (string s in ci) Console.WriteLine( + s); WyjĂcie 3.7. Oryginalna kolejnoĂÊ: jAnina JAnina janina Janina UwzglÚdniajÈce wielkoĂci liter (domyĂlne): janina jAnina Janina JAnina NieuwzglÚdniajÈce wielkoĂci liter: jAnina JAnina janina Janina Jak sortowaÊ wyniki 77 Wskazywanie wïasnych funkcji komparatora Aby obsłużyć wszelkiego rodzaju kolejności sortowania, istnieje możliwość łatwego wskazania własnych funkcji komparatora. Własna klasa komparatora będzie oparta na standardowym interfejsie .NET o nazwie IComparer T udostępniającym jedną metodą: Compare. Interfejs ten nie służy wyłącznie LINQ, jest podstawą wszystkich klas .NET wymagających możliwości sortowania (w tym na podstawie własnych kryteriów). Funkcje komparatora zwracają wyniki w postaci liczby całkowitej, wskazującej na relację pomiędzy parą instancji typów. Jeśli dwa typy zostaną uznane za równe, funkcja zwraca zero. Jeśli pierwsza instancja jest mniejsza od drugiej, zwracana jest wartość ujemna; jeśli pierwsza instancja jest większa od drugiej, zwracana jest wartość dodatnia. To, w jaki sposób uzyskasz określone wyniki w postaci liczb całkowitych, zależy wyłącznie od Ciebie. Aby zademonstrować działanie własnego IComparer T , kod w listingu 3.11 zawiera funkcję komparatora, która po prostu miesza (w sposób losowy) elementy ze źródła. Algo- rytm podejmuje losową decyzję o tym, czy dany element jest mniejszy czy większy od drugiego. Wyjście 3.8 pokazuje wynik w konsoli dla prostego źródła, którym jest zbiór ciągów znaków w tablicy; wynik będzie (potencjalnie) inny za każdym wykonaniem kodu. Listing 3.11. Sortowanie za pomocÈ naszej wïasnej implementacji IComparer T w celu uzyskania losowych wyników — zobacz wyjĂcie 3.8 public class RandomShuffleStringSort T : IComparer T { internal Random random = new Random(); public int Compare(T x, T y) { // liczba losowa: 0 lub 1 int i = random.Next(2); if (i == 0) return -1; else return 1; } } string[] strings = new string[] { 1-jeden , 2-dwa , 3-trzy , 4-cztery , 5-piÚÊ }; var normal = strings.OrderBy(s = s); var custom = strings.OrderBy(s = s, new RandomShuffleStringSort string ()); Console.WriteLine( Zwykïa kolejnoĂÊ sortowania: ); foreach (string s in normal) { Console.WriteLine( + s); } 78 Rozdziaï 3. Pisanie podstawowych zapytañ Console.WriteLine( Wïasna kolejnoĂÊ sortowania: ); foreach (string s1 in custom) { Console.WriteLine( + s1); } WyjĂcie 3.8. Zwykïa kolejnoĂÊ sortowania: 1-jeden 2-dwa 3-trzy 4-cztery 5-piÚÊ Wïasna kolejnoĂÊ sortowania: 2-dwa 5-piÚÊ 3-trzy 4-cztery 1-jeden Częsty przypadek, który zawsze sprawiał mi kłopot, polega na tym, że proste sorto- wanie alfabetyczne nie zawsze prawidłowo przetwarza alfanumeryczne ciągi znaków. Przykładem jest sortowanie następujących ciągów: File1, File10, File2. Oczywiście, pożądaną kolejnością będzie File1, File2, File10, ale nie jest to kolejność alfabetyczna. Do osiągnięcia tego rezultatu konieczny będzie własny IComparer, który posortuje część alfabetyczną, a następnie numeryczną osobno. Jest to tzw. sortowanie naturalne. Listing 3.12 i wyjście 3.9 pokazują własną klasę sortowania, która prawidłowo sor- tuje ciągi alfanumeryczne kończące się liczbami. W każdym przypadku, kiedy konieczne okaże się sortowanie tego rodzaju, nazwę klasy należy przekazać do dowolnej z metod rozszerzeń OrderBy lub ThenBy w następujący sposób: string[] partNumbers = new string[] { SCW10 , SCW1 , SCW2 , SCW11 , NUT10 , NUT1 , NUT2 , NUT11 }; var custom = partNumbers.OrderBy(s = s, new AlphaNumberSort()); Kod w listingu 3.12 na początku sprawdza, czy którykolwiek z wejściowych ciągów znaków ma wartość null lub jest pusty. Jeśli którykolwiek jest pusty, to kończy działanie i zwraca wynik z domyślnego komparatora (brak ciągu alfanumerycznego do sprawdze- nia). Po ustaleniu, że istnieją dwa prawidłowe ciągi do porównania, dołączona część numeryczna każdego ciągu jest wydzielana w zmiennych numericX i numericY. Jeżeli ża- den z ciągów nie ma części numerycznej, zwracany jest wynik z domyślnego kompara- tora (nie ma dołączonej części numerycznej dla jednego z ciągów, a więc wystarczające jest zwykłe porównanie). Jeśli oba ciągi zawierają część numeryczną, porównywana jest część nienumeryczna. Jeśli ciągi znaków są różne, zwracany jest wynik domyślnego komparatora (w takim przypadku część numeryczna nie ma znaczenia). Jeśli obie części Jak sortowaÊ wyniki 79 nienumeryczne są takie same, porównywane są wartości numeryczne numericX i numericY i ten wynik zostaje zwrócony. Ostatecznym wynikiem jest to, że elementy zostają po- sortowane alfabetycznie, a jeśli część literowa jest taka sama, o ostatecznej kolejności decyduje część numeryczna. Listing 3.12. Sortowanie za pomocÈ wïasnego komparatora. Ten komparator prawidïowo sortuje ciÈgi znaków, które koñczÈ siÚ liczbÈ — zobacz wyjĂcie 3.9 public class AlphaNumberSort : IComparer string { public int Compare(string a, string b) { StringComparer sc = StringComparer.CurrentCultureIgnoreCase; // jeĞli którakolwiek wartoĞü wejĞciowa jest null lub pusta, // wykonaj proste porównanie ciągów if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) return sc.Compare(a, b); // znajdywanie czĊĞci numerycznych string numericX = FindTrailingNumber(a); string numericY = FindTrailingNumber(b); // jeĞli w obu ciągach istnieje czĊĞü numeryczna, // musimy badaü dalej if (numericX != string.Empty numericY != string.Empty) { // na początku porównujemy przyrostek int stringPartCompareResult = sc.Compare( a.Remove(a.Length - numericX.Length), b.Remove(b.Length - numericY.Length)); //
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

LINQ to Objects w C# 4.0
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: