Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00280 009844 7515197 na godz. na dobę w sumie
C# 6.0 w pigułce. Wydanie VI - ebook/pdf
C# 6.0 w pigułce. Wydanie VI - ebook/pdf
Autor: , Liczba stron: 1064
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-283-2424-4 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> c# - programowanie
Porównaj ceny (książka, ebook (-20%), audiobook).
C# jest jednym z najlepszych projektów firmy Microsoft. Język ten został od podstaw zaprojektowany jako obiektowy. Charakteryzuje się niezwykłą elastycznością i wszechstronnością. Udostępnia wysokopoziomowe abstrakcje, takie jak wyrażenia, zapytania i kontynuacje asynchroniczne, a równocześnie pozwala na korzystanie z niskopoziomowych mechanizmów, jak własne typy wartościowe programisty czy opcjonalne wskaźniki. C# w wersji 6.0 jest kolejną istotną aktualizacją języka. Programista piszący w C# powinien konsekwentnie poznawać te zmiany.

Niniejsza książka jest zwięzłym kompendium wiedzy o C#, CLR oraz o związanej z C# platformie. Napisano ją z myślą o programistach na co najmniej średnim poziomie zaawansowania. W zrozumiały, a równocześnie dogłębny sposób wyjaśniono takie trudne kwestie, jak współbieżność, bezpieczeństwo i domeny aplikacji. Informacje o nowych składnikach języka C# 6.0 i związanej z nim platformy zostały oznaczone. Szczególnie istotny z punktu widzenia programisty może okazać się rozdział o nowym kompilatorze Roslyn, zwanym kompilatorem usługowym.

Najważniejsze zagadnienia ujęte w książce:

C# jest szybki, efektywny, wygodny — to narzędzie w sam raz dla Ciebie!


Joseph Albahari jest autorem kilku książek dotyczących C# oraz LINQ. Napisał też LINQPad, popularny program do testowania zapytań LINQ.
Ben Albahari — były kierownik programowy w Microsofcie; współtworzył takie projekty, jak .NET Compact Framework i ADO.NET. Jeden z założycieli firmy Genamics zajmującej się produkcją narzędzi dla programistów C# i J++ oraz oprogramowania do analizy DNA i sekwencjonowania białek. Jest autorem i współautorem kilku książek dotyczących C#.
Znajdź podobne książki

Darmowy fragment publikacji:

Tytuł oryginału: C# 6.0 in a Nutshell, 6th Edition Tłumaczenie: Robert Górczyński (12 – 18, 26 – 27) Jakub Hubisz (rozdz. 19, 25) Łukasz Piwko (wstęp, rozdz. 1 – 11, 20 – 24) ISBN: 978-83-283-2423-7 © 2016 Helion SA Authorized Polish translation of the English edition C# 6.0 in a Nutshell, 6th Edition ISBN 9781491927069 © 2016 Joseph Albahari, Ben Albahari. This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same. 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 the Publisher. 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) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/c6pig6 Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/c6pig6.zip Printed in Poland. • Kup książkę • Poleć książkę • Oceń książkę • Księgarnia internetowa • Lubię to! » Nasza społeczność Spis treści Wstęp .............................................................................................................................11 1 Wprowadzenie do C# i .NET Framework ............................................................................17 17 18 19 19 19 20 21 22 24 24 25 Obiektowość Bezpieczeństwo typów Zarządzanie pamięcią Platformy Powiązania C# z CLR CLR i .NET Framework C# i środowisko wykonawcze systemu Windows Co nowego w C# 6.0 Co było nowego w C# 5.0 Co było nowego w C# 4.0 Co było nowego w C# 3.0 2 Podstawy języka C# .........................................................................................................27 27 30 33 42 49 51 53 57 65 70 71 79 Pierwszy program w języku C# Składnia Podstawy typów Typy liczbowe Typ logiczny i operatory Łańcuchy znaków i pojedyncze znaki Tablice Zmienne i parametry Wyrażenia i operatory Operatory null Instrukcje Przestrzenie nazw 3 Poleć książkęKup książkę 3 Tworzenie typów w języku C# ...........................................................................................87 87 101 109 113 114 116 121 124 125 Klasy Dziedziczenie Typ object Struktury Modyfikatory dostępu Interfejsy Wyliczenia Typy zagnieżdżone Typy generyczne 4 Zaawansowane elementy języka C# ...............................................................................139 139 147 153 157 158 166 171 177 180 182 183 191 193 194 198 200 Delegaty Zdarzenia Wyrażenia lambda Metody anonimowe Instrukcje try i wyjątki Wyliczanie i iteratory Typy wartościowe dopuszczające wartość null Przeciążanie operatorów Metody rozszerzające Typy anonimowe Wiązanie dynamiczne Atrybuty Atrybuty informacji wywołującego Niebezpieczny kod i wskaźniki Dyrektywy preprocesora Dokumentacja XML 5 Ogólny zarys platformy ..................................................................................................205 207 212 CLR i rdzeń platformy Technologie praktyczne 6 Podstawowe wiadomości o platformie ...........................................................................219 219 232 239 244 250 257 261 262 Obsługa łańcuchów i tekstu Data i godzina Daty i strefy czasowe Formatowanie i parsowanie Standardowe łańcuchy formatu i flagi parsowania Inne mechanizmy konwersji Globalizacja Praca z liczbami 4 (cid:95) Spis treści Poleć książkęKup książkę Wyliczenia Krotki Struktura Guid Sprawdzanie równości Określanie kolejności Klasy pomocnicze 266 269 271 271 281 284 7 Kolekcje ........................................................................................................................289 289 296 300 308 316 322 328 Przeliczalność Interfejsy ICollection i IList Klasa Array Listy, kolejki, stosy i zbiory Słowniki Kolekcje i pośredniki z możliwością dostosowywania Dołączanie protokołów równości i porządkowania 8 Zapytania LINQ ..............................................................................................................335 335 337 343 347 353 356 360 362 368 381 Podstawy Składnia płynna Wyrażenia zapytań Wykonywanie opóźnione Podzapytania Tworzenie zapytań złożonych Strategie projekcji Zapytania interpretowane LINQ to SQL i Entity Framework Budowanie wyrażeń zapytań 9 Operatory LINQ ..............................................................................................................387 388 391 395 406 413 416 419 420 423 425 429 430 Informacje ogólne Filtrowanie Projekcja Łączenie Porządkowanie Grupowanie Operatory zbiorów Metody konwersji Operatory elementów Metody agregacyjne Kwantyfikatory Metody generujące Spis treści (cid:95) 5 Poleć książkęKup książkę 10 LINQ to XML ...................................................................................................................433 433 434 437 440 444 447 450 453 458 459 Przegląd architektury Informacje ogólne o X-DOM Tworzenie drzewa X-DOM Nawigowanie i wysyłanie zapytań Modyfikowanie drzewa X-DOM Praca z wartościami Dokumenty i deklaracje Nazwy i przestrzenie nazw Adnotacje Projekcja do X-DOM 11 Inne technologie XML ....................................................................................................465 466 Klasa XmlReader 474 Klasa XmlWriter 476 Typowe zastosowania klas XmlReader i XmlWriter XSD i sprawdzanie poprawności schematów 480 483 XSLT 12 Zwalnianie zasobów i mechanizm usuwania nieużytków ................................................485 485 491 493 498 503 507 IDisposable, Dispose i Close Automatyczne usuwanie nieużytków Finalizatory Jak działa mechanizm usuwania nieużytków? Wycieki pamięci zarządzanej Słabe odwołania 13 Diagnostyka i kontrakty kodu .........................................................................................511 511 515 518 523 527 529 531 532 534 536 538 539 540 542 Kompilacja warunkowa Debugowanie i klasy monitorowania Ogólne omówienie kontraktów kodu Warunki początkowe Warunki końcowe Asercje i metody inwariantów obiektu Kontrakty w interfejsach i metodach abstrakcyjnych Rozwiązywanie problemów z awariami podczas użycia kontraktów Selektywne egzekwowanie kontraktów Statyczne sprawdzenie kontraktu Integracja z debuggerem Procesy i wątki procesów Klasy StackTrace i StackFrame Dziennik zdarzeń Windows 6 (cid:95) Spis treści Poleć książkęKup książkę Liczniki wydajności Klasa Stopwatch 544 549 14 Współbieżność i asynchroniczność ..................................................................................551 551 552 569 577 582 598 606 Wprowadzenie Wątkowanie Zadania Reguły asynchroniczności Funkcje asynchroniczne w języku C# Wzorce asynchroniczności Wzorce uznane za przestarzałe 15 Strumienie i wejście-wyjście ..........................................................................................611 611 613 626 634 636 637 648 650 653 Architektura strumienia Użycie strumieni Adapter strumienia Kompresja strumienia Praca z plikami w postaci archiwum ZIP Operacje na plikach i katalogach Plikowe operacje wejścia-wyjścia w środowisku uruchomieniowym Windows Mapowanie plików w pamięci Odizolowany magazyn danych 16 Sieć ...............................................................................................................................661 661 664 665 667 680 685 688 690 691 692 695 697 Architektura sieci Adresy i porty Adresy URI Klasy po stronie klienta Praca z HTTP Utworzenie serwera HTTP Użycie FTP Użycie DNS Wysyłanie poczty elektronicznej za pomocą SmtpClient Użycie TCP Otrzymywanie poczty elektronicznej POP3 za pomocą TCP TCP w środowisku uruchomieniowym Windows 17 Serializacja ....................................................................................................................699 699 703 713 715 Koncepcje serializacji Serializacja kontraktu danych Kontrakty danych i kolekcje Rozszerzenie kontraktu danych Spis treści (cid:95) 7 Poleć książkęKup książkę Serializacja binarna Atrybuty serializacji binarnej Serializacja binarna za pomocą ISerializable Serializacja XML 718 720 724 727 18 Podzespoły ....................................................................................................................737 737 742 745 748 751 754 762 768 769 771 Co znajduje się w podzespole? Silne nazwy i podpisywanie podzespołu Nazwy podzespołów Technologia Authenticode Global Assembly Cache Zasoby i podzespoły satelickie Wyszukiwanie i wczytywanie podzespołów Wdrażanie podzespołów poza katalogiem bazowym Umieszczenie w pojedynczym pliku wykonywalnym Praca z podzespołami, do których nie ma odwołań 19 Refleksje i metadane .....................................................................................................773 774 780 792 793 799 805 809 814 816 819 Refleksje i aktywacja typów Refleksje i wywoływanie składowych Refleksje dla podzespołów Praca z atrybutami Generowanie dynamicznego kodu Emitowanie podzespołów i typów Emitowanie składowych typów Emitowanie generycznych typów i klas Kłopotliwe cele emisji Parsowanie IL 20 Programowanie dynamiczne ..........................................................................................825 825 827 828 833 836 Dynamiczny system wykonawczy języka Unifikacja typów liczbowych Dynamiczne wybieranie przeciążonych składowych Implementowanie obiektów dynamicznych Współpraca z językami dynamicznymi 21 Bezpieczeństwo .............................................................................................................839 839 843 846 848 Uprawnienia Zabezpieczenia dostępu kodu Dopuszczanie częściowo zaufanych wywołujących Model transparentności 8 (cid:95) Spis treści Poleć książkęKup książkę Ograniczanie innego zestawu Zabezpieczenia systemu operacyjnego Tożsamości i role Kryptografia Windows Data Protection Obliczanie skrótów Szyfrowanie symetryczne Szyfrowanie kluczem publicznym i podpisywanie 856 860 862 864 864 865 867 871 22 Zaawansowane techniki wielowątkowości .....................................................................877 878 878 886 892 897 905 906 909 911 912 913 Przegląd technik synchronizacji Blokowanie wykluczające Blokady i bezpieczeństwo ze względu na wątki Blokowanie bez wykluczania Sygnalizacja przy użyciu uchwytów zdarzeń oczekiwania Klasa Barrier Leniwa inicjalizacja Pamięć lokalna wątku Metody Interrupt i Abort Metody Suspend i Resume Zegary 23 Programowanie równoległe ..........................................................................................917 917 920 933 939 948 951 954 Dlaczego PFX? PLINQ Klasa Parallel Równoległe wykonywanie zadań Klasa AggregateException Kolekcje współbieżne Klasa BlockingCollection T 24 Domeny aplikacji ........................................................................................................... 959 959 961 962 964 965 965 967 Architektura domeny aplikacji Tworzenie i likwidowanie domen aplikacji Posługiwanie się wieloma domenami aplikacji Metoda DoCallBack Monitorowanie domen aplikacji Domeny i wątki Dzielenie danych między domenami Spis treści (cid:95) 9 Poleć książkęKup książkę 25 Współpraca ....................................................................................................................973 973 974 977 977 978 981 985 986 990 990 991 Odwołania do natywnych bibliotek DLL Szeregowanie Wywołania zwrotne z kodu niezarządzanego Symulowanie unii C Pamięć współdzielona Mapowanie struktury na pamięć niezarządzaną Współpraca COM Wywołanie komponentu COM z C# Osadzanie typów współpracujących Główne moduły współpracujące Udostępnianie obiektów C# dla COM 26 Wyrażenia regularne .....................................................................................................993 994 998 999 1002 1003 1005 1008 Podstawy wyrażeń regularnych Kwantyfikatory Asercje o zerowej wielkości Grupy Zastępowanie i dzielenie tekstu Receptury wyrażeń regularnych Leksykon języka wyrażeń regularnych 27 Kompilator Roslyn .......................................................................................................1013 1014 1015 1030 Architektura Roslyn Drzewa składni Kompilacja i model semantyczny Skorowidz ...................................................................................................................1041 10 (cid:95) Spis treści Poleć książkęKup książkę 7 Kolekcje Platforma .NET Framework zapewnia standardowy zestaw typów do sortowania i obsługiwania kolekcji obiektów. Wśród nich można znaleźć listy o zmiennym rozmiarze, listy powiązane, sortowane i niesortowane słowniki oraz tablice. Z tych wszystkich typów jedynie tablice należą do języka C#. Pozostałe kolekcje są tylko klasami, których obiekty można tworzyć tak samo jak obiekty wszystkich innych klas. Typy kolekcji platformy można podzielić na następujące kategorie: (cid:120) interfejsy definiujące standardowe protokoły kolekcji; (cid:120) gotowe do użycia klasy kolekcji (listy, słowniki itd.); (cid:120) klasy bazowe do pisania kolekcji specjalnie dostosowanych do potrzeb konkretnych aplikacji. W tym rozdziale opisujemy wszystkie te kategorie oraz dodatkowo poświęcamy nieco miejsca typom wykorzystywanym do porównywania i porządkowania elementów. Oto wykaz przestrzeni nazw, w których znajdują się różne kolekcje. Przestrzeń nazw System.Collections System.Collections.Specialized System.Collections.Generic System.Collections.ObjectModel System.Collections.Concurrent Zawartość Niegeneryczne klasy i interfejsy kolekcji Silnie typizowane niegeneryczne klasy kolekcji Generyczne klasy i interfejsy kolekcji Klasy pośrednie i bazowe do tworzenia niestandardowych kolekcji Kolekcje bezpieczne wątkowo (zob. rozdział 23.) Przeliczalność Istnieje wiele różnych rodzajów kolekcji, od prostych struktur danych, przez tablice i listy powiązane po złożone struktury, takie jak drzewa czerwono-czarne i tablice skrótów. Choć konstrukcje te znacznie różnią się pod względem budowy zarówno wewnętrznych, jak i zewnętrznych cech, prawie wszystkie z nich można przeglądać. Platforma .NET zapewnia tę możliwość przez dwa interfejsy 289 Poleć książkęKup książkę (IEnumerable i IEnumerator oraz ich generyczne odpowiedniki), dzięki którym różne struktury danych udostępniają jednakowy interfejs API do przeglądania ich zawartości. Wymienione interfejsy należą do szerszego zbioru przedstawionego na rysunku 7.1. Rysunek 7.1. Interfejsy kolekcji Interfejsy IEnumerable i IEnumerator Interfejs IEnumerator definiuje podstawowy niskopoziomowy protokół określający sposób przeglą- dania elementów kolekcji do przodu. Jego deklaracja wygląda tak: public interface IEnumerator { bool MoveNext(); object Current { get; } void Reset(); } Metoda MoveNext przesuwa bieżący element zwany kursorem o jedną pozycję dalej i zwraca fałsz, jeśli był to ostatni element tej kolekcji. Metoda Current zwraca bieżący element (zazwyczaj rzutowany z typu object na bardziej konkretny typ). Metoda MoveNext musi zostać wywołana przed pobraniem pierwszego elementu — zasadę tę wprowadzono, aby było możliwe tworzenie pustych kolekcji. Metoda Reset, jeśli jest zaimplementowana, przenosi kursor z powrotem na początek, aby można było od nowa przejrzeć kolekcję. Metoda ta znajduje zastosowanie głównie przy współpracy z tech- nologią COM. Raczej nie wywołuje się jej bezpośrednio, ponieważ nie jest wszędzie obsługiwana (ani niezbędna, gdyż w większości przypadków równie dobrze można utworzyć nowy enumerator). Tylko nieliczne kolekcje implementują enumeratory. Większość udostępnia enumeratory przez interfejs IEnumerable: public interface IEnumerable { IEnumerator GetEnumerator(); } Dzięki definicji tylko jednej metody zwracającej enumerator interfejs IEnumerable zapewnia ela- styczność umożliwiającą dostarczenie logiki iteracji w innej klasie. Ponadto dzięki temu kolekcję może przeglądać kilku konsumentów jednocześnie i w żaden sposób nie będą sobie przeszkadzać. Interfejs IEnumerable można traktować jak „IEnumeratorProvider” i jest to najbardziej podstawowy interfejs implementowany przez klasy kolekcji. 290 (cid:95) Rozdział 7. Kolekcje Poleć książkęKup książkę Poniżej znajduje się przykład niskopoziomowego wykorzystania interfejsów IEnumerable i IEnumerator: string s = Cze(cid:258)(cid:202) ; // klasa string implementuje interfejs IEnumerable, wi(cid:266)c mo(cid:298)emy wywo(cid:225)a(cid:252) metod(cid:266) GetEnumerator(): IEnumerator rator = s.GetEnumerator(); while (rator.MoveNext()) { char c = (char) rator.Current; Console.Write (c + . ); } // wynik: C.z.e.(cid:286).(cid:252). Jednak taki bezpośredni sposób wywoływania metod na enumeratorach należy do rzadkości, ponie- waż w języku C# istnieje wygodny skrót składniowy w postaci instrukcji foreach. Oto ten sam przykład napisany z użyciem tej właśnie instrukcji: string s = Cze(cid:258)(cid:202) ; // klasa string implementuje interfejs IEnumerable foreach (char c in s) Console.Write (c + . ); Interfejsy IEnumerable T i IEnumerator T Interfejsy IEnumerator i IEnumerable są prawie zawsze implementowane w parze ze swoimi rozszerzo- nymi generycznymi wersjami: public interface IEnumerator T : IEnumerator, IDisposable { T Current { get; } } public interface IEnumerable T : IEnumerable { IEnumerator T GetEnumerator(); } Dzięki definicjom typizowanych wersji metod Current i GetEnumerator interfejsy te wzmacniają bezpieczeństwo typów, eliminują konieczność pakowania elementów typów wartościowych oraz są wygodniejsze w użyciu. Tablice automatycznie implementują interfejs IEnumerable T (gdzie T jest typem elementów przechowywanych w tablicy). Dzięki zwiększonemu bezpieczeństwu dla typów wywołanie poniższej metody z tablicą znaków spowoduje błąd kompilacji: void Test (IEnumerable int numbers) { ... } W klasach kolekcji standardowo udostępnia się publicznie elementy interfejsu IEnumerable T , a „ukrywa” elementy niegenerycznego interfejsu IEnumerable przez zastosowanie jawnej implementa- cji tego pierwszego. Dzięki temu, jeśli programista bezpośrednio wywoła metodę GetEnumerator(), to otrzyma bezpieczny pod względem typów IEnumerator T . Zdarzają się jednak przypadki złamania tej zasady ze względu na zgodność ze starym kodem (typy generyczne wprowadzono dopiero w C# 2.0). Dobrym przykładem są tablice, które muszą zwracać niegeneryczny (można ładniej powiedzieć: klasyczny) IEnumerator, aby nie spowodować awarii starych partii kodu. Aby otrzymać generycz- ny IEnumerator T , należy dokonać rzutowania, by udostępnić jawny interfejs: Przeliczalność (cid:95) 291 Poleć książkęKup książkę int[] data = { 1, 2, 3 }; var rator = ((IEnumerable int )data).GetEnumerator(); Na szczęście dzięki instrukcji foreach rzadko jest to konieczne. Interfejsy IEnumerable T i IDisposable IEnumerator T dziedziczy po IDisposable. Dzięki temu enumeratory mogą przechowywać referencje do takich zasobów jak połączenia z bazą danych i zwalniać je po zakończeniu lub przerwaniu pracy. Instrukcja foreach „ma świadomość” tego szczegółu i tłumaczy taki kod: foreach (var element in somethingEnumerable) { ... } na następujący logiczny ekwiwalent: using (var rator = somethingEnumerable.GetEnumerator()) while (rator.MoveNext()) { var element = rator.Current; ... } Kiedy używać interfejsów niegenerycznych Biorąc pod uwagę zwiększone bezpieczeństwo typowe generycznych interfejsów kolekcji, takich jak IEnumerable T , można zadać pytanie: czy niegeneryczna wersja IEnumerable (lub ICollection albo IList) w ogóle jest do czegoś potrzebna? Jeśli chodzi o interfejs IEnumerable, to musi być implementowany w połączeniu z IEnumerable T , ponieważ ten drugi korzysta z pierwszego. Jednak niezmiernie rzadko pisze się implementacje tych interfejsów od początku — najczęściej można wykorzystać metody iteracyjne, Collection T oraz LINQ. A co z konsumentem? Prawie zawsze wystarczają interfejsy generyczne. Wprawdzie niegeneryczne wersje też są czasami przydatne, choć raczej do zapewnienia spójności typów wszystkich elementów kolekcji. Poniższa metoda np. rekurencyjnie liczy elementy w każdej kolekcji: public static int Count (IEnumerable e) { int count = 0; foreach (object element in e) { var subCollection = element as IEnumerable; if (subCollection != null) count += Count (subCollection); else count++; } return count; } Ponieważ w języku C# występuje kowariancja interfejsów generycznych, można się spodziewać, że ta metoda powinna przyjmować typ IEnumerable object . To jednak nie udałoby się z elementami typów wartościowych i ze starymi kolekcjami, które nie implementują interfejsu IEnumerable T — przykładem jest ControlCollection z Windows Forms. Tak na marginesie: uważny czytelnik mógł zauważyć w naszym przykładzie potencjalny błąd — cykliczne referencje spowodują nieskończoną rekurencję i awarię metody. Problem ten można łatwo usunąć, używając kolekcji HashSet (zob. sekcję „Klasy HashSet T i SortedSet T ”). 292 (cid:95) Rozdział 7. Kolekcje Poleć książkęKup książkę Blok using zapewnia odpowiednie załatwienie zasobów — szerzej na temat interfejsu IDisposable piszemy w rozdziale 12. Implementowanie interfejsów przeliczeniowych Interfejsy IEnumerable i IEnumerable T można zaimplementować z następujących powodów: (cid:120) aby umożliwić korzystanie z instrukcji foreach; (cid:120) aby zapewnić możliwość współpracy ze wszystkim, co oczekuje standardowej kolekcji; (cid:120) aby spełnić wymagania bardziej zaawansowanego interfejsu kolekcji; (cid:120) aby obsługiwać inicjalizatory kolekcji. Aby zaimplementować interfejs IEnumerable lub IEnumerable T , należy dostarczyć enumerator. Można to zrobić na jeden z trzech sposobów: (cid:120) jeżeli klasa „opakowuje” inną kolekcję, można zwrócić enumerator tej opakowanej kolekcji; (cid:120) przez iterator za pomocą instrukcji yield return; (cid:120) tworząc własną implementację interfejsu IEnumerable lub IEnumerable T . Można też utworzyć podklasę istniejącej kolekcji. Klasa Collection T służy właśnie do tego celu (zob. podrozdział „Kolekcje i pośredniki z możliwością dostosowywania”). Inną możliwością jest użycie operatorów zapytań LINQ, o których mowa w następnym rozdziale. Zwrócenie enumeratora innej kolekcji jest zaledwie kwestią wywołania metody GetEnumerator na wewnętrznej kolekcji. Jednak takie rozwiązanie jest możliwe jedynie w najprostszych przypadkach, gdy elementy wewnętrznej kolekcji są dokładnie tym, czym powinny być. Bardziej elastyczne rozwiązanie polega na napisaniu iteratora przy użyciu instrukcji yield return. Iterator to element języka C# pomocny w pisaniu kolekcji na podobnej zasadzie, jak instrukcja foreach jest pomocna w ich konsumowaniu. Iterator automatycznie rozwiązuje kwestię implementacji interfejsów IEnumerable i IEnumerator lub ich generycznych wersji. Oto prosty przykład: public class MyCollection : IEnumerable { int[] data = { 1, 2, 3 }; public IEnumerator GetEnumerator() { foreach (int i in data) yield return i; } } Zwróć uwagę na zawartą w tym kodzie „czarną magię” — metoda GetEnumerator nie wygląda tak, jakby miała zwracać enumerator! Kompilator, parsując instrukcję yield return, tworzy ukrytą za- gnieżdżoną klasę enumeratora, a następnie refaktoryzuje metodę GetEnumerator w taki sposób, aby tworzyła i zwracała egzemplarz tej klasy. Iteratory są proste i potężne (i często znajdują zastosowa- nie w implementacji standardowych operatorów zapytań LINQ to Object). Przeliczalność (cid:95) 293 Poleć książkęKup książkę Zgodnie z tą linią możemy też zaimplementować generyczny interfejs IEnumerable T : public class MyGenCollection : IEnumerable int { int[] data = { 1, 2, 3 }; public IEnumerator int GetEnumerator() { foreach (int i in data) yield return i; } IEnumerator IEnumerable.GetEnumerator() // jawna implementacja { // ukrywa go return GetEnumerator(); } } Jako że interfejs IEnumerable T dziedziczy po IEnumerable, musimy zaimplementować zarówno generyczną, jak i niegeneryczną wersję metody GetEnumerator. Dodatkowo zgodnie ze standardową praktyką zaimplementowaliśmy też jawnie niegeneryczną wersję. Może ona po prostu wywoływać generyczną metodę GetEnumerator, ponieważ interfejs IEnumerator T dziedziczy po IEnumerator. Napisana przez nas klasa mogłaby zostać wykorzystana jako baza do napisania bardziej zaawanso- wanej kolekcji. Jeśli jednak potrzebna jest tylko prosta implementacja interfejsu IEnumerable T , instrukcja yield return umożliwia skorzystanie z prostszego rozwiązania. Zamiast pisać klasę, logikę iteracji można umieścić w metodzie zwracającej generyczną kolekcję typu IEnumerable T i resztę zostawić kompilatorowi. Oto przykład: public class Test { public static IEnumerable int GetSomeIntegers() { yield return 1; yield return 2; yield return 3; } } A oto przykład użycia naszej metody: foreach (int i in Test.GetSomeIntegers()) Console.WriteLine (i); // wynik 1 2 3 Ostatnim sposobem napisania metody GetEnumerator jest napisanie klasy bezpośrednio implementu- jącej interfejs IEnumerator. Dokładnie to robi kompilator niejawnie, przy rozpoznawaniu iteratorów. (Na szczęście nieczęsto trzeba posuwać się tak daleko we własnym kodzie). Poniżej znajduje się przykład kolekcji z wpisanymi na stałe wartościami 1, 2 i 3: public class MyIntList : IEnumerable { int[] data = { 1, 2, 3 }; 294 (cid:95) Rozdział 7. Kolekcje Poleć książkęKup książkę public IEnumerator GetEnumerator() { return new Enumerator (this); } class Enumerator : IEnumerator // definicja wewn(cid:266)trznej klasy { // dla enumeratora MyIntList collection; int currentIndex = -1; public Enumerator (MyIntList collection) { this.collection = collection; } public object Current { get { if (currentIndex == -1) throw new InvalidOperationException ( Enumeracja nie zosta(cid:239)a rozpocz(cid:218)ta! ); if (currentIndex == collection.data.Length) throw new InvalidOperationException ( Za ko(cid:241)cem listy! ); return collection.data [currentIndex]; } } public bool MoveNext() { if (currentIndex = collection.data.Length - 1) return false; return ++currentIndex collection.data.Length; } public void Reset() { currentIndex = -1; } } } Implementacja metody Reset jest nieobowiązkowa — ewentualnie można zgłaszać wyjątek NotSupportedException. Podkreślmy, że pierwsze wywołanie metody MoveNext powinno powodować przejście do pierwszego (a nie drugiego) elementu listy. Aby uzyskać funkcjonalność zbliżoną do iteratora, musimy jeszcze dodatkowo zaimplementować in- terfejs IEnumerator T . Poniżej przedstawiamy przykład z pominięciem testów granic dla uproszczenia: class MyIntList : IEnumerable int { int[] data = { 1, 2, 3 }; // Generyczny enumerator jest zgodny zarówno z IEnumerable, jak i IEnumerable T . // Implementujemy niegeneryczn(cid:261) metod(cid:266) GetEnumerator jawnie, aby unikn(cid:261)(cid:252) konfliktów nazw. public IEnumerator int GetEnumerator() { return new Enumerator(this); } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(this); } Przeliczalność (cid:95) 295 Poleć książkęKup książkę class Enumerator : IEnumerator int { int currentIndex = -1; MyIntList collection; public Enumerator (MyIntList collection) { this.collection = collection; } public int Current = collection.data [currentIndex]; object IEnumerator.Current = Current; public bool MoveNext() = ++currentIndex collection.data.Length; public void Reset() = currentIndex = -1; // Je(cid:286)li nie jest potrzebna metoda Dispose, to dobrym zwyczajem jest jej // jawne zaimplementowanie tak, aby by(cid:225)a niedost(cid:266)pna w interfejsie publicznym. void IDisposable.Dispose() {} } } Przykład z użyciem typów generycznych jest szybszy, ponieważ metoda IEnumerator int .Current nie wymaga rzutowania z int na object, a więc eliminuje pakowanie. Interfejsy ICollection i IList Choć interfejsy enumeracyjne stanowią protokół iteracji kolekcji tylko do przodu, nie zapewniają możliwości sprawdzania rozmiaru kolekcji, dostępu do składowych za pomocą indeksów, przeszu- kiwania struktur danych ani ich modyfikowania. Wszystkie te funkcje zapewniają interfejsy .NET Framework ICollection, IList oraz IDictionary. Każdy z nich występuje w wersji generycznej i nie- generycznej, choć te drugie istnieją głównie ze względu na zgodność ze starym kodem. Hierarchia dziedziczenia tych interfejsów jest pokazana na rysunku 7.1. Najprościej można je podsu- mować w następujący sposób: IEnumerable T (i IEnumerable) Zapewnia minimalną funkcjonalność (tylko przeglądanie). ICollection T (i ICollection) Zapewnia średni poziom funkcjonalności (np. własność Count). IList T /IDictionary K,V i ich niegeneryczne wersje Zapewnia najwyższy poziom funkcjonalności (wliczając dostęp „swobodny” przy użyciu indek- sów i kluczy). Konieczność implementowania któregokolwiek z tych interfejsów jest rzadkością. Gdy trzeba napisać klasę kolekcji, to prawie zawsze można wykorzystać do tego klasę ba- zową Collection T (zob. podrozdział „Kolekcje i pośredniki z możliwością dostosowy- wania”). W niektórych przypadkach inną możliwość zapewnia też technologia LINQ. 296 (cid:95) Rozdział 7. Kolekcje Poleć książkęKup książkę Różnice między wersjami generycznymi i niegenerycznymi są większe niż można się spodziewać, zwłaszcza w przypadku interfejsu ICollection. Przyczyny tego są w głównej mierze historyczne — typy generyczne powstały później, więc interfejsy generyczne tworzono z pewnym bagażem doświadczenia, dzięki czemu udało się dobrać inne (i lepsze) składowe. Dlatego właśnie interfejs ICollection T nie rozszerza interfejsu ICollection, IList T nie rozszerza interfejsu IList, a IDictionary TKey, TValue nie rozszerza interfejsu IDictionary. Oczywiście klasa kolekcji może implementować obie wersje interfejsu, jeśli jest to korzystne (a często jest). Innym, mniej oczywistym powodem, dla którego IList T nie rozszerza interfejsu IList, jest to, że rzutowanie na IList T zwracałoby interfejs ze składowymi Add(T) i Add(object). To z kolei oznaczałoby zniweczenie bezpieczeństwa typowego, po- nieważ można byłoby wywołać metodę Add z obiektem dowolnego typu. W tym podrozdziale są opisane interfejsy ICollection T , IList T i ich niegeneryczne wersje. Opis interfejsów słownikowych znajduje się w podrozdziale „Słowniki”. W obrębie platformy .NET Framework słowa kolekcja i lista są używane bez dającej się uchwycić logiki. Na przykład interfejs IList T jest bardziej funkcjonalną wersją interfejsu ICollection T , więc można oczekiwać, że klasa List T będzie tak samo bardziej funkcjonalna niż klasa Collection T . Jednak tak nie jest. Dlatego terminy kolekcja i lista najlepiej traktować jako synonimy, chyba że chodzi o konkretny typ. Interfejsy ICollection T i ICollection ICollection T to standardowy interfejs dla policzalnych kolekcji obiektów. Zapewnia możliwość sprawdzenia rozmiaru kolekcji (Count), sprawdzenia, czy dany element znajduje się w kolekcji (Contains), skopiowania kolekcji do tablicy (ToArray) oraz stwierdzenia, czy strukturę można modyfi- kować (IsReadOnly). W przypadku kolekcji z możliwością zapisu dostępne są także operacje Add, Remove i Clear. A ponieważ interfejs ten rozszerza IEnumerable T , kolekcje można przeglądać za pomocą instrukcji foreach: public interface ICollection T : IEnumerable T , IEnumerable { int Count { get; } bool Contains (T item); void CopyTo (T[] array, int arrayIndex); bool IsReadOnly { get; } void Add(T item); bool Remove (T item); void Clear(); } Niegeneryczny interfejs ICollection także opisuje policzalną kolekcję, ale nie zapewnia funkcji do modyfikowania listy ani sprawdzania, czy zawiera określony element: public interface ICollection : IEnumerable { int Count { get; } bool IsSynchronized { get; } Interfejsy ICollection i IList (cid:95) 297 Poleć książkęKup książkę object SyncRoot { get; } void CopyTo (Array array, int index); } Niegeneryczny interfejs dodatkowo definiuje własności przydatne w synchronizacji (rozdział 14.) — wstawiono je do wersji generycznej, ponieważ bezpieczeństwo wątków nie jest już uważane za cechę wewnętrzną kolekcji. Implementacja obu interfejsów jest łatwa. W przypadku implementacji interfejsu tylko do odczytu ICollection T metody Add, Remove i Clear powinny zgłaszać wyjątek NotSupportedException. Z reguły interfejsy te implementuje się łącznie z IList lub IDictionary. Interfejsy IList T i IList IList T to standardowy interfejs kolekcji indeksowanych pozycyjnie. Oprócz składników odziedzi- czonych z interfejsów ICollection T i IEnumerable T zawiera funkcje pozwalające odczytywać, zapisywać (za pomocą indeksatora), wstawiać oraz usuwać elementy wg pozycji: public interface IList T : ICollection T , IEnumerable T , IEnumerable { T this [int index] { get; set; } int IndexOf (T item); void Insert (int index, T item); void RemoveAt (int index); } Metody IndexOf wykonują przeszukiwanie liniowe listy i zwracają wartość -1, jeśli nie znajdą szukanego elementu. Niegeneryczna wersja interfejsu IList ma więcej składowych, ponieważ mniej dziedziczy po ICollection: public interface IList : ICollection, IEnumerable { object this [int index] { get; set } bool IsFixedSize { get; } bool IsReadOnly { get; } int Add (object value); void Clear(); bool Contains (object value); int IndexOf (object value); void Insert (int index, object value); void Remove (object value); void RemoveAt (int index); } Metoda Add niegenerycznego interfejsu IList zwraca liczbę całkowitą będącą indeksem nowo do- danego elementu. Dla porównania metoda Add interfejsu ICollection T ma typ zwrotny void. Ogólna klasa List T jest typową implementacją interfejsów IList T i IList. W języku C# także tablice implementują zarówno generyczną, jak i niegeneryczną wersję (choć metody dodające i usu- wające elementy są ukryte przez jawną implementację interfejsu i w razie wywołania zgłaszają wyjątek NotSupportedException). 298 (cid:95) Rozdział 7. Kolekcje Poleć książkęKup książkę Przy próbie uzyskania dostępu do wielowymiarowej tablicy za pomocą indeksatora interfejsu IList zgłaszany jest wyjątek ArgumentException. Jest to pułapka dla progra- mistów piszących takie metody jak poniższa: public object FirstOrNull (IList list) { if (list == null || list.Count == 0) return null; return list[0]; } Na pierwszy rzut oka może się wydawać, że to bardzo solidna funkcja, ale zgłosi wyjątek, jeśli ktoś wywoła ją na tablicy wielowymiarowej. W razie potrzeby w czasie działania programu można testować, czy argument nie jest tablicą wielowymiarową, za pomocą poniższego wyrażenia (szerzej na ten temat piszemy w rozdziale 19.): list.GetType().IsArray list.GetType().GetArrayRank() 1 Interfejs IReadOnlyList T W celu zapewniania możliwości współpracy z kolekcjami tylko do odczytu Windows Runtime w .NET Framework 4.5 wprowadzono nowy interfejs kolekcji o nazwie IReadOnlyList T . Jest on jednak przydatny sam w sobie i można go traktować jak okrojoną wersję interfejsu IList T udostęp- niającego tylko składowe potrzebne do wykonywania operacji odczytu na listach: public interface IReadOnlyList out T : IEnumerable T , IEnumerable { int Count { get; } T this[int index] { get; } } Ponieważ parametr typu jest wykorzystywany wyłącznie na pozycjach wyjściowych, oznaczono go jako kowariantny. Dzięki temu np. listę kotów można traktować jako listę zwierząt tylko do odczy- tu. Natomiast w interfejsie IList T parametr T nie jest oznaczony jako kowariantny, ponieważ znajduje zastosowanie zarówno na pozycjach wejściowych, jak i wyjściowych. Interfejs IReadOnlyList T reprezentuje widok listy tylko do odczytu. Nie oznacza to jednak, że podstawowa implementacja także musi być tylko do odczytu. Zgodnie z logiką interfejs IList T powinien dziedziczyć po IReadOnlyList T . Jednak firma Microsoft nie mogła wprowadzić takiej zmiany, ponieważ wymagałaby ona przeniesienia składowych z IList T do IReadOnlyList T , co spowodowałoby problemy z programami działającymi pod kontrolą CLR 4.5 (konsumenci musieliby ponownie skompilować swoje programy, aby uniknąć błędów wykonawczych). Dlatego implementatorzy interfejsu IList T muszą ręcznie dodawać do swoich kolekcji implementację interfejsu IReadOnlyList T . Interfejs IReadOnlyList T odpowiada typowi Windows Runtime IVectorView T . Interfejsy ICollection i IList (cid:95) 299 Poleć książkęKup książkę Klasa Array Klasa Array to podstawa wszystkich jedno- i wielowymiarowych tablic oraz jeden z podstawowych typów implementujących standardowe interfejsy kolekcji. Ponieważ zapewnia unifikację typów, wszystkie tablice dysponują takim samym zestawem metod, niezależnie od ich deklaracji i typu elementów. Ze względu na wielkie znaczenie tablic w języku C# utworzono specjalną składnię do ich deklaro- wania i inicjalizowania, której opis znajduje się w rozdziałach 2. i 3. Gdy programista deklaruje tablicę za pomocą składni C#, system CLR niejawnie generuje podtyp klasy Array, tworząc pseudotyp o odpowiednich wymiarach i typie elementów. Ten typ implementuje typizowane generyczne inter- fejsy kolekcji, np. IList string . Ponadto system CLR traktuje typy tablicowe w specjalny sposób, przypisując tworzonym obiektom ciągły obszar pamięci. Dzięki temu indeksowanie jest bardzo szybkie, ale za cenę braku możliwości zwiększenia rozmiaru struktury w późniejszym czasie. Klasa Array implementuje interfejsy kolekcji do IList T w wersjach generycznych i niegenerycz- nych. Natomiast interfejs IList T jest zaimplementowany jawnie w celu utrzymania porządku w interfejsie publicznym tablicy, który nie może udostępniać takich metod jak Add czy Remove, ponieważ zgłaszają one wyjątki, jeśli wywoła się je na kolekcji o niezmiennym rozmiarze. Wprawdzie klasa Array udostępnia statyczną metodę o nazwie Resize do zmieniania rozmiaru tablicy, ale jej działanie polega na utworzeniu nowej tablicy i skopiowaniu do niej wszystkich elementów. Nie dość, że ta operacja jest bardziej nieefektywna, to na dodatek wszystkie znajdujące się w różnych miejscach programu referencje nadal będą wskazywać starą strukturę. Jeśli potrzebny jest obiekt o zmiennym rozmiarze, to lepiej użyć klasy List T (opisanej w następnym podrozdziale). W tablicy można przechowywać elementy typów zarówno wartościowych, jak i referencyjnych. Elementy wartościowe są przechowywane bezpośrednio w tablicy, więc struktura zawierająca trzy liczby całkowite (każda po 8 bajtów) zajmuje ciągły obszar pamięci o rozmiarze 24 bajtów. Natomiast elementy typów referencyjnych zajmują w tablicy tylko tyle miejsca, ile potrzeba do przechowywania referencji (4 bajty w środowisku 32-bitowym i 8 bajtów w środowisku 64-bitowym). Na rysunku 7.2 pokazano, co dzieje się w pamięci po wykonaniu poniższego programu: StringBuilder[] builders = new StringBuilder [5]; builders [0] = new StringBuilder ( builder1 ); builders [1] = new StringBuilder ( builder2 ); builders [2] = new StringBuilder ( builder3 ); long[] numbers = new long [3]; numbers [0] = 12345; numbers [1] = 54321; Jako że Array to klasa, same tablice są typami referencyjnymi, niezależnie od rodzaju przechowywa- nych w nich elementów. Oznacza to, że wynikiem wykonania instrukcji tablicaB = tablicaA będzie powstanie dwóch zmiennych odnoszących się do tej samej tablicy. Jednocześnie test równości dwóch różnych tablic zawsze będzie miał wynik negatywny, chyba że programista użyje własnego 300 (cid:95) Rozdział 7. Kolekcje Poleć książkęKup książkę Rysunek 7.2. Tablice w pamięci komparatora. Jeden taki komparator wprowadzono w .NET Framework 4.0, aby umożliwić porów- nywanie elementów w tablicach lub krotkach. Dostęp do niego można uzyskać przez typ Structural (cid:180)Comparisons: object[] a1 = { string , 123, true }; object[] a2 = { string , 123, true }; Console.WriteLine (a1 == a2); // fa(cid:225)sz Console.WriteLine (a1.Equals (a2)); // fa(cid:225)sz IStructuralEquatable se1 = a1; Console.WriteLine (se1.Equals (a2, StructuralComparisons.StructuralEqualityComparer)); // prawda Tablice można kopiować za pomocą metody Clone: tablicaB = tablicaA.Clone(). Jednak w ten sposób można wykonać kopię płytką, czyli obejmującą tylko reprezentowany przez tablicę obszar pamięci. Jeżeli struktura zawiera elementy typów wartościowych, to zostaną one skopiowane. Ale jeżeli tablica zawiera obiekty typów referencyjnych, to skopiowane zostaną tylko referencje (w efekcie powstaną dwie tablice, których składowe odnoszą się do tych samych obiektów). Na rysunku 7.3 przedstawiono skutek dodania do naszego przykładu poniższego kodu: StringBuilder[] builders2 = builders; StringBuilder[] shallowClone = (StringBuilder[]) builders.Clone(); Jeśli potrzebna jest kopia głęboka — z duplikatami wszystkich obiektów typu referencyjnego — należy przejrzeć tablicę za pomocą pętli i każdy element sklonować ręcznie. Te same zasady dotyczą także innych typów kolekcji .NET. Choć klasa Array jest głównie przeznaczona do pracy z indeksatorami 32-bitowymi, to dzięki kilku metodom przyjmującym parametry Int32 i Int64 do pewnego stopnia obsługuje także indeksatory 64-bitowe (co teoretycznie zwiększa jej przestrzeń adresową do 264 elementów). W praktyce te przecią- żone wersje metod są jednak bezużyteczne, ponieważ maksymalny rozmiar obiektu w systemie CLR — w tym także tablic — wynosi 2 GB (zarówno w środowisku 32-bitowym, jak i 64-bitowym). Klasa Array (cid:95) 301 Poleć książkęKup książkę Rysunek 7.3. Płytkie kopiowanie tablicy Wiele z metod klasy Array, które mogą się wydawać metodami egzemplarzowymi, w istocie jest metodami statycznymi. Projektanci tej klasy podjęli dziwną decyzję pod tym względem i przez to programista szukający odpowiedniej metody musi sprawdzać zarówno wśród metod statycznych, jak i egzemplarzowych. Tworzenie i indeksowanie tablic Najprostszym sposobem na utworzenie i zindeksowanie tablicy jest użycie konstrukcji języka C#: int[] myArray = { 1, 2, 3 }; int first = myArray [0]; int last = myArray [myArray.Length - 1]; Ewentualnie tablicę można utworzyć dynamicznie za pomocą metody Array.CreateInstance, która pozwala na określenie typu elementów i liczby wymiarów oraz tworzenie tablic indeksowanych od innego numeru niż zero (przez określenie dolnej granicy). Tablice indeksowane w ten sposób są jednak niezgodne z CLS (ang. Common Language Specification). Dostęp do elementów dynamicznie utworzonej tablicy zapewniają metody GetValue i SetValue (działają też na zwykłych tablicach): // utworzenie tablicy (cid:225)a(cid:276)cuchów o d(cid:225)ugo(cid:286)ci 2 Array a = Array.CreateInstance (typeof(string), 2); a.SetValue ( Witaj, , 0); // (cid:314) a[0] = Witaj, ; a.SetValue ( (cid:258)wiecie , 1); // (cid:314) a[1] = (cid:286)wiecie ; string s = (string) a.GetValue (0); // (cid:314) s = a[0]; // istnieje te(cid:298) mo(cid:298)liwo(cid:286)(cid:252) rzutowania na tablice C# string[] cSharpArray = (string[]) a; string s2 = cSharpArray [0]; Tworzone dynamicznie tablice indeksowane od zera można rzutować na tablice C# takiego samego lub zgodnego typu (zgodnego wg zasad wariancji tablic). Jeśli np. klasa Apple jest pochodną klasy Fruit, to tablicę typu Apple[] można rzutować na typ Fruit[]. W tym momencie niektórzy mogą 302 (cid:95) Rozdział 7. Kolekcje Poleć książkęKup książkę się zastanawiać, dlaczego do unifikacji tablicy nie użyto typu object[], tylko klasy Array. Przyczyną jest to, że typ object[] jest niezgodny z wielowymiarowymi tablicami typów wartościowych (a także indeksowanych nie od zera). Tablicy typu int[] nie można rzutować na object[]. Dlatego potrzebu- jemy klasy Array w celu zapewnienia kompletnej unifikacji. Metody GetValue i SetValue działają też na tablicach stworzonych przez kompilator oraz są przy- datne przy pisaniu metod działających na tablicach dowolnego typu i rzędu. Dla tablic wielowymia- rowych przyjmują tablicę indeksatorów: public object GetValue (params int[] indices) public void SetValue (object value, params int[] indices) Poniższa metoda drukuje pierwszy element tablicy, niezależnie od tego, ile ma ona wymiarów: void WriteFirstValue (Array a) { Console.Write (a.Rank + -dimensional; ); // Tablica indeksatorów automatycznie zainicjalizuje wszystkie warto(cid:286)ci zerami, wi(cid:266)c przekazanie // jej do metody GetValue lub SetValue spowoduje pobranie lub ustawienie pierwszego elementu tablicy. int[] indexers = new int[a.Rank]; Console.WriteLine ( Pierwsza warto(cid:258)(cid:202) to + a.GetValue (indexers)); } void Demo() { int[] oneD = { 1, 2, 3 }; int[,] twoD = { {5,6}, {8,9} }; WriteFirstValue (oneD); // jednowymiarowa; pierwsza warto(cid:286)(cid:252) to 1 WriteFirstValue (twoD); // dwuwymiarowa; pierwsza warto(cid:286)(cid:252) to 5 } Do pracy z tablicami nieznanego typu, ale znanego rzędu można zastosować prostsze i efektywniejsze rozwiązanie z wykorzystaniem typów generycznych: void WriteFirstValue T (T[] array) { Console.WriteLine (array[0]); } Metoda SetValue zgłasza wyjątek, jeżeli element jest nieodpowiedniego dla danej tablicy typu. Gdy tworzona jest tablica, zarówno za pomocą składni języka, jak i metody Array.CreateInstance, jej elementy zostają automatycznie zainicjalizowane. Jeżeli elementy są typu referencyjnego, począt- kowo przypisywana jest im wartość null. Natomiast w przypadku elementów typów wartościowych zostaje wywołany konstruktor domyślny odpowiedniego typu (co w istocie oznacza wyzerowanie składowych). Ponadto klasa Array zawiera metodę Clear, za pomocą której w dowolnym mo- mencie można wyzerować tablicę: public static void Clear (Array array, int index, int length); Metoda ta nie zmienia rozmiaru tablicy, co kłóci się z jej typowym zastosowaniem (np. w ICollec (cid:180)tion T .Clear), polegającym na redukcji struktury do zera elementów. Klasa Array (cid:95) 303 Poleć książkęKup książkę Przeglądanie zawartości tablicy Zawartość tablicy można łatwo przejrzeć za pomocą instrukcji foreach: int[] myArray = { 1, 2, 3}; foreach (int val in myArray) Console.WriteLine (val); Ewentualnie można też użyć statycznej metody Array.ForEach o następującej definicji: public static void ForEach T (T[] array, Action T action); Wykorzystuje ona delegat Action o następującej sygnaturze: public delegate void Action T (T obj); Tak wygląda pierwszy przykład napisany z użyciem metody Array.ForEach: Array.ForEach (new[] { 1, 2, 3 }, Console.WriteLine); Długość i liczba wymiarów tablicy W klasie Array dostępne są następujące metody i własności do sprawdzania długości i liczby wymia- rów tablicy: public int GetLength (int dimension); public long GetLongLength (int dimension); public int Length { get; } public long LongLength { get; } public int GetLowerBound (int dimension); public int GetUpperBound (int dimension); public int Rank { get; } // zwraca liczb(cid:266) wymiarów tablicy Metody GetLength i GetLongLength zwracają długość wybranego wymiaru (0, jeśli tablica jest jedno- wymiarowa), natomiast metody Length i LongLength zwracają liczbę wszystkich elementów tablicy z wszystkich wymiarów łącznie. Metody GetLowerBound i GetUpperBound służą do pracy z tablicami indeksowanymi od innego numeru niż zero. Metoda GetUpperBound zwraca wynik równy sumie wartości GetLowerBound i GetLength dla wybranego wymiaru. Przeszukiwanie tablic Klasa Array udostępnia bogaty wybór metod do znajdowania elementów w tablicach jednowymia- rowych: Metody BinarySearch Do szybkiego szukania elementu w posortowanej tablicy. Metody IndexOf/LastIndex Do szukania elementu w nieposortowanej tablicy. Find, FindLast, FindIndex, FindLastIndex, FindAll, Exists, TrueForAll Do szukania w nieposortowanych tablicach elementów spełniających warunek Predicate T . 304 (cid:95) Rozdział 7. Kolekcje Poleć książkęKup książkę Żadna z metod przeszukujących nie zgłasza wyjątku, jeśli nie znajdzie szukanej wartości. Zamiast tego metody zwracające liczbę całkowitą zwracają -1 (dla tablic indeksowanych od zera), a metody zwracające typ generyczny zwracają domyślną wartość tego typu (np. 0 w przypadku int i null w przypadku string). Metody przeszukiwania binarnego są szybkie, ale działają tylko na posortowanych tablicach i wyma- gają porównywania elementów pod względem kolejności, a nie równości. W efekcie metody te przyj- mują obiekty typu IComparer i IComparer T określające definicję porządku w danym przypadku (zob. sekcję „Dołączanie protokołów równości i porządkowania”). Definicja ta musi być zgodna z komparatorem użytym pierwotnie do posortowania tablicy. Jeżeli nie zostanie przekazany żaden komparator, zostanie zastosowany domyślny algorytm porządkujący dla danego typu oparty na jego implementacji interfejsu IComparable/IComparable T . Metody IndexOf i LastIndexOf służą do prostego przeglądania elementów tablicy i zwracają numer pierwszej (lub ostatniej) pozycji podanej wartości. Metody działające na bazie predykatów przyjmują delegaty i wyrażenia lambda pozwalające stwierdzić, czy dana wartość „pasuje” do szukanej. Predykat to po prostu delegat przyjmujący obiekt i zwracający prawdę lub fałsz: public delegate bool Predicate T (T object); W poniższym przykładzie szukamy w tablicy łańcuchów imienia zawierającego literę a: static void Main() { string[] names = { Robert , Jacek , Juliusz }; string match = Array.Find (names, ContainsA); Console.WriteLine (match); // Jacek } static bool ContainsA (string name) { return name.Contains ( a ); } To jest ten sam kod, tylko skrócony dzięki użyciu metody anonimowej: string[] names = { Robert , Jacek , Juliusz }; string match = Array.Find (names, delegate (string name) { return name.Contains ( a ); } ); Wyrażenie lambda pozwala jeszcze bardziej skrócić kod: string[] names = { Robert , Jacek , Juliusz }; string match = Array.Find (names, n = n.Contains ( a )); // Jacek Metoda FindAll zwraca tablicę wszystkich elementów spełniających warunek predykatu. Metoda ta, równoznaczna z Enumerable.Where z przestrzeni nazw System.Linq, zwraca tylko tablicę pasujących elementów, a nie obiekt typu IEnumerable T . Metoda Exists zwraca prawdę, jeżeli którykolwiek element tablicy spełnia warunki danego predykatu, i jest równoważna metodzie Any z System.Linq.Enumerable. Metoda TrueForAll zwraca prawdę, jeżeli wszystkie elementy spełniają warunek predykatu, i jest równoważna z All z System.Linq.Enumerable. Klasa Array (cid:95) 305 Poleć książkęKup książkę Sortowanie Klasa Array ma następujące wbudowane metody sortujące: // do sortowania pojedynczej tablicy public static void Sort T (T[] array); public static void Sort (Array array); // do sortowania pary tablic public static void Sort TKey,TValue (TKey[] keys, TValue[] items); public static void Sort (Array keys, Array items); Każda z tych metod jest dodatkowo przeciążona i przyjmuje: int index // indeks, od którego ma by(cid:252) rozpocz(cid:266)te sortowanie int length // liczba elementów do posortowania IComparer T comparer // obiekt definiuj(cid:261)cy sposób porównywania Comparison T comparison // delegat definiuj(cid:261)cy sposób porównywania Poniżej znajduje się najprostszy przykład użycia metody Sort: int[] numbers = { 3, 2, 1 }; Array.Sort (numbers); // teraz tablica ma warto(cid:286)ci { 1, 2, 3 } Metody przyjmujące pary tablic przekładają elementy w każdej z tablic łącznie i opierają decyzje porządkowe na pierwszej tablicy. W następnym przykładzie zarówno liczby, jak i odpowiadające im słowa są sortowane w porządku numerycznym: int[] numbers = { 3, 2, 1 }; string[] words = { trzy , dwa , jeden }; Array.Sort (numbers, words); // teraz kolejno(cid:286)(cid:252) elementów w tablicy numbers to { 1, 2, 3 } // teraz kolejno(cid:286)(cid:252) elementów w tablicy words to { jeden , dwa , trzy } Metoda Array.Sort wymaga, aby znajdujące się w tablicy elementy implementowały interfejs IComparable (zob. podrozdział „Określanie kolejności” w rozdziale 6.). Oznacza to, że większość wbudowanych typów języka C# (takich jak użyte w poprzednim przykładzie liczby całkowite) można sortować. Jeżeli elementy nie mają określonego wewnętrznego porządku albo programista chce zmienić domyślną kolejność, konieczne jest przekazanie metodzie Sort własnego dostawcy comparison określającego względne ustawienie dwóch elementów. Można to zrobić na dwa sposoby: (cid:120) przy użyciu pomocniczego obiektu implementującego interfejs IComparer/Icomparer T (zob. podrozdział „Dołączanie protokołów równości i porządkowania”); (cid:120) przy użyciu delegatu Comparison: public delegate int Comparison T (T x, T y); Delegat Comparison posługuje się taką samą semantyką jak metoda IComparer T .CompareTo: jeśli x jest przed y, zostaje zwrócona ujemna liczba całkowita; jeśli x jest za y, zostaje zwrócona dodatnia liczba całkowita; jeśli x i y zajmują to samo miejsce, zostaje zwrócone 0. W poniższym przykładzie sortujemy tablicę liczb całkowitych w taki sposób, że najpierw ustawiane są liczby nieparzyste: 306 (cid:95) Rozdział 7. Kolekcje Poleć książkęKup książkę int[] numbers = { 1, 2, 3, 4, 5 }; Array.Sort (numbers, (x, y) = x 2 == y 2 ? 0 : x 2 == 1 ? -1 : 1); // zawarto(cid:286)(cid:252) tablicy numbers to { 1, 3, 5, 2, 4 } Zamiast metody Sort można też użyć operatorów LINQ OrderBy i ThenBy. W odróż- nieniu od metody Array.Sort operatory LINQ nie zmieniają pierwotnej tablicy, tylko wysyłają posortowane wyniki do nowej sekwencji IEnumerable T . Odwracanie kolejności elementów Poniższe metody klasy Array odwracają kolejność wszystkich lub niektórych elementów tablicy: public static void Reverse (Array array); public static void Reverse (Array array, int index, int length); Kopiowanie Klasa Array zawiera cztery metody do kopiowania płytkiego: Clone, CopyTo, Copy oraz ConstrainedCopy. Dwie pierwsze są metodami egzemplarzowymi, a pozostałe — statycznymi. Metoda Clone zwraca nową tablicę (stanowiącą płytką kopię oryginału). Metody CopyTo i Copy kopiują ciągły podzbiór elementów tablicy. Kopiowanie wielowymiarowej prostokątnej tablicy wymaga przeprowadzenia mapowania wielowymiarowych indeksów na liniowe. Na przykład środko- wy element (position[1,1]) w tablicy o wymiarach 3×3 ma indeks 4, który wynika z obliczeń 1*3+1. Zakresy źródłowy i docelowy mogą się bez problemu nakładać. Metoda ConstrainedCopy wykonuje operację atomową, tzn. jeśli nie można skopiować wszystkich potrzebnych elementów (np. z powodu błędnego typu), to cała operacja jest wycofywana. Ponadto klasa Array zawiera metodę AsReadOnly, która zwraca opakowanie uniemożliwiające ponowne przypisywanie wartości elementom. Konwertowanie i zmienianie rozmiarów tablic Metoda Array.ConvertAll tworzy i zwraca nową tablicę typu TOutput. Wywołuje w tym celu przeka- zany jej delegat Converter. Jej definicja wygląda następująco: public delegate TOutput Converter TInput,TOutput (TInput input) Poniżej znajduje się przykład konwersji tablicy liczb zmiennoprzecinkowych na tablicę liczb całko- witych: float[] reals = { 1.3f, 1.5f, 1.8f }; int[] wholes = Array.ConvertAll (reals, r = Convert.ToInt32 (r)); // zawarto(cid:286)(cid:252) tablicy wholes to { 1, 2, 2 } Metoda Resize tworzy nową tablicę i kopiuje do niej wszystkie elementy, a następnie zwraca tę nową strukturę przez parametr referencyjny. Mimo to referencje do pierwotnej tablicy przechowywane w innych obiektach pozostają niezmienione. Klasa Array (cid:95) 307 Poleć książkęKup książkę Przestrzeń System.Linq zawiera dodatkowy asortyment metod do konwersji tablic. Wszystkie one zwracają obiekty typu IEnumerable T , który można przekonwertować na tablicę za pomocą metody ToArray z klasy Enumerable. Listy, kolejki, stosy i zbiory Platforma zapewnia podstawowy zestaw konkretnych klas kolekcji implementujących opisane wcześniej interfejsy. W tym podrozdziale skupiamy się na kolekcjach listowych (których nie należy mylić ze strukturami słownikowymi opisanymi w podrozdziale „Słowniki”). Tak jak w przypadku opisanych wcześniej interfejsów, często mamy do wyboru zarówno generyczne, jak i niegeneryczne wersje każdego typu. Pod względem elastyczności i wydajności lepsze są klasy generyczne, przez co ich niegeneryczne odpowiedniki są w zasadzie niepotrzebne z wyjątkiem sytuacji, kiedy trzeba zapewnić zgodność ze starym kodem. Jest więc inaczej niż w przypadku interfejsów kolekcji, które bywają przydatne także w wersjach niegenerycznych. Z opisanych w tym podrozdziale klas najczęściej używana jest generyczna klasa List. Klasy List T i ArrayList Generyczna klasa List i niegeneryczna klasa ArrayList umożliwiają tworzenie tablic obiektów o dy- namicznym rozmiarze i należą do najczęściej używanych klas kolekcji. Klasa ArrayList implementuje interfejs IList, podczas gdy List T implementuje zarówno IList, jak i IList T (jak również nową wersję tylko do odczytu o nazwie IReadOnlyList T ). Inaczej niż w przypadku tablic, wszystkie interfejsy są zaimplementowane publicznie i zgodnie z oczekiwaniami metody takie jak Add czy Remove są dostępne. Wewnętrznie klasy List T i ArrayList utrzymują tablicę obiektów, którą w razie osiągnięcia maksimum pojemności zamieniają na większą. Dodawanie elementów na końcu to efektywna operacja (ponieważ zazwyczaj na końcu jest wolne miejsce), natomiast wstawianie elementów w środ- ku pomiędzy innymi elementami może być powolne (ponieważ trzeba przesunąć wszystkie elementy znajdujące się za miejscem wstawiania). Jeśli chodzi o przeszukiwanie, to podobnie jak w przypadku tablic operacja jest efektywna, jeżeli przeprowadza się ją za pomocą metody BinarySearch na posorto- wanej liście. W innych sytuacjach wyszukiwanie jest mało wydajne, gdyż wymaga sprawdzenia każ- dego elementu po kolei. Klasa List T jest do kilku razy szybsza od ArrayList, gdy T jest typem wartościo- wym, ponieważ List T nie wymaga pakowania ani odpakowywania elementów. Klasy List T i ArrayList zawierają konstruktory przyjmujące istniejące kolekcje elementów — kopiują one wszystkie elementy z przekazanej kolekcji do nowej struktury: public class List T : IList T , IReadOnlyList T { public List (); public List (IEnumerable T collection); public List (int capacity); 308 (cid:95) Rozdział 7. Kolekcje Poleć książkęKup książkę // Add+Insert public void Add (T item); public void AddRange (IEnumerable T collection); public void Insert (int index, T item); public void InsertRange (int index, IEnumerable T collection); // Remove public bool Remove (T item); public void RemoveAt (int index); public void RemoveRange (int index, int count); public int RemoveAll (Predicate T match); // indeksowanie public T this [int index] { get; set; } public List T GetRange (int index, int count); public Enumerator T GetEnumerator(); // eksportowanie, kopiowanie i konwertowanie public T[] ToArray(); public void CopyTo (T[] array); public void CopyTo (T[] array, int arrayIndex); public void CopyTo (int index, T[] array, int arrayIndex, int count); public ReadOnlyCollection T AsReadOnly(); public List TOutput ConvertAll TOutput (Converter T,TOutput converter); // inne public void Reverse(); // odwraca kolejno(cid:286)(cid:252) elementów listy public int Capacity { get;set; } // wymusza rozszerzenie wewn(cid:266)trznej tablicy public void TrimExcess(); // obcina wewn(cid:266)trzn(cid:261) tablic(cid:266) do potrzebnego rozmiaru public void Clear(); // usuwa wszystkie elementy, tak (cid:298)e Count=0 } public delegate TOutput Converter TInput, TOutput (TInput input); Oprócz tych składowych klasa List T zawiera egzemplarzowe wersje wszystkich metod przeszuki- wania i sortowania klasy Array. Poniżej znajduje się przykład demonstrujący sposób użycia własności i metod klasy List. Przykłady przeszukiwania i sortowania zamieściliśmy w podrozdziale „Klasa Array”. List string words = new List string (); // nowa lista typu string words.Add ( melon ); words.Add ( awokado ); words.AddRange (new[] { banan , pomara(cid:241)cza } ); words.Insert (0, liczi ); // wstawianie na pocz(cid:261)tku words.InsertRange (0, new[] { pomelo , nashi }); // wstawianie na pocz(cid:261)tku words.Remove ( melon ); words.RemoveAt (3); // usuni(cid:266)cie czwartego elementu words.RemoveRange (0, 2); // usuni(cid:266)cie dwóch pierwszych elementów // usuni(cid:266)cie wszystkich (cid:225)a(cid:276)cuchów zaczynaj(cid:261)cych si(cid:266) liter(cid:261) n words.RemoveAll (s = s.StartsWith ( n )); Console.WriteLine (words [0]); // pierwsze s(cid:225)owo Console.WriteLine (words [words.Count - 1]); // ostatnie s(cid:225)owo foreach (string s in words) Console.WriteLine (s); // wszystkie s(cid:225)owa List string subset = words.GetRange (1, 2); // drugie i trzecie s(cid:225)owo Listy, kolejki, stosy i zbiory (cid:95) 309 Poleć książkęKup książkę string[] wordsArray = words.ToArray(); // tworzy now(cid:261) tablic(cid:266) typizowan(cid:261) // kopiowanie pierwszych dwóch elementów na koniec istniej(cid:261)cej tablicy string[] existing = new string [1000]; words.CopyTo (0, existing, 998, 2); List string upperCastWords = words.ConvertAll (s = s.ToUpper()); List int lengths = words.ConvertAll (s = s.Lengt
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

C# 6.0 w pigułce. Wydanie VI
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ą: