Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00258 006122 13651808 na godz. na dobę w sumie
Język C#. Programowanie. Wydanie III. Microsoft .NET Development Series - książka
Język C#. Programowanie. Wydanie III. Microsoft .NET Development Series - książka
Autor: , , , Liczba stron: 784
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-246-2195-8 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> c# - programowanie
Porównaj ceny (książka, ebook, audiobook).

Poznaj możliwości języka C# i twórz wysoko wydajne aplikacje

Nowoczesny i bezpieczny język programowania C# posiada kilka cech, które ułatwiają opracowywanie solidnych i wydajnych aplikacji -- na przykład obsługę wyjątków, wymuszanie bezpieczeństwa typów lub mechanizm odzyskiwania pamięci, czyli automatyczne odzyskiwanie pamięci operacyjnej zajmowanej przez nieużywane obiekty. C# 3.0 oferuje możliwość programowania funkcjonalnego oraz technologię LINQ (zapytań zintegrowanych z językiem), co znacząco poprawia wydajność pracy programisty.

Książka 'Język C#. Programowanie. Wydanie III. Microsoft .NET Development Series' zawiera pełną specyfikację techniczną języka programowania C#, opatrzoną najnowszymi zaktualizowanymi informacjami, m.in. na temat inicjalizatorów obiektów i kolekcji, typów anonimowych czy wyrażeń lambda. Dzięki licznym komentarzom i praktycznym poradom, które uzupełniają główną treść podręcznika, szybko nauczysz się posługiwać zmiennymi, przeprowadzać konwersje funkcji i wyznaczać przeładowania. Dowiesz się, jak optymalnie i z fascynującym efektem końcowym wykorzystywać ten nowoczesny język programowania.

Wykorzystaj wiedzę i doświadczenie najlepszych specjalistów, aby sprawnie posługiwać się językiem C#

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

Darmowy fragment publikacji:

Jêzyk C#. Programowanie. Wydanie III. Microsoft .NET Development Series Autor: Anders Hejlsberg, Mads Torgersen, Scott Wiltamuth, Peter Golde T³umaczenie: £ukasz Suma ISBN: 978-83-246-2195-8 Tytu³ orygina³u: The C# Programming Format: 170x230, stron: 784 Poznaj mo¿liwoœci jêzyka C# i twórz wysoko wydajne aplikacje • Jak u¿ywaæ instrukcji wyra¿eñ? • Jak korzystaæ z typów wyliczeniowych? • Jak definiowaæ i stosowaæ atrybuty? Nowoczesny i bezpieczny jêzyk programowania C# posiada kilka cech, które u³atwiaj¹ opracowywanie solidnych i wydajnych aplikacji — na przyk³ad obs³ugê wyj¹tków, wymuszanie bezpieczeñstwa typów lub mechanizm odzyskiwania pamiêci, czyli automatyczne odzyskiwanie pamiêci operacyjnej zajmowanej przez nieu¿ywane obiekty. C# 3.0 oferuje mo¿liwoœæ programowania funkcjonalnego oraz technologiê LINQ (zapytañ zintegrowanych z jêzykiem), co znacz¹co poprawia wydajnoœæ pracy programisty. Ksi¹¿ka „Jêzyk C#. Programowanie. Wydanie III. Microsoft .NET Development Series” zawiera pe³n¹ specyfikacjê techniczn¹ jêzyka programowania C#, opatrzon¹ najnowszymi zaktualizowanymi informacjami, m.in. na temat inicjalizatorów obiektów i kolekcji, typów anonimowych czy wyra¿eñ lambda. Dziêki licznym komentarzom i praktycznym poradom, które uzupe³niaj¹ g³ówn¹ treœæ podrêcznika, szybko nauczysz siê pos³ugiwaæ zmiennymi, przeprowadzaæ konwersje funkcji i wyznaczaæ prze³adowania. Dowiesz siê, jak optymalnie i z fascynuj¹cym efektem koñcowym wykorzystywaæ ten nowoczesny jêzyk programowania. • Typy i zmienne • Klasy i obiekty • Struktura leksykalna • Deklaracje struktur • Sk³adowe • Konwersje i wyra¿enia • Instrukcje i operatory • Tablice • Interfejsy • Kod nienadzorowany • WskaŸniki w wyra¿eniach • Bufory o ustalonym rozmiarze • Dynamiczne alokowanie pamiêci Wykorzystaj wiedzê i doœwiadczenie najlepszych specjalistów, aby sprawnie pos³ugiwaæ siê jêzykiem C# Spis treści Słowo wstępne 11 Przedmowa 13 O autorach 15 O komentatorach 17 1. Wprowadzenie 19 Struktura programu 22 1.1. Witaj, świecie 20 1.2. 1.3. Typy i zmienne 24 1.4. Wyrażenia 29 1.5. Instrukcje 32 1.6. Klasy i obiekty 36 1.7. 1.8. Tablice 62 1.9. 1.10. Typy wyliczeniowe 66 1.11. Delegacje 68 1.12. Atrybuty 72 Struktury 59 Interfejsy 64 2. Struktura leksykalna 75 2.1. Programy 75 2.2. Gramatyka 75 2.3. Analiza leksykalna 77 2.4. Tokeny 81 2.5. Dyrektywy preprocesora 94 5 Spis treści 3. Podstawowe pojęcia 107 3.1. Uruchomienie aplikacji 107 3.2. Zakończenie aplikacji 108 3.3. Deklaracje 109 3.4. Składowe 113 3.5. Dostęp do składowych 115 3.6. 3.7. Zakresy 126 3.8. 3.9. Automatyczne zarządzanie pamięcią 138 3.10. Kolejność wykonania 143 Przestrzeń nazw i nazwy typów 133 Sygnatury i przeładowywanie 124 4. Typy 145 Pakowanie i rozpakowywanie 160 4.1. Typy wartościowe 146 4.2. Typy referencyjne 157 4.3. 4.4. Typy skonstruowane 164 4.5. 4.6. Typy drzew wyrażeń 169 Parametry typu 168 5. Zmienne 171 5.1. Kategorie zmiennych 171 5.2. Wartości domyślne 177 5.3. Ustalenie niewątpliwe 177 5.4. Referencje zmiennych 194 5.5. Niepodzielność referencji zmiennych 194 6. Konwersje 195 6.1. Konwersje niejawne 196 6.2. Konwersje jawne 202 6.3. Konwersje standardowe 210 6.4. Konwersje definiowane przez użytkownika 211 6.5. Konwersje funkcji anonimowych 216 6.6. Konwersje grup metod 223 6 7. Wyrażenia 227 Funkcje składowe 242 7.1. Klasyfikacje wyrażeń 227 7.2. Operatory 230 7.3. Odnajdywanie składowych 239 7.4. 7.5. Wyrażenia podstawowe 262 7.6. Operatory jednoargumentowe 306 7.7. Operatory arytmetyczne 311 7.8. Operatory przesunięcia 320 7.9. Operatory relacyjne i testowania typu 322 7.10. Operatory logiczne 332 7.11. Logiczne operatory warunkowe 334 7.12. Operator łączenia pustego 337 7.13. Operator warunkowy 339 7.14. Wyrażenia funkcji anonimowych 340 7.15. Wyrażenia zapytań 350 7.16. Operatory przypisań 363 7.17. Wyrażenia 369 7.18. Wyrażenia stałe 369 7.19. Wyrażenia boole’owskie 371 8. Punkty końcowe i osiągalność 374 Bloki 375 Instrukcja pusta 377 Instrukcje oznaczone 378 Instrukcje deklaracji 379 Instrukcje wyrażeń 383 Instrukcje wyboru 383 Instrukcje iteracji 390 Instrukcje skoku 398 Instrukcje 373 8.1. 8.2. 8.3. 8.4. 8.5. 8.6. 8.7. 8.8. 8.9. 8.10. Instrukcja try 405 8.11. Instrukcje checked i unchecked 409 8.12. Instrukcja lock 410 8.13. Instrukcja using 412 8.14. Instrukcja yield 414 Spis treści 7 Spis treści 9. Przestrzenie nazw 419 Jednostki kompilacji 419 9.1. 9.2. Deklaracje przestrzeni nazw 420 9.3. Synonimy zewnętrzne 421 9.4. Dyrektywy używania 422 9.5. 9.6. Deklaracje typów 429 9.7. Kwalifikatory synonimów przestrzeni nazw 430 Składowe przestrzeni nazw 429 10. Klasy 433 10.1. Deklaracje klas 433 10.2. Typy częściowe 446 10.3. Składowe klas 455 10.4. Stałe 469 10.5. Pola 471 10.6. Metody 481 10.7. Właściwości 503 10.8. Zdarzenia 516 10.9. Indeksatory 524 10.10. Operatory 528 10.11. Konstruktory instancji 535 10.12. Konstruktory statyczne 543 10.13. Destruktory 545 10.14. Iteratory 547 11. Struktury 563 11.1. Deklaracje struktur 563 11.2. Składowe struktury 565 11.3. Różnice między klasą a strukturą 565 11.4. Przykłady struktur 574 12. Tablice 579 12.1. Typy tablicowe 579 12.2. Tworzenie tablic 581 12.3. Dostęp do elementów tablic 582 8 12.4. Składowe tablic 582 12.5. Kowariancja tablic 582 12.6. Inicjalizatory tablic 583 13. Interfejsy 587 13.1. Deklaracje interfejsów 587 13.2. Składowe interfejsu 590 13.3. W pełni kwalifikowane nazwy składowych interfejsu 595 13.4. Implementacje interfejsów 596 14. Typy wyliczeniowe 611 14.1. Deklaracje typów wyliczeniowych 611 14.2. Modyfikatory typów wyliczeniowych 612 14.3. Składowe typów wyliczeniowych 612 14.4. Typ System.Enum 615 14.5. Wartości typów wyliczeniowych i związane z nimi operacje 616 15. Delegacje 617 15.1. Deklaracje delegacji 618 15.2. Zgodność delegacji 621 15.3. Realizacja delegacji 621 15.4. Wywołanie delegacji 622 16. Wyjątki 625 16.1. Przyczyny wyjątków 625 16.2. Klasa System.Exception 626 16.3. Sposób obsługi wyjątków 626 16.4. Najczęściej spotykane klasy wyjątków 627 17. Atrybuty 629 17.1. Klasy atrybutów 629 17.2. Specyfikacja atrybutu 633 17.3. Instancje atrybutów 639 17.4. Atrybuty zarezerwowane 641 17.5. Atrybuty umożliwiające współdziałanie 646 Spis treści 9 Spis treści 18. Kod nienadzorowany 649 18.1. Konteksty nienadzorowane 650 18.2. Typy wskaźnikowe 653 18.3. Zmienne utrwalone i ruchome 656 18.4. Konwersje wskaźników 657 18.5. Wskaźniki w wyrażeniach 660 18.6. Instrukcja fixed 667 18.7. Bufory o ustalonym rozmiarze 672 18.8. Alokacja na stosie 675 18.9. Dynamiczne alokowanie pamięci 677 A. Komentarze dokumentacji 679 A.1. Wprowadzenie 679 A.2. Zalecane znaczniki 681 A.3. Przetwarzanie pliku dokumentacji 691 A.4. Przykład 697 B. Gramatyka 703 B.1. Gramatyka leksykalna 703 B.2. Gramatyka składniowa 712 B.3. Rozszerzenia gramatyczne związane z kodem nienadzorowanym 742 C. Źródła informacji uzupełniających 747 Skorowidz 749 10 8. Instrukcje Język C# oferuje szeroką gamę instrukcji. Większość z nich powinna być dobrze znana progra- mistom, którzy mieli okazję korzystać z języków C i C++. instrukcja: instrukcja-oznaczona instrukcja-deklaracji instrukcja-osadzona instrukcja-osadzona: blok instrukcja-pusta instrukcja-wyrażenia instrukcja-wyboru instrukcja-iteracji instrukcja-skoku instrukcja-try instrukcja-checked instrukcja-unchecked instrukcja-lock instrukcja-using instrukcja-yield Nieterminalny element instrukcja-osadzona jest używany w przypadku instrukcji, które wystę- pują w ramach innych instrukcji. Zastosowanie elementu instrukcja-osadzona zamiast elementu instrukcja wyklucza użycie instrukcji deklaracji i instrukcji oznaczonych w tych kontekstach. Skompilowanie przedstawionego poniżej fragmentu kodu: void F(bool b) { if (b) int i = 44; } powoduje zgłoszenie błędu, ponieważ instrukcja if wymaga zastosowania w swojej gałęzi elementu instrukcja-osadzona, nie zaś elementu instrukcja. Gdyby było to dopuszczalne, zmienna i mogłaby zostać zadeklarowana, lecz mogłaby nie zostać nigdy użyta. Umieszczenie deklaracji i w bloku powoduje, że przykładowy kod staje się poprawny. 373 8. Instrukcje 8.1. Punkty końcowe i osiągalność Każda instrukcja ma punkt końcowy (ang. end point). W intuicyjnym znaczeniu punktem końcowym instrukcji jest miejsce, które znajduje się bezpośrednio za instrukcją. Reguły wykonania instrukcji złożonych (czyli instrukcji zawierających instrukcje osadzone) określają działanie, które jest przeprowadzane, gdy sterowanie osiąga punkt końcowy instrukcji osadzonej. Na przykład gdy sterowanie osiąga punkt końcowy instrukcji w bloku, jest ono przekazywane do kolejnej instrukcji znajdującej się w tym bloku. Jeśli instrukcja może potencjalnie zostać osiągnięta przez sterowanie, wówczas instrukcja nazy- wana jest osiągalną (ang. reachable). Dla odmiany, jeśli nie istnieje możliwość, aby instrukcja została wykonana, nosi ona nazwę nieosiągalnej (ang. unreachable). W przedstawionym poniżej przykładzie: void F() { Console.WriteLine( osiągalna ); goto Label; Console.WriteLine( nieosiągalna ); Label: Console.WriteLine( osiągalna ); } drugie wywołanie funkcji Console.WriteLine jest nieosiągalne, ponieważ nie ma możliwości, aby instrukcja ta została wykonana. Gdy kompilator stwierdza, że jakaś instrukcja jest nieosiągalna, generowane jest ostrzeżenie. Warto jednak podkreślić, że sytuacja taka nie stanowi błędu. Aby określić, czy pewna instrukcja lub punkt końcowy są osiągalne, kompilator przeprowadza ana- lizę przepływu sterowania zgodnie z regułami osiągalności zdefiniowanymi dla każdej instrukcji. W analizie przepływu sterowania bierze się pod uwagę wartości wyrażeń stałych (opisanych dokładniej w podrozdziale 7.18), które kontrolują zachowanie instrukcji, lecz nie są uwzględniane potencjalne wartości wyrażeń niestałych. Innymi słowy, dla potrzeb analizy przepływu sterowania przyjmuje się, że wyrażenie niestałe danego typu może mieć dowolną wartość, jaką można przed- stawić za pomocą tego typu. W przedstawionym poniżej przykładzie: void F() { const int i = 1; if (i == 2) Console.WriteLine( nieosiągalna ); } wyrażenie boole’owskie instrukcji if jest wyrażeniem stałym, ponieważ obydwa operandy ope- ratora == są stałe. Jako takie wyrażenie to jest przetwarzane na etapie kompilacji, dając w wyniku wartość false, przez co wywołanie funkcji Console.WriteLine zostaje uznane za nieosiągalne. Jeśli jednak i zastąpione jest zmienną lokalną, tak jak zostało to pokazane poniżej: 374 8.2. Bloki void F() { int i = 1; if (i == 2) Console.WriteLine( osiągalna ); } wywołanie funkcji Console.WriteLine zostaje uznane za instrukcję osiągalną, mimo że w rzeczy- wistości nigdy nie będzie wykonywane. Blok funkcji składowej jest zawsze uważany za osiągalny. Dzięki sukcesywnemu stosowaniu zasad osiągalności dla każdej instrukcji należącej do bloku da się określić osiągalność każdej podanej instrukcji. W przedstawionym poniżej przykładzie: void F(int x) { Console.WriteLine( początek ); if (x 0) Console.WriteLine( ujemna ); } osiągalność drugiego wywołania funkcji Console.WriteLine jest określana w następujący sposób: • Pierwsza instrukcja wyrażenia Console.WriteLine jest osiągalna, ponieważ osiągalny jest blok metody F. • Punkt końcowy pierwszej instrukcji wyrażenia Console.WriteLine jest osiągalny, ponieważ osiągalna jest ta instrukcja. • Instrukcja if jest osiągalna, ponieważ osiągalny jest punkt końcowy instrukcji wyrażenia Console.WriteLine. • Druga instrukcja wyrażenia Console.WriteLine jest osiągalna, ponieważ wyrażenie boole’owskie instrukcji if nie ma stałej wartości false. Istnieją dwie sytuacje, w których pojawia się błąd czasu kompilacji, gdy punkt końcowy instrukcji jest osiągalny: • Ponieważ przypadku instrukcji switch nie jest dozwolone, aby sekcja „wpadała” do kolejnej sekcji, za błąd czasu kompilacji uważa się przypadek, gdy osiągalny jest punkt końcowy listy instrukcji sekcji switch. Wystąpienie takiego błędu oznacza zwykle brak instrukcji break. • Błąd czasu kompilacji pojawia się, gdy osiągalny jest punkt końcowy bloku funkcji składowej, która oblicza wartość. Wystąpienie takiego błędu oznacza zwykle brak instrukcji return. 8.2. Bloki Blok umożliwia zapisywanie wielu instrukcji w kontekstach, w których dopuszczalne jest użycie pojedynczej instrukcji: blok: { lista-instrukcjiopc } 375 8. Instrukcje Blok składa się z ujętej w nawiasy klamrowe opcjonalnej lista-instrukcji (o której więcej w punkcie 8.2.1). Jeśli lista instrukcji nie występuje, blok nazywa się pustym. Blok może zawierać instrukcje deklaracji (o których więcej w podrozdziale 8.5). Zakresem lokalnej zmiennej lub stałej zadeklarowanej w bloku jest ten blok. W ramach bloku znaczenie nazwy używanej w kontekście wyrażenia zawsze musi być takie samo (o czym więcej w podpunkcie 7.5.2.1). Blok jest wykonywany w następujący sposób: • Jeśli blok jest pusty, sterowanie jest przekazywane do punktu końcowego bloku. • Jeśli blok nie jest pusty, sterowanie jest przekazywane do listy instrukcji. Gdy i jeśli sterowanie osiąga punkt końcowy listy instrukcji, sterowanie jest przekazywane do punktu końcowego bloku. Lista instrukcji bloku jest osiągalna, jeśli sam blok jest osiągalny. Punkt końcowy bloku jest osiągalny, jeśli blok ten jest osiągalny i jeśli jest on pusty lub jeśli osiągalny jest punkt końcowy listy wyrażeń. Blok zawierający jedno lub większą liczbę instrukcji yield (o których więcej w podrozdziale 8.14) nosi nazwę bloku iteratora. Bloki iteratorów są używane do implementacji funkcji składowych jako iteratorów (o czym więcej w podrozdziale 10.14). Bloków iteratorów dotyczą pewne dodatkowe ograniczenia: • Błędem czasu kompilacji jest, gdy w bloku iteratora pojawia się instrukcja return (dozwolone są tu jednak instrukcje yield return). • Błędem czasu kompilacji jest, gdy blok iteratora zawiera kontekst nienadzorowany (opisany w podrozdziale 18.1). Blok iteratora zawsze określa kontekst nadzorowany, nawet jeśli jego deklaracja jest zagnieżdżona w kontekście nienadzorowanym. 8.2.1. Lista instrukcji Lista instrukcji (ang. statement list) zawiera jedną lub większą liczbę instrukcji zapisanych w postaci ciągu. Listy instrukcji występują w elementach blok (opisanych w podrozdziale 8.2) oraz w elementach blok-switch (o których więcej w punkcie 8.7.2): lista-instrukcji: instrukcja lista-instrukcji instrukcja Lista instrukcji jest wykonywana przez przekazanie sterowania do pierwszej instrukcji. Gdy i jeśli sterowanie osiąga końcowy punkt instrukcji, jest ono przekazywane do następnej. Gdy i jeśli ste- rowanie osiąga końcowy punkt ostatniej instrukcji, jest ono przekazywane do końcowego punktu listy instrukcji. 376 8.3. Instrukcja pusta Instrukcja znajdująca się na liście instrukcji jest osiągalna, jeśli spełniony jest przynajmniej jeden z następujących warunków: • Instrukcja jest pierwszą instrukcją i sama lista instrukcji jest osiągalna. • Końcowy punkt poprzedzającej instrukcji jest osiągalny. • Instrukcja jest instrukcją oznaczoną, a do jej etykiety odnosi się osiągalna instrukcja goto. VLADIMIR RESHETNIKOV Reguła ta nie ma zastosowania, gdy instrukcja goto znajduje się wewnątrz bloku try lub bloku catch instrukcji try, która zawiera blok finally, a instrukcja oznaczona występuje poza instrukcją try, zaś końcowy punkt bloku finally jest nieosiągalny. Punkt końcowy listy instrukcji jest osiągalny, jeśli osiągalny jest końcowy punkt ostatniej instrukcji znajdującej się na liście. 8.3. Instrukcja pusta Instrukcja-pusta nie powoduje wykonania żadnych działań: instrukcja-pusta: ; Instrukcja pusta jest używana, gdy nie ma żadnych operacji do wykonania w kontekście, w którym wymagana jest instrukcja. Wykonanie instrukcji pustej powoduje po prostu przekazanie sterowania do końcowego punktu instrukcji. Z tego powodu końcowy punkt instrukcji pustej jest osiągalny, jeśli osiągalna jest ta instrukcja pusta. Instrukcja pusta może być zastosowana przy pisaniu instrukcji while z pustym ciałem, tak jak zostało to zaprezentowane poniżej: bool ProcessMessage() {...} void ProcessMessages() { while (ProcessMessage()) ; } Dodatkowo instrukcja pusta może być wykorzystywana do deklarowania etykiety tuż przed zamy- kającym nawiasem klamrowym (}) bloku, tak jak zostało to przedstawione poniżej: void F() { ... if (done) goto exit; ... exit: ; } 377 8. Instrukcje 8.4. Instrukcje oznaczone Dzięki elementowi instrukcja-oznaczona możliwe jest poprzedzanie instrukcji za pomocą etykiety. Instrukcje oznaczone są dopuszczalne w blokach, nie mogą jednak występować jako instrukcje osadzone: instrukcja-oznaczona: identyfikator : instrukcja Za pomocą instrukcji oznaczonej można deklarować etykietę o nazwie określonej przez identy (cid:180)fikator. Zakresem etykiety jest cały blok, w którym została ona zadeklarowana, w tym również wszelkie bloki zagnieżdżone. Błędem czasu wykonania jest, gdy dwie etykiety o tej samej nazwie mają zachodzące na siebie zakresy. Do etykiety mogą odnosić się instrukcje goto (opisane w punkcie 8.9.3) występujące w ramach jej zakresu. W konsekwencji instrukcje goto mogą przekazywać sterowanie w obrębie bloków oraz poza bloki, nigdy jednak do bloków. Etykiety mają swoją własną przestrzeń deklaracji i nie kolidują z innymi identyfikatorami. Przed- stawiony poniżej przykład: int F(int x) { if (x = 0) goto x; x = -x; x: return x; } jest prawidłowym fragmentem kodu, w którym nazwa x jest wykorzystywana zarówno w roli parametru, jak i etykiety. Wykonanie instrukcji oznaczonej odpowiada dokładnie wykonaniu instrukcji, która znajduje się bezpośrednio po niej. Oprócz osiągalności zapewnianej przez normalny przepływ sterowania instrukcja oznaczona może również zawdzięczać osiągalność odwołującej się do niej osiągalnej instrukcji goto. Wyjątkiem jest tu sytuacja, w której instrukcja goto znajduje się wewnątrz instrukcji try zawierającej blok finally, a instrukcja oznaczona występuje poza instrukcją try, zaś końcowy punkt bloku finally jest nie- osiągalny; wówczas instrukcja oznaczona nie jest osiągalna za pomocą tej instrukcji goto. ERIC LIPPERT Może się na przykład zdarzyć, że blok finally zawsze będzie zgłaszał wyjątek; w takim przypadku będzie istniała osiągalna instrukcja goto przekierowująca do potencjalnie nieosiągalnej etykiety. CHRIS SELLS Proszę nie używać etykiet ani instrukcji goto. Nigdy nie zdarzyło mi się ujrzeć kodu, który nie byłby bardziej czytelny bez nich. 378 8.5. Instrukcje deklaracji 8.5. Instrukcje deklaracji Instrukcja-deklaracji umożliwia deklarowanie lokalnej zmiennej lub stałej. Instrukcji deklaracji można używać w blokach, ale nie są one dozwolone w roli instrukcji zagnieżdżonych: instrukcja-deklaracji: deklaracja-zmiennej-lokalnej ; deklaracja-stałej-lokalnej ; 8.5.1. Deklaracje zmiennych lokalnych Deklaracja-zmiennej-lokalnej umożliwia deklarowanie jednej lub większej liczby zmiennych lokalnych: deklaracja-zmiennej-lokalnej: typ-zmiennej-lokalnej deklaratory-zmiennych-lokalnych typ-zmiennej-lokalnej: typ var deklaratory-zmiennych-lokalnych: deklarator-zmiennej-lokalnej deklaratory-zmiennych-lokalnych , deklarator-zmiennej-lokalnej deklarator-zmiennej-lokalnej: identyfikator identyfikator = inicjalizator-zmiennej-lokalnej inicjalizator-zmiennej-lokalnej: wyrażenie inicjalizator-tablicy Typ-zmiennej-lokalnej elementu deklaracja-zmiennej-lokalnej określa typ zmiennych wpro- wadzanych przez deklarację lub wskazuje za pomocą słowa kluczowego var, że typ ten powinien zostać wywnioskowany na podstawie inicjalizatora. Po typie występuje lista elementów deklarator- (cid:180)zmiennej-lokalnej, z których każdy wprowadza nową zmienną. Deklarator-zmiennej-lokalnej składa się z elementu identyfikator określającego nazwę zmiennej, po którym może opcjonalnie występować token = oraz inicjalizator-zmiennej-lokalnej, który nadaje zmiennej wartość początkową. Gdy typ-zmiennej-lokalnej jest określony jako var, a w zakresie nie ma typu o takiej nazwie, deklaracja jest deklaracją zmiennej lokalnej niejawnie typowanej (ang. implicitly typed local variable declaration), której typ jest wnioskowany na podstawie powiązanego elementu wyra (cid:180)żenie inicjalizatora. Deklaracje zmiennych lokalnych typowanych niejawnie podlegają ogra- niczeniom: 379 8. Instrukcje • Deklaracja-zmiennej-lokalnej nie może zawierać wielu elementów deklarator-zmiennej- (cid:180)lokalnej. ERIC LIPPERT Na wczesnych etapach opracowywania tej możliwości dopuszczalne było korzystanie w tym miejscu z wielu deklaratorów, tak jak zostało to pokazane poniżej: var a = 1, b = 2.5; Gdy programiści C# ujrzeli ten kod, mniej więcej połowa z nich stwierdziła, że zapis ten powinien mieć tę samą semantykę co przedstawiony poniżej: double a = 1, b = 2.5; Druga połowa powiedziała jednak, że powinien mieć taką semantykę jak poniżej: int a = 1; double b = 2.5; Obydwie grupy uważały, że ich interpretacja zagadnienia była „w oczywisty sposób poprawna”. Gdy ma się do czynienia ze składnią, która umożliwia dwie niezgodne, „w oczywisty sposób poprawne” interpretacje, najlepszą rzeczą, jaką można zwykle zrobić, to całkowicie zabronić korzystania z tej składni, zamiast wprowadzać zbędne zamieszanie. • Deklarator-zmiennej-lokalnej musi zawierać inicjalizator-zmiennej-lokalnej. • Inicjalizator-zmiennej-lokalnej musi być wyrażenie. • Wyrażenie inicjalizatora musi mieć typ czasu kompilacji. • Wyrażenie inicjalizatora nie może odwoływać się do samej deklarowanej zmiennej. ERIC LIPPERT Podczas gdy w deklaracji zmiennej lokalnej jawnie typowanej może występować odwołanie do niej samej z poziomu inicjalizatora, w przypadku deklaracji zmien- nej lokalnej niejawnie typowanej jest to niedopuszczalne. Na przykład instrukcja int j = M(out j); jest dziwna, ale w pełni prawidłowa. Gdyby jednak wyrażenie miało postać var j = M(out j), wówczas mechanizm wyznaczania przełado- wania nie mógłby określić typu zwracanego przez metodę M, a więc również typu zmiennej j, dopóki nie byłby znany typ argumentu. Typ argumentu jest oczywiście dokładnie tym, co próbujemy tu określić. Zamiast rozwiązywać tu problem „pierwszeństwa kury lub jajka”, specyfikacja języka po prostu uniemożliwia stosowanie tego rodzaju konstrukcji. Poniżej zostały przedstawione przykłady nieprawidłowych deklaracji zmiennych lokalnych typo- wanych niejawnie: 380 8.5. Instrukcje deklaracji var x; // Błąd, brak inicjalizatora, na podstawie którego można wywnioskować typ var y = {1, 2, 3}; // Błąd, nie jest tu dozwolony inicjalizator tablicowy var z = null; // Błąd, null nie ma typu var u = x = x + 1; // Błąd, funkcje anonimowe nie mają typu var v = v++; // Błąd, inicjalizator nie może odnosić się do samej zmiennej Wartość zmiennej lokalnej jest otrzymywana za pomocą wyrażenia, w którym używa się elementu nazwa-prosta (o którym więcej w punkcie 7.5.2). Wartość zmiennej lokalnej modyfikuje się za pomocą elementu przypisanie (o którym więcej w podrozdziale 7.16). Zmienna lokalna musi być niewątpliwie ustalona (o czym więcej w podrozdziale 5.3) w każdym miejscu, w którym jej wartość jest uzyskiwana. CHRIS SELLS Naprawdę uwielbiam deklaracje zmiennych lokalnych typowanych nie- jawnie, gdy typ jest anonimowy (w którym to przypadku musisz ich używać) lub gdy typ zmiennej jest ujawniony jako część wyrażenia inicjalizującego, a nie dlatego, że jesteś zbyt leniwy, aby pisać! Przykładem może tu być przedstawiony poniżej fragment kodu: var a = new { Name = Bob , Age = 42 }; // Dobrze var b = 1; // Dobrze var v = new Person(); // Dobrze var d = GetPerson(); // ŹLE! Kompilator nie ma najmniejszego problemu z tym, że d jest niejawnie typowaną zmienną, za to człowiek, który musi czytać taki kod — wręcz przeciwnie! Zakresem zmiennej lokalnej deklarowanej w deklaracja-zmiennej-lokalnej jest blok, w którym następuje ta deklaracja. Odwoływanie się do zmiennej lokalnej występujące w tekście programu przed elementem deklarator-zmiennej-lokalnej jest błędem czasu kompilacji. Błędem czasu kompilacji jest również zadeklarowanie w ramach zakresu zmiennej lokalnej innej lokalnej zmiennej lub stałej o tej samej nazwie. Deklaracja zmiennej lokalnej, za pomocą której deklaruje się wiele zmiennych, jest równoważna z wieloma deklaracjami pojedynczych zmiennych tego samego typu. Ponadto inicjalizator zmiennej w deklaracji zmiennej lokalnej dokładnie odpowiada instrukcji przypisania, która jest umieszczona bezpośrednio za deklaracją. Przedstawiony poniżej przykładowy fragment kodu: void F() { int x = 1, y, z = x * 2; } dokładnie odpowiada następującemu fragmentowi: 381 8. Instrukcje void F() { int x; x = 1; int y; int z; z = x * 2; } W deklaracji zmiennej lokalnej typowanej niejawnie przyjmuje się, że typ deklarowanej zmiennej lokalnej jest taki sam jak typ wyrażenia użytego do jej zainicjalizowania. Przykładem tego może być fragment kodu pokazany poniżej: var i = 5; var s = Hello ; var d = 1.0; var numbers = new int[] {1, 2, 3}; var orders = new Dictionary int,Order (); Deklaracje zmiennych lokalnych typowanych niejawnie przedstawione w powyższym przykładzie są dokładnie równoznaczne z typowanymi jawnie deklaracjami, które zostały zaprezentowane poniżej: int i = 5; string s = Hello ; double d = 1.0; int[] numbers = new int[] {1, 2, 3}; Dictionary int,Order orders = new Dictionary int,Order (); 8.5.2. Deklaracje stałych lokalnych Deklaracja-stałej-lokalnej umożliwia deklarowanie jednej stałej lokalnej lub większej liczby stałych lokalnych: deklaracja-stałej-lokalnej: const typ deklaratory-stałych deklaratory-stałych: deklarator-stałej deklaratory-stałych , deklarator-stałej deklarator-stałej: identyfikator = wyrażenie-stałe Typ elementu deklaracja-stałej-lokalnej określa typ stałych wprowadzonych przez deklarację. Po typie występuje lista elementów deklarator-stałej, z których każdy wprowadza nową stałą. Deklarator-stałej składa się z elementu identyfikator określającego nazwę stałej, po którym następuje token =, po nim zaś wyrażenie-stałe (opisane w podrozdziale 7.18), które określa wartość stałej. Typ i wyrażenie-stałe deklaracji stałej lokalnej muszą stosować się do tych samych reguł, które działają w przypadku odpowiednich elementów deklaracji składowej stałej (o której więcej w podrozdziale 10.4). 382 8.6. Instrukcje wyrażeń Wartość stałej lokalnej jest uzyskiwana w wyrażeniu wykorzystującym element nazwa-prosta (opisany w punkcie 7.5.2). Zakresem stałej lokalnej jest blok, w którym znajduje się ta deklaracja. Odwoływanie się do stałej lokalnej występujące w tekście programu przed elementem deklarator-stałej jest błędem czasu kompilacji. Błędem czasu kompilacji jest również zadeklarowanie w ramach zakresu stałej lokalnej innej stałej lub zmiennej lokalnej o tej samej nazwie. Deklaracja stałej lokalnej, za pomocą której deklaruje się wiele stałych, jest równoważna z wieloma deklaracjami pojedynczych stałych tego samego typu. 8.6. Instrukcje wyrażeń Instrukcja-wyrażenia powoduje przetworzenie danego wyrażenia. Wartość obliczona przez wyrażenie, jeśli występuje, jest odrzucana: instrukcja-wyrażenia: wyrażenie-instrukcji ; wyrażenie-instrukcji: wyrażenie-wywołania wyrażenie-tworzenia-obiektu przypisanie wyrażenie-poinkrementacyjne wyrażenie-podekrementacyjne wyrażenie-przedinkrementacyjne wyrażenie-przeddekrementacyjne Nie wszystkie wyrażenia mogą być stosowane jako instrukcje. W szczególności wyrażenia takie jak x + y czy x == 1 , które jedynie obliczają jakąś wartość (która i tak zostałaby następnie odrzu- cona), nie mogą występować w roli instrukcji. Wykonanie elementu instrukcja-wyrażenia powoduje przetworzenie zawartego w nim wyrażenia, a następnie przekazanie sterowania do końcowego punktu elementu instrukcja-wyrażenia. Końcowy punkt elementu instrukcja-wyrażenia jest osiągalny, gdy ta instrukcja-wyrażenia jest osiągalna. 8.7. Instrukcje wyboru Instrukcje wyboru powodują wybranie jednej z wielu możliwych instrukcji do wykonania na podstawie wartości pewnego wyrażenia: instrukcja-wyboru: instrukcja-if instrukcja-switch 383 8. Instrukcje 8.7.1. Instrukcja if Instrukcja if powoduje wybranie instrukcji do wykonania na podstawie wartości wyrażenia boole’owskiego: instrukcja-if: if ( wyrażenie-boole’owskie ) instrukcja-osadzona if ( wyrażenie-boole’owskie ) instrukcja-osadzona else instrukcja-osadzona Część else jest związana z najbliższą, poprzedzającą ją leksykalnie częścią if, która jest dozwolona przez składnię. Z tego powodu instrukcja if o przedstawionej poniżej postaci: if (x) if (y) F(); else G(); jest równoważna z następującą: if (x) { if (y) { F(); } else { G(); } } Instrukcja if jest wykonywana w następujący sposób: • Przetwarzane jest wyrażenie-boole’owskie (przedstawione w podrozdziale 7.19). • Jeśli w wyniku przetwarzania wyrażenia boole’owskiego otrzymana zostaje wartość true, ste- rowanie jest przekazywane do pierwszej osadzonej instrukcji. Gdy sterowanie osiąga jej punkt końcowy, przekazywane jest do punktu końcowego instrukcji if. • Jeśli w wyniku przetwarzania wyrażenia boole’owskiego otrzymana zostaje wartość false i jeśli obecna jest część else, sterowanie przekazywane jest do drugiej osadzonej instrukcji. Gdy ste- rowanie osiąga jej punkt końcowy, przekazywane jest do punktu końcowego instrukcji if. • Jeśli w wyniku przetwarzania wyrażenia boole’owskiego otrzymana zostaje wartość false i jeśli nie jest obecna część else, sterowanie przekazywane jest do punktu końcowego instrukcji if. Pierwsza instrukcja osadzona instrukcji if jest osiągalna, jeśli osiągalna jest instrukcja if i wyra- żenie boole’owskie nie ma stałej wartości false. Druga instrukcja osadzona instrukcji if, jeśli obecna, jest osiągalna, jeśli osiągalna jest instrukcja if i wyrażenie boole’owskie nie ma stałej wartości true. Punkt końcowy instrukcji if jest osiągalny, jeśli osiągalny jest punkt końcowy przynajmniej jednej z jej osadzonych instrukcji. Ponadto punkt końcowy instrukcji if pozbawionej części else jest osiągalny, jeśli osiągalna jest instrukcja if i wyrażenie boole’owskie nie ma stałej war- tości true. 384 8.7.2. Instrukcja switch Instrukcja switch powoduje wybranie do wykonania listy instrukcji związanej z etykietą prze- łącznika, która odpowiada wartości wyrażenia przełączającego: 8.7. Instrukcje wyboru instrukcja-switch: switch ( wyrażenie ) blok-switch blok-switch: { sekcje-switchopc } sekcje-switch: sekcja-switch sekcje-switch sekcja-switch sekcja-switch: etykiety-switch lista-instrukcji etykiety-switch: etykieta-switch etykiety-switch etykieta-switch etykieta-switch: case wyrażenie-stałe : default : Instrukcja-switch składa się ze słowa kluczowego switch, po którym występuje ujęte w nawiasy wyrażenie (noszące nazwę wyrażenia przełączającego), po nim zaś blok-switch. Blok-switch składa się z zera lub większej liczby ujętych w nawiasy klamrowe elementów sekcja-switch. Każda sekcja-switch zawiera jedną lub większą liczbę elementów etykiety-switch, po których wystę- puje lista-instrukcji (opisana w punkcie 8.2.1). Typ rządzący (ang. governing type) instrukcji switch jest ustalany na podstawie wyrażenia prze- łączającego w następujący sposób: • Jeśli typem wyrażenia przełączającego jest sbyte, byte, short, ushort, int, uint, long, ulong, bool, char, string bądź typ-wyliczeniowy lub jeśli jest to odpowiadający jednemu z wymienio- nych typ dopuszczający wartość pustą, wówczas jest on typem rządzącym instrukcji switch. • W innym przypadku musi istnieć dokładnie jedna zdefiniowana przez użytkownika konwersja niejawna (o czym więcej w podrozdziale 6.4) z typu wyrażenia przełączającego na jeden z następujących typów rządzących: sbyte, byte, short, ushort, int, uint, long, ulong, bool, char, string lub typ dopuszczający wartość pustą odpowiadający jednemu z wymienionych typów. • W innym przypadku, jeśli nie istnieje tego rodzaju konwersja niejawna lub jeśli istnieje więcej takich konwersji niejawnych, pojawia się błąd czasu kompilacji. 385 8. Instrukcje DON BOX Często przyłapuję się na marzeniu o możliwości używania typu System.Type jako typu rządzącego (znanego też jako „typeswitch”). Kocham funkcje wirtualne równie mocno, jak wszyscy wokół, ale niezmiennie pragnę pisać kod, który interpretuje istniejące typy i podejmuje różne działania na podstawie typu pewnej wartości. Dużo radości sprawiłby mi również operator dopasowania w rodzaju ML, który byłby nawet bardziej przydatny. Wyrażenie stałe każdej etykiety case musi oznaczać wartość typu, który da się niejawnie kon- wertować (o czym więcej w podrozdziale 6.1) na typ rządzący instrukcji switch. Jeśli dwie lub większa liczba etykiet należących do tej samej instrukcji switch określają tę samą wartość stałą, generowany jest błąd czasu kompilacji. W instrukcji przełączającej może znajdować się co najwyżej jedna etykieta default. Instrukcja switch jest przetwarzana w następujący sposób: • Wyrażenie przełączające jest przetwarzane i konwertowane na typ rządzący. • Jeśli jedna ze stałych określonych w etykiecie case należącej do tej samej instrukcji switch jest równa wartości wyrażenia przełączającego, sterowanie jest przekazywane do listy instrukcji znajdującej się po dopasowanej etykiecie case. • Jeśli żadna ze stałych określonych w etykiecie case należącej do tej samej instrukcji switch nie jest równa wartości wyrażenia przełączającego i jeśli obecna jest etykieta default, sterowanie jest przekazywane do listy instrukcji znajdującej się po etykiecie default. • Jeśli żadna ze stałych określonych w etykiecie case należącej do tej samej instrukcji switch nie jest równa wartości wyrażenia przełączającego i jeśli nie jest obecna etykieta default, sterowanie jest przekazywane do punktu końcowego instrukcji switch. Jeśli osiągalny jest końcowy punkt listy instrukcji sekcji switch, generowany jest błąd czasu kompilacji. Zasada ta znana jest jako reguła „niewpadania” (ang. no-fall-through). Przedstawiony poniżej fragment kodu: switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; default: CaseOthers(); break; } jest prawidłowy, ponieważ żadna z sekcji instrukcji przełączającej nie ma osiągalnego punktu końcowego. W przeciwieństwie do języków C i C++ wykonanie sekcji switch nie może tu „wpadać” do kolejnej sekcji, a próba skompilowania pokazanego poniżej przykładu: 386 8.7. Instrukcje wyboru switch (i) { case 0: CaseZero(); case 1: CaseZeroOrOne(); default: CaseAny(); } powoduje wystąpienie błędu czasu kompilacji. Gdy po wykonaniu pewnej sekcji instrukcji prze- łączającej ma następować wykonanie innej sekcji, zastosowana musi zostać jawna instrukcja goto lub goto default, tak jak zostało to zaprezentowane poniżej: switch (i) { case 0: CaseZero(); goto case 1; case 1: CaseZeroOrOne(); goto default; default: CaseAny(); break; } W elemencie sekcja-switch dozwolone jest stosowanie wielu etykiet. Przedstawiony poniżej przy- kład kodu: switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; case 2: default: CaseTwo(); break; } jest prawidłowy. Zaprezentowany kod nie łamie reguły „niewpadania”, ponieważ etykiety case 2: oraz default: są częściami tego samego elementu sekcja-switch. Reguła „niewpadania” zabezpiecza przed występowaniem powszechnej klasy błędów, które zdarzają się w językach C i C++, gdy pominięte zostają przypadkiem instrukcje break. Dodatkowo dzięki tej regule sekcje instrukcji przełączającej mogą być dowolnie przestawiane bez wpływu na zachowanie całej instrukcji. Na przykład kolejność sekcji instrukcji switch przedstawionej w zaprezentowanym wcześniej fragmencie kodu można odwrócić bez modyfikowania sposobu działania tej instrukcji, tak jak zostało to pokazane poniżej: switch (i) { default: CaseAny(); 387 8. Instrukcje break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; } Lista instrukcji sekcji przełączającej kończy się zwykle instrukcją break, goto case lub goto default, ale dopuszczalna jest tu każda konstrukcja, która sprawia, że końcowy punkt listy instrukcji staje się nieosiągalny. Przykładem może tu być instrukcja while sterowana przez wyrażenie boole’owskie true, o której wiadomo, że nigdy nie osiąga swojego punktu końcowego. Podobnie jest z instruk- cjami throw i return, które zawsze przekazują sterowanie w inne miejsce i nigdy nie osiągają swojego punktu końcowego. Z tego powodu prawidłowy jest następujący przykładowy frag- ment kodu: switch (i) { case 0: while (true) F(); case 1: throw new ArgumentException(); case 2: return; } DON BOX Kocham regułę „niewpadania”, ale aż po dziś dzień stale zapominam o umieszczeniu instrukcji break na końcu klauzuli default i ciągle musi mi o tym przy- pominać kompilator. Typem rządzącym instrukcji switch może być typ string. Przykład zastosowania tej możliwości został pokazany poniżej: void DoCommand(string command) { switch (command.ToLower()) { case run : DoRun(); break; case save : DoSave(); break; case quit : DoQuit(); break; default: InvalidCommand(command); break; } } 388 8.7. Instrukcje wyboru Podobnie jak operatory równości łańcuchów znakowych (opisane w punkcie 7.9.7), instrukcja switch rozróżnia wielkie i małe litery, dlatego dana sekcja zostanie wykonana tylko wówczas, jeśli łańcuch wyrażenia przełączającego będzie dokładnie pasował do stałej etykiety case. Gdy typem rządzącym instrukcji switch jest string, wartość null jest dozwolona jako stała ety- kiety case. Lista-instrukcji lub blok-switch może zawierać instrukcje deklaracji (przedstawione w pod- rozdziale 8.5). Zakresem zmiennej lub stałej lokalnej zadeklarowanej w bloku przełączającym jest ten blok przełączający. BILL WAGNER Wynika z tego, że każdy blok switch otaczają niejawne nawiasy klamrowe. W ramach bloku przełączającego znaczenie nazw użytych w kontekście wyrażenia musi zawsze być takie samo (o czym więcej w podpunkcie 7.5.2.1). Lista instrukcji danej sekcji instrukcji przełączającej jest osiągalna, jeśli ta instrukcja switch jest osiągalna i spełniony jest przynajmniej jeden z przedstawionych poniżej warunków: • Wyrażenie przełączające jest wartością niestałą. • Wyrażenie przełączające jest stałą wartością, która pasuje do etykiety case znajdującej się w tej sekcji instrukcji przełączającej. • Wyrażenie przełączające jest wartością stałą, która nie pasuje do żadnej etykiety case, a ta sekcja instrukcji przełączającej zawiera etykietę default. • Do etykiety przełączającej tej sekcji instrukcji przełączającej odwołuje się osiągalna instrukcja goto case lub goto default. VLADIMIR RESHETNIKOV Reguła ta nie ma zastosowania, gdy instrukcja goto case lub goto default znajduje się wewnątrz bloku try lub bloku catch instrukcji try, która zawiera blok finally, a etykieta instrukcji przełączającej występuje poza instrukcją try, zaś końcowy punkt bloku finally jest nieosiągalny. Końcowy punkt instrukcji switch jest osiągalny, jeśli spełniony jest co najmniej jeden z wymie- nionych poniżej warunków: • Instrukcja switch zawiera osiągalną instrukcję break, która powoduje opuszczenie instrukcji switch. 389 8. Instrukcje VLADIMIR RESHETNIKOV Reguła ta nie ma zastosowania, gdy instrukcja break znajduje się wewnątrz bloku try lub bloku catch instrukcji try, która zawiera blok finally, a cel instrukcji break występuje poza instrukcją try, zaś końcowy punkt bloku finally jest nieosiągalny. • Instrukcja switch jest osiągalna, wyrażenie przełączające jest wartością niestałą i nie jest obecna etykieta default. • Instrukcja switch jest osiągalna, wyrażenie przełączające jest wartością stałą, która nie pasuje do żadnej etykiety case, i nie jest obecna etykieta default. 8.8. Instrukcje iteracji Instrukcje iteracji powodują wielokrotne wykonywanie instrukcji osadzonej: instrukcja-iteracji: instrukcja-while instrukcja-do instrukcja-for instrukcja-foreach 8.8.1. Instrukcja while Instrukcja while warunkowo wykonuje osadzoną instrukcję zero lub większą liczbę razy: instrukcja-while: while ( wyrażenie-boole’owskie ) instrukcja-osadzona Instrukcja while jest wykonywana w następujący sposób: • Przetwarzane jest wyrażenie-boole’owskie (przedstawione w podrozdziale 7.19). • Jeśli w wyniku przetwarzania wyrażenia boole’owskiego otrzymana zostaje wartość true, ste- rowanie jest przekazywane do instrukcji osadzonej. Gdy sterowanie osiąga jej punkt końcowy (potencjalnie w wyniku wykonania instrukcji continue), przekazywane jest do początku instruk- cji while. • Jeśli w wyniku przetwarzania wyrażenia boole’owskiego otrzymana zostaje wartość false, sterowanie przekazywane jest do punktu końcowego instrukcji while. W ramach instrukcji osadzonej instrukcji while może zostać wykorzystana instrukcja break (opi- sana w punkcie 8.9.1) w celu przekazania sterowania do końcowego punktu instrukcji while (i tym samym zakończenia iteracji instrukcji osadzonej), a instrukcja continue (opisana w punkcie 8.9.2) może zostać użyta w celu przekazania sterowania do końcowego punktu instrukcji osadzonej (i tym samym przeprowadzenia kolejnej iteracji instrukcji while). 390 8.8. Instrukcje iteracji Instrukcja osadzona instrukcji while jest osiągalna, jeśli osiągalna jest instrukcja while i wyra- żenie boole’owskie nie ma stałej wartości false. Końcowy punkt instrukcji while jest osiągalny, jeśli przynajmniej jeden z wymienionych poniżej warunków jest spełniony: • Instrukcja while zawiera osiągalną instrukcję break, która powoduje opuszczenie instrukcji while. VLADIMIR RESHETNIKOV Reguła ta nie ma zastosowania, gdy instrukcja break znaj- duje się wewnątrz bloku try lub bloku catch instrukcji try, która zawiera blok finally, a cel instrukcji break występuje poza instrukcją try, zaś końcowy punkt bloku finally jest nie- osiągalny. • Instrukcja while jest osiągalna i wyrażenie boole’owskie nie ma stałej wartości true. 8.8.2. Instrukcja do Instrukcja do warunkowo wykonuje osadzoną instrukcję jeden raz lub większą liczbę razy: instrukcja-do: do instrukcja-osadzona while ( wyrażenie-boole’owskie ) ; Instrukcja do jest wykonywana w następujący sposób: • Sterowanie jest przekazywane do instrukcji osadzonej. • Gdy i jeśli sterowanie osiąga końcowy punkt instrukcji osadzonej (potencjalnie w wyniku wykonania instrukcji continue), przetwarzane jest wyrażenie-boole’owskie (przedstawione w podrozdziale 7.19). Jeśli w wyniku przetwarzania wyrażenia boole’owskiego otrzymana zostaje wartość true, sterowanie jest przekazywane do początku instrukcji do. W innym przypadku sterowanie przekazywane jest do punktu końcowego instrukcji do. W ramach instrukcji osadzonej instrukcji do może zostać wykorzystana instrukcja break (opisana w punkcie 8.9.1) do przekazania sterowania do końcowego punktu instrukcji do (i tym samym zakończenia iteracji instrukcji osadzonej), a instrukcja continue (opisana w punkcie 8.9.2) może zostać użyta do przekazania sterowania do końcowego punktu instrukcji osadzonej. Instrukcja osadzona instrukcji do jest osiągalna, jeśli osiągalna jest instrukcja do. Końcowy punkt instrukcji do jest osiągalny, jeśli przynajmniej jeden z wymienionych poniżej warunków jest spełniony: • Instrukcja do zawiera osiągalną instrukcję break, która powoduje opuszczenie instrukcji do. • Osiągalny jest końcowy punkt instrukcji osadzonej i wyrażenie boole’owskie nie ma stałej wartości true. 391 8. Instrukcje VLADIMIR RESHETNIKOV Reguła ta nie ma zastosowania, gdy instrukcja break znajduje się wewnątrz bloku try lub bloku catch instrukcji try, która zawiera blok finally, a cel instrukcji break występuje poza instrukcją try, zaś końcowy punkt bloku finally jest nieosiągalny. 8.8.3. Instrukcja for Instrukcja for powoduje przetworzenie ciągu wyrażeń inicjalizujących, a następnie — dopóki spełniony jest warunek — powtarza wykonanie osadzonej instrukcji i przetwarzanie ciągu wyra- żeń iteracji: instrukcja-for: for ( inicjalizator-foropc ; warunek-foropc ; iterator-foropc ) instrukcja- (cid:180)osadzona inicjalizator-for: deklaracja-zmiennej-lokalnej lista-wyrażeń-instrukcji warunek-for: wyrażenie-boole’owskie iterator-for: lista-wyrażeń-instrukcji lista-wyrażeń-instrukcji: wyrażenie-instrukcji lista-wyrażeń-instrukcji , wyrażenie-instrukcji Inicjalizator-for, jeśli jest obecny, składa się z elementu deklaracja-zmiennej-lokalnej (opi- sanego w punkcie 8.5.1) lub listy rozdzielonych za pomocą przecinków elementów wyrażenie- (cid:180)instrukcji (opisanych w podrozdziale 8.6). Zakres zmiennej lokalnej zadeklarowanej za pomocą elementu inicjalizator-for zaczyna się od miejsca występowania deklaracja-zmiennej-lokalnej tej zmiennej i rozciąga aż do końca instrukcji osadzonej. Zakres zawiera element warunek-for i iterator-for. Warunek-for, jeśli jest obecny, musi być elementem wyrażenie-boole’owskie (o którym więcej w podrozdziale 7.19). Iterator-for, jeśli jest obecny, składa się z listy elementów wyrażenie-instrukcji (o którym więcej w podrozdziale 8.6) rozdzielonych za pomocą przecinków. Instrukcja for jest wykonywana w następujący sposób: • Jeśli obecny jest inicjalizator-for, w kolejności, w jakiej je zapisano, są wykonywane inicja- lizatory zmiennych lub wyrażenia instrukcji. Krok ten wykonywany jest tylko raz. 392 8.8. Instrukcje iteracji • Jeśli obecny jest warunek-for, jest on przetwarzany. • Jeśli nie jest obecny warunek-for lub jeśli w wyniku przetwarzania go zostaje uzyskana wartość true, sterowanie jest przekazywane do instrukcji osadzonej. Gdy i jeśli sterowanie osiąga koń- cowy punkt instrukcji osadzonej (potencjalnie w wyniku wykonania instrukcji continue), przetwarzane w ciągu są wyrażenia elementu iterator-for, jeśli są one obecne, a następnie przeprowadzana jest kolejna iteracja, począwszy od przetworzenia elementu warunek-for w poprzednim kroku. • Jeśli obecny jest warunek-for i jeśli w wyniku przetwarzania go zostaje uzyskana wartość false, sterowanie jest przekazywane do końcowego punktu instrukcji for. W ramach instrukcji osadzonej instrukcji for może zostać użyta instrukcja break (opisana w punkcie 8.9.1) w celu przekazania sterowania do końcowego punktu instrukcji for (i tym samym zakończenia iteracji instrukcji osadzonej), a instrukcja continue (opisana w punkcie 8.9.2) może zostać wykorzystana w celu przekazania sterowania do końcowego punktu instrukcji osadzonej (i tym samym w celu wykonania elementu iterator-for i przeprowadzenia kolejnej iteracji instruk- cji for, począwszy od elementu warunek-for). Instrukcja osadzona instrukcji for jest osiągalna, jeśli spełniony jest jeden z przedstawionych poniżej warunków: • Instrukcja for jest osiągalna i nie jest obecny warunek-for. • Instrukcja for jest osiągalna i obecny jest warunek-for, i nie ma on stałej wartości false. Końcowy punkt instrukcji for jest osiągalny, jeśli przynajmniej jeden z wymienionych poniżej warunków jest spełniony: • Instrukcja for zawiera osiągalną instrukcję break, która powoduje opuszczenie instrukcji for. VLADIMIR RESHETNIKOV Reguła ta nie ma zastosowania, gdy instrukcja break znaj- duje się wewnątrz bloku try lub bloku catch instrukcji try, która zawiera blok finally, a cel instrukcji break występuje poza instrukcją try, zaś końcowy punkt bloku finally jest nie- osiągalny. • Instrukcja for jest osiągalna, obecny jest warunek-for i nie ma on stałej wartości true. 8.8.4. Instrukcja foreach Instrukcja foreach powoduje wyliczenie elementów kolekcji, któremu towarzyszy wykonanie osadzonej instrukcji dla każdego elementu tej kolekcji: instrukcja-foreach: foreach ( typ-zmiennej-lokalnej identyfikator in wyrażenie ) instrukcja- (cid:180)osadzona 393 8. Instrukcje Typ i identyfikator instrukcji foreach stanowią deklarację zmiennej iteracji (ang. iteration varia- ble) instrukcji. Jeśli jako typ-zmiennej-lokalnej podane jest słowo kluczowe var, zmienną iteracji nazywa się niejawnie typowaną zmienną iteracji (ang. implicitly typed iteration variable), a za jej typ przyjmuje się typ elementu instrukcji foreach, tak jak zostało to opisane poniżej. Zmienna iteracji odpowiada zmiennej lokalnej tylko do odczytu o zakresie rozciągającym się na instrukcję osadzoną. Podczas wykonywania instrukcji foreach zmienna iteracji reprezentuje element kolekcji, dla którego iteracja jest właśnie przeprowadzana. Jeśli instrukcja osadzona próbuje zmodyfikować zmienną iteracji (przez przypisanie lub użycie operatorów ++ oraz--) lub przekazać ją jako para- metr ref lub out, wówczas generowany jest błąd czasu kompilacji. CHRIS SELLS Ze względu na czytelność kodu powinieneś raczej starać się stosować instrukcje foreach zamiast for. VLADIMIR RESHETNIKOV Ze względu na konieczność zapewnienia zgodności wstecz- nej reguła ta nie jest stosowana, jeśli synonim typu jest wprowadzany za pomocą elementu dyrektywa-użycia-synonimu lub jeśli w zakresie znajduje się nieogólny typ o nazwie var. Przetwarzanie czasu kompilacji instrukcji foreach zaczyna się od określenia typu kolekcji (ang. collection type), typu enumeratora (ang. enumeraton type) oraz typu elementu (ang. element type) wyrażenie. Określanie odbywa się w następujący sposób: • Jeśli typem X elementu wyrażenie jest typ tablicowy, wówczas istnieje niejawna konwersja referencyjna z X do interfejsu System.Collections.IEnumerable (ponieważ typ System.Array implementuje ten interfejs). Typem kolekcji jest interfejs System.Collections.IEnumerable, typem enumeratora jest interfejs System.Collections.IEnumerator, a typem elementu jest typ elementu tablicy X. • W innym przypadku sprawdzane jest, czy typ X ma odpowiednią metodę GetEnumerator: − Przeprowadzane jest odnajdywanie składowej dla typu X z identyfikatorem GetEnumerator i bez argumentów typu. Jeśli w wyniku tej operacji nie zostaje zwrócone dopasowanie, pojawia się niejednoznaczność lub zostaje zwrócone dopasowanie niebędące grupą metod, należy sprawdzić interfejs enumerowalny, tak jak zostało to opisane poniżej. Zaleca się, aby zgłaszane było ostrzeżenie, jeśli odnajdywanie składowej daje w wyniku coś innego niż grupę metod lub brak dopasowania. ERIC LIPPERT To oparte na „wzorcu” podejście zostało opracowane wcześniej, niż wprowadzono udostępnienie ogólnego typu IEnumerable T , dlatego też autorzy kolekcji mogli zapewnić mocniejsze komentarze typów w swoich obiektach enumeratorów. 394 8.8. Instrukcje iteracji Implementacje nieogólnego typu IEnumerable zawsze kończą się pakowaniem każdej skła- dowej kolekcji liczb całkowitych, ponieważ typem zwracanym przez właściwość Current jest object. Dostawca kolekcji liczb całkowitych mógłby zapewnić implementacje GetEnumerator, MoveNext i Current niebazujące na interfejsie, tak aby właściwość Current zwracała niespa- kowaną wartość całkowitą. Oczywiście w świecie ogólnego typu IEnumerable T cały ten wysiłek staje się niepotrzebny. Ogromna większość iterowanych kolekcji zaimplementuje ten interfejs. − Przeprowadzane jest wyznaczanie przeładowania dla otrzymanej grupy metod i pustej listy argumentów. Jeśli operacja ta nie daje w wyniku żadnych możliwych do zastosowania metod, uzyskany wynik jest niejednoznaczny lub otrzymana zostaje pojedyncza najlepsza metoda, lecz jest to metoda statyczna lub niepubliczna, wówczas należy sprawdzić interfejs enumerowalny, tak jak zostało to opisane poniżej. Zaleca się, aby zgłaszane było ostrzeże- nie, jeśli wyznaczanie przeładowania daje w wyniku coś innego niż jednoznaczną publiczną metodę instancji lub brak możliwych do zastosowania metod. − Jeśli typ zwracany E metody GetEnumerator nie jest typem klasy, struktury lub interfejsu, generowany jest błąd i nie są podejmowane żadne dalsze kroki. − Przeprowadzane jest odnajdywanie składowej dla E z identyfikatorem Current i bez argu- mentów typu. Jeśli proces ten nie zwraca dopasowania, wynikiem jest błąd lub cokolwiek innego niż publiczna właściwość instancji umożliwiająca odczyt, generowany jest błąd i nie są podejmowane żadne dalsze kroki. − Przeprowadzane jest odnajdywanie składowej dla E z identyfikatorem MoveNext i bez argu- mentów typu. Jeśli proces ten nie zwraca dopasowania, wynikiem jest błąd lub cokolwiek innego niż publiczna właściwość instancji umożliwiająca odczyt, generowany jest błąd i nie są podejmowane żadne dalsze kroki. − Przeprowadzane jest wyznaczanie przeładowania dla grupy metod z pustą listą argumen- tów. Jeśli w wyniku tej operacji nie uzyskuje się żadnych możliwych do zastosowania metod, wynik operacji jest niejednoznaczny lub otrzymuje się pojedynczą najlepszą metodę, lecz jest to metoda statyczna lub niepubliczna, lub też zwracanym przez nią typem nie jest bool, generowany jest błąd i nie są wykonywane żadne dalsze kroki. − Typem kolekcji jest X, typem enumeratora jest E, a typem elementu jest typ właściwości Current. • W innym przypadku sprawdzany jest interfejs enumerowalny: − Jeśli jest dokładnie jeden typ T, taki że istnieje niejawna konwersja z X na interfejs System. (cid:180)Collections.Generic.IEnumerable T , wówczas typem kolekcji jest ten interfejs, typem enumeratora jest System.Collections.Generic.IEnumerator T , a typem elementu jest T. − W innym przypadku, jeśli istnieje więcej niż jeden taki typ T, wówczas generowany jest błąd i nie są podejmowane żadne dalsze kroki. 395 8. Instrukcje − W innym przypadku, jeśli istnieje niejawna konwersja z X do interfejsu System.Collections. (cid:180)IEnumerable, wówczas typem kolekcji jest ten interfejs, typem enumeratora jest System. (cid:180)Collections.IEnumerator, a typem elementu jest object. − W innym przypadku generowany jest błąd i nie są podejmowane żadne dalsze kroki. W wyniku podjęcia przedstawionych tu kroków, jeśli działanie zakończy się sukcesem, otrzymuje się typ kolekcji C, typ enumeratora E oraz typ elementu T. Instrukcja foreach o postaci: foreach (V v in x) instrukcja-osadzona jest rozwijana do postaci: { E e = ((C)(x)).GetEnumerator(); try { V v; while (e.MoveNext()) { v = (V)(T)e.Current; instrukcja-osadzona } } finally { ... // Zwolnienie zmiennej e } } Zmienna e nie jest widoczna ani dostępna dla wyrażenia x, instrukcji osadzonej ani jakiegokol- wiek innego kodu źródłowego programu. Zmienna v jest tylko do odczytu w instrukcji osadzonej. Jeśli nie istnieje jawna konwersja (o której więcej w podrozdziale 6.2) z typu T (typu elementu) na typ V (typu typ-zmiennej-lokalnej w instrukcji foreach), generowany jest błąd i nie są wyko- nywane żadne dalsze kroki. Jeśli zmienna x ma wartość null, w czasie wykonania zgłaszany jest wyjątek System.NullReferenceException. Implementacja może definiować dany element instrukcja-foreach na różne sposoby — na przy- kład z powodów związanych z wydajnością — jeśli tylko jego zachowanie jest zgodne z przed- stawionym powyżej rozwinięciem. Ciało bloku finally jest konstruowane zgodnie z przedstawionymi poniżej krokami: • Jeśli istnieje niejawna konwersja z typu E na interfejs System.IDisposable, wówczas − Jeśli E jest typem wartościowym niedopuszczającym wartości pustej, klauzula finally jest rozwijana do postaci równoznacznej z zapisem: finally { ((System.IDisposable)e).Dispose(); } − W innym przypadku klauzula finally jest rozwijana do postaci równoznacznej z zapisem: finally { if (e != null) ((System.IDisposable)e).Dispose(); } 396 8.8. Instrukcje iteracji z wyjątkiem tego, że jeśli E jest typem wartościowym lub parametrem typu zrealizowanym do typu wartościowego, wówczas rzutowanie na interfejs System.IDisposable nie powoduje wystąpienia operacji pakowania. • W innym przypadku, jeśli E jest typem ostatecznym, klauzula finally jest rozwijana do pustego bloku: finally { } • W innym przypadku klauzula finally jest rozwijana do postaci: finally { System.IDisposable d = e as System.IDisposable; if (d != null) d.Dispose(); } Zmienna lokalna d nie jest widoczna ani dostępna dla jakiegokolwiek fragmentu kodu użytkow- nika. W szczególności nie powoduje konfliktu z jakąkolwiek inną zmienną, której zakres obejmuje blok finally. Kolejność, w jakiej instrukcja foreach przechodzi przez elementy tablicy, jest następująca: w przypadku tablic jednowymiarowych elementy są przetwarzane zgodnie z porządkiem rosnących indeksów, zaczynając od indeksu 0, a kończąc na indeksie Lenght - 1. W przypadku tablic wielowymiarowych elementy są przetwarzane w taki sposób, że indeksy wymiaru znajdującego się najbardziej na prawo są zwiększane najpierw, a następnie zwiększane są indeksy następnego wymiaru na lewo i tak dalej, aż do wymiaru znajdującego się najbardziej na lewo. Wykonanie przedstawionego poniżej przykładowego programu powoduje wyświetlenie na ekranie każdej wartości dwuwymiarowej tablicy z zachowaniem kolejności elementów: using System; class Test { static void Main() { double[,] values = { {1.2, 2.3, 3.4, 4.5}, {5.6, 6.7, 7.8, 8.9} }; foreach (double elementValue in values) Console.Write( {0} , elementValue); Console.WriteLine(); } } W wyniku skompilowania i uruchomienia programu na ekranie komputera pojawiają się nastę- pujące dane: 1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9 W przedstawionym poniżej przykładzie: 397 8. Instrukcje int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) Console.WriteLine(n); typ n jest wnioskowany jako int, bowiem właśnie taki jest typ elementu tablicy liczb. 8.9. Instrukcje skoku Instrukcje skoku powodują bezwarunkowe przekazanie sterowania: instrukcja-skoku: instrukcja-break instrukcja-continue instrukcja-goto instrukcja-return instrukcja-throw Miejsce, do którego instrukcja skoku przekazuje sterowanie, nosi nazwę celu (ang. target) instruk- cji skoku. Gdy instrukcja skoku występuje w bloku, a jej cel znajduje się poza tym blokiem, mówi się, że instrukcja skoku opuszcza (ang. exit) blok. Mimo że instrukcja skoku może przekazywać stero- wanie poza blok, nigdy nie jest w stanie przekazać go do bloku. Wykonanie instrukcji skoków jest komplikowane przez obecność ingerujących instrukcji try. W przypadku braku takich instrukcji try instrukcja skoku bezwarunkowo przekazuje sterowanie z instrukcji skoku do jej celu. Jeśli są obecne ingerujące instrukcje try, wykonanie jest bardziej skomplikowane. Jeśli instrukcja skoku opuszcza jeden lub większą liczbę bloków try z powią- zanymi blokami finally, sterowanie jest początkowo przekazywane do bloku finally najbardziej wewnętrznej instrukcji try. Gdy i jeśli sterowanie osiąga końcowy punkt bloku finally, jest ono przekazywane do bloku finally kolejnej obejmującej instrukcji try. Proces ten jest powtarzany aż do momentu wykonania bloków finally wszystkich ingerujących instrukcji try. W przedstawionym poniżej przykładzie: using System; class Test { static void Main() { while (true) { try { try { Console.WriteLine( Przed instrukcją break ); break; } finally { Console.WriteLine( Najbardziej wewnętrzny blok finally ); } } finally { Console.WriteLine( Najbardziej zewnętrzny blok finally ); 398 8.9. Instrukcje skoku } } Console.WriteLine( Po instrukcji break ); } } bloki finally związane z dwoma instrukcjami try są wykonywane przed przekazaniem sterowania do celu instrukcji skoku. W wyniku skompilowania i uruchomienia przedstawionego powyżej programu na ekranie kom- putera pojawiają się następujące dane: Przed instrukcją break Najbardziej wewnętrzny blok finally Najbardziej zewnętrzny blok finally Po instrukcji break 8.9.1. Instrukcja break Instrukcja break powoduje opuszczenie najbliższej obejmującej instrukcji switch, while, do, for lub foreach. instrukcja-break: break ; Celem instrukcji break jest końcowy punkt najbliższej obejmującej instrukcji switch, while, do, for lub foreach. Jeśli instrukcji break nie obejmuje instrukcja switch, while, do, for lub foreach, generowany jest błąd czasu kompilacji. Gdy wiele instrukcji switch, while, do, for lub foreach jest zagnieżdżonych w sobie, instrukcja break jest stosowana jedynie do najbardziej wewnętrznej instrukcji obejmującej. Aby przekazać sterowanie przez wiele zagnieżdżonych poziomów, trzeba skorzystać z instrukcji goto (opisanej w punkcie 8.9.3). Instrukcja break nie może spowodować opuszczenia bloku finally (o czym więcej w podroz- dziale 8.10). Gdy instrukcja break występuje w ramach bloku finally, cel tej instrukcji musi znaj- dować się w tym samym bloku finally. W innym przypadku generowany jest błąd czasu kompilacji. Instrukcja break jest wykonywana w następujący sposób: • Jeśli instrukcja break powoduje opuszczenie jednego lub większej liczby bloków try z powią- zanymi blokami finally, sterowanie jest początkowo przekazywane do bloku finally najbar- dziej wewnętrznej instrukcji try. Gdy i jeśli sterowanie osiąga końcowy punkt bloku finally, jest ono przekazywane do bloku finally kolejnej obejmującej instrukcji try. Proces ten jest powtarzany aż do momentu wykonania bloków finally wszystkich ingerujących instrukcji try. • Sterowanie jest przekazywane do celu instrukcji break. Ponieważ instrukcja break bezwarunkowo przekazuje sterowanie w jakieś inne miejsce, końcowy punkt tej instrukcji nigdy nie jest osiągalny. 399 8. Instrukcje 8.9.2. Instrukcja continue Instrukcja continue powoduje rozpoczęcie nowej iteracji najbliższej obejmującej instrukcji while, do, for lub foreach: instrukcja-continue: continue ; Celem instrukcji continue jest końcowy punkt najbliższej instrukcji osadzonej najbliższej obej- mującej instrukcji while, do, for lub foreach. Jeśli instrukcji continue nie obejmuje instrukcja while, do, for lub
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Język C#. Programowanie. Wydanie III. Microsoft .NET Development Series
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ą: