Język C# 6.0 i platforma .NET 4.6
Autorzy: Andrew Troelsen Phiplip Japikse
Wydawnictwo: DW PWN
Kategorie: Informatyka
Typ: e-book
Formaty: MOBI EPUB
Ilość stron: 1412
cena od: 119.40 zł
Materiał do nowego, siódmego wydania książki, został całkowicie poprawiony i napisany tak, aby uwzględnić ostatnie zmiany w specyfikacji języka C#, a także nowości na platformie .NET. W książce tej znajdziesz omówienie wszystkich nowych funkcji, które sprawiają, że .NET 4.6 jest obecnie wersją najbardziej kompletną. Celem książki jest udostępnienie czytelnikowi kompleksowego, dogłębnego omówienia podstaw języka programowania C# oraz kluczowych aspektów platformy .NET, jak również przeglądu technologii opartych na C# i .NET (ADO.NET i Entity Framework, WCF [Windows Communication Foundation\, WPF [Windows Presentation Foundation\ i ASP.NET [WebForms, MVC, WebAPI\).
Title: C# 6.0 AND THE .NET 4.6 FRAMEWORK
Author: Andrew Troelsen and Philip Japikse
ISBN: 9781484213339
Edition: Seventh Edition
Original edition copyright © 2015 by Andrew Troelsen and Philip Japikse. All rights reserved.
Przekład WITKOM Witold Sikorski: Maciej Baranowski
Projekt okładki polskiego wydania Hubert Zacharski
Wydawca Łukasz Łopuszański
Redaktor prowadzący Adam Kowalski
Redaktor Izabela Mika
Koordynator produkcji Anna Bączkowska
Skład wersji elektronicznej na zlecenie Wydawnictwa Naukowego PWN: mobisfera.pl
Zastrzeżonych nazw firm i produktów użyto w książce wyłącznie w celu identyfikacji.
Copyright © for the Polish edition by Wydawnictwo Naukowe PWN S.A.
Warszawa 2017
ISBN 978-83-01-19832-9
eBook został przygotowany na podstawie wydania papierowego z 2017 r., (wyd. I)
Warszawa 2018
Wydawnictwo Naukowe PWN SA
02-460 Warszawa, ul. Gottlieba Daimlera 2
tel. 22 69 54 321, faks 22 69 54 288
infolinia 801 33 33 88
e-mail: pwn@pwn.com.pl, www.pwn.pl
Dedykuję całemu klanowi Troelsenów:
Mary (mamie), Walterowi (tacie),
Mandy (żonie) i Sorenowi (synowi).
Mikko (kocie), brakuje nam ciebie.
Andrew
Moja rodzino: Amy (żono), Connerze (synu), Loganie (synu) i Skylar (córko),
dziękuję wam za okazane mi wsparcie i cierpliwość.
Philip
Ogólny spis treści
Okładka
Dedykacja
Strona tytułowa
Strona redakcyjna
Ogólny spis treści
Spis treści
O autorach
O recenzencie technicznym
Podziękowania
Wprowadzenie
Część I Wprowadzenie do C# i platformy .NET Rozdział 1 Filozofia .NET
Rozdział 2 Tworzenie aplikacji w języku C#
Część II Podstawy programowania w języku C# Rozdział 3 Podstawowe konstrukcje programistyczne języka C#, część 1
Rozdział 4 Podstawowe konstrukcje programistyczne języka C#, część 2
Część III Programowanie obiektowe w języku C# Rozdział 5 Hermetyzacja
Rozdział 6 Dziedziczenie i polimorfizm
Rozdział 7 Strukturalna obsługa wyjątków
Rozdział 8 Interfejsy
Część IV Zaawansowane programowanie w języku C# Rozdział 9 Kolekcje i typy generyczne
Rozdział 10 Delegaty, zdarzenia i wyrażenia lambda
Rozdział 11 Zaawansowane elementy języka C#
Rozdział 12 LINQ to Objects
Rozdział 13 Czas życia obiektu
Część V Programowanie z wykorzystaniem pakietów .NET Rozdział 14 Tworzenie i konfiguracja bibliotek klas
Rozdział 15 Refleksja typów, późne wiązanie i programowanie z wykorzystaniem atrybutów
Rozdział 16 Typy dynamiczne i środowisko DLR
Rozdział 17 Procesy, domeny aplikacji i konteksty obiektów
Rozdział 18 CIL i rola pakietów dynamicznych
Część VI Wprowadzenie do bibliotek klas bazowych .NET Rozdział 19 Programowanie wielowątkowe, równoległe i asynchroniczne
Rozdział 20 Plikowe operacje we/wy oraz serializacja obiektów
Rozdział 21 ADO.NET – część 1: warstwa połączeniowa
Rozdział 22 ADO.NET – część 2: warstwa bezpołączeniowa
Rozdział 23 ADO.NET – część 3: Entity Framework
Rozdział 24 Wprowadzenie do LINQ to XML
Rozdział 25 Wprowadzenie do WCF (Windows Communication Foundation)
Część VII Windows Presentation Foundation Rozdział 26 Wprowadzenie do WPF (Windows Presentation Foundation) i XAML
Rozdział 27 Programowanie z wykorzystaniem kontrolek WPF
Rozdział 28 Usługi przetwarzania grafiki WPF
Rozdział 29 Zasoby, animacje, style i szablony WPF
Rozdział 30 Powiadomienia, polecenia, sprawdzanie prawidłowości danych i MVVM
Część VIII ASP.NET Rozdział 31 Wprowadzenie do ASP.NET Web Forms
Rozdział 32 Kontrolki internetowe, strony wzorcowe i motywy ASP.NET
Rozdział 33 Techniki zarządzania stanem ASP.NET
Rozdział 34 ASP.NET MVC i API Web
Spis treści
Okładka
Dedykacja
Strona tytułowa
Strona redakcyjna
Ogólny spis treści
Spis treści
O autorach
O recenzencie technicznym
Podziękowania
Wprowadzenie
Część I Wprowadzenie do C# i platformy .NET Rozdział 1 Filozofia .NET Pierwsze spojrzenie na platformę .NET Wybór kluczowych zalet platformy .NET
Wprowadzenie do części składowych platformy .NET (CLR, CTS i CLS)
Rola bibliotek klas bazowych
Co nowego wnosi C#?
Kod zarządzany a kod niezarządzany
Dodatkowe języki programowania na platformie .NET Życie w świecie wielojęzycznym
Przegląd pakietów .NET Rola języka CIL
Zalety CIL
Kompilowanie CIL do instrukcji na konkretną platformę
Rola metadanych typów .NET
Rola manifestu pakietu
Wspólny system typów (CTS) Klasy CTS
Interfejsy CTS
Struktury CTS
Wyliczenia CTS
Delegaty CTS
Składowe typów CTS
Wbudowane typy danych CTS
Specyfikacja wspólnego języka (CLS) Zapewnianie zgodności z CLS
Wspólne środowisko uruchomieniowe (CLR)
Różnica między pakietem, przestrzenią nazw i typem Rola głównej przestrzeni nazw Microsoft
Dostęp do przestrzeni nazw w kodzie programu
Odwołania do pakietów zewnętrznych
Analiza pakietu w programie ildasm.exe Przeglądanie kodu CIL
Przeglądanie metadanych typu
Przeglądanie metadanych pakietu (manifestu)
Niezależność .NET od platformy systemowej Projekt Mono
Microsoft .NET Core
Podsumowanie
Rozdział 2 Tworzenie aplikacji w języku C# Tworzenie aplikacji C# w systemie operacyjnym Windows Rodzina IDE: Visual Studio Express
Środowisko Visual Studio Community Edition
Projektant klas – Class Designer
Środowisko Visual Studio 2015 Professional
System dokumentacji .NET Framework
Tworzenie aplikacji .NET w innych systemach operacyjnych Xamarin Studio
Podsumowanie
Część II Podstawy programowania w języku C# Rozdział 3 Podstawowe konstrukcje programistyczne języka C#, część 1 Anatomia prostego programu C# Warianty metody Main( )
Określanie kodu błędu aplikacji
Przetwarzanie argumentów z wiersza poleceń
Podawanie argumentów wiersza poleceń w Visual Studio
Ciekawostka: kilka kolejnych składowych klasy System.Environment
Klasa System.Console Podstawowe operacje we/wy z klasą Console
Formatowanie wyników na konsoli
Formatowanie danych liczbowych
Formatowanie danych liczbowych poza aplikacjami konsolowymi
Systemowe typy danych i odpowiadające im słowa kluczowe C# Deklarowanie i inicjalizowanie zmiennych
Wbudowane typy danych i operator new
Hierarchia klas typów danych
Składowe liczbowych typów danych
Składowe typu System.Boolean
Składowe typu System.Char
Analiza składniowa (parsing) wartości z danych łańcuchowych
Typy System.DateTime i System.TimeSpan
Pakiet System.Numerics.dll
Używanie danych łańcuchowych Podstawowe operacje na łańcuchach
Konkatenacja łańcuchów
Znaki ucieczki
Definiowanie łańcuchów dosłownych
Porównywanie łańcuchów
Łańcuchów nie można modyfikować
Typ System.Text.StringBuilder
Interpolacja łańcuchów
Zawężające i rozszerzające konwersje typów danych Słowo kluczowe checked
Ustawianie sprawdzania nadmiaru w całym projekcie
Słowo kluczowe unchecked
Zmienne lokalne z typizacją niejawną Ograniczenia niejawnej typizacji zmiennych
Dane z niejawną typizacją to dane z silną typizacją
Przydatność zmiennych lokalnych z niejawną typizacją
Konstrukcje iteracyjne w języku C# Pętla for
Pętla foreach
Stosowanie niejawnej typizacji w konstrukcjach foreach
Pętle while i do/while
Konstrukcje decyzyjne i operatory porównania/równości Instrukcja if/else
Operatory równości i relacyjne
Operatory warunkowe
Instrukcja switch
Podsumowanie
Rozdział 4 Podstawowe konstrukcje programistyczne języka C#, część 2 Metody i modyfikatory parametrów Domyślne przekazywanie parametrów – przez wartość
Modyfikator out
Modyfikator ref
Modyfikator params
Definiowanie parametrów opcjonalnych
Wywoływanie metod za pomocą parametrów nazwanych
Przeciążanie metod
Tablice w języku C# Składnia inicjalizacji tablic w języku C#
Tablice lokalne z typizacją niejawną
Definiowanie tablicy obiektów
Tablice wielowymiarowe
Tablice jako argumenty lub wartości zwracane
Klasa bazowa System.Array
Wyliczenia Określanie typu bazowego
Deklarowanie zmiennych typu wyliczeniowego
Typ System.Enum
Dynamiczne wykrywanie par nazwa/wartość wyliczenia
Struktury (typy wartościowe) Tworzenie zmiennych strukturalnych
Typy wartościowe a typy referencyjne Typy wartościowe, typy referencyjne i operator przypisania
Typy wartościowe zawierające typy referencyjne
Przekazywanie typów referencyjnych przez wartość
Przekazywanie typów referencyjnych przez referencję
Ostatnie słowo na temat typów wartościowych i typów referencyjnych
Typy dopuszczające wartości null Używanie typów dopuszczających wartości null
Operator scalający wartości null
Operator warunkowy dla wartości null
Podsumowanie
Część III Programowanie obiektowe w języku C# Rozdział 5 Hermetyzacja Wprowadzenie do klas w języku C# Alokowanie obiektów za pomocą słowa kluczowego new
Konstruktory Rola konstruktora domyślnego
Definiowanie konstruktorów niestandardowych
Konstruktor domyślny raz jeszcze
Rola słowa kluczowego this Szeregowanie wywołań konstruktorów z użyciem słowa kluczowego this
Obserwacja przepływu konstruktorów
Argumenty opcjonalne raz jeszcze
Słowo kluczowe static Definiowanie statycznych pól danych
Definiowanie statycznych metod
Definiowanie statycznych konstruktorów
Definiowanie statycznych klas
Importowanie statycznych składowych za pomocą słowa kluczowego using
Filary programowania obiektowego Znaczenie hermetyzacji
Znaczenie dziedziczenia
Znaczenie polimorfizmu
Modyfikatory dostępu w języku C# Domyślne modyfikatory dostępu
Modyfikatory dostępu a typy zagnieżdżone
Pierwszy filar: usługi hermetyzacji C# Hermetyzacja z użyciem tradycyjnych metod pobierającej i ustawiającej
Hermetyzacja za pomocą właściwości .NET
Używanie właściwości w definicji klasy
Właściwości tylko do odczytu i tylko do zapisu
Słowo kluczowe static raz jeszcze: definiowanie właściwości statycznych
Właściwości automatyczne Używanie właściwości automatycznych
Właściwości automatyczne i wartości domyślne
Inicjalizacja właściwości automatycznych
Składnia inicjalizacji obiektów Wywoływanie niestandardowych konstruktorów za pomocą składni inicjalizacji
Inicjalizacja danych z użyciem składni inicjalizacji
Używanie stałych pól danych Pola tylko do odczytu
Statyczne pola tylko do odczytu
Klasy częściowe Kiedy używać klas częściowych?
Podsumowanie
Rozdział 6 Dziedziczenie i polimorfizm Podstawowe zasady dziedziczenia Określanie klasy nadrzędnej dla klasy istniejącej
Słowo kluczowe sealed
Diagramy klas w Visual Studio raz jeszcze
Drugi filar programowania obiektowego: szczegóły na temat dziedziczenia Tworzenie klas bazowych pod kontrolą – słowo kluczowe base
Co w rodzinie, to nie zginie: słowo kluczowe protected
Dodawanie klasy zapieczętowanej
Programowanie na zasadzie zawierania/delegacji Definiowanie typów zagnieżdżonych
Trzeci filar programowania obiektowego: polimorfizm Słowa kluczowe virtual i override
Nadpisywanie wirtualnych składowych w Visual Studio
Pieczętowanie wirtualnych składowych
Klasy abstrakcyjne
Interfejs polimorficzny
Przesłanianie składowych
Zasady rzutowania klasa bazowa/klasa pochodna Słowo kluczowe as
Słowo kluczowe is
Najważniejsza klasa nadrzędna: System.Object Nadpisywanie metody System.Object.ToString()
Nadpisywanie metody System.Object.Equals()
Nadpisywanie metody System.Object.GetHashCode()
Testowanie zmodyfikowanej klasy Person
Statyczne składowe klasy System.Object
Podsumowanie
Rozdział 7 Strukturalna obsługa wyjątków Oda do błędów i wyjątków
Rola obsługi wyjątków .NET Kluczowe elementy obsługi wyjątków na platformie .NET
Klasa bazowa System.Exception
Przykład najprostszy z możliwych Zgłaszanie ogólnego wyjątku
Przechwytywanie wyjątków
Konfigurowanie stanu wyjątku Właściwość TargetSite
Właściwość StackTrace
Właściwość HelpLink
Właściwość Data
Wyjątki systemowe (System.SystemException)
Wyjątki na poziomie aplikacji (System.ApplicationException) Tworzenie wyjątków niestandardowych, odsłona 1
Tworzenie wyjątków niestandardowych, odsłona 2
Tworzenie wyjątków niestandardowych, odsłona 3
Przetwarzanie wielu wyjątków Ogólne instrukcje catch
Ponowne zgłaszanie wyjątków
Wyjątki wewnętrzne
Blok finally
Filtrowanie wyjątków
Debugowanie nieobsłużonych wyjątków w Visual Studio
Podsumowanie
Rozdział 8 Interfejsy Interfejsy Interfejsy a abstrakcyjne klasy bazowe
Definiowanie niestandardowych interfejsów
Implementowanie interfejsu
Wywoływanie składowych interfejsu na poziomie obiektu Uzyskiwanie referencji do interfejsu: słowo kluczowe as
Uzyskiwanie referencji do interfejsu: słowo kluczowe is
Interfejsy jako parametry
Interfejsy jako wartości zwracane
Tablice interfejsów
Implementowanie interfejsów w Visual Studio
Jawna implementacja interfejsu
Projektowanie hierarchii interfejsów Dziedziczenie wielokrotne a interfejsy
Interfejsy IEnumerable i IEnumerator Tworzenie iteratorów za pomocą słowa kluczowego yield
Tworzenie nazwanego iteratora
Interfejs ICloneable Bardziej złożony przykład klonowania
Interfejs IComparable Określanie wielu porządków sortowania za pomocą interfejsu IComparer
Niestandardowe właściwości i typy sortowania
Podsumowanie
Część IV Zaawansowane programowanie w języku C# Rozdział 9 Kolekcje i typy generyczne Uzasadnienie używania klas kontenerowych Przestrzeń nazw System.Collections
Przegląd przestrzeni nazw System.Collections.Specialized
Problemy z kolekcjami niegenerycznymi Problem z wydajnością
Problem z bezpieczeństwem typów
Kolekcje generyczne – pierwsze spotkanie
Generyczne parametry określające typ Określanie parametrów typu dla generycznych klas/struktur
Określanie parametrów typu dla generycznych składowych
Określanie parametrów typu dla generycznych interfejsów
Przestrzeń nazw System.Collections.Generic Składnia inicjalizacji kolekcji
Używanie klasy List<T>
Używanie klasy Stack<T>
Używanie klasy Queue<T>
Używanie klasy SortedSet<T>
Używanie klasy Dictionary<TKey, TValue>
Przestrzeń nazw System.Collections.ObjectModel Używanie klasy ObservableCollection<T>
Tworzenie niestandardowych metod generycznych Inferencja parametrów określających typ
Tworzenie niestandardowych generycznych struktur i klas Słowo kluczowe default w generycznym kodzie
Ograniczenia stosowania parametrów określających typ Przykłady ze słowem kluczowym where
Brak operatorów w ograniczeniach
Podsumowanie
Rozdział 10 Delegaty, zdarzenia i wyrażenia lambda Delegaty na platformie .NET Definiowanie delegatów w języku C#
Klasy bazowe System.MulticastDelegate i System.Delegate
Najprostszy z możliwych przykładowy delegat Analiza obiektu delegatu
Wysyłanie powiadomień o stanie obiektu za pomocą delegatów Możliwość multiemisji
Usuwanie celów z listy wywołań delegatu
Technika grupowej konwersji metod
Delegaty generyczne Generyczne delegaty Action<> i Func<>
Zdarzenia w języku C# Słowo kluczowe event w języku C#
Zdarzenia pod maską
Nasłuchiwanie nadchodzących zdarzeń
Upraszczanie rejestracji zdarzeń za pomocą Visual Studio
Oczyszczanie wywoływania zdarzeń za pomocą operatora warunkowego dla wartości null języka C# w wersji 6.0
Tworzenie niestandardowych argumentów zdarzeń
Generyczny delegat EventHandler<T>
Anonimowe metody C# Dostęp do zmiennych lokalnych
Wyrażenia lambda Analiza wyrażenia lambda
Przetwarzanie argumentów w wielu instrukcjach
Wyrażenia lambda z wieloma parametrami (i bez parametrów)
Wyposażanie przykładu CarEvents w wyrażenia lambda
Wyrażenie lambda i implementacje składowych w jednej instrukcji
Podsumowanie
Rozdział 11 Zaawansowane elementy języka C# Indeksery Indeksowanie danych za pomocą wartości łańcuchowych
Przeciążanie indekserów
Indeksery z wieloma wymiarami
Definiowanie indekserów w interfejsach
Przeciążanie operatorów Przeciążanie operatorów dwuargumentowych
A co z operatorami += i –+?
Przeciążanie operatorów jednoargumentowych
Przeciążanie operatorów równości
Przeciążanie operatorów porównania
Ostatnie uwagi na temat przeciążania operatorów
Konwersje niestandardowych typów Powtórka: konwersje liczbowe
Powtórka: konwersje między spokrewnionymi klasami
Tworzenie niestandardowych procedur konwersji
Dodatkowe jawne konwersje dla typu Square
Definiowanie procedur niejawnej konwersji
Metody rozszerzające Definiowanie metod rozszerzających
Wywoływanie metod rozszerzających
Importowanie metod rozszerzających
IntelliSense dla metod rozszerzających
Rozszerzanie typów, w których zaimplementowano konkretne interfejsy
Typy anonimowe Definiowanie typu anonimowego
Wewnętrzna reprezentacja typów anonimowych
Implementacja metod ToString() i GetHashCode()
Semantyka równości dla typów anonimowych
Typy anonimowe zawierające typy anonimowe
Używanie typów wskaźnikowych Słowo kluczowe unsafe
Używanie operatorów * i &
Nienadzorowana (i nadzorowana) funkcja wymiany
Dostęp do pól za pomocą wskaźników (operator ->)
Słowo kluczowe stackalloc
Przypinanie typu za pomocą słowa kluczowego fixed
Słowo kluczowe sizeof
Podsumowanie
Rozdział 12 LINQ to Objects Konstrukcje programistyczne związane z zapytaniami LINQ Niejawna typizacja zmiennych lokalnych
Składnia inicjalizacji obiektów i kolekcji
Wyrażenia lambda
Metody rozszerzające
Typy anonimowe
Rola zapytań LINQ Wyrażenia LINQ mają silną typizację
Najważniejsze pakiety LINQ
Używanie zapytań LINQ z prostymi tablicami To samo raz jeszcze, ale bez LINQ
Refleksja na zbiorze wyników LINQ
LINQ a zmienne lokalne z typizacją niejawną
LINQ a metody rozszerzające
Rola odroczonego wykonywania
Rola wykonywania natychmiastowego
Zwracanie wyników zapytania LINQ Zwracanie wyników LINQ za pomocą natychmiastowego wykonania
Stosowanie zapytań LINQ do obiektów kontenerowych Dostęp do podobiektów
Używanie zapytań LINQ z kolekcjami niegenerycznymi
Filtrowanie danych za pomocą OfType<T>( )
Omówienie operatorów do zapytań LINQ Składnia podstawowego wybierania
Uzyskiwanie podzbiorów danych
Projekcja nowych typów danych
Uzyskiwanie liczników za pomocą Enumerable
Odwracanie kolejności elementów w zbiorze wyników
Sortowanie wyrażeń
LINQ jako ulepszone narzędzie do tworzenia diagramów Venna
Usuwanie duplikatów
Działania agregacyjne LINQ
Wewnętrzna reprezentacja instrukcji z zapytaniami LINQ Konstruowanie wyrażeń z zapytaniami za pomocą operatorów (powtórka)
Konstruowanie wyrażeń z zapytaniami za pomocą typu Enumerable i wyrażeń lambda
Konstruowanie wyrażeń z zapytaniami za pomocą typu Enumerable i metod anonimowych
Konstruowanie wyrażeń z zapytaniem za pomocą typu Enumerable i bezpośrednio delegatów
Podsumowanie
Rozdział 13 Czas życia obiektu Klasy, obiekty i referencje
Podstawowe informacje o czasie życia obiektów Kod CIL słowa kluczowego new
Ustawianie referencji do obiektów na null
Korzenie aplikacji
Generacje obiektów
Współbieżne odzyskiwanie pamięci przed .NET 4.0
Drugoplanowe odzyskiwanie pamięci od wersji .NET 4.0 wzwyż
Typ System.GC Wymuszanie odzyskiwania pamięci
Tworzenie obiektów do sfinalizowania Nadpisywanie metody System.Object.Finalize()
Szczegóły na temat procesu finalizacji
Tworzenie obiektów usuwalnych Wielokrotne użycie słowa kluczowego using
Tworzenie typów finalizowalnych i usuwalnych Formalny schemat usuwania
Leniwe tworzenie instancji obiektów Dostosowywanie tworzenia leniwych danych
Podsumowanie
Część V Programowanie z wykorzystaniem pakietów .NET Rozdział 14 Tworzenie i konfiguracja bibliotek klas Definiowanie niestandardowych przestrzeni nazw Rozwiązywanie konfliktów nazw za pomocą pełnych nazw jednoznacznych
Rozwiązywanie konfliktów nazw za pomocą aliasów
Tworzenie zagnieżdżonych przestrzeni nazw
Domyślna przestrzeń nazw w Visual Studio
Rola pakietów .NET Pakiety sprzyjają tworzeniu kodu wielokrotnego użytku
Pakiety wyznaczają granice typu
Pakiety to jednostki z numerami wersji
Pakiety zawierają własny opis
Pakiety można konfigurować
Format pakietu .NET Nagłówek pliku Windows
Nagłówek pliku CLR
Kod CIL, metadane typu i manifest pakietu
Opcjonalne zasoby pakietu
Tworzenie i używanie niestandardowej biblioteki z klasą Analiza manifestu
Analiza kodu CIL
Analiza metadanych typu
Tworzenie aplikacji klienckiej w języku C#
Tworzenie aplikacji klienckiej w języku Visual Basic
Dziedziczenie wielojęzyczne w działaniu
Pakiety prywatne Tożsamość pakietu prywatnego
Proces sondowania
Konfigurowanie pakietów prywatnych
Rola pliku App.Config
Pakiety współdzielone Globalny katalog pakietów (GAC)
Silne nazwy
Generowanie silnych nazw w wierszu poleceń
Generowanie silnych nazw w Visual Studio
Instalowanie pakietów z silnymi nazwami w GAC
Używanie współdzielonego pakietu Analiza manifestu SharedCarLibClient
Konfigurowanie pakietów współdzielonych Zamrażanie bieżącego pakietu współdzielonego
Tworzenie współdzielonego pakietu wersja 2.0.0.0
Dynamiczne przekierowywanie do konkretnych wersji współdzielonego pakietu
Pakiety z zasadami wydawcy Wyłączanie zasad wydawcy
Element <codeBase>
Przestrzeń nazw System.Configuration
Dokumentacja na temat schematu pliku konfiguracyjnego
Podsumowanie
Rozdział 15 Refleksja typów, późne wiązanie i programowanie z wykorzystaniem atrybutów Zapotrzebowanie na metadane typów Przeglądanie (fragmentu) metadanych dla wyliczenia EngineState
Przeglądanie (fragmentu) metadanych typu Car
Omówienie TypeRef
Informacje o bieżącym pakiecie
Informacje o używanych pakietach zewnętrznych
Informacje o literałach łańcuchowych
Refleksja Klasa System.Type
Uzyskiwanie referencji do typu za pomocą System.Object.GetType()
Uzyskiwanie referencji do typu za pomocą typeof()
Uzyskiwanie referencji do typu za pomocą System.Type.GetType()
Tworzenie niestandardowej przeglądarki metadanych Refleksja na metodach
Refleksja na polach i właściwościach
Refleksja na zaimplementowanych interfejsach
Wyświetlanie różnych mniej istotnych informacji
Implementacja metody Main()
Refleksja na typach generycznych
Refleksja na parametrach i wartościach zwracanych
Dynamiczne wczytywanie pakietów
Refleksja na pakietach współdzielonych
Późne dowiązywanie Klasa System.Activator
Wywoływanie metod bez parametrów
Wywoływanie metod z parametrami
Rola atrybutów .NET Użytkownicy atrybutów
Stosowanie atrybutów w języku C#
Skrótowy zapis atrybutów C#
Podawanie parametrów konstrukcyjnych dla atrybutów
Atrybut [Obsolete] w działaniu
Tworzenie niestandardowych atrybutów Stosowanie atrybutów niestandardowych
Składnia nazwanych właściwości
Ograniczanie stosowania atrybutów
Atrybuty na poziomie pakietu Plik AssemblyInfo.cs w Visual Studio
Refleksja na atrybutach z użyciem wczesnego dowiązywania
Refleksja na atrybutach z użyciem późnego dowiązywania
Refleksja, późne dowiązywanie i niestandardowe atrybuty z lotu ptaka
Tworzenie rozszerzalnej aplikacji Tworzenie pakietu CommonSnappableTypes.dll
Tworzenie wtyczki w języku C#
Tworzenie wtyczki w języku Visual Basic
Tworzenie rozszerzalnej aplikacji Windows Forms
Podsumowanie
Rozdział 16 Typy dynamiczne i środowisko DLR Rola słowa kluczowego dynamic Wywoływanie składowych względem dynamicznie zadeklarowanych danych
Rola pakietu Microsoft.CSharp.dll
Zasięg słowa kluczowego dynamic
Ograniczenia słowa kluczowego dynamic
Praktyczne zastosowania słowa kluczowego dynamic
Rola środowiska DLR Drzewa wyrażeń
Przestrzeń nazw System.Dynamic
Dynamiczne wyszukiwanie drzew wyrażeń
Upraszczanie wywołań z późnym dowiązywaniem za pomocą typów dynamicznych Używanie słowa kluczowego dynamic do przekazania argumentów
Upraszczanie współpracy z COM za pomocą danych dynamicznych Podstawowe pakiety współdziałania
Osadzanie metadanych współdziałania
Najczęściej występujące problemy ze współpracą z kodem COM
Współpraca z COM z użyciem dynamicznych danych C# Współpraca z COM bez danych dynamicznych
Podsumowanie
Rozdział 17 Procesy, domeny aplikacji i konteksty obiektów Rola procesów Windows Rola wątków
Komunikacja z procesami na platformie .NET Wyliczanie uruchomionych procesów
Analiza konkretnego procesu
Analiza zbioru wątków procesu
Analiza zbioru modułów procesu
Rozpoczynanie i zatrzymywanie procesów w kodzie programu
Kontrolowanie rozpoczynania procesów za pomocą klasy ProcessStartInfo
Domeny aplikacji .NET Klasa System.AppDomain
Komunikacja z domyślną domeną aplikacji Wyliczanie załadowanych pakietów
Odbiór powiadomień o załadowaniu pakietu
Tworzenie nowych domen aplikacji Wczytywanie pakietów do niestandardowych domen aplikacji
Usuwanie domen aplikacji w kodzie programu
Granice kontekstowe obiektów Typy niezwiązane i związane kontekstem
Definiowanie obiektu kontekstowego
Analiza kontekstu obiektu
Podsumowanie procesów, domen aplikacji i kontekstów
Podsumowanie
Rozdział 18 CIL i rola pakietów dynamicznych Motywacja do nauki gramatyki CIL
Analiza dyrektyw, atrybutów i kodów operacyjnych CIL Dyrektywy CIL
Atrybuty CIL
Kody operacyjne CIL
Różnica między kodami operacyjnymi a mnemonikami
Wrzucanie i zrzucanie: znaczenie stosu w języku CIL
Inżynieria wahadłowa Etykiety kodu CIL
Komunikacja z kodem CIL: modyfikowanie pliku *.il
Kompilowanie kodu CIL w programie ilasm.exe
Rola programu peverify.exe
Dyrektywy i atrybuty CIL Podawanie wymaganych pakietów zewnętrznych w CIL
Definiowanie bieżącego pakietu w CIL
Definiowanie przestrzeni nazw w CIL
Definiowanie klas w CIL
Definiowanie i implementowanie interfejsów w CIL
Definiowanie struktur w CIL
Definiowanie wyliczeń w CIL
Definiowanie typów generycznych w kodzie CIL
Kompilowanie pliku CILTypes.il
Związek między bazowymi bibliotekami klas, językiem C# i typami danych CIL
Definiowanie składowych w CIL Definiowanie pól z danymi w CIL
Definiowanie konstruktorów w CIL
Definiowanie właściwości w CIL
Definiowanie parametrów składowych
Kody operacyjne CIL Dyrektywa .maxstack
Deklarowanie zmiennych lokalnych w CIL
Odwzorowywanie parametrów na zmienne lokalne w CIL
Ukryta referencja this
Reprezentacja konstrukcji iteracyjnych w CIL
Tworzenie pakietu .NET w CIL Tworzenie pakietu CILCars.dll
Tworzenie pakietu CILCarClient.exe
Pakiety dynamiczne Analiza przestrzeni nazw System.Reflection.Emit
Typ System.Reflection.Emit.ILGenerator
Generowanie pakietu dynamicznego
Generowanie pakietu i zbioru modułów
Znaczenie typu ModuleBuilder
Generowanie typu HelloClass i łańcuchowej zmiennej składowej
Generowanie konstruktorów
Generowanie metody SayHello()
Używanie dynamicznie wygenerowanego pakietu
Podsumowanie
Część VI Wprowadzenie do bibliotek klas bazowych .NET Rozdział 19 Programowanie wielowątkowe, równoległe i asynchroniczne Relacja między procesami, domenami aplikacji, kontekstem i wątkami Problem współbieżności
Synchronizacja wątków
Krótka powtórka z delegatów .NET
Asynchroniczna natura delegatów Metody BeginInvoke() i EndInvoke()
Interfejs System.IAsyncResult
Asynchroniczne wywoływanie metod Synchronizacja wątku wywołującego
Delegat AsyncCallback
Klasa AsyncResult
Przekazywanie i odbieranie niestandardowych danych o stanie
Przestrzeń nazw System.Threading
Klasa System.Threading.Thread Uzyskiwanie statystyk dotyczących bieżącego wątku wykonywania
Właściwość Name
Właściwość Priority
Ręczne tworzenie wątków podrzędnych Używanie delegatu ThreadStart
Używanie delegatu ParameterizedThreadStart
Klasa AutoResetEvent
Wątki pierwszo- i drugoplanowe
Problem współbieżności Synchronizacja za pomocą słowa kluczowego lock
Synchronizacja za pomocą typu System.Threading.Monitor
Synchronizacja za pomocą typu System.Threading.Interlocked
Synchronizacja za pomocą atrybutu [Synchronization]
Programowanie z użyciem delegatu TimerCallback
Pula wątków CLR
Programowanie równoległe z wykorzystaniem TPL (Task Parallel Library) Przestrzeń nazw System.Threading.Tasks
Rola klasy Parallel
Równoległość danych i klasa Parallel
Dostęp do elementów IU z wątków podrzędnych
Klasa Task
Obsługa żądań anulowania
Równoległość zadań a klasa Parallel
Równoległe zapytania LINQ (PLINQ) Wybór zapytania PLINQ
Anulowanie zapytania PLINQ
Wywołania asynchroniczne z użyciem słowa kluczowego async Pierwsze spojrzenie na słowa kluczowe async i await
Konwencja nazewnicza metod asynchronicznych
Metody asynchroniczne zwracające void
Metody asynchroniczne z wieloma słowami kluczowymi await
Przeróbka przykładu AddWithThreads ze słowami kluczowymi async/await
Podsumowanie
Rozdział 20 Plikowe operacje we/wy oraz serializacja obiektów Przestrzeń nazw System.IO
Typy Directory(Info) i File(Info) Abstrakcyjna klasa bazowa FileSystemInfo
Używanie typu DirectoryInfo Wyliczanie plików za pomocą typu DirectoryInfo
Tworzenie podkatalogów za pomocą typu DirectoryInfo
Używanie typu Directory
Używanie klasy DriveInfo
Używanie klasy FileInfo The FileInfo.Create() Method
Metoda FileInfo.Open()
Metody FileInfo.OpenRead() i FileInfo.OpenWrite()
Metoda FileInfo.OpenText()
Metody FileInfo.CreateText() i FileInfo.AppendText()
Używanie typu File Inne składowe typu File
Abstrakcyjna klasa Stream Używanie klasy FileStream
Używanie klas StreamWriter i StreamReader Zapisywanie do pliku tekstowego
Odczytywanie z pliku tekstowego
Bezpośrednie tworzenie obiektów StreamWriter/StreamReader
Używanie klas StringWriter i StringReader
Używanie klas BinaryWriter i BinaryReader
Programistyczne monitorowanie plików
Serializacja obiektów Rola grafów obiektów
Konfiguracja obiektów do serializacji Definiowanie serializowalnych typów
Pola publiczne, pola prywatne i właściwości publiczne
Wybór typu z kategorii Formatter do serializacji Interfejsy IFormatter i IRemotingFormatter
Jak różne formatery utrwalają typy
Serializacja obiektów za pomocą typu BinaryFormatter Deserializacja obiektów za pomocą typu BinaryFormatter
Serializacja obiektów za pomocą typu SoapFormatter
Serializacja obiektów za pomocą typu XmlSerializer Kontrola nad generowanymi danymi XML
Serializacja kolekcji obiektów
Dostosowywanie serializacji SOAP/binarnej Serializacja obiektów pod lupą
Dostosowywanie serializacji za pomocą interfejsu ISerializable
Dostosowywanie serializacji za pomocą atrybutów
Podsumowanie
Rozdział 21 ADO.NET – część 1: warstwa połączeniowa Ogólna definicja ADO.NET Trzy oblicza ADO.NET
Dostawcy danych ADO.NET Dostawcy danych ADO.NET od Microsoftu
Słowo o pakiecie System.Data.OracleClient.dll
Uzyskiwanie dostawców danych ADO.NET od innych producentów
Dodatkowe przestrzenie nazw ADO.NET
Typy z przestrzeni nazw System.Data Interfejs IDbConnection
Interfejs IDbTransaction
Interfejs IDbCommand
Interfejsy IDbDataParameter i IDataParameter
Interfejsy IDbDataAdapter i IDataAdapter
Interfejsy IDataReader i IDataRecord
Ukrywanie dostawców danych za pomocą interfejsów Zwiększanie elastyczności za pomocą plików konfiguracyjnych aplikacji
Tworzenie bazy danych AutoLot Tworzenie tabeli Inventory
Dodawanie do tabeli Inventory rekordów testowych
Tworzenie składowanej procedury GetPetName()
Tworzenie tabel Customers i Orders
Tworzenie relacji między tabelami w Visual Studio
Model fabryki dostawcy danych ADO.NET Pełny przykład z fabryką dostawcy danych
Potencjalna wada modelu fabryki dostawcy danych
Element <connectionStrings>
Warstwa połączeniowa ADO.NET Używanie obiektów połączenia
Używanie obiektów ConnectionStringBuilder
Używanie obiektów polecenia
Używanie typów DataReader Uzyskiwanie wielu zbioru wyników za pomocą czytnika danych
Tworzenie biblioteki dostępu do danych wielokrotnego użytku Dodawanie kodu połączenia
Dodawanie kodu wstawiania
Dodawanie kodu usuwania
Dodawanie kodu aktualizacji
Dodawanie kodu wybierania
Używanie sparametryzowanych obiektów polecenia
Podawanie parametrów za pomocą typu DbParameter
Wywoływanie procedury składowanej
Tworzenie nakładki na aplikację konsolową Implementowanie metody Main()
Implementowanie metody ShowInstructions()
Implementowanie metody ListInventory()
Implementowanie metody DeleteCar()
Implementowanie metody InsertNewCar()
Implementowanie metody UpdateCarPetName()
Implementowanie metody LookUpPetName()
Transakcje bazodanowe Najważniejsze składowe obiektu transakcji ADO.NET
Dodawanie tabeli CreditRisks do bazy danych AutoLot
Dodawanie metody transakcyjnej do bazy danych InventoryDAL
Testowanie transakcji bazodanowej
Podsumowanie
Rozdział 22 ADO.NET – część 2: warstwa bezpołączeniowa Warstwa bezpołączeniowa ADO.NET
Rola typu DataSet Najważniejsze właściwości typu DataSet
Najważniejsze metody typu DataSet
Tworzenie obiektu DataSet
Używanie typu DataColumn Tworzenie typu DataColumn
Włączanie automatycznej inkrementacji w polach
Dodawanie obiektów DataColumn do DataTable
Używanie typu DataRow Właściwość RowState
Właściwość DataRowVersion
Używanie typu DataTable Wstawianie obiektów DataTable do obiektów DataSet
Uzyskiwanie danych z obiektu DataSet
Przetwarzanie danych DataTable za pomocą obiektów DataTableReader
Serializacja obiektów DataTable/DataSet w formacie XML
Serializacja obiektów DataTable/DataSet w formacie binarnym
Dowiązywanie obiektów DataTable do GUI Windows Forms Wyodrębnianie obiektu DataTable z generycznego List<T>
Usuwanie wierszy z obiektu DataTable
Wybieranie wierszy na podstawie kryteriów filtrowania
Aktualizacja wierszy w obiekcie DataTable
Używanie typu DataView
Używanie adapterów danych Prosty przykład z adapterem danych
Odwzorowanie nazw bazodanowych na nazwy przyjazne
Dodawanie funkcjonalności bezpołączeniowej do pakietu AutoLotDAL.dll Definiowanie początkowej klasy
Konfiguracja adaptera danych za pomocą typu SqlCommandBuilder
Implementacja metody GetAllInventory()
Implementacja metody UpdateInventory()
Ustawianie numeru wersji
Testowanie funkcji bezpołączeniowych
Obiekty DataSet z wieloma tabelami i relacjami danych Przygotowanie adapterów danych
Tworzenie relacji między tabelami
Modyfikacja tabel w bazie danych
Nawigacja między powiązanymi ze sobą tabelami
Narzędzia do projektowania baz danych Windows Forms Projektowanie kontrolki DataGridView w trybie graficznym
Wygenerowany plik App.config
Analiza obiektów DataSet z silną typizacją
Analiza obiektów DataTable z silną typizacją
Analiza obiektów DataRow z silną typizacją
Analiza adaptera danych z silną typizacją
Kończenie aplikacji Windows Forms
Wyodrębnianie kodu bazy danych z silną typizacją do biblioteki klasy Przeglądanie wygenerowanego kodu
Wybieranie danych za pomocą wygenerowanego kodu
Wstawianie danych za pomocą wygenerowanego kodu
Usuwanie danych za pomocą wygenerowanego kodu
Wywoływanie składowanej procedury za pomocą wygenerowanego kodu
Programowanie z wykorzystaniem technologii LINQ to DataSet Biblioteka DataSetExtensions
Uzyskiwanie obiektu DataTable kompatybilnego z LINQ
Metoda rozszerzająca DataRowExtensions.Field<T>()
Wypełnianie nowych obiektów DataTable na podstawie zapytań LINQ
Podsumowanie
Rozdział 23 ADO.NET – część 3: Entity Framework Rola Entity Framework Rola encji
Bloki budulcowe Entity Framework
Code First z istniejącej bazy danych Generowanie modelu
I co się wydarzyło?
Zmiana domyślnego mapowania
Dodawanie do wygenerowanych klas modeli
Używanie klas modeli w kodzie Wstawianie rekordów
Wybieranie rekordów
Rola właściwości nawigacyjnych
Usuwanie rekordów
Aktualizacja rekordów
Obsługa zmian w bazie danych
AutoLotDAL wersja 4 Adnotacje w Entity Framework
Dodawanie lub aktualizacja klas modeli
Dodawanie klasy wywodzącej się z DbContext
Dodawanie repozytoriów
Inicjalizacja bazy danych
Testowanie AutoLotDAL Wyświetlanie wszystkich rekordów z repozytorium Inventory
Dodawanie nowych rekordów do Inventory
Edytowanie rekordów
Używanie właściwości nawigacyjnych
Działania na wielu tabelach/transakcje niejawne
Migracje Entity Framework Aktualizowanie modelu
Testowanie aplikacji
Wprowadzenie do migracji EF
Tworzenie migracji linii bazowej
Ustawienie początkowych wartości bazy danych
Testowanie transakcji raz jeszcze
Współbieżność Korygowanie repozytoriów
Testowanie współbieżności
Przechwytywanie Interfejs IDbCommandInterceptor
Dodawanie przechwytywania do AutoLotDAL
Rejestrowanie interceptorów
Dodawanie interceptora DatabaseLogger
Zdarzenia ObjectMaterialized i SavingChanges Dostęp do kontekstu obiektu
ObjectMaterialized
SavingChanges
Wdrażanie na SQL Server
Podsumowanie
Rozdział 24 Wprowadzenie do LINQ to XML Opowieść o dwóch API XML LINQ to XML jako ulepszony model DOM
Składnia literałów VB jako ulepszony LINQ to XML
Składowe przestrzeni nazw System.Xml.Linq Metody osiowe LINQ to XML
Osobliwość XName (i XNamespace)
Używanie typów XElement i XDocument Generowanie dokumentów z tablic i kontenerów
Wczytywanie i analiza zawartości XML
Operacje na dokumentach XML znajdujących się w pamięci Tworzenie interfejsu użytkownika aplikacji LINQ to XML
Importowanie pliku Inventory.xml
Definiowanie klasy pomocniczej LINQ to XML
Dołączanie interfejsu użytkownika do klasy pomocniczej
Podsumowanie
Rozdział 25 Wprowadzenie do WCF (Windows Communication Foundation) Potpourri rozproszonych API Rola modelu DCOM
Rola COM+/Enterprise Services
Rola MSMQ
Warstwa zdalnego dostępu .NET
Rola usług sieciowych XML
Standardy usług sieciowych
Znaczenie WCF Przegląd własności WCF
Przegląd architektury usługowej
Dogmat 1: Granice są jasno sprecyzowane
Dogmat 2: Usługi są autonomiczne
Dogmat 3: Usługi komunikują się na podstawie kontraktu, a nie implementacji
Dogmat 4: Kompatybilność usług opiera się na zasadach
WCF: Podsumowanie
Analiza najważniejszych pakietów WCF
Szablony projektów WCF w Visual Studio Szablon projektu witryny internetowej usługi WCF
Podstawowa budowa aplikacji WCF
ABC usług WCF Kontrakty WCF
Dowiązania WCF
Dowiązania HTTP
Dowiązania TCP
Dowiązania MSMQ
Adresy WCF
Tworzenie usługi WCF Atrybut [ServiceContract]
Atrybut [OperationContract]
Typy usługowe jako kontrakty operacyjne
Hostowanie usługi WCF Ustalanie ABC w pliku App.config
Kod dla typu ServiceHost
Podawanie adresów bazowych
Typ ServiceHost pod lupą
Element <system.serviceModel> pod lupą
Umożliwianie wymiany metadanych
Tworzenie klienckiej aplikacji WCF Generowanie kodu proxy za pomocą svcutil.exe
Generowanie kodu proxy w Visual Studio
Konfigurowanie dowiązania TCP
Upraszczanie ustawień konfiguracyjnych Wykorzystywanie domyślnych punktów końcowych
Udostępnianie jednej usługi WCF z użyciem wielu dowiązań
Zmiana ustawień dowiązania WCF
Wykorzystywanie domyślnej konfiguracji działania MEX
Odświeżanie klienckiego proxy i wybór dowiązania
Używanie szablonu projektu WCF Service Library Tworzenie prostej usługi matematycznej
Testowanie usługi WCF za pomocą WcfTestClient.exe
Modyfikowanie plików konfiguracyjnych w programie SvcConfigEditor.exe
Hostowanie usługi WCF w usłudze Windows Określanie ABC w kodzie
Włączanie MEX
Tworzenie instalatora usługi Windows
Instalowanie usługi Windows
Asynchroniczne wywoływanie usługi przez klienta
Projektowanie kontraktów WCF Używanie sieciowego szablonu projektu usługi WCF
Implementowanie kontraktu usługi
Rola pliku *.svc
Analiza pliku Web.config
Testowanie usługi
Podsumowanie
Część VII Windows Presentation Foundation Rozdział 26 Wprowadzenie do WPF (Windows Presentation Foundation) i XAML Po co jest WPF? Unifikacja różnych API
Podział zadań za pomocą języka XAML
Optymalny model renderowania
Uproszczenie programowania skomplikowanych interfejsów użytkownika
Różne odmiany WPF Tradycyjne aplikacje okienkowe
Nawigacyjne aplikacje WPF
Aplikacje XBAP
Związek między WPF a Silverlight
Analiza pakietów WPF Klasa Application
Konstruowanie klasy Application
Wyliczanie kolekcji Windows
Klasa Window
Tworzenie aplikacji WPF bez XAML Tworzenie klasy Window z silną typizacją
Tworzenie prostego interfejsu użytkownika
Używanie danych na poziomie aplikacji
Obsługa zamykania obiektu Window
Przechwytywanie zdarzeń myszy
Przechwytywanie zdarzeń klawiatury
Tworzenie aplikacji WPF jedynie przy użyciu XAML Definiowanie obiektu Window w XAML
Definiowanie obiektu Application w XAML
Przetwarzanie plików XAML za pomocą msbuild.exe
Przekształcanie znaczników na pakiet .NET Odwzorowanie znakowania XAML dla Window w kodzie C#
Rola BAML
Odwzorowanie znakowania XAML aplikacji na kod C#
Podsumowanie procesu przekształcania XAML na pakiet
Składnia XAML WPF Wprowadzenie do Kaxaml
Przestrzenie nazw i „słowa kluczowe” XAML
Określanie widoczności klas i zmiennych składowych
Elementy i atrybuty XAML oraz konwertery typów
Składnia właściwość-element w XAML
Załączane właściwości XAML
Rozszerzenia znakowania XAML
Tworzenie aplikacji WPF z użyciem plików code-behind Dodawanie pliku z kodem dla klasy MainWindow
Dodawanie pliku z kodem dla klasy MyApp Przetwarzanie plików z kodem w programie msbuild.exe
Tworzenie aplikacji WPF w Visual Studio Szablony projektów WPF
Przybornik i projektant/edytor XAML
Ustawianie właściwości w oknie Properties
Obsługa zdarzeń w oknie Properties
Obsługa zdarzeń w edytorze XAML
Okno Document Outline
Przeglądanie automatycznie wygenerowanych plików z kodem
Tworzenie niestandardowego edytora XAML w Visual Studio Projektowanie graficznego interfejsu użytkownika obiektu Window
Implementacja zdarzenia Loaded
Implementacja zdarzenia Click przycisku
Implementacja zdarzenia Closed
Testowanie aplikacji
Poznawanie dokumentacji WPF
Podsumowanie
Rozdział 27 Programowanie z wykorzystaniem kontrolek WPF Przegląd podstawowych kontrolek WPF Kontrolki WPF Ink
Kontrolki dokumentów WPF
Najpopularniejsze okna dialogowe WPF
Szczegóły są w dokumentacji
Krótkie omówienie projektanta WPF w Visual Studio Używanie kontrolek WPF w Visual Studio
Używanie edytora Document Outline
Kontrolowanie rozmieszczenia zawartości za pomocą paneli Pozycjonowanie zawartości za pomocą paneli Canvas
Pozycjonowanie zawartości za pomocą paneli WrapPanel
Pozycjonowanie zawartości za pomocą paneli StackPanel
Pozycjonowanie zawartości za pomocą paneli Grid
Siatki z typami GridSplitter
Pozycjonowanie zawartości za pomocą paneli DockPanel
Włączanie przewijania dla typów Panel
Konfigurowanie paneli w projektantach Visual Studio
Tworzenie ramki okna za pomocą zagnieżdżonych paneli Tworzenie systemu menu
Tworzenie menu w trybie graficznym
Tworzenie paska narzędzi
Tworzenie paska stanu
Wykończenie projektu interfejsu użytkownika
Implementacja obsługi zdarzeń MouseEnter/MouseLeave
Implementacja kodu do sprawdzania pisowni
Omówienie poleceń WPF Wbudowane obiekty Command
Łączenie poleceń z właściwością Command
Łączenie poleceń z arbitralnymi działaniami
Używanie poleceń Open i Save
Zdarzenia trasowane Wstępujące zdarzenia trasowane
Kontynuowanie lub zatrzymywanie wstępowania
Zstępujące zdarzenia trasowane
API i kontrolki WPF pod lupą Używanie typu TabControl
Tworzenie karty Ink API Projektowanie paska narzędzi
Kontrolka RadioButton
Obsługa zdarzeń na karcie API Ink
Kontrolka InkCanvas
Kontrolka ComboBox
Zapisywanie, wczytywanie i sprzątanie danych InkCanvas
Wprowadzenie do API Documents Elementy blokowe i elementy wierszowe
Menedżery rozplanowania dokumentu
Tworzenie karty Documents Wypełnianie FlowDocument za pomocą kodu
Włączanie adnotacji i przyklejanych karteczek
Zapisywanie i wczytywanie FlowDocument
Wprowadzenie do modelu dowiązywania danych WPF Tworzenie karty z dowiązywaniem danych
Określanie dowiązań danych za pomocą Visual Studio
Właściwość DataContext
Konwersja danych za pomocą IValueConverter
Określanie dowiązań danych w kodzie
Tworzenie karty DataGrid
Rola właściwości zależnych Analiza istniejącej właściwości zależnej
Kilka ważnych faktów dotyczących osłon właściwości CLR
Tworzenie niestandardowej właściwości zależnej Dodawanie kodu sprawdzania prawidłowości danych
Reagowanie na zmianę właściwości
Podsumowanie
Rozdział 28 Usługi przetwarzania grafiki WPF Graficzne usługi renderowania WPF Opcje renderowania grafiki WPF
Renderowanie danych graficznych za pomocą kształtów Wstawianie prostokątów, elips i linii na płótno
Usuwanie prostokątów, elips i linii z płótna
Używanie klas Polyline i Polygon
Używanie klasy Path
Pędzle i pióra WPF Konfiguracja pędzli w Visual Studio
Konfiguracja pędzli w kodzie
Konfiguracja piór
Stosowanie przekształceń graficznych Pierwsze spojrzenie na przekształcenia
Przekształcanie danych na płótnie
Używanie edytora przekształceń w Visual Studio Tworzenie początkowego rozmieszczenia
Stosowanie przekształceń w fazie projektowania
Przekształcanie płótna w kodzie
Renderowanie danych graficznych za pomocą rysunków i geometrii Tworzenie obiektów DrawingBrush za pomocą geometrii
Malowanie z użyciem DrawingBrush
Zawieranie typów Drawing w DrawingImage
Używanie obrazów wektorowych Konwersja przykładowego pliku z grafiką wektorową na XAML
Importowanie danych graficznych do projektu WPF
Komunikacja ze znakiem
Renderowanie danych graficznych za pomocą warstwy wizualizacji Bazowa klasa Visual i jej klasy potomne
Pierwsze kroki w używaniu klasy DrawingVisual
Renderowanie danych wizualnych w niestandardowym menedżerze rozplanowania
Odpowiadanie na testowanie trafień
Podsumowanie
Rozdział 29 Zasoby, animacje, style i szablony WPF System zasobów WPF Używanie zasobów binarnych
Używanie zasobów obiektowych (logicznych) Właściwość Resources
Definiowanie zasobów dla całego okna
Rozszerzenie {StaticResource}
Rozszerzenie {DynamicResource}
Zasoby dla całej aplikacji
Definiowanie łączonych słowników zasobów
Definiowanie pakietu zawierającego wyłącznie zasoby
Usługi animacji WPF Rola klas z rodziny Animation
Właściwości To, From i By
Rola klasy bazowej Timeline
Tworzenie animacji w kodzie C#
Kontrolowanie tempa animacji
Odtwarzanie animacji od tyłu i zapętlanie
Tworzenie animacji w XAML Rola scenopisów
Rola wyzwalaczy zdarzeń
Animacja z użyciem nieciągłych ramek kluczowych
Rola stylów WPF Definiowanie i stosowanie stylu
Nadpisywanie ustawień stylu
Ograniczanie stosowania stylu za pomocą TargetType
Automatycznie stosowanie stylu za pomocą TargetType
Wyprowadzanie podklas z istniejących stylów
Definiowanie stylów za pomocą wyzwalaczy
Definiowanie stylów z wieloma wyzwalaczami
Style animowane
Przypisywanie stylów programistycznie
Drzewa logiczne, drzewa wizualne i szablony domyślne Analiza drzewa logicznego w kodzie programu
Analiza drzewa wizualnego w kodzie programu
Analiza domyślnego szablonu kontrolki w kodzie programu
Tworzenie szablonu kontrolki za pomocą platformy wyzwalaczy Szablony jako zasoby
Wstawianie wizualnych wskazówek za pomocą wyzwalaczy
Rola rozszerzenia {TemplateBinding}
Klasa ContentPresenter
Wstawianie szablonów do stylów
Podsumowanie
Rozdział 30 Powiadomienia, polecenia, sprawdzanie prawidłowości danych i MVVM Wprowadzenie do wzorca MVVM (Model-View-ViewModel) Model
View
ViewModel
Anemic Model czy Anemic ViewModel
System dowiązywania powiadomień WPF Obserwowane modele i kolekcje
Dodawanie dowiązań i danych
Zmienianie danych samochodu w kodzie programu
Obserwowane modele
Obserwowane kolekcje
Walidacja Modyfikacja przykładu na potrzeby walidacji
Klasa Validation
Opcje walidacji
Używanie adnotacji danych Dodawanie adnotacji
Sprawdzanie błędów walidacyjnych na podstawie adnotacji
Dostosowywanie ErrorTemplate
Tworzenie niestandardowych poleceń Implementowanie interfejsu ICommand
Modyfikacja pliku MainWindow.xaml.cs
Modyfikacja pliku MainWindow.xaml
Załączanie polecenia do CommandManager
Testowanie aplikacji
Dodawanie pozostałych poleceń
Pełna implementacja MVVM Usuwanie źródła danych z widoku
Przenoszenie poleceń do klasy ViewModel
Modyfikacja AutoLotDAL pod kątem MVVM Modyfikacja modeli AutoLotDAL
Pełny przykład MVVM Używanie ObjectMaterialized z Entity Framework
Podsumowanie
Część VIII ASP.NET Rozdział 31 Wprowadzenie do ASP.NET Web Forms Rola HTTP Cykl żądania/odpowiedzi HTTP
HTTP to protokół bezstanowy
Aplikacje internetowe i serwery www Rola wirtualnych katalogów IIS
IIS Express
Rola HTML Struktura dokumentu HTML
Rola formularza HTML
Narzędzia do projektowania HTML w Visual Studio
Tworzenie formularza HTML
Rola skryptów po stronie klienta Przykładowy skrypt po stronie klienta
Odesłania na serwer www Odesłania w Web Forms
Przegląd API Web Forms Najważniejsze funkcje Web Forms 2.0 i wyższych wersji
Najważniejsze funkcje Web Forms 3.5 (i .NET 3.5 SP1) i wyższych wersji
Najważniejsze funkcje Web Forms 4.0
Najważniejsze funkcje Web Forms 4.5 i 4.6
Tworzenie jednoplikowej internetowej aplikacji Web Forms Odwołanie do AutoLotDAL.dll
Projektowanie interfejsu użytkownika
Dodawanie kodu dostępu do danych
Dyrektywy ASP.NET
Analiza bloku ze „skryptem”
Analiza deklaracji kontrolek ASP.NET
Tworzenie internetowej strony ASP.NET za pomocą plików z kodem Odwołanie do projektu AutoLotDAL
Modyfikacja pliku z kodem
Debugowanie i śledzenie stron ASP.NET
Witryny i aplikacje internetowe ASP.NET Włączanie C# 6 dla internetowych witryn ASP.NET
Struktura katalogów witryny internetowej ASP.NET Odwołania do pakietów
Rola folderu App_Code
Łańcuch dziedziczenia typu Page
Komunikacja z wchodzącym żądaniem HTTP Uzyskiwanie statystyk przeglądarki
Dostęp do przychodzących danych z formularza
Właściwość IsPostBack
Komunikacja z wychodzącą odpowiedzią HTTP Wysyłanie zawartości HTML
Przekierowywanie użytkowników
Cykl życiowy internetowej strony ASP.NET Rola atrybutu AutoEventWireup
Zdarzenie Error
Rola pliku Web.config Program narzędziowy do zarządzania internetową witryną ASP.NET
Podsumowanie
Rozdział 32 Kontrolki internetowe, strony wzorcowe i motywy ASP.NET Omówienie roli kontrolek internetowych Obsługa zdarzeń po stronie serwera
Właściwość AutoPostBack
Bazowe klasy Control i WebControl Wyliczanie zawieranych kontrolek
Dynamiczne dodawanie i usuwanie kontrolek
Komunikacja z utworzonymi dynamicznie kontrolkami
Funkcjonalność bazowej klasy WebControl
Najważniejsze kategorie kontrolek Web Forms Kilka słów na temat System.Web.UI.HtmlControls
Dokumentacja kontrolek www
Tworzenie internetowej witryny Web Forms o nazwie Cars Używanie stron wzorcowych Web Forms
Konfiguracja nawigacji po witrynie za pomocą kontrolki TreeView
Nawigacja okruszkowa z wykorzystaniem typu SiteMapPath
Konfiguracja kontrolki AdRotator
Definiowanie niestandardowej strony z zawartością
Projektowanie strony z zawartością Inventory
Dodawanie AutoLotDAL i Entity Framework do AspNetCarsSite
Wypełnianie kontrolki GridView danymi
Włączanie edycji na miejscu
Włączanie sortowania i stronicowania
Włączanie filtrowania
Projektowanie strony z zawartością Build-a-Car
Rola kontrolek walidacyjnych Włączanie obsługi walidacji JavaScript po stronie klienta
Kontrolka RequiredFieldValidator
Kontrolka RegularExpressionValidator
Kontrolka RangeValidator
Kontrolka CompareValidator
Tworzenie podsumowania walidacji
Definiowanie grup walidacji
Walidacja z adnotacjami danych
Używanie motywów Omówienie plików *.skin
Stosowanie motywów na całą witrynę
Stosowanie motywów na poziomie strony
Właściwość SkinID
Przypisywanie motywów programistycznie
Podsumowanie
Rozdział 33 Techniki zarządzania stanem ASP.NET Kwestia stanu
Techniki zarządzania stanem ASP.NET
Rola stanu widoku ASP.NET Przykład ze stanem widoku
Dodawanie niestandardowych danych stanu widoku
Znaczenie pliku Global.asax Globalna procedura ostatniej szansy obsługi wyjątku
Bazowa klasa HttpApplication
Różnica między aplikacją i sesją Zarządzanie danymi o stanie na poziomie aplikacji
Modyfikowanie danych aplikacji
Obsługa zamykania aplikacji internetowej
Używanie bufora aplikacji Przykład z buforowaniem danych
Modyfikowanie pliku *.aspx
Zarządzanie danymi sesji Dodatkowe składowe HttpSessionState
Omówienie plików cookie Tworzenie plików cookie
Wczytywanie wejściowych danych cookie
Rola elementu <sessionState> Zapisywanie danych sesji w serwerze stanu sesji ASP.NET
Zapisywanie danych sesji w dedykowanej bazie danych
Wprowadzenie do API ASP.NET Profile Baza danych ASPNETDB.mdf
Definiowanie profilu użytkownika w pliku web.config
Programistyczny dostęp do danych profilu
Grupowanie danych profili i utrwalanie obiektów niestandardowych
Podsumowanie
Rozdział 34 ASP.NET MVC i API Web Wprowadzenie do wzorca MVC Model
Widok
Kontroler
Dlaczego MVC?
Pojawia się ASP.NET MVC
Tworzenie pierwszej aplikacji ASP.NET MVC Kreator nowego projektu
Komponenty bazowego projektu MVC
Aktualizacja pakietów NuGet do bieżących wersji
Testowanie witryny
Trasowanie Wzorce URL
Tworzenie tras dla stron Contact i About
Przekierowywanie użytkowników za pomocą trasowania
Dodawanie AutoLotDAL
Kontrolery i akcje Dodawanie kontrolera Inventory
Omówienie „rusztowań” widoków
Kontrolery MVC
Widoki MVC Silnik widoków Razor
Rozmieszczenia
Widoki częściowe
Wysyłanie danych do widoku
Widok Index
Widok Details
Widok Create
Widok Delete
Widok Edit
Walidacja
Wykańczanie interfejsu użytkownika
Ostatnie słowo na temat ASP.NET MVC
Wprowadzenie do API ASP.NET Web Dodawanie projektu API Web
Analiza projektu API Web
Konfiguracja projektu
Słowo na temat JSON
Dodawanie kontrolera
Modyfikacja CarLotMVC, aby używać CarLotWebAPI
Podsumowanie
Przypisy
O autorach
Andrew Troelsen ma ponad 20 lat doświadczenia w branży oprogramowania. Przez ten czas pracował jako programista, nauczyciel, pisarz i prelegent, a teraz przewodzi grupie inżynierów w Thomson Reuters. Jest autorem wielu książek o produktach Microsoftu, w tym na temat programowania COM w języku C++ (z wykorzystaniem ATL), współdziałania COM z .NET, języka Visual Basic oraz nagradzanej Język C# i platforma .NET (którą właśnie czytasz). Ma tytuł magistra inżynierii oprogramowania (MSSE) uzyskany na University of St. Thomas i pracuje nad drugim takim tytułem z lingwistyki obliczeniowej (CLMS) na University of Washington.
Kariera Philipa Japikse’a w branży komputerowej zaczęła się w połowie lat osiemdziesiątych XX w. od składania komputerów, łączenia ich w sieci i pisania programów. Obecnie Phil zajmuje się przede wszystkim tworzeniem oprogramowania, architekturami dla przedsiębiorstw i zwinnymi przekształceniami. Od roku 2009 ma certyfikat Microsoft MVP i aktywnie udziela się w społeczności developerów, przewodzi grupie użytkowników .NET w Cincinnati (www.cinnug.org), założył Cincinnati Day of Agile (www.dayofagile.org). Jest dyrektorem ds. zawartości i współprowadzącym podcast Hallway Conversations (www.hallway.conversations.com). Możesz spotkać go osobiście jako prelegenta w USA i Europie, jak również online: pod adresami @skimedic (twitter) i http://www.skimedic.com (blog). Poza światem technologii Phil to dumny mąż i ojciec trojga dzieci, zapalony narciarz (20 lat przynależności do National Ski Patrol) oraz łódkarz.
O recenzencie technicznym
Andy Olsen to pracujący na własny rachunek programista i szkoleniowiec, osiadły w Swansea w Wielkiej Brytanii. Używa platformy .NET od pierwszego wydania i zna wszystkie jej wersje aż do .NET 4.6 włącznie oraz wydanie beta ASP.NET 5. Spędza dużo czasu na pracy zarówno z nowymi, jak i dojrzałymi technologiami i platformami sieciowymi, takimi jak Angular, Ember, Bootstrap i Node.
Po pracy Andy lubi biegać, jeździć na nartach i oglądać football (kiedyś sam grał). Możesz się z nim skontaktować, pisząc na adres andyo@olsensoft.com.
Podziękowania
Andrew Troelsen: Jak zawsze chciałbym szczerze z serca podziękować całemu zespołowi Apress. Mam szczęście współpracować z tym wydawnictwem nad wieloma tytułami już od 2001 roku. Oprócz wydawania wysokiej jakości materiałów technicznych, personel jest doskonały, a bez nich książka ta by nie mogła powstać. Dziękuję wszystkim!
Chcę również podziękować mojemu (nowemu) współautorowi Philipowi Japikse’owi. Dzięki Phil, za ciężką pracę, którą włożyłeś w to, aby dodając własną wiedzę i spostrzeżenia, zachować przystępny charakter tej książki. Wierzę, że nasza książka (i ci, którzy ją czytają) skorzystają z tego nowego partnerstwa!
Nie mniejsze podziękowania należą się mojej żonie, Mandy, i mojemu synowi, Sorenowi, za wsparcie w trakcie ostatnich pisarskich projektów: dziękuję wam. Kocham was mocno.
Philip Japikse: Ja również chcę podziękować wydawnictwu Apress i całemu zespołowi zaangażowanemu w napisanie tej książki. To już moja druga książka w Apress i jestem pod wielkim wrażeniem poświęcenia i wsparcia, jakie otrzymaliśmy w trakcie pisania. Chcę również podziękować Andrew za zaproszenie mnie do pracy przy tym projekcie. Od samego początku książka ta była nieodłącznym elementem mojej kariery związanej z platformą .NET; mam na półce każde jej wydanie! Jestem dumny z tej pracy i współpracy wiążącej się z przygotowaniem obecnego wydania. Chcę też podziękować tobie, czytelniku, za przeczytanie tej książki, żywiąc nadzieję, że okaże ci się równie przydatna w karierze co mi. Poza tym nie dałbym rady tego zrobić bez mojej rodziny i wsparcia od niej. Bez czasu, który poświęciliście na przeczytanie i korektę mojej pracy, nie dałbym rady! Kocham was wszystkich!
Wprowadzenie
Pierwsze wydanie tej książki pojawiło się w roku 2001, w tym samym czasie, gdy Microsoft wydał wersję Beta 2 platformy .NET 1.0. Praca nad tym pierwszym wydaniem była z pewnością wyzwaniem, ponieważ w czasie pisania API i sam język C# ciągle się zmieniały. Już wtedy, z punktu widzenia programisty, całością platformy .NET można było całkiem sprawnie zarządzać. Jedyną opcją do tworzenia okienkowych aplikacji z GUI był API Windows Forms, ASP.NET skupiało się wyłącznie na modelu programowania formularzy sieciowych, a język C# był efektywnym narzędziem do programowania obiektowego.
Przez sześć pierwszych wydań tego tekstu byłem jedyną osobą odpowiedzialną za aktualizowanie materiału, aby uwzględniać liczne zmiany w języku C# i nowe API na platformie .NET. W ciągu tych 14 lat książka była aktualizowana; pojawiło się w niej omówienie modelu programowania LINQ (Language Integrated Query), WPF (Windows Presentation Foundation), WCF (Windows Communication Foundation), nowych modeli wątków i słów kluczowych, nowych narzędzi programistycznych i zmian na platformie programowania dla sieci (między innymi).
Począwszy od tego, siódmego wydania, stało się dla mnie jasne, że samodzielne zaktualizowanie książki trwałoby bardzo długo. Oczywiście mam już więcej zajęć niż w roku 2001 (czy nawet 2011; podejrzewam, że może mieć to coś wspólnego z ojcostwem, a może prowadzę za dużo zajęć… hmm).
W każdym razie, gdy Apress zwrócił się do mnie o przygotowanie książki z uwzględnieniem ostatniego wydania platformy .NET, rozważaliśmy kilka różnych możliwości, aby materiał był gotowy w rozsądnym terminie. W końcu uzgodniliśmy, że być może nadeszła pora, aby część tekstu oddać w kompetentne, pomocne ręce. Wydawnictwo zasugerowało mi kontakt z Philipem Japikse’em. Po kilku rozmowach telefonicznych, wymianie maili i dłuższym namyśle, z radością zaprosiłem go do pracy nad tym projektem. Teraz z radością ogłaszam, że i>C# 6.0 i platforma .NET 4.6 to owoc wspólnego wysiłku mojego i współautora, Philipa Japikse’a. Oddaję głos Philipowi…
Andrew Troelsen
Twoja kolej, Philip!
Gdy Microsoft wydawał pierwsze wersje beta platformy .NET, byłem już mocno przywiązany do technologii Microsoftu. Kilka innych technologii zamieniłem na Visual Basic, tworząc zarówo aplikacje klienckie, jak i klasyczne internetowe witryny ASP za pomocą VB i MTS. Jak na tamte czasy były to dobre narzędzia, ale ich kres był widoczny. Testowałem inne zestawy technologiczne i tak właśnie dotarłem do pierwszego wydania tej książki. Przeczytałem ją od deski do deski; nie tylko rozbudziła we mnie nadzieje związane z platformą .NET, ale zajęła ważne miejsce w mojej zawodowej bibliotece. Posiadanie jednej książki, która wyczerpująco omawia język C# i cały ekosystem .NET, jest nie do przecenienia. Nie umiem powiedzieć, ile egzemplarzy tej książki sprzedałem przez te lata, ale swoim klientom i słuchaczom każde wydanie polecałem jako pierwszą książkę do kupienia na ten temat. Niezależnie od tego, czy dopiero stawiasz pierwsze kroki na platformie .NET, czy też chcesz dowiedzieć się o nowościach w ostatniej wersji, nie znajdziesz lepszej książki.
Dla wydawnictwa Apress napisałem już jedną książkę na temat Windows 8.1 i C#, więc znałem personel i byłem pod wrażeniem tego, w jaki sposób firma prowadzi działalność i wspiera autorów. Gdy mój redaktor skontaktował się ze mną z pytaniem, czy chcę współpracować nad siódmym wydaniem „książki Troelsena” (tak ją zawsze nazywałem), czułem się podekscytowany i zaszczycony. Wykonałem tę pracę z pasją i nie znajduję słów, aby wyrazić swój entuzjazm i zachwyt, że jestem częścią tej książki. Mam nadzieję, że satysfakcja z jej przeczytania dorówna mojej radości z pisania i współpracy z Andrew oraz całą niesamowitą załogą Apress.
Philip Japikse
Jesteśmy zespołem, którego częścią jesteś ty
Autorzy dzieł technicznych piszą dla bardzo wymagającej (i bardzo dobrze) grupy odbiorców. Wiesz, że tworzenie oprogramowania z wykorzystaniem dowolnej platformy i w jakimkolwiek języku jest bardzo skomplikowane i ściśle związane z wymaganiami danej organizacji, firmy, klientów czy też tematyki. Może pracujesz w cyfrowym wydawnictwie, opracowujesz systemy dla władz państwowych lub samorządów, może zarabiasz w NASA albo w wojskowości. My mamy doświadczenie w różnych dziedzinach, na przykład w tworzeniu programów edukacyjnych dla najmłodszych (Oregon Trail/Amazon Trail), projektowaniu różnych systemów dla przedsiębiorstw i rozwiązań dla branży medycznej oraz finansowej. Prawdopodobieństwo, że kod, który piszesz w swojej pracy, ma niewiele wspólnego z kodem, który my napisaliśmy w tym czasie, jest bliskie 100 procent.
Z tego powodu w książce celowo unikamy przedstawiania przykładowego kodu, który byłby ściśle związany z konkretną branżą lub gałęzią programowania i wyjaśniamy język C#, programowanie obiektowe, środowisko CLR i biblioteki klas bazowych .NET na przykładach niezwiązanych z żadną konkretną dziedziną. Zamiast w każdym przykładzie wypełniać siatkę danymi, obliczać wynagrodzenia itd., trzymamy się tematu, który każdemu z nas jest bliski: samochodów (a na dokładkę dorzuciliśmy kilka struktur geometrycznych i systemów wynagradzania pracowników). I tutaj właśnie wchodzisz ty.
Nasze zadanie polega na wyjaśnieniu języka programowania C# i najważniejszych aspektów platformy .NET najlepiej jak potrafimy. Poza tym zrobimy, co w naszej mocy, aby przekazać ci do rąk narzędzia i strategie potrzebne do kontynuowania nauki po przeczytaniu tej książki.
Twoim zadaniem jest przyswojenie tej wiedzy i zastosowanie jej w swoich konkretnych zleceniach programistycznych. Oczywiście zdajemy sobie sprawę, że w twoich projektach prawdopodobnie nie ma aut o przyjaznych nazwach (jak BMW Zippy czy Yugo Clunker), ale na tym właśnie polega praktyczna wiedza!
Zapewniam, że zrozumienie przedstawionych w tym tekście zagadnień i pojęć to świetny punkt wyjścia do tworzenia rozwiązań .NET pasujących do twojego własnego, niepowtarzalnego środowiska programistycznego.
Przegląd książki
Książka C# 6.0 i platforma .NET 4.6 została podzielona na osiem logicznych części, a każda z nich zawiera kilka związanych ze sobą rozdziałów. Poniżej krótkie omówienie każdej części i każdego rozdziału.
Część 1. Wprowadzenie do C# i platformy .NET
Celem części I jest oswojenie z platformą .NET i różnymi narzędziami programistycznymi (w tym wieloplatformowymi IDE) używanymi w trakcie tworzenia aplikacji .NET.
Rozdział 1. Filozofia .NET
Pierwszy rozdział to szkielet, na którym opiera się jest reszta tekstu. Głównym celem tego rozdziału jest zapoznanie cię z wieloma podstawowymi elementami platformy .NET, jak wspólne środowisko uruchomieniowe (CLR), wspólny system typów (CTS), wspólna specyfikacja języka (CLS) i biblioteki klas bazowych. W rozdziale tym po raz pierwszy spotkasz się z językiem programowania C# i formatem pakietów .NET. Zakończymy omówieniem niezależności technologii .NET od platformy systemowej.
Rozdział 2. Tworzenie aplikacji w języku C#
Celem tego rozdziału jest wprowadzenie do procesu kompilowania plików z kodem źródłowym C# za pomocą różnych narzędzi i technik. Dowiesz się tutaj o roli narzędzi programistyczych z rodziny Microsoft Express i całkowicie bezpłatnym (a zarazem w pełni funcjonalnym) środowisku Visual Studio Community Edition, na którym bazuje ten materiał. Wspomnimy również o IDE Xamarin, które pozwala tworzyć aplikacje .NET w systemach operacyjnych Linux i Mac OS X. Dowiesz się też, jak zainstalować na swoim komputerze (używanym do programowania) bardzo ważną dokumentację .NET 4.6 Framework SDK.
Część 2. Podstawy programowania w języku C#
Zagadnienia omówione w tej części książki są dość ważne, ponieważ będą ci potrzebne niezależnie od tego, jakiego rodzaju oprogramowanie .NET chcesz tworzyć (np. aplikacje internetowe, okienkowe aplikacje z GUI, biblioteki kodu, usługi Windows). Poznasz tu podstawowe typy danych .NET, nauczysz się wykonywać operacje na tekście i dowiesz się o różnych modyfikatorach .NET (w tym o argumentach opcjonalnych i nazwanych).
Rozdział 3. Podstawowe konstrukcje programistyczne języka C#, część I
Od tego rozdziału zaczyna się formalne omówienie języka programowania C#. Dowiesz się tutaj o roli metody Main() i wielu szczegółach dotyczących wewnętrznych typów danych na platformie .NET, w tym operacjach na danych tekstowych za pomocą System.String i System.Text.StringBuilder. Poznasz również konstrukcje iteracyjne i decyzyjne, operacje zwężające i poszerzające oraz słowo kluczowe unchecked.
Rozdział 4. Podstawowe konstrukcje programistyczne języka C#, część II
W tym rozdziale zakończymy omawianie najważniejszych aspektów języka C#, przedstawiając konstrukcje przeciążonych metod i definiowanie parametrów za pomocą słów kluczowych out, ref i params. Omawiane również są dwie funkcje języka C#, a mianowicie argumenty i parametry opcjonalne. Dowiesz się również, jak tworzyć tablice danych i wykonywać na nich operacje, definiować typy danych dopuszczające wartości null (za pomocą operatorów ? i ??), a także poznasz różnicę między typami wartościowymi (w tym wyliczeniami i strukturami niestandardowymi) a typami referencyjnymi.
Część 3. Programowanie obiektowe w języku C#
W tej części zapoznasz się z najważniejszymi konstrukcjami składniowymi języka C#, w tym ze szczegółami programowania obiektowego. Dowiesz się również, jak przetwarzać wyjątki w czasie wykonywania programu i zagłębisz się w szczegóły używania interfejsów z silną typizacją.
Rozdział 5. Hermetyzacja
W tym rozdziale zaczniesz poznawanie programowania obiektowego (OOP) w języku C#. Po wprowadzeniu do filarów programowania obiektowego (hermetyzacja, dziedziczenie i polimorfizm) w pozostałej części rozdziału dowiesz się, jak tworzyć solidne klasy za pomocą konstruktorów, właściwości, składowych statycznych, stałych i pól tylko do odczytu. Zakończymy omówieniem definicji typów częściowych, składni inicjalizacji obiektów i właściwości automatycznych.
Rozdział 6. Dziedziczenie i polimorfizm
Tutaj omówimy pozostałe filary programowania obiektowego (dziedziczenie i polimorfizm), pozwalające tworzyć rodziny pokrewnych typów. Następnie poznasz rolę metod wirtualnych, metod abstrakcyjnych (i abstrakcyjnych klas bazowych), a także interfejs polimorficzny. Na zakończenie tego rozdziału przedstawimy nie mniej ważną klasę bazową najwyższego poziomu na platformie .NET, czyli System.Object.
Rozdział 7. Strukturalna obsługa wyjątków
Celem tego rozdziału jest wyjaśnienie, jak w bazie kodu, używając strukturalnej obsługi wyjątków, radzić sobie z anomaliami występującymi w czasie działania programu. Nie tylko poznasz słowa kluczowe C#, które pozwalają rozwiązywać problemy tego typu (try, catch, throw, when i finally), ale też zrozumiesz różnicę między wyjątkami aplikacji i na poziomie systemu. Oprócz tego w tym rozdziale znajdziesz omówienie różnych narzędzi Visual Studio służących do debugowania wyjątków, które umkną twojej uwadze.
Rozdział 8. Interfejsy
Materiał w tym rozdziale wzbogaci twoją wiedzę o programowaniu obiektowym o zagadnienia związane z programowaniem z wykorzystaniem interfejsów. Dowiesz się, jak definiować klasy i struktury, które obsługują wiele działań/zachowań, jak je wykrywać w czasie działania programu i jak wybiórczo ukrywać poszczególne działania za pomocą jawnej implementacji interfejsu. Utworzysz kilka niestadardowych interfejsów, nauczysz się również implementować standardowe interfejsy, które znajdziesz na platformie .NET. Użyjesz ich do utworzenia obiektów, które można sortować, kopiować, wyliczać i porównywać.
Część 4. Zaawansowane programowanie w języku C#
W tej części książki, która traktuje o kilku bardziej zaawansowanych (ale ważnych) zagadnieniach, pogłębisz swoją wiedzę na temat języka C#. Przedstawianie systemu typów .NET zakończymy omawianiem interfejsów i delegatów. Poznasz również rolę elementów generycznych, po raz pierwszy spotkasz się z LINQ i zbadasz kilka bardziej zaawansowanych funkcji języka C# (np. metody rozszerzające, metody częściowe i używanie wskaźników).
Rozdział 9. Kolekcje i typy generyczne
W tym rozdziale omawiamy typy generyczne. Jak zobaczysz, programowanie generyczne pozwala tworzyć typy i składowe zawierające wypełniacze, które mogą zostać podane przez procedurę wywołującą. Krótko mówiąc, typy generyczne znacznie podnoszą wydajność aplikacji i bezpieczeństwo typów. Poza przeanalizowaniem różnych typów generycznych z przestrzeni nazw System.Collections.Generic, dowiesz się również, jak tworzyć własne generyczne metody i typy (z ograniczeniami i bez).
Rozdział 10. Delegaty, zdarzenia i wyrażenia lambda
Celem rozdziału 10 jest odczarowanie delegatów. Mówiąc najprościej, delegat .NET to obiekt, który wskazuje na inne metody w aplikacji. Używając tego typu, możesz tworzyć systemy, które umożliwiają wielu obiektom uczestniczenie w konwersacji dwukierunkowej. Po delegatach .NET poznasz słowo kluczowe C# event, za pomocą którego możesz uprościć wykonywanie operacji bezpośrednio na delegatach. Rozdział zakończymy omówieniem roli operatora lambda (=>) i związku między delegatami, metodami anonimowymi i wyrażeniami lambda.
Rozdział 11. Zaawansowane elementy języka C#
W tym rozdziale wzbogacisz swoją wiedzę na temat języka programowania C#, poznając kilka zaawansowanych technik programistycznych. Dowiesz się, jak przeciążać operatory i tworzyć niestandardowe procedury konwersji (zarówno niejawne, jak i jawne) dla swoich typów. Nauczysz się również tworzyć i używać indekserów, a także używać metod rozszerzających, typów anonimowych, metod częściowych i wskaźników C# z wykorzystaniem nienadzorowanego kontekstu kodu.
Rozdział 12. LINQ to Objects
W tym rozdziale zaczniesz poznawać LINQ (Language Integrated Query). LINQ umożliwia tworzenie wyrażeń z zapytaniami z silną typizacją, które można stosować względem wielu celów LINQ, aby operować na danych w najszerszym tego słowa znaczeniu. Poznasz LINQ to Objects, który pozwala stosować wyrażenia LINQ względem kontenerów danych (np. tablic, kolekcji i typów niestandardowych). Informacje te przydadzą się się w dalszej części książki, gdzie spotkasz wiele dodatkowych API LINQ (p. LINQ to XML, LINQ to DataSet, PLINQ i LINQ to Entities).
Rozdział 13. Czas życia obiektu
W ostatnim rozdziale tej części wyjaśnimy, w jaki sposób środowisko CLR zarządza pamięcią za pomocą mechanizmu odśmiecania. Dowiesz się tutaj o roli korzeni aplikacji, generacjach obiektów i typie System.GC. Gdy już zrozumiesz podstawy, przejdziemy do zagadnienia obiektów usuwalnych (z wykorzystaniem interfejsu IDisposable) i procesu finalizacji (z wykorzystaniem metody System.Object.Finalize( )). W rozdziale tym zbadamy również klasę Lazy<T>, która służy do definiowania danych, które nie będą alokowane do czasu, aż zażąda tego wywołujący. Jak się przekonasz, funkcja ta może przydać się do zagwarantowania, że nie zagracasz sterty obiektami, które tak naprawdę nie są wymagane przez programy.
Część 5. Programowanie z wykorzystaniem pakietów .NET
W części 5 zajmiemy się szczegółowo formatem pakietu .NET. Dowiesz się nie tylko, jak wdrażać i konfigurować biblioteki z kodem .NET, ale też zrozumiesz wewnętrzną budowę binarnego obrazu .NET. W tej części wyjaśnimy również rolę atrybutów .NET i wykrywania informacji o typie w trakcie działania programu. Poza tym omówimy rolę środowiska DLR (Dynamic Language Runtime) i słowa kluczowego dynamic. W ostatnich rozdziałach tej części przedstawimy kilka bardziej zaawansowanych tematów związanych z pakietami, w tym domeny aplikacji, składnię języka CIL i tworzenie pakietów znajdujących się w pamięci.
Rozdział 14. Tworzenie i konfiguracja bibliotek klas
Ogólnie rzecz biorąc, pakiet to określenie binarnego pliku *.dll lub *.exe utworzonego za pomocą kompilatora .NET. Jednak prawdziwa historia pakietów .NET jest znacznie bogatsza. W tym rozdziale poznasz różnicę między pakietami jedno- i wieloplikowymi, a także nauczysz się tworzyć i wdrażać jedne i drugie. Dowiesz się również, jak konfigurować pakiety prywatne i współdzielone za pomocą plików XML *.config i pakietów z zasadami wydawcy. Przy okazji omówimy wewnętrzną strukturę globalnego katalogu pakietów (GAC).
Rozdział 15. Refleksja typów, późne wiązanie i programowanie z wykorzystaniem atrybutów
W rozdziale 15 kontynuujemy omawianie pakietów .NET, analizując proces wykrywania typów w trakcie wykonywania programu za pomocą przestrzeni nazw System.Reflection. Używając typów z tej przestrzeni nazw, możesz tworzyć aplikacje, które potrafią w locie odczytywać metadane pakietu. Dowiesz się również, jak dynamicznie, w trakcie działania programu, tworzyć typy za pomocą późnego dowiązywania. Ostatnim zagadnieniem tego rozdziału jest rola atrybutów .NET (zarówno standardowych, jak i niestandardowych). Ilustracją przydatności każdej z tych technik będzie napisanie rozszerzalnej aplikacji Windows Forms.
Rozdział 16. Typy dynamiczne i środowisko DLR
W wersji .NET 4.0 wprowadzono nowy element środowiska uruchomieniowego .NET o nazwie dynamiczne środowisko uruchomieniowe (DLR). Używając DLR i dostępnego od wersji C# 2010 słowa kluczowego dynamic, możesz definiować dane, które tak naprawdę są poznawane dopiero w czasie wykonywania programu. Można w ten sposób znacznie uprościć sobie niektóre złożone programistyczne zadania .NET. W tym rozdziale poznasz kilka praktycznych zastosowań danych dynamicznych, w tym wykorzystywanie API refleksji .NET w uproszczony sposób, a także używanie przestarzałych bibliotek COM niewielkim nakładem pracy.
Rozdział 17. Procesy, domeny aplikacji i konteksty obiektów
Znasz już bardzo dobrze pakiety, a w tym rozdziale poznasz dokładniej budowę wczytanego do pamięci pliku wykonywalnego .NET. Celem tego rozdziału jest przedstawienie relacji między procesami, domenami aplikacji i granicami kontekstów. Zagadnienia te to podstawa rozdziału 19, w którym poznasz budowę aplikacji wielowątkowych.
Rozdział 18. CIL i rola pakietów dynamicznych
Cel ostatniego rozdziału tej części jest dwojaki. Mniej więcej pierwsza połowa jest poświęcona składni i semantyce języka CIL, znacznie bardziej szczegółowo niż w poprzednich rozdziałach. W pozostałej części rozdziału omówimy przestrzeń nazw System.Reflection.Emit. Używając tych typów, możesz tworzyć oprogramowanie, które potrafi generować pakiety .NET w pamięci w czasie wykonywania programu. Z formalnego punktu widzenia pakiety zdefiniowane i wykonywane w pamięci są nazywane pakietami dynamicznymi.
Część 6. Wprowadzenie do bibliotek klas bazowych .NET
Na tym etapie nauki masz już solidnie opanowany język C# i szczegóły formatu pakietu .NET. W części VI wykorzystasz tę właśnie nabytą wiedzę do poznania kilku często używanych usług, które znajdują się w bibliotekach klas bazowych; są to m.in. tworzenie aplikacji wielowątkowych, plikowe operacje we/wy i dostęp do baz danych za pomocą ADO.NET. W części tej omówimy również tworzenie aplikacji rozproszonych za pomocą Windows Communication Foundation i API LINQ to XML.
Rozdział 19. Programowanie wielowątkowe, równoległe i asynchroniczne
W rozdziale tym wyjaśnimy, jak tworzyć aplikacje wielowątkowe i przedstawimy kilka technik, które możesz wykorzystać do tworzenia kodu zachowującego bezpieczeństwo wątków. Rozdział zaczniemy od powtórki z delegatów .NET i wyjaśnienia wbudowanej obsługi asynchronicznego wywoływania metod. Następnie poznasz typy znajdujące się w przestrzeni nazw System.Threading. Pozostała część rozdziału jest poświęcona bibliotece TPL (Task Parallel Library). Używając TPL, programiści .NET mogą tworzyć aplikacje, które w niesłychanie prosty sposób rozkładają swoją pracę na wszystkie dostępne procesory. Poznasz również rolę Parallel LINQ, czyli sposobu na tworzenie zapytań LINQ, które można skalować na wiele rdzeni. Zakończymy omówieniem różnych słów kluczowych C#, które umożliwiają zintegrowanie asynchronicznego wywołania metod bezpośrednio z językiem.
Rozdział 20. Plikowe operacje we/wy oraz serializacja obiektów
Przestrzeń nazw System.IO umożliwia komunikację ze strukturą plików i katalogów komputera. W rozdziale tym dowiesz się, jak programistycznie tworzyć (i niszczyć) system katalogów. Nauczysz się również przenosić dane do i z różnych strumieni (np. plikowych, łańcuchowych i pamięciowych). W ostatniej części tego rozdziału omówimy usługi serializacji obiektów na platformie .NET. Krótko mówiąc, serializacja pozwala utrwalić stan obiektu (lub zbioru związanych ze sobą obiektów) w strumieniu, aby można go było później użyć. Deserializacja (jak się domyślasz) to proces polegający na wyłuskiwaniu obiektu ze strumienia do pamięci, aby mógł być używany przez aplikację. Po zrozumieniu podstaw nauczysz się dostosowywać proces serializacji za pomocą interfejsu ISerializable i zbioru atrybutów .NET.
Rozdział 21. ADO.NET część I: Warstwa połączeniowa
W tym pierwszym z trzech rozdziałów poświęconych bazom danych po raz pierwszy zetkniesz się z API dostępu do baz danych na platformie .NET, czyli ADO.NET. W szczególności rozdział ten jest wprowadzeniem do roli dostawców danych .NET i komunikowania się z relacyjną bazą danych za pomocą połączeniowej warstwy ADO.NET, która jest reprezentowana przez obiekty połączenia, obiekty polecenia, obiekty transakcji i obiekty czytnika danych. Warto dodać, że w rozdziale tym utworzysz również niestandardową bazę danych i pierwszą wersję niestandardowej biblioteki dostępu do danych (AutoLotDAL.dll).
Rozdział 22. ADO.NET część II: Warstwa bezpołączeniowa
W tym rozdziale będziesz kontynuować naukę używania baz danych, poznając bezpołączeniową warstwę ADO.NET. Dowiesz się o roli typu Dataset i obiektach adaptera danych, a także wielu narzędziach Visual Studio 2010, które mogą istotnie uprościć tworzenie aplikacji bazodanowych. Przy okazji nauczysz się dowiązywać obiekty DataTable do elementów interfejsu użytkownika, a także stosować zapytania LINQ względem znajdujących się w pamięci obiektów DataSet, używając LINQ to DataSet.
Rozdział 23. ADO.NET część III: Entity Framework
W tym rozdziale zakończymy poznawanie ADO.NET, omawiając rolę Entity Framework (EF). EF to przede wszystkim sposób tworzenia kodu dostępu do danych za pomocą klas z silną typizacją, które są bezpośrednim odwzorowaniem modelu biznesowego. Poznasz tu rolę DbContext, używania adnotacji danych do kształtowania bazy danych i implementowania repozytoriów w celu hermetyzacji wspólnego kodu, obsługi transakcji, migracji, współbieżności oraz przechwytywania. Jednocześnie dowiesz się, jak komunikować się z relacyjnymi bazami danych za pomocą LINQ to Entities. Poza tym utworzysz ostateczną wersję niestandardowej biblioteki dostępu do bazy danych (AutoLotDAL.dll), której użyjesz w kilku kolejnych rozdziałach tej książki.
Rozdział 24. Wprowadzenie do LINQ to XML
Rozdział 12 był wprowadzeniem do serca modelu programowania LINQ, a konkretnie do LINQ to Objects. W tym rozdziale dokładniej poznasz LINQ, ucząc się stosować zapytania LINQ do dokumentów XML. Na początku dowiesz się o „wadach”, które występowały w pierwszym podejściu platformy .NET do używania XML na przykładzie typów z pakietu System.Xml.dll. Po tej krótkiej lekcji historii dowiesz się, jak tworzyć dokumenty XML w pamięci, jak utrwalać je na dysku twardym i jak nawigować po ich zawartości za pomocą modelu programowania LINQ (LINQ to XML).
Rozdział 25. Wprowadzenie do WCF (Windows Communication Foundation)
Aż do tego miejsca książki wszystkie przykładowe aplikacje będziesz wykonywać na jednym komputerze. W tym rozdziale poznasz API WCF (Windows Communication Foundation), które pozwala tworzyć aplikacje w sposób symetryczny, niezależnie od ich bazowej infrastruktury. Rozdział ten przybliży budowę usług, hostów i klientów WCF. Jak się przekonasz, usługi WCF są maksymalnie elastyczne, ponieważ pozwalają klientom i hostom deklaratywnie podawać adresy, dowiązania i kontrakty w plikach konfiguracyjnych XML.
Część 7. Windows Presentation Foundation
Pierwszy API do tworzenia okienkowych aplikacji z GUI na platformie .NET nosił nazwę Windows Forms. Jest on w dalszym ciągu w pełni obsługiwany na platformie, ale w wersji .NET 3.0 udostępniono programistom niesamowity API o nazwie WPF (Windows Presentation Foundation). Szybko zajął on miejsce Windows Forms jako model programowania aplikacji stacjonarnych. Przede wszystkim WPF pozwala za pomocą deklaratywnej gramatyki o nazwie XAML tworzyć aplikacje okienkowe zawierające grafikę wektorową i interaktywne animacje oraz dowiązywać dane. Co więcej, architektura kontrolek WPF to prosty sposób na to, aby kompletnie zmienić wygląd typowej kontrolki, używając prawie wyłącznie prawidłowego znakowania XAML.
Rozdział 26. Wprowadzenie do WPF (Windows Presentation Foundation) i XAML
WPF pozwala przede wszystkim tworzyć bardzo responsywne i medialne nakładki na aplikacje okienkowe (a pośrednio też aplikacje internetowe). W przeciwieństwie do Windows Forms na tej podrasowanej platformie do interfejsów użytkownika wiele kluczowych usług (np. grafikę 2D i 3D, animacje i bogate dokumenty) zintegrowano w jeden, jednolity model obiektowy. W rozdziale tym zaczniesz poznawać WPF i XAML (Extendable Application Markup Language). Nauczysz się pisać programy WPF bez XAML, a także wyłącznie w XAML, a także łącząc obie te opcje. Rozdział zakończymy napisaniem niestandardowego edytora XAML, którego będziesz używać w pozostałych rozdziałach poświęconych WPF.
Rozdział 27. Programowanie z wykorzystaniem kontrolek WPF
W tym rozdziale zaznajomisz się z procesem używania wbudowanych kontrolek WPF i menedżerów rozmieszczenia. Nauczysz się na przykład tworzyć systemy menu, okna z podziałkami, paski narzędzi i paski stanu. Rozdział zawiera również wprowadzenie do kilku API WPF (i związanych z nimi kontrolek), w tym API Documents i API Ink, a także poleceń, zdarzeń trasowanych, modelu dowiązywania danych i właściwości zależnych.
Rozdział 28. Usługi przetwarzania grafiki WPF
WPF to API oparty w dużej mierze na grafice i dlatego dostępne są trzy sposoby renderowania grafiki: kształty, rysunki i wizualizacje. W tym rozdziale rozważysz każdą opcję, a przy okazji poznasz wiele prostych typów graficznych (np. pędzle, pióra i przekształcenia). W rozdziale tym omówimy również sposoby wplatania obrazków wektorowych do grafiki WPF, a także wykonywanie testowania trafień na bazie danych graficznych.
Rozdział 29. Zasoby, animacje, style i szablony WPF
Ten rozdział jest wprowadzeniem do trzech ważnych (i związanych ze sobą) zagadnień, które pogłębią twoją wiedzę na temat API Windows Presentation Foundation. W pierwszej kolejności poznasz rolę zasobów logicznych. Jak się przekonasz, system zasobów logicznych (zwanych również zasobami obiektowymi) to sposób nazywania i odwoływania się do często używanych obiektów w aplikacji WPF. Następnie nauczysz się definiować, wykonywać i sterować animowaną sekwencją. Wbrew temu, co mogłoby się wydawać, animacje WPF nie ograniczają się do gier wideo czy też aplikacji multimedialnych. Rozdział zakończymy omówieniem roli stylów WPF. Podobnie jak strony internetowe używają CSS lub silnika motywów ASP.NET, w aplikacji WPF można zdefiniować wspólny wygląd zbioru kontrolek.
Rozdział 30. Powiadomienia, polecenia, sprawdzanie prawidłowości danych i MVVM
Ten rozdział zaczniemy od omówienia trzech podstawowych możliwości platformy: powiadomień, poleceń WPF i walidacji. W pierwszej części dowiesz się o obserwowanych modelach i kolekcjach i o tym, w jaki sposób zapewniają synchronizację danych aplikacji z interfejsem użytkownika. Następnie zagłębisz się w polecenia, tworząc niestandardowe polecenia, aby hermetyzować kod. W części poświęconej sprawdzaniu prawidłowości danych nauczysz się używać kilku mechanizmów walidacji dostępnych w aplikacjach WPF. Rozdział zakończymy omówieniem wzorca MVVM (Model View ViewModel) i utworzeniem aplikacji demonstrującej jego działanie.
Część 8. ASP.NET
Część 8 jest poświęcona tworzeniu aplikacji internetowych za pomocą API ASP.NET. Microsoft zaprojektował ASP.NET do modelowania tworzenia okienkowych interfejsów użytkownika, umieszczając obiektową, opartą na zdarzeniach warstwę nad standardowymi mechanizmami żądania/odpowiedzi HTTP. W trzech pierwszych rozdziałach omawiamy podstawy pisania aplikacji internetowych i Web Forms, a w ostatnim – dwa najnowsze elementy ASP.NET: MVC i API Web.
Rozdział 31. Wprowadzenie do ASP.NET Web Forms
W tym rozdziale zaczniemy naukę tworzenia aplikacji internetowych za pomocą ASP.NET. Jak się przekonasz, miejsce skryptu po stronie serwera zajęły teraz prawdziwe języki obiektowe (np. C# i VB .NET). W tym rozdziale omówimy budowę internetowej strony ASP.NET, bazowy model programowania i inne kluczowe aspekty ASP.NET, jak np. wybór serwera www i używanie plików Web.config.
Rozdział 32. Kontrolki internetowe, strony wzorcowe i motywy ASP.NET
Poprzedni rozdział dotyczył konstruowania obiektów ASP.NET Page, w tym natomiast omówimy kontrolki, które wypełniają wewnętrzne drzewo kontrolek. Zbadasz podstawowe kontrolki sieciowe ASP.NET, w tym kontrolki walidacyjne, kontrolki do nawigacji wewnątrz witryny i różne operacje dowiązywania danych. Przedstawimy również rolę stron wzorcowych i silnik motywów ASP.NET, czyli serwerową alternatywę dla tradycyjnych arkuszy stylów.
Rozdział 33. Techniki zarządzania stanem ASP.NET
W tym rozdziale wiedzę na temat ASP.NET rozszerzysz o różne sposoby zarządzania stanem na platformie .NET. ASP.NET, tak jak klasyczny ASP, pozwala bez problemu tworzyć pliki cookie oraz zmienne na poziomie aplikacji i na poziomie sesji. Jednak w ASP.NET wprowadzono również nową technikę zarządzania stanem: bufor aplikacji. Po zapoznaniu się z kilkoma sposobami zarządzania stanem w ASP.NET dowiesz się o roli klasy bazowej HttpApplication i nauczysz się, jak dynamicznie zmieniać działanie aplikacji internetowej za pomocą pliku Web.config.
Rozdział 34. ASP.NET MVC i API Web
W tym rozdziale są omówione dwie najnowsze platformy ASP.NET: MVC i API Web. ASP.NET MVC opiera się na wzorcu Model Widok Kontroler i po zapoznaniu się z nim napiszesz aplikację MVC. Dowiesz się o rusztowaniach, trasowaniu, kontrolerach, akcjach i widokach w Visual Studio. Następnie napiszesz internetową usługę RESTful API Web, która będzie wykonywać wszystkie operacje CRUD (tworzenie, odczyt, aktualizacja i usuwanie) na danych Inventory (za pomocą AutoLotDAL), a na końcu zaktualizujesz swoją aplikację MVC, aby zamiast bezpośrednio wywoływać AutoLotDAL, używała tej nowej usługi.
Załączniki do pobrania
W pliku ZIP towarzyszącym wydrukowanemu materiałowi i zawierającym kod źródłowy z przykładami z tej książki (znajdziesz go na witrynie Apress pod adresem www.apress.com) są też dodatkowe rozdziały w formacie PDF. Omówiono w nich kilka dodatkowych API na platformie .NET, które mogą ci się przydać w pracy. W szczególności znajdziesz tam następujący dodatkowy materiał:
Załącznik A, „Programming with Windows Forms”;
Załącznik B, „Platform-Independent .NET Development with Mono”.
Pierwszy załącznik to podstawowe informacje o API do tworzenia okienkowych aplikacji Windows Forms, niezbędne do odtworzenia kilku okienkowych GUI z pierwszych rozdziałów tego tekstu (aż do wprowadzenia do Windows Presentation Foundation). Drugi pochodzi ze starszego wydania tego tekstu; omawia rolę platformy Mono bardziej szczegółowo niż w rozdziałach 1 i 2. Musisz jednak wiedzieć, że zrzuty ekranowe w załączniku B zostały wykonane w starszej wersji IDE MonoDevelop, której miejsce zajmuje teraz Xamarin Studio (patrz rozdz. 2). Niemniej podstawowe przykłady z kodem działają w Mono tak jak powinny.
Kod źródłowy do tej książki
Całość przykładowego kodu z tej książki możesz pobrać za darmo z witryny wydawnictwa Apress (www.apress.com). Po prostu znajdź tytuł książki i poszukaj na stronie domowej odnośnika do pobierania plików. Po pobraniu odpowiedniego pliku *.zip rozpakuj jego zawartość. Zobaczysz, że projekty zostały podzielone na poszczególne rozdziały.
Przy okazji należy wspomnieć o znajdujących się we wszystkich rozdziałach książki ramkach „Kod źródłowy”, takich jak ta poniżej. Są to wskazówki, że możesz pobrać przykładowy, właśnie omawiany kod, otworzyć go w Visual Studio, a następnie analizować i modyfikować.
Kod źródłowy. Jest to uwaga na temat kodu źródłowego, która odsyła do konkretnego katalogu w archiwum ZIP.
Aby otworzyć rozwiązanie w Visual Studio, z menu File wybierz kolejno Open i Project/Solution, a następnie przejdź do odpowiedniego pliku *.sln w odpowiednim podkatalogu rozpakowanego archiwum.
Aktualizacje do tej książki
Czytając ten tekst, możesz od czasu do czasu natrafić na jakiś błąd gramatyczny albo w kodzie (mamy nadzieję, że tak się nie stanie). Jeśli tak się stanie, przyjmij nasze przeprosiny. Jesteśmy ludźmi i mimo naszych najlepszych starań błąd czy dwa mogą się zdarzyć. A jeśli tak się stanie, aktualną erratę znajdziesz na internetowej witrynie wydawnictwa Apress pod adresem www.apress.com (ona również znajduje się na stronie domowej tej książki). Poza tym, w tym właśnie miejscu możesz powiadomić nas o wszystkich błędach, które znajdziesz.
Część I
Wprowadzenie do C# i platformy .NET
Rozdział 1
Filozofia .NET
Platforma .NET (oraz związany z nią język C#) zostały formalnie wprowadzone przez Microsoft około roku 2002 i szybko stały się podstawą współczesnego tworzenia oprogramowania. Jak wspomniałem we wprowadzeniu, cel tego tekstu jest dwojaki. Po pierwsze, mam zamiar dogłębnie i szczegółowo omówić składnię i semantykę C#. Druga sprawa, równie ważna, to zilustrowanie przykładami używania wielu dostępnych na platformie .NET interfejsów API, w tym związanych z dostępem do baz danych (za pośrednictwem ADO.NET i Entity Framework), zbioru technologii LINQ, a także WPF, WCF i tworzenia internetowych witryn za pomocą ASP.NET. Mówi się, że aby przebyć tysiąc mil, trzeba zrobić pierwszy krok. I tymi słowami witam wszystkich w rozdziale 1.
Celem tego rozdziału jest wyjaśnienie fundamentalnych pojęć, na których opiera się reszta przedstawionego materiału. Znajdziesz tu ogólne omówienie kilku zagadnień związanych z .NET, w tym pakietów, wspólnego języka pośredniego (Common Intermediate Language, CIL) i kompilacji „w locie/w samą porę” (ang. just-in-time, JIT). Poznasz kilka słów kluczowych języka C#, a jednocześnie dowiesz się o zależnościach między częściami składowymi platformy .NET, czyli wspólnym środowiskiem uruchomieniowym (Common Language Runtime, CLR), wspólnym systemem typów (Common Type System, CTS) i wspólną specyfikacją języka (Common Language Specification, CLS).
Rozdział zawiera również przegląd funkcjonalności zapewnianych przez biblioteki klas bazowych .NET (ang. basic class libraries, BCL). Przy okazji nie omieszkaliśmy podkreślić charakterystycznej dla technologii .NET niezależności od platformy systemowej i dużego wyboru języków programowania (zgadza się; używanie platformy .NET nie ogranicza się do systemu operacyjnego Windows). Rzecz jasna, wiele z tych zagadnień omawiam bardziej szczegółowo w dalszej części tego tekstu.
Pierwsze spojrzenie na platformę .NET
Zanim Microsoft wydał język C# i platformę .NET, programiści piszący aplikacje przeznaczone dla rodziny systemów operacyjnych Windows często używali modelu programowania COM. COM (Component Object Model, model komponentów obiektowych) pozwalał tworzyć biblioteki kodu, które mogły być wykorzystywane w różnych językach programowania. Na przykład, napisanej przez programistę C++ biblioteki COM mógł używać programista Visual Basic. Ta niezależność COM od języka była bez wątpienia zaletą; wadami natomiast były skomplikowana infrastruktura i zawodny model wdrażania, na dodatek ograniczone do systemów operacyjnych Windows.
Mimo wspomnianych trudności, opierając się na architekturze COM, napisano mnóstwo świetnych aplikacji. Jednak dziś większość aplikacji tworzonych dla rodziny systemów operacyjnych Windows nie powstaje z wykorzystaniem modelu COM. Współcześnie aplikacje okienkowe, witryny internetowe, usługi systemowe i zawierające kod wielokrotnego użytku biblioteki zapewniające dostęp do danych i realizację logiki biznesowej powstają z zastosowaniem technologii .NET.
Wybór kluczowych zalet platformy .NET
Jak wspomniałem, język C# i platformę .NET wprowadzono mniej więcej w roku 2002 w celu udostępnienia modelu programowania potężniejszego, elastyczniejszego i prostszego niż wspomniany COM. W dalszej części książki przekonasz się, że .NET Framework to platforma programistyczna do tworzenia oprogramowania dla rodziny Windows, a także wielu systemów operacyjnych od innych producentów, jak np. Mac OS X i różne dystrybucje Uniksa/Linuksa. Na dobry początek poniżej przedstawiam zwięzły spis najważniejszych możliwości platformy .NET:
Współpraca z istniejącym kodem: Jest to (oczywiście) zaleta. Istniejące oprogramowanie COM może współistnieć (współpracować) z nowszym oprogramowaniem .NET i vice versa. Od wersji .NET 4.0 współpraca jest jeszcze prostsza, a to dzięki wprowadzeniu słowa kluczowego dynamic (które jest omówione w rozdz. 16).
Wiele języków programowania: Aplikacje .NET można tworzyć w różnych językach programowania (C#, Visual Basic, F# itd.).
Wspólny dla wszystkich języków .NET silnik środowiska uruchomieniowego: Jednym z aspektów tego silnika jest klarowny zbiór typów rozumiany przez każdy z języków .NET.
Integracja języków: Platforma .NET obsługuje międzyjęzykowe dziedziczenie, obsługę wyjątków i debugowanie kodu. Można na przykład zdefiniować klasę bazową w języku C#, a następnie rozbudować ją w języku Visual Basic.
Kompleksowa biblioteka klas bazowych: Biblioteka ta zawiera tysiące predefiniowanych typów, których można używać do tworzenia bibliotek kodu, prostych aplikacji terminalowych, aplikacji z interfejsem graficznym i komercyjnych witryn internetowych.
Uproszczony model wdrażania: Inaczej niż w modelu COM informacje o bibliotekach .NET nie są przechowywane w systemowym rejestrze. Co więcej, platforma .NET pozwala na bezkonfliktowe współistnienie wielu wersji tej samej biblioteki *.dll na jednym komputerze.
Każde z tych zagadnień (i wiele innych) omówię w kolejnych rozdziałach.
Wprowadzenie do części składowych platformy .NET (CLR, CTS i CLS)
Skoro znamy już kilka największych zalet platformy .NET, przyjrzyjmy się trzem kluczowym (i związanym ze sobą) zagadnieniom, dzięki którym wszystko to jest możliwe: chodzi o CLR, CTS i CLS. Z punktu widzenia programisty .NET można uznać za środowisko uruchomieniowe i kompleksowe biblioteki klas bazowych. Właściwa nazwa warstwy uruchomieniowej to wspólne środowisko uruchomieniowe (ang. Common Language Runtime, CLR). Główną rolą CLR jest znajdowanie, wczytywanie i zarządzanie obiektami .NET w imieniu programisty. CLR zajmuje się również wieloma szczegółami na niższym poziomie, jak na przykład zarządzanie pamięcią, udostępnianie aplikacji, koordynowanie wątków i podstawowe sprawdzanie zabezpieczeń.
Kolejnym składnikiem platformy .NET jest wspólny system typów (ang. Common Type System, CTS). W specyfikacji CTS wyczerpująco opisano wszystkie dozwolone typy danych i konstrukcje programistyczne obsługiwane przez warstwę wykonawczą, określono zależności między tymi elementami i szczegółowo wyjaśniono sposób ich reprezentacji w formacie metadanych .NET (więcej informacji na temat metadanych w dalszej części tego rozdziału, a pełne omówienie tego zagadnienia w rozdz. 15).
Należy pamiętać, że dany język .NET nie musi mieć każdej własności zdefiniowanej w CTS. Wspólna specyfikacja języka (ang. Common Language Specification, CLS) to wyszczególnienie zawierające podzbiór typów i konstrukcji programistycznych, z którym zgodność jest wymagana od wszystkich języków programowania .NET. Dzięki temu, tworząc typy, których wszystkie zewnętrzne własności są zgodne z CLS, masz gwarancję, że wszystkie pozostałe języki .NET będą mogły tych typów używać. I na odwrót: jeśli użyjesz typu danych albo konstrukcji programistycznej, która wykracza poza CTS, to gwarancję taką tracisz. Na szczęście, jak przekonasz się w dalszej części tego rozdziału, nakazanie kompilatorowi C# sprawdzenia kodu pod kątem zgodności z CLS jest bardzo proste.
Rola bibliotek klas bazowych
Oprócz specyfikacji CLR, CTS i CLS na platformie .NET wszystkim językom programowania .NET udostępniono biblioteki klas bazowych. Zawarto w nich nie tylko rozliczne podstawowe operacje, takie jak obsługa wątków, plikowe operacje we/wy, systemy przetwarzania grafiki i komunikacja z różnymi urządzeniami zewnętrznymi, ale również obsługę wielu usług wymaganych przez aplikacje z prawdziwego zdarzenia.
W bibliotekach klas bazowych zdefiniowano typy, których można używać do tworzenia dowolnego rodzaju aplikacji. Na przykład, ASP.NET używamy do tworzenia internetowych witryn i usług REST, WCF do tworzenia systemów rozproszonych, WPF do tworzenia aplikacji z graficznym interfejsem użytkownika i tak dalej. Poza tym, w bibliotekach klas bazowych znajdują się typy umożliwiające komunikację z dokumentami XML oraz systemem plików i katalogów na danym komputerze, komunikację z relacyjnymi bazami danych (za pośrednictwem ADO.NET) i tak dalej. W ogólnym zarysie relacja między CLR, CTS, CLS i bibliotekami klas bazowych przedstawia się tak jak na rysunku 1.1.
Rysunek 1.1. Zależności między CLR, CTS, CLS i bibliotekami klas bazowych
Co nowego wnosi C#?
C# to język programowania, którego podstawowa składnia jest bardzo podobna do składni Javy. Jednak nazwanie C# klonem tego ostatniego języka jest nietrafne. Podobieństwo składni wynika z faktu, że zarówno C#, jak i Java należą do rodziny języków programowania C (np. C, Objective C, C++).
Prawda jest taka, że wiele konstrukcji składniowych języka C# wymodelowano na wzór różnych aspektów Visual Basic (VB) i C++. Na przykład, podobnie jak w VB w C# funkcjonuje pojęcie właściwości klas (w przeciwieństwie do tradycyjnych metod pobierających i ustawiających) i parametry opcjonalne. Tak jak w C++ w C# można przeciążać operatory a także tworzyć struktury, wyliczenia i wywołania zwrotne (używając delegatów).
Poza tym, w trakcie lektury szybko zauważysz, że C# obsługuje wiele elementów, które tradycyjnie można znaleźć w różnych językach funkcyjnych (np. LISP-ie lub Haskellu), takich jak wyrażenia lambda i typy anonimowe. Co więcej, od momentu wprowadzenia LINQ (Language Integrated Query) w języku C# można używać kilku konstrukcji, które przesądzają o jego unikatowości na programistycznym krajobrazie. Niemniej większa część C# faktycznie powstała pod wpływem języków opartych na C.
Jako hybryda wielu języków C# jest w rezultacie produktem składniowo równie (jeśli nie bardziej) przejrzystym jak Java, prawie tak prostym jak VB i mniej więcej dorównującym możliwościami i elastycznością językowi C++. Na poniższej liście podałem wybór kluczowych cech C#, które można znaleźć we wszystkich wersjach tego języka. I tak w C#:
Wskaźniki nie są wymagane! W programach C# zwykle nie trzeba bezpośrednio manipulować wskaźnikami (jeśli jednak jest to absolutnie konieczne, można zejść na ten poziom, co pokazałem w rozdz. 11).
Istnieje automatyczne zarządzanie pamięcią poprzez mechanizm „odśmiecania” (odzyskiwania pamięci). Z tego powodu w języku C# nie używamy słowa kluczowego delete.
Występują formalne konstrukcje składniowe dla klas, interfejsów, struktur, wyliczeń i delegatów.
Podobnie jak w C++ jest możliwe przeciążanie operatorów dla niestandardowych typów, ale bez złożoności (tzn. programista nie musi już pamiętać o zwracaniu wskaźnika *this, aby umożliwić kaskadowe wywoływanie funkcji).
Programowanie z wykorzystaniem atrybutów. Ta odmiana programowania pozwala precyzować działanie typów i ich składowych za pomocą adnotacji. Jeśli na przykład oznaczymy daną metodę atrybutem [Obsolete], programiści, którzy będą chcieli jej użyć, przeczytają zdefiniowany przez użytkownika komunikat z ostrzeżeniem.
Wraz z wydaniem .NET 2.0 (ok. 2005 r.) język programowania C# unowocześniono, wprowadzając doń wiele atrakcyjnych nowości, z których najważniejsze to:
możliwość konstruowania typów i składowych generycznych (ogólnych). Pozwalają one na pisanie zachowującego bezpieczeństwo typów kodu. W kodzie tym definiujemy elementy zastępcze, które zostaną skonkretyzowane dopiero w momencie użycia generycznego elementu;
obsługa metod anonimowych pozwalająca na podanie funkcji wierszowych (inline) wszędzie tam, gdzie wymagany jest typ delegowany (delegat);
możliwość rozłożenia definicji pojedynczego typu na kilka plików z kodem (albo, jeśli jest to konieczne, w pamięci) za pomocą słowa kluczowego partial.
Wydanie .NET 3.5 (ok. 2008 r.) jeszcze bardziej zwiększyło funkcjonalność języka programowania C#, a to za sprawą takich elementów, jak:
obsługa zapytań z silną typizacją (np. LINQ) używanych do interakcji z danymi pod różną postacią. Nasze pierwsze spotkanie z LINQ będzie miało miejsce w rozdziale 12;
obsługa typów anonimowych pozwalających modelować strukturę typu (a nie jego działanie) w locie w kodzie;
możliwość rozbudowy funkcjonalności istniejącego typu (bez wyprowadzania podklas) za pomocą metod rozszerzających;
wprowadzenie operatora lambda (=>), który jeszcze bardziej upraszcza pracę z delegatami .NET;
nowa składnia inicjalizacji obiektów, pozwalająca ustawiać wartości właściwości w czasie tworzenia obiektów.
Wydanie .NET 4.0 w 2010 roku oznaczało kolejną aktualizację C# i wprowadzenie kilku nowych elementów. Były to:
opcjonalne parametry metod, jak również nazwane argumenty metod;
dynamiczne wyszukiwanie składowych w czasie wykonywania programu za pomocą słowa kluczowego dynamic. Jak przekonasz się w rozdziale 18, oznacza to ujednolicenie wywoływania składowych w locie, niezależnie od bazowej platformy tej składowej (COM, IronRuby, IronPython lub za pośrednictwem usług refleksji .NET);
możliwość łatwego, obustronnego odwzorowywania danych generycznych i kolekcji System.Object poprzez kowariancję i kontrawariancję, dzięki czemu używanie typów generycznych stało się o wiele bardziej intuicyjne.
Wraz z wydaniem .NET 4.5 język C# wzbogacił się o parę nowych słów kluczowych (async i await), które znacznie upraszczają programowanie wielowątkowe i asynchroniczne. Użytkownicy poprzednich wersji C# pamiętają zapewne, że wywoływanie metod z wątków podrzędnych wymagało napisania sporej ilości niezrozumiałego kodu i używania różnych przestrzeni nazw .NET. Ponieważ w języku C# są teraz specjalnie do tego celu przeznaczone słowa kluczowe, asynchroniczne wywoływanie metod jest tylko nieznacznie trudniejsze od synchronicznego. Zagadnienia te omawiam szczegółowo w rozdziale 19.
W ten sposób doszliśmy do bieżącej wersji języka C# i platformy .NET 4.6, w których wprowadzono kilka pomniejszych nowinek umożliwiających uszczuplenie bazy kodu. Więcej szczegółów znajdziesz na kolejnych stronach książki. Wcześniej jednak krótka lista kilku nowych własności języka C#:
inicjalizacja wierszowa (inline) właściwości automatycznych jak również obsługa właściwości automatycznych tylko do odczytu;
jednowierszowa implementacja metod za pomocą operatora lambda;
obsługa „statycznego importu” zapewniającego bezpośredni dostęp do statycznych składowych danej przestrzeni nazw;
operator warunkowy dla wartości null pomagający sprawdzać parametry null w implementacji metody;
nowa składnia formatowania łańcuchów zwana interpolacją łańcuchów;
możliwość filtrowania wyjątków za pomocą nowego słowa kluczowego when.
Kod zarządzany a kod niezarządzany
Istotną rzeczą jest to, że języka C# można używać jedynie do pisania oprogramowania hostowanego w środowisku uruchomieniowym .NET (nigdy nie możesz użyć C# do utworzenia natywnego serwera COM albo niezarządzanej aplikacji w stylu C/C++). Oficjalne określenie kodu przeznaczonego do środowiska uruchomieniowego .NET to kod zarządzany (ang. managed code), a jednostkę binarną zawierającą taki kod nazywamy pakietem (ang. assembly; więcej na ten temat już za moment). Natomiast kod, który nie może być bezpośrednio wykonywany w środowisku uruchomieniowym .NET, określa się mianem kodu niezarządzanego.
Jak już wspomniałem (i szczegółowo omawiam w dalszej części tego rozdziału i w kolejnym), technologii .NET można używać w wielu różnych systemach operacyjnych. Tym samym nie ma żadnych przeciwwskazań, aby napisać program C# w Visual Studio na komputerze z systemem Windows, a następnie uruchomić go na komputerze z systemem Mac OS X (z zainstalowanym środowiskiem uruchomieniowym .NET o nazwie Mono). Można również napisać aplikację w C# w Xamarin Studio (patrz rozdz. 2) na komputerze z Linuksem, a potem używać tego programu w systemach Windows, Mac itd. Oczywiście możliwość tworzenia, wdrażania i uruchamiania programów .NET na wielu różnych urządzeniach docelowych wynika z tego, że środowisko to jest zarządzane.
Dodatkowe języki programowania na platformie .NET
Musisz wiedzieć, że C# nie jest jedynym językiem, którego można używać do tworzenia aplikacji .NET. Zaraz po instalacji Visual Studio masz do dyspozycji pięć zarządzanych języków, a konkretnie: C#, Visual Basic, C++/CLI, JavaScript i F#.
Uwaga. F# to język .NET oparty na składni języków funkcyjnych. Pomimo tego, że F# może być używany jako język czysto funkcyjny, obsługuje on również konstrukcje obiektowe i biblioteki klas bazowych. NET. Jeśli chcesz dowiedzieć się więcej na temat tego zarządzanego języka, możesz zajrzeć na jego oficjalną stronę domową, która znajduje się pod adresem http://msdn.microsoft.com/fsharp.
Istnieją kompilatory .NET nie tylko dla zarządzanych języków dostarczanych przez Microsoft, w tym dla języków Smalltalk, Ruby, Python, COBOL i Pascal (aby wymienić tylko kilka z nich). Pomimo tego, że książka ta dotyczy niemal wyłącznie języka C#, warto zajrzeć na poniższą stronę w Wikipedii. Znajduje się na niej obszerna lista języków programowania przeznaczonych dla platformy .NET:
https://en.wikipedia.org/wiki/List_of_CLI_languages
Choć książka jest pisana głównie z myślą o osobach zainteresowanych pisaniem programów .NET w języku C#, zachęcamy do odwiedzenia tej strony, ponieważ z całą pewnością można na niej znaleźć wiele języków .NET wartych zbadania w wolnej chwili (może np. zainteresuje cię LISP.NET?).
Życie w świecie wielojęzycznym
Gdy programiści zaczęli zdawać sobie sprawę z niezależności platformy .NET od języków programowania, zaczęli stawiać wiele pytań. Najczęściej padało takie: „Po co nam więcej niż jeden język/kompilator, skoro wszystkie języki .NET i tak są kompilowane do zarządzanego kodu?”.
Na to pytanie można odpowiedzieć na wiele sposobów. Po pierwsze, my, programiści, jesteśmy bardzo wybredni, jeśli chodzi o wybór języka. Niektórzy z nas wolą języki pełne średników i klamr, z maksymalnie małą liczbą słów kluczowych. Innym podobają się języki, w których elementy składniowe są bardziej zrozumiałe z ludzkiego punktu widzenia (jak np. Visual Basic). Jeszcze inni, przenosząc się na platformę .NET, liczą na to, że uda im się wykorzystać umiejętności zdobyte na komputerach mainframe (z pomocą kompilatora COBOL.NET).
Ale bądźmy szczerzy. Gdyby Microsoft przygotował jeden, „oficjalny” język .NET oparty na językach z rodziny BASIC, to czy wszyscy programiści byliby z tego zadowoleni? Albo, gdyby jedyny „oficjalny” język był oparty na składni Fortrana – wyobraź sobie, ile osób całkowicie zignorowałoby platformę .NET. Ponieważ środowisku uruchomieniowemu .NET jest zupełnie wszystko jedno, w jakim języku napisano blok zarządzanego kodu, programiści .NET mogą udostępniać skompilowany kod współpracownikom z zespołu, osobom z innych oddziałów firmy i organizacjom zewnętrznym (niezależnie od tego, jaki język .NET jest używany przez drugą stronę), dochowując wierności swoim preferencjom syntaktycznym.
Kolejnym pozytywnym efektem ubocznym zintegrowania wielu języków w jedno, ujednolicone rozwiązanie programistyczne jest taki prosty fakt, że wszystkie języki programowania mają własne zbiory wad i zalet. Na przykład, niektóre języki kapitalnie obsługują zaawansowane działania matematyczne. Inne z kolei lepiej radzą sobie z obliczeniami finansowymi, logicznymi czy też komunikacją z komputerami mainframe. Gdy do zalet danego języka programowania dodamy korzyści wynikające z używania platformy .NET, wszyscy będą zadowoleni.
Oczywiście w praktyce najprawdopodobniej większość czasu spędzisz, pisząc programy tylko w wybranym przez siebie języku. Jednak gdy opanujesz już składnię jednego języka .NET, bardzo łatwo nauczyć się kolejnego. Jest to również korzystne dla doradców/konsultantów ds. oprogramowania. Jeśli wybranym przez ciebie językiem jest C#, ale znajdziesz się u klienta będącego fanem Visual Basic, w dalszym ciągu masz możliwość korzystania z funkcji .NET Framework, a zrozumienie ogólnej struktury bazy kodu nie powinno stanowić większego problemu.
Przegląd pakietów .NET
Niezależnie od tego, w jakim języku .NET zdecydujesz się programować, musisz pamiętać, że choć pliki binarne .NET mają takie same rozszerzenia jak niezarządzane pliki binarne Windows (*.dll lub *.exe), ich wewnętrzna struktura jest zupełnie inna. W szczególności, pliki binarne .NET nie zawierają instrukcji związanych z żadną konkretną platformą, lecz niezależny od platformy język pośredni (Intermediate Language, IL) i metadane opisujące typy. Rysunek 1.2 pokazuje ogólny schemat przedstawionych dotychczas informacji.
Rysunek 1.2. Wszystkie kompilatory .NET generują instrukcje IL i metadane
Uwaga. Jeśli chodzi o skrót IL, to konieczna jest jedna uwaga. Inne nazwy tego języka to MSIL (Microsoft Intermediate Language) oraz CIL (Common Intermediate Language). Dlatego też, czytając literaturę poświęconą .NET, należy pamiętać, że akronimy IL, MSIL i CIL odnoszą się w gruncie rzeczy do tego samego pojęcia. W tym tekście, pisząc o tym zbiorze niskopoziomowych instrukcji, będę używał skrótu CIL.
Binarny plik *.dll lub *.exe utworzony za pomocą kompilatora przeznaczonego dla platformy .NET nazywany jest pakietem (ang. assembly). Szczegółowe omówienie pakietów .NET jest tematem rozdziału 14. Jednak już teraz, aby ten punkt był zrozumiały, przedstawię kilka podstawowych właściwości tego nowego formatu plików.
Jak wspomniałem, pakiet zawiera kod CIL, który jest podobny do kodu bajtowego Javy pod tym względem, że o ile nie jest to nieodzowne, nie jest on kompilowany do instrukcji przeznaczonych na konkretną platformę. Zwykle „nieodzowne” oznacza moment, w którym do bloku instrukcji CIL (jak np. implementacja metody) odwołuje się środowisko uruchomieniowe .NET.
Oprócz instrukcji CIL pakiety zawierają również metadane, które opisują bardzo szczegółowo charakterystyczne cechy każdego „typu” znajdującego się w pliku binarnym. Jeśli na przykład mamy klasę o nazwie SportsCar, w metadanych typu jest opis takich szczegółów jak klasa bazowa SportsCar, interfejsy zaimplementowane w klasie SportsCar (jeśli takowe istnieją), jak również pełny opis każdej składowej obsługiwanej przez typ SportsCar. Metadane .NET zawsze znajdują się w pakiecie i są automatycznie generowane przez kompilator danego języka .NET.
Poza tym same pakiety też mają opis pod postacią metadanych, które w tej roli nazywane są manifestem. Manifest zawiera informacje o bieżącej wersji pakietu, informację o kulturze (służącą do lokalizacji łańcuchów i zasobów graficznych) oraz listę wszystkich zewnętrznych pakietów wymaganych do prawidłowego działania. W kilku kolejnych rozdziałach poznasz różne narzędzia, których można używać do analizy typów, metadanych i manifestu pakietu.
Rola języka CIL
Przyjrzyjmy się dokładniej kodowi CIL, metadanym typów i manifestowi pakietu. CIL to język znajdujący się poziom powyżej zbioru poleceń dla konkretnej platformy. Na przykład, poniższy kod C# modeluje prosty kalkulator. W tej chwili nie musisz przejmować się składnią, zwróć uwagę na format metody Add() w klasie Calc.
// Calc.cs
using System;
namespace CalculatorExample
{
// Ta klasa zawiera punkt wejścia aplikacji.
class Program
{
static void Main()
{
Calc c = new Calc();
int ans = c.Add(10, 84);
Console.WriteLine("10 + 84 is {0}.", ans);
// Przed zamknięciem zaczekaj, aż użytkownik naciśnie ENTER.
Console.ReadLine();
}
}
// Kalkulator w C#.
class Calc
{
public int Add(int x, int y)
{ return x + y; }
}
}
Po skompilowaniu tego kodu za pomocą kompilatora C# (csc.exe), otrzymasz jednoplikowy pakiet *.exe zawierający manifest, instrukcje CIL oraz metadane opisujące każdy aspekt klas Calc i Program.
Uwaga. W rozdziale 2 wyjaśniam, jak kompilować pliki z kodem w graficznych środowiskach IDE (takich jak Visual Studio Community Edition).
Po otwarciu tego pakietu w programie ildasm.exe (który został omówiony w dalszej części tego rozdziału) zobaczysz metodę Add() pod postacią poniższego kodu CIL:
.method public hidebysig instance int32 Add(int32 x,
int32 y) cil managed
{
// Code size 9 (0x9)
.maxstack 2
.locals init (int32 V_0)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: ldarg.2
IL_0003: add
IL_0004: stloc.0
IL_0005: br.s IL_0007
IL_0007: ldloc.0
IL_0008: ret
} // end of method Calc::Add
Nie przejmuj się, jeśli w tej chwili nic nie rozumiesz z wynikowego kodu CIL dla tej metody – podstawy języka programowania CIL są przedstawione w rozdziale 18. W tym momencie istotne jest to, że kompilator C# generuje kod CIL, a nie polecenia dla konkretnej platformy.
A teraz przypomnę, że odnosi się to do wszystkich kompilatorów przeznaczonych na platformę .NET. Dla potwierdzenia tych słów załóżmy, że ta sama aplikacja została napisana nie w C#, ale w Visual Basic.
' Calc.vb
Imports System
Namespace CalculatorExample
' "Moduł" w VB to klasa, która zawiera jedynie
' statyczne składowe.
Module Program
Sub Main()
Dim c As New Calc
Dim ans As Integer = c.Add(10, 84)
Console.WriteLine("10 + 84 is {0}.", ans)
Console.ReadLine()
End Sub
End Module
Class Calc
Public Function Add(ByVal x As Integer, ByVal y As Integer)
As Integer
Return x + y
End Function
End Class
End Namespace
W kodzie CIL z metodą Add() zobaczysz podobne polecenia (nieznacznie zmodyfikowane przez kompilator dla Visual Basic, vbc.exe).
.method public instance int32 Add(int32 x,
int32 y) cil managed
{
// Code size 8 (0x8)
.maxstack 2
.locals init (int32 V_0)
IL_0000: ldarg.1
IL_0001: ldarg.2
IL_0002: add.ovf
IL_0003: stloc.0
IL_0004: br.s IL_0006
IL_0006: ldloc.0
IL_0007: ret
} // end of method Calc::Add
Kod źródłowy. Pliki z kodem Calc.cs i Calc.vb znajdują się w podkatalogu Chapter 1.
Zalety CIL
W tym momencie można zadać pytanie, co tak naprawdę zyskujemy, kompilując kod źródłowy do CIL, a nie bezpośrednio do konkretnego zbioru instrukcji. Jedną z zalet jest integracja języków. Jak już wiadomo, każdy kompilator .NET generuje niemal identyczne instrukcje CIL. Tym samym wszystkie języki mogą komunikować się w obrębie klarownie zdefiniowanego obszaru binarnego.
Co więcej, ponieważ język CIL, podobnie jak sama platforma .NET, jest niezależny od systemu operacyjnego, mamy te same korzyści, do których są przyzwyczajeni programujący w Javie (tzn. jedna baza kodu działająca w wielu systemach operacyjnych). Prawdę mówiąc, istnieje międzynarodowy standard języka C# jak również obszerny podzbiór implementacji platformy .NET dla wielu różnych systemów operacyjnych (więcej szczegółów pod koniec rozdziału).
Kompilowanie CIL do instrukcji na konkretną platformę
Ponieważ pakiety nie zawierają instrukcji przeznaczonych na konkretną platformę, tylko kod CIL, ten ostatni przed użyciem musi zostać skompilowany w locie. Za kompilację kodu CIL do sensownych instrukcji CPU odpowiada kompilator JIT, czasami pieszczotliwie nazywany Jitterem. Środowisko uruchomieniowe .NET wykorzystuje kompilator JIT zoptymalizowany pod kątem danego procesora i platformy sprzętowej.
Jeśli na przykład tworzymy aplikację .NET, która ma być wdrażana na urządzeniach przenośnych (np. z Windows Mobile), to odpowiedni Jitter jest doskonale przystosowany do działania w środowisku z niewielką ilością pamięci. Jeśli natomiast instalujemy pakiet na komercyjnym serwerze (gdzie niedobór pamięci rzadko jest problemem), Jitter będzie zoptymalizowany pod kątem funkcjonowania w środowisku, w którym jest dużo pamięci. W ten sposób programiście wystarczy raz napisać kod, a JIT skompiluje go odpowiednio do architektury danego urządzenia.
Co więcej, ponieważ dany Jitter kompiluje instrukcje CIL do odpowiedniego kodu maszynowego, będzie przechowywać wyniki w pamięci w sposób odpowiedni dla docelowego systemu operacyjnego. Na przykład, po pierwszym wywołaniu metody PrintDocument() kod CIL zostanie skompilowany do instrukcji przeznaczonych na konkretną platformę i zachowany w pamięci do późniejszego użytku. Przy ponownym wywołaniu tej metody nie trzeba po raz kolejny kompilować kodu CIL.
Uwaga. Za pomocą konsolowego narzędzia ngen.exe, będącego częścią .NET Framework SDK, można również przeprowadzić wstępną kompilację JIT („pre-JIT”). Można w ten sposób skrócić czas uruchamiania tych aplikacji, które przetwarzają dużo grafiki.
Rola metadanych typów .NET
Oprócz instrukcji CIL pakiet .NET zawiera pełne, kompletne i dokładne metadane, które opisują zarówno każdy typ (np. klasę, strukturę, wyliczenie) zdefiniowany w pliku binarnym, jak również składowe każdego typu (np. właściwości, metody, zdarzenia). Na szczęście wygenerowanie najbardziej aktualnych i najlepszych metadanych typu jest zawsze obowiązkiem kompilatora, a nie programisty. Ponieważ metadane .NET są tak bardzo drobiazgowe, każdy pakiet zawiera własny, pełny opis.
Aby zapoznać się z formatem metadanych typu .NET, spójrzmy na metadane, które zostały wygenerowane dla metody Add() z poznanej wcześniej klasy Calc napisanej w języku C# (metadane wygenerowane dla metody Add() w wersji Visual Basic są podobne; więcej na temat używania ildasm już za moment).
TypeDef #2 (02000003)
-------------------------------------------------------
TypDefName: CalculatorExample.Calc (02000003)
Flags : [NotPublic] [AutoLayout] [Class]
[AnsiClass] [BeforeFieldInit] (00100001)
Extends : 01000001 [TypeRef] System.Object
Method #1 (06000003)
-------------------------------------------------------
MethodName: Add (06000003)
Flags : [Public] [HideBySig] [ReuseSlot] (00000086)
RVA : 0x00002090
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: I4
2 Arguments
Argument #1: I4
Argument #2: I4
2 Parameters
(1) ParamToken : (08000001) Name : x flags: [none] (00000000)
(2) ParamToken : (08000002) Name : y flags: [none] (00000000)
Metadane są używane w wielu sferach działania środowiska uruchomieniowego .NET, jak również przez różne narzędzia programistyczne. Na przykład, funkcja IntelliSense, dostępna w narzędziach takich jak Visual Studio, jest możliwa dzięki odczytywaniu metadanych pakietu w czasie projektowania. Metadane są używane również przez różnego rodzaju programy do przeglądania obiektów, narzędzia do debugowania oraz sam kompilator C#. Można powiedzieć, że metadane to kręgosłup wielu technologii .NET, w tym WCF (Windows Communication Foundation), mechanizmu refleksji, późnego dowiązywania oraz serializacji obiektów. Dokładne omówienie roli metadanych .NET znajduje się w rozdziale 15.
Rola manifestu pakietu
Nie można zapomnieć o tym, że pakiet .NET zawiera również metadane z własnym opisem (których techniczna nazwa to manifest). Manifest zawiera m.in. informacje o wszystkich zewnętrznych pakietach wymaganych do prawidłowego działania pakietu bieżącego, numer wersji tego pakietu, informacje o prawach autorskich itd. Podobnie jak w przypadku metadanych typów jego wygenerowanie jest zadaniem kompilatora. Poniżej kilka istotnych szczegółów z manifestu wygenerowanego przy okazji kompilowania pokazanego wcześniej w tym rozdziale pliku z kodem Calc.cs (zakładając, że nakazaliśmy kompilatorowi nazwać ten pakiet Calc.exe):
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 4:0:0:0
}
.assembly Calc
{
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module Calc.exe
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 0x00000200
.corflags 0x00000001
Mówiąc w skrócie, w manifeście tym widzimy listę zewnętrznych pakietów wymaganych przez Calc.exe (pod dyrektywą .assembly extern), jak również różne cechy charakterystyczne samego pakietu (np. numer wersji, nazwa modułu). W rozdziale 14 bardziej szczegółowo omawiamy przydatność danych zawartych w manifeście.
Wspólny system typów (CTS)
Dany pakiet może zawierać dowolną liczbę różnych typów. W świecie .NET typ to po prostu ogólne określenie składowych należących do zbioru {klasa, interfejs, struktura, wyliczenie, delegat}. Tworząc rozwiązania w języku .NET, prawdopodobnie będziesz używać wielu typów tego rodzaju. Możesz na przykład zdefiniować w pakiecie jedną klasę a w niej zaimplementować jakąś liczbę interfejsów; jedna z metod tej klasy jako parametr wejściowy może przyjmować typ wyliczeniowy, a do procedury wywołującej zwracać strukturę.
Przypomnę, że CTS to formalna specyfikacja mówiąca o tym, w jaki sposób należy definiować typy, aby mogły być one hostowane przez CLR. Zwykle jedynymi osobami, które muszą się przejmować wewnętrznymi mechanizmami działania CTS, są ci, którzy tworzą programy narzędziowe i/lub kompilatory przeznaczone na platformę .NET. Istotne jest jednak, aby wszyscy programiści .NET umieli używać pięciu typów zdefiniowanych w CTS w wybranym przez siebie języku. Poniżej znajduje się ich krótki przegląd.
Klasy CTS
W każdym języku .NET występuje przynajmniej pojęcie klasy będącej podstawą programowania obiektowego (ang. object-oriented programming, OOP). Klasa może składać się z dowolnej liczby składowych (takich jak konstruktory, właściwości, metody i zdarzenia) oraz punktów danych (pól). W języku C# klasy deklarujemy za pomocą słowa kluczowego class:
// Klasa C# z jedną metodą.
class Calc
{
public int Add(int x, int y)
{
return x + y;
}
}
Właściwe omówienie tworzenia klas w języku C# zaczyna się w rozdziale 5; na razie w tabeli 1.1 jest przedstawione kilka charakterystycznych cech tego typu.
Tabela 1.1. Cechy charakterystyczne klas
Cecha klasy
Znaczenie w praktyce
Zapieczętowana/niezapieczętowana
Klasy zapieczętowane nie mogą być klasami bazowymi dla innych klas.
Ma zaimplementowane interfejsy/brak zaimplementowanych interfejsów
Interfejs to kolekcja abstrakcyjnych składowych, które stanowią „kontrakt” między obiektem a użytkownikiem obiektu. W systemie CTS w danej klasie można zaimplementować dowolną liczbę interfejsów.
Abstrakcyjna/ konkretna
Bezpośrednio tworzenie instancji klas abstrakcyjnych jest niedozwolone. Służą one do definiowania funkcjonalności wspólnej dla wszystkich typów pochodnych. Możliwe jest bezpośrednie tworzenie instancji klas konkretnych.
Widoczność
Dla każdej klasy trzeba skonfigurować słowo kluczowe określające jej widoczność, jak np. public lub internal. W ten sposób przesądza się o tym, czy dana klasa może być używana przez pakiety zewnętrzne, czy tylko przez pakiet, w którym została zdefiniowana.
Interfejsy CTS
Interfejsy to nic innego jak nazwane zbiory definicji abstrakcyjnych składowych, które mogą być obsługiwane (tzn. implementowane) przez daną klasę lub strukturę. W języku C# typy te definiujemy za pomocą słowa kluczowego interface. Zgodnie z konwencją nazwy wszystkich interfejsów .NET powinny zaczynać się dużą literą I, tak jak w poniższym przykładzie:
// Interfejs C# jest zwykle deklarowany jako publiczny,
// aby typy z innych pakietów mogły implementować jego funkcjonalność.
public interface IDraw
{
void Draw();
}
Z samych interfejsów nie ma wielkiego pożytku, jednak po zaimplementowaniu interfejsu w klasie lub strukturze w niepowtarzalny sposób można zażądać dostępu do jego funkcjonalności, używając referencji do interfejsu na zasadzie polimorfizmu. Programowanie z wykorzystaniem interfejsów jest dokładnie omówione w rozdziale 8.
Struktury CTS
W ramach systemu CTS zdefiniowano również pojęcie struktur. Jeśli masz doświadczenie w programowaniu w języku C, to z pewnością ucieszy cię fakt, że w świecie .NET znalazło się miejsce na te typy definiowane przez użytkownika (ang. user-defined types, UDT), choć pod maską działają nieco inaczej. Mówiąc najprościej, struktura to taka lekka klasa z semantyką opartą na wartościach. Więcej szczegółowych informacji na temat struktur znajdziesz w rozdziale 4. Struktury zazwyczaj najlepiej nadają się do modelowania danych geometrycznych i matematycznych, a w języku C# do ich tworzenia używa się słowa kluczowego struct w następujący sposób:
// Struktura C#.
struct Point
{
// Struktury mogą zawierać pola.
public int xPos, yPos;
// Struktury mogą zawierać sparametryzowane konstruktory.
public Point(int x, int y)
{ xPos = x; yPos = y;}
// W strukturach można definiować metody.
public void PrintPosition()
{
Console.WriteLine("({0}, {1})", xPos, yPos);
}
}
Wyliczenia CTS
Wyliczenia (ang. enumeration) to przydatna konstrukcja programistyczna, która pozwala łączyć w grupy pary nazwa-wartość. Dla przykładu załóżmy, że piszesz grę wideo i chcesz dać graczowi możliwość wyboru postaci należącej do z jednej z trzech kategorii (Czarodziej, Wojownik lub Złodziej). Nie musisz śledzić prostych wartości liczbowych reprezentujących każdą z tych możliwości: wystarczy, że za pomocą słowa kluczowego enum utworzysz typ wyliczeniowy z silną typizacją.
// Wyliczenie C#.
enum CharacterType
{
Wizard = 100,
Fighter = 200,
Thief = 300
}
Domyślnie każdy element jest przechowywany w 32-bitowej wartości całkowitoliczbowej. Można jednak w razie potrzeby zmienić tę wielkość (np. gdy programujemy na urządzenia z niewielką ilością pamięci, jak urządzenia przenośne). Poza tym CTS wymaga, aby wyliczenia wywodziły się ze wspólnej klasy bazowej, System.Enum. Jak przekonasz się w rozdziale 4, w tej klasie bazowej zdefiniowano wiele interesujących składowych, które umożliwiają wyodrębnianie, używanie i przekształcanie par nazwa-wartość w kodzie programu.
Delegaty CTS
Delegat to na platformie .NET odpowiednik wskaźnika do funkcji w stylu języka C z zachowaniem bezpieczeństwa typów. Kluczowa różnica polega na tym, że delegat .NET to klasa wywodząca się z System.MulticastDelegate, a nie prosty wskaźnik do surowego adresu pamięci. W języku C# delegaty tworzymy za pomocą słowa kluczowego delegate:
// Ten delegat C# może "wskazywać" na dowolną metodę
// zwracającą wartość typu int, a przyjmującą dwie wartości int.
delegate int BinaryOp(int x, int y);
Delegaty stanowią fundament architektury zdarzeń .NET; odgrywają kluczową rolę w tym, aby jeden obiekt miał możliwość przekazywania wywołania do innego obiektu. W rozdziałach 10 i 19 dowiesz się o wbudowanej w delegaty obsłudze multiemisji (tzn. przekazywania żądania do wielu odbiorców) i asynchronicznego wywoływania metod (tzn. wywoływania metod w wątku podrzędnym).
Składowe typów CTS
Po tym przejrzeniu wszystkich typów formalnie opisanych w systemie CTS dowiedz się, że większość typów może mieć dowolną liczbę składowych (ang. member). Z formalnego punktu widzenia składowa typu to element ze zbioru {konstruktor, finalizator, konstruktor statyczny, typ zagnieżdżony, operator, metoda, właściwość, indekser, pole, pole tylko do odczytu, stała, zdarzenie}.
W systemie CTS zdefiniowano różne ozdobniki (ang. adornments), które można powiązać z daną składową. Na przykład, każda składowa ma określoną widoczność (może być np. publiczna, prywatna lub chroniona). Niektóre składowe można zadeklarować zarówno jako abstrakcyjne (aby wymusić polimorficzne działanie typów pochodnych), jak również jako wirtualne (aby predefiniować implementację zachowując możliwość jej nadpisania). Poza tym większość składowych można skonfigurować jako statyczne (związane z klasą) lub instancyjne (związane z obiektem). Tworzenie składowych typu jest omawiane w kilku kolejnych rozdziałach.
Uwaga. Jak wyjaśniam w rozdziale 9, język C# obsługuje również tworzenie typów i składowych generycznych.
Wbudowane typy danych CTS
Ostatnim aspektem systemu CTS, o którym wspominamy w tym wprowadzeniu, jest fakt, że zdefiniowano w nim przejrzysty zbiór podstawowych typów danych. Choć w każdym języku zwykle jest unikatowe słowo kluczowe służące do deklarowania podstawowego typu danych, słowa kluczowe wszystkich języków .NET są ostatecznie przekształcane na jeden z typów CTS zdefiniowanych w pakiecie o nazwie mscorlib.dll. W tabeli 1.2 są zebrane informacje na temat tego, jak kluczowe typy danych CTS są wyrażane w różnych językach .NET.
Tabela 1.2. Wbudowane typy danych CTS
Typ danych CTS
Słowo kluczowe VB
Słowo kluczowe C#
Słowo kluczowe C++/CLI
System.Byte
Byte
byte
unsigned char
System.SByte
SByte
sbyte
signed char
System.Int16
Short
short
short
System.Int32
Integer
int
int lub long
System.Int64
Long
long
__int64
System.UInt16
UShort
ushort
unsigned short
System.UInt32
UInteger
uint
unsigned int lub unsigned long
System.UInt64
ULong
ulong
unsigned __int64
System.Single
Single
float
float
System.Double
Double
double
double
System.Object
Object
object
object^
System.Char
Char
char
wchar_t
System.String
String
string
String^
System.Decimal
Decimal
decimal
Decimal
System.Boolean
Boolean
bool
bool
Ponieważ unikatowe słowa kluczowe zarządzanego języka to jedynie skrótowa notacja dla rzeczywistego typu z przestrzeni nazw System, nie musisz się już przejmować ani arytmetycznym nadmiarem/niedomiarem, ani sposobem wewnętrznej reprezentacji łańcuchów i wartości logicznych w różnych językach. Spójrz na poniższe fragmenty kodu, w których zdefiniowano 32-bitowe zmienne liczbowe w językach C# i Visual Basic zarówno za pomocą słów kluczowych danego języka, jak i formalnego typu danych CTS.
// Zdefiniuj kilka zmiennych typu "int" w C#.
int i = 0;
System.Int32 j = 0;
' Zdefiniuj kilka zmiennych typu "int" w VB.
Dim i As Integer = 0
Dim j As System.Int32 = 0
Specyfikacja wspólnego języka (CLS)
Jak wiadomo, każdy język programowania pozwala wyrażać te same konstrukcje programistyczne na swój własny, niepowtarzalny sposób. Na przykład, w języku C# konkatenację łańcuchów oznacza się operatorem +, a w Visual Basic służy do tego zwykle ampersand (&). Istnieje duże prawdopodobieństwo, że składnia tego samego idiomu programistycznego (np. funkcji bez wartości zwracanej) w dwóch różnych językach będzie wyglądała zupełnie inaczej:
// Metoda C#, która nic nie zwraca.
public void MyMethod()
{
// Jakiś interesujący kod...
}
' Metoda VB, która nic nie zwraca.
Public Sub MyMethod()
' Jakiś interesujący kod...
End Sub
Jak już widzieliśmy, te drobne różnice w składni z punktu widzenia środowiska uruchomieniowego .NET nie mają znaczenia, ponieważ odpowiednie kompilatory (w tym przypadku odpowiednio csc.exe lub vbc.exe) generują zbiory podobnych instrukcji CIL. Jednakże języki różnią się również pod względem całościowego poziomu funkcjonalności. Na przykład, w danym języku .NET może być, ale nie musi, dostępne słowo kluczowe reprezentujące dane bez znaku albo dany język może, ale nie musi, obsługiwać typów wskaźnikowych. Z uwagi na te możliwe różnice ideałem byłoby istnienie linii bazowej, z którą musiałyby być zgodne wszystkie języki przeznaczone na platformę .NET.
Specyfikacja CLS to zbiór reguł wyczerpująco opisujący minimalny, a zarazem kompletny zbiór funkcji, które muszą być obsługiwane przez dany kompilator .NET, aby mógł on wygenerować kod nadający się do hostowania przez CLR i jednocześnie zapewniający jednolity dostęp wszystkim językom .NET. Pod wieloma względami CLS można uznać za podzbiór pełnej funkcjonalności zdefiniowanej w systemie CTS.
CLS to ostatecznie zbiór reguł, do których dostosować się muszą twórcy kompilatorów, jeśli chcą zapewnić swoim produktom bezproblemowe działanie w świecie .NET. Każda z tych reguł ma swoją prostą nazwę (np. Reguła 6) i zawiera wyjaśnienie zarówno dla twórców kompilatorów, jak i dla ich użytkowników. Wisienką na torcie CLS jest reguła numer 1:
Reguła 1: Reguły CLS odnoszą się tylko do tych części typu, które są udostępnione poza pakietem, w którym został on zdefiniowany.
Na podstawie tej reguły można (słusznie) wywnioskować, że pozostałe reguły CLS nie odnoszą się do zasad konstruowania wewnętrznych mechanizmów danego typu .NET. Wymóg zgodności ze specyfikacją CLS dotyczy jedynie definicji składowych (tzn. chodzi o konwencje nazewnicze, parametry i zwracane typy). Sama implementacja danej składowej może opierać się na dowolnych technikach, o których CLS nie wspomina, ponieważ na zewnątrz i tak nie będzie widać różnicy.
Dla przykładu, poniższa napisana w C# metoda Add() nie jest zgodna z CLS, ponieważ parametry i wartości zwracane używają danych bez znaku (co nie jest wymaganiem CLS):
class Calc
{
// Udostępnianie danych bez znaku nie jest zgodne z CLS!
public ulong Add(ulong x, ulong y)
{
return x + y;
}
}
Jeśli jednak danych bez znaku użyjesz tylko wewnątrz metody, w taki oto sposób:
class Calc
{
public int Add(int x, int y)
{
// Ta zmienna ulong jest używana tylko wewnętrznie,
// więc zachowujemy zgodność ze specyfikacją CLS.
ulong temp = 0;
...
return x + y;
}
}
to zachowujesz zgodność z regułami CLS i możesz mieć pewność, że wszystkie języki .NET będą mogły wywoływać metodę Add().
Oczywiście w specyfikacji CLS jest znacznie więcej reguł niż wspomniana reguła 1. Na przykład opisano, w jaki sposób dany język musi reprezentować łańcuchy tekstowe, w jaki sposób należy wewnętrznie przedstawiać typ wyliczeniowy (typ bazowy używany do przechowywania informacji), jak definiować statyczne składowe i tak dalej. Na szczęście, aby zostać produktywnym programistą .NET, nie trzeba tych reguł uczyć się na pamięć. Także w tym przypadku szczegółowe zrozumienie specyfikacji CTS i CLS jest, ogólnie rzecz biorąc, wymogiem jedynie dla twórców narzędzi i kompilatorów.
Zapewnianie zgodności z CLS
Jak przekonasz się na kolejnych stronach tej książki, w języku C# zdefiniowano wiele konstrukcji programistycznych, które nie są zgodne z CLS. Dobra wiadomość jest jednak taka, że wystarczy jeden atrybut .NET, aby nakazać kompilatorowi C# sprawdzenie kodu pod kątem zgodności ze specyfikacją CLS.
// Niech kompilator C# sprawdzi zgodność kodu z CLS.
[assembly: CLSCompliant(true)]
Szczegóły programowania z wykorzystaniem atrybutów są przedstawione w rozdziale 15. Na razie wystarczy wiedzieć, że atrybut [CLSCompliant] nakazuje kompilatorowi C# sprawdzić każdy wiersz kodu pod kątem zgodności z CLS. Jeśli wykryte zostaną jakieś niezgodności, kompilator wyświetli informację o błędzie i opis problematycznego kodu.
Wspólne środowisko uruchomieniowe (CLR)
Kolejnym po CTS i CLS trójznakowym akronimem (TzA), z którym musisz się oswoić, jest CLR. Z programistycznego punktu widzenia określenie środowisko uruchomieniowe (ang. runtime) można rozumieć jako zbiór usług wymaganych do wykonania danej skompilowanej jednostki kodu. Na przykład, gdy programista Javy chce wdrożyć swoje oprogramowanie na nowym komputerze, musi wcześniej dopilnować, aby zainstalowano na nim wirtualną maszynę Javy (JVM, Java Virtual Machine); inaczej jego program nie będzie działać.
Platforma .NET to kolejne tego typu środowisko uruchomieniowe. Kluczowa różnica między nią a innymi wspomnianymi środowiskami sprowadza się do tego, że .NET to jedna, ściśle zdefiniowana warstwa uruchomieniowa, z której wspólnie korzystają wszystkie języki i platformy sprzętowe, które obsługują technologię .NET.
Fizyczną reprezentacją serca CLR jest biblioteka o nazwie mscoree.dll (inaczej Common Object Runtime Execution Engine). Gdy chcąc użyć jakiegoś pakietu, odwołujemy się do niego, do pamięci automatycznie wczytywana jest biblioteka mscoree.dll, a następnie zażądany pakiet. Silnik uruchomieniowy jest odpowiedzialny za wiele zadań. Po pierwsze, to właśnie on odpowiada za przekształcenie informacji o adresie pakietu i znalezienie pożądanego typu w pliku binarnym; w tym celu odczytuje metadane. Następnie umieszcza typ w pamięci, kompiluje związane z nim instrukcje CIL do instrukcji na konkretną platformę, sprawdza wszystkie wymagane zabezpieczenia, a na końcu wykonuje odpowiedni kod.
Oprócz załadowania niestandardowych pakietów i utworzenia niestandardowych typów CLR, gdy jest to wymagane, sięga po typy znajdujące się w bibliotekach klas bazowych .NET. Biblioteki te są podzielone na wiele samodzielnych pakietów, a kluczową rolę odgrywa mscorlib.dll zawierający dużą liczbę podstawowych typów, w których zawarty jest zarówno szeroki wachlarz popularnych zadań programistycznych, jak również podstawowe typy danych używane przez wszystkie języki .NET. Tworząc rozwiązania .NET, automatycznie używasz tego konkretnego pakietu.
Na rysunku 1.3 jest pokazany schemat przepływu pracy między kodem źródłowym (który używa typów z bibliotek klas bazowych) a danym kompilatorem .NET oraz środowiskiem uruchomieniowym .NET.
Rysunek 1.3. Pakiet mscoree.dll w działaniu
Różnica między pakietem, przestrzenią nazw i typem
Każdy z nas zdaje sobie sprawę ze znaczenia bibliotek kodu. Biblioteki wymyślono po to, aby dać programistom do dyspozycji czytelnie zdefiniowany zbiór kodu, gotowy do używania w aplikacjach. Nie ma jednak żadnej biblioteki kodu związanej konkretnie z językiem C#. Programiści C# używają neutralnych językowo bibliotek .NET. Czytelna organizacja wszystkich typów w bibliotekach klas bazowych na platformie .NET jest wynikiem kompleksowego stosowania koncepcji przestrzeni nazw (ang. namespace).
Przestrzeń nazw to grupa semantycznie pokrewnych typów połączonych w pakiet albo w kilka powiązanych ze sobą pakietów. Na przykład, w przestrzeni nazw System.IO znajdują się typy związane z operacjami we/wy, w przestrzeni nazw System.Data zdefiniowano podstawowe typy bazodanowe itd. Należy podkreślić, że jeden pakiet (np. mscorlib.dll) może zawierać dowolną liczbę przestrzeni nazw, a każda z nich dowolną liczbę typów.
Aby to wyjaśnić, na rysunku 1.4 jest pokazany dostępny w Visual Studio program narzędziowy Object Browser (przeglądarka obiektów; można ją znaleźć w menu View). Narzędzie to pozwala przeglądać pakiety, do których odwołuje się bieżący projekt, przestrzenie nazw w danym pakiecie, typy w danej przestrzeni nazw oraz składowe określonego typu. Zwróć uwagę, że w pakiecie mscorlib.dll znajduje się wiele różnych przestrzeni nazw (w tym System.IO), a każda z nich zawiera własne, pokrewne semantycznie typy (np. BinaryReader).
Rysunek 1.4. W jednym pakiecie może znajdować się dowolna liczba przestrzeni nazw, a w jednej przestrzeni nazw dowolna liczba typów
Kluczowa różnica pomiędzy tym rozwiązaniem a bibliotekami konkretnych języków polega na tym, że każdy język .NET używa tych samych przestrzeni nazw i tych samych typów. Na przykład, trzy poniższe programy, napisane kolejno w C#, VB i C++/CLI, pokazują popularną aplikację „Hello World”:
// Hello world w języku C#.
using System;
public class MyApp
{
static void Main()
{
Console.WriteLine("Hi from C#");
}
}
' Hello world w języku VB.
Imports System
Public Module MyApp
Sub Main()
Console.WriteLine("Hi from VB")
End Sub
End Module
// Hello world w języku C++/CLI.
#include "stdafx.h"
using namespace System;
int main(array<System::String ^> ^args)
{
Console::WriteLine(L"Hi from C++/CLI");
return 0;
}
Jak widać, każdy język używa zdefiniowanej w przestrzeni nazw System klasy Console. Pomijając kilka oczywistych różnic w składni, te trzy aplikacje wyglądają bardzo podobnie, zarówno fizycznie, jak i logicznie.
Rzecz jasna, po oswojeniu się z wybranym językiem .NET kolejnym celem programisty .NET jest zapoznanie się z bogactwem typów zdefiniowanych w (licznych) przestrzeniach nazw .NET. Najważniejszą przestrzenią nazw, od której należy zacząć, jest System. To w niej znajduje się najbardziej podstawowy zbiór typów używanych nieustannie przez każdego programistę .NET. Prawdę mówiąc, nie da się napisać żadnej funkcjonalnej aplikacji w języku C#, unikając odwołania do przestrzeni nazw System, ponieważ w niej właśnie zdefiniowane są najważniejsze typy danych (np. System.Int32, System.String). W tabeli 1.3 są wymienione niektóre (lecz oczywiście nie wszystkie) przestrzenie nazw; sa one pogrupowane na podstawie pokrewieństwa funkcji.
Tabela 1.3. Przykładowe przestrzenie nazw .NET
Przestrzeń nazw .NET
Znaczenie w praktyce
System
Przestrzeń nazw System zawiera wiele przydatnych typów związanych z używaniem wbudowanych danych, wykonywaniem obliczeń matematycznych, generowaniem liczb losowych, zmiennymi środowiskowymi oraz mechanizmem odzyskiwania pamięci oraz wiele powszechnie używanych wyjątków i atrybutów.
System.Collections
System.Collections.Generic
W tych przestrzeniach nazw zdefiniowano zarówno wiele bazowych typów kontenerowych, jak i podstawowe typy oraz interfejsy umozliwiajace tworzenie niestandardowych kolekcji.
System.Data
System.Data.Common
System.Data.EntityClient
System.Data.SqlClient
Przestrzenie nazw służące do komunikacji z relacyjnymi bazami danych za pośrednictwem ADO.NET.
System.IO
System.IO.Compression
System.IO.Ports
W tych przestrzeniach nazw zdefiniowano wiele typów związanych z plikowymi operacjami we/wy, kompresją danych i używaniem portów.
System.Reflection
System.Reflection.Emit
Przestrzenie nazw związane z obsługą wykrywania typów w trakcie wykonywania programów oraz dynamicznym tworzeniem typów.
System.Runtime.InteropServices
Przestrzeń nazw ułatwiająca typom .NET komunikację z kodem niezarządzanym jest (np. biblioteki DLL i serwery COM napisane w języku C) i vice versa.
System.Drawing
System.Windows.Forms
W tych przestrzeniach nazw zdefiniowano typy używane do tworzenia aplikacji okienkowych za pomocą dostępnego na platformie .NET zbioru narzędzi do tworzenia interfejsu użytkownika (Windows Forms).
System.Windows
System.Windows.Controls
System.Windows.Shapes
Przestrzeń nazw System.Windows to najwyższy poziom kilku przestrzeni nazw, w których jest zdefiniowany zbiór narzędzi do tworzenia interfejsu użytkownika (Windows Presentation Foundation, WPF).
System.Linq
System.Xml.Linq
System.Data.DataSetExtensions
W tych przestrzeniach nazw zdefiniowano typy używane przy programowaniu z użyciem API LINQ.
System.Web
Jedna z wielu przestrzeni nazw do tworzenia internetowych aplikacji ASP.NET.
System.Web.Http
Jedna z wielu przestrzeni nazw do tworzenia internetowych usług REST.
System.ServiceModel
Jedna z wielu przestrzeni nazw do tworzenia aplikacji rozproszonych za pomocą API WCF (Windows Communication Foundation).
System.Workflow.Runtime
System.Workflow.Activities
Są to dwie z wielu przestrzeni nazw, w których zdefiniowano typy pozwalające na bazie API WWF (Windows Workflow Foundation) tworzyć aplikacje z „przepływem pracy”.
System.Threading
System.Threading.Tasks
W tej przestrzeni nazw zdefiniowano sporą liczbę typów przeznaczonych do tworzenia aplikacji wielowątkowych, które potrafią podzielić obciążenie pracą na wiele procesorów.
System.Security
Bezpieczeństwo to integralny składnik technologii .NET. W tych przestrzeniach nazw można znaleźć wiele typów związanych z uprawnieniami, kryptografią itd.
System.Xml
Te przestrzenie nazw zawierają wiele typów używanych do obsługi danych XML.
Rola głównej przestrzeni nazw Microsoft
Zapoznając się z zawartością tabeli 1.3, każdy z pewnością zauważył, że System jest przestrzenią nazw najwyższego poziomu dla większości zagnieżdżonych przestrzeni nazw (np. System.IO, System.Data). Okazuje się jednak, że w bibliotekach klas bazowych .NET zdefiniowano wiele innych przestrzeni nazw najwyższego poziomu, a najbardziej przydatna z nich nosi nazwę Microsoft.
Każda przestrzeń nazw zagnieżdżona w Microsoft (np. Microsoft.CSharp, Microsoft.ManagementConsole, Microsoft.Win32) zawiera typy, które służą do używania usług ściśle związanych z systemem operacyjnym Windows. Dlatego też bezpieczniej nie zakładać, że typów tych będzie można z powodzeniem używać w innych systemach obsługujących technologię .NET, jak np. Mac OS X. Generalnie w tym tekście nie będziemy zajmowali się szczegółami przestrzeni nazw, których głównym poziomem jest Microsoft, więc zainteresowanych odsyłamy do dokumentacji .NET Framework 4.6 SDK.
Uwaga. W rozdziale 2 jest omówione używanie dokumentacji .NET Framework 4.6 SDK, w tym wyszukiwanie szczegółów na temat każdej przestrzeni nazw, typu i składowej z bibliotek klas bazowych.
Dostęp do przestrzeni nazw w kodzie programu
Warto powtórzyć, że przestrzeń nazw to nic więcej jak tylko praktyczny sposób na ułatwienie zwykłym ludziom logicznego kojarzenia i organizowania pokrewnych typów. Zastanówmy się raz jeszcze nad przestrzenią nazw System. Ze swojego punktu widzenia możesz założyć, że System.Console reprezentuje klasę o nazwie Console, która znajduje się w przestrzeni nazw o nazwie System. Jednak z punktu widzenia środowiska uruchomieniowego .NET wcale tak nie jest. Silnik uruchomieniowy widzi tylko jedną klasę – tę o nazwie System.Console.
W języku C# słowo kluczowe using upraszcza proces odwoływania się do typów zdefiniowanych w określonej przestrzeni nazw. Na czym to polega? Załóżmy, że chcesz napisać aplikację z graficznym interfejsem użytkownika, wykorzystując do tego API WPF. Poznanie typów znajdujących się w każdej przestrzeni nazw wymaga czasu i doświadczenia; poniżej wymieniono kilka typów, które mogłyby pojawić się w takim programie:
// Oto przestrzenie nazw, których można
// użyć, tworząc aplikację WPF
using System; // Ogólne typy z biblioteki klas bazowych.
using System.Windows.Shapes; // Renderowanie grafiki.
using System.Windows.Controls; // Formanty GUI Windows Forms.
using System.Data; // Ogólne typy związane z danymi.
using System.Data.SqlClient; // Dostęp do danych MS SQL Server.
Po wymienieniu jakiejś liczby przestrzeni nazw (i ustawieniu referencji do pakietów, w których zostały zdefiniowane) możesz swobodnie tworzyć instancje zawartych w nich typów. Jeśli na przykład zechcesz utworzyć instancję klasy Button (zdefiniowanej w przestrzeni nazw System.Windows.Controls), możesz napisać następujący kod:
// Wymień przestrzenie nazw używane w tym pliku.
using System;
using System.Windows.Controls;
class MyGUIBuilder
{
public void BuildUI()
{
// Utwórz kontrolkę - przycisk.
Button btnOK = new Button();
...
}
}
Ponieważ w pliku z kodem importujesz przestrzeń nazw System.Windows.Controls, kompilator jest w stanie ustalić, że klasa Button jest składową tej przestrzeni nazw. Jeśli przestrzeń nazw System.Windows.Controls nie zostanie zaimportowana, kompilator zgłosi błąd. Nie ma jednak żadnego zakazu deklarowania zmiennych również za pomocą pełnej nazwy jednoznacznej (ang. fully qualified name).
// Na liście nie ma przestrzeni nazw System.Windows.Controls!
using System;
class MyGUIBuilder
{
public void BuildUI()
{
// Używanie pełnej nazwy jednoznacznej.
System.Windows.Controls.Button btnOK =
new System.Windows.Controls.Button();
...
}
}
Definiowanie typu za pomocą pełnej nazwy jednoznacznej zwiększa przejrzystość kodu, ale chyba zgodzisz się, że użycie słowa kluczowego C# using to znacznie mniej pisania. W tym tekście będę unikał używania pełnych nazw (o ile nie będzie konieczne rozwiązanie jakiejś dwuznaczności) i postawię na prostszy sposób, czyli słowo kluczowe using.
Należy jednak zawsze pamiętać, że słowo kluczowe using to po prostu skrótowa notacja pozwalająca określić pełną nazwę typu i oba te sposoby dają w rezultacie taki sam kod CIL (biorąc pod uwagę fakt, że kod CIL zawsze używa pełnych nazw) i nie mają żadnego wpływu na wydajność programu ani na rozmiar pakietu.
Odwołania do pakietów zewnętrznych
Oprócz podania przestrzeni nazw za pomocą słowa kluczowego using musisz też podać kompilatorowi C# nazwę pakietu zawierającego właściwą implementację CIL używanego typu. Jak wspomniałem, wiele podstawowych przestrzeni nazw .NET jest zdefiniowanych w pliku mscorlib.dll, jednak klasa System.Drawing.Bitmap znajduje się w osobnym pakiecie o nazwie System.Drawing.dll. Znaczna większość pakietów .NET Framework znajduje się w konkretnym katalogu o nazwie GAC (global assembly cache, globalny katalog pakietów). Na komputerze z systemem Windows ten katalog to domyślnie C:\Windows\Assembly\GAC (patrz rys. 1.5).
Rysunek 1.5. Wiele bibliotek .NET znajduje się w katalogu GAC
W zależności od tego, jakiego narzędzia używasz do pisania aplikacji .NET, masz do dyspozycji różne sposoby, aby poinformować kompilator o tym, które pakiety chcesz uwzględnić w trakcie kompilacji. Jest to omówione w rozdziale 2, więc w tym momencie powstrzymam się od podawania szczegółów.
Uwaga. Jak przekonasz się w rozdziale 14, biblioteki platformy mogą zostać zainstalowane w wielu różnych miejscach systemu operacyjnego Windows, jednak zwykle programista nie ma na to wpływu. Na komputerach z innym systemem (np. Mac OS X lub Linux) położenie folderu GAC zależy od dystrybucji .NET.
Analiza pakietu w programie ildasm.exe
Jeśli przytłacza cię sama myśl, że trzeba biegle opanować wszystkie przestrzenie nazw, przypomnę, że niepowtarzalność każdej z nich opiera się na tym, że zawiera ona typy znaczeniowo pokrewne. Dlatego też, jeżeli wystarczy ci prosta aplikacja konsolowa, możesz spokojnie zapomnieć o przestrzeniach nazw System.Windows.Forms, System.Windows i System.Web (oraz wielu innych). Pisząc program do rysowania, nie musisz zawracać sobie głowy przestrzeniami nazw związanymi z dostępem do baz danych. Tak jak w przypadku każdego zbioru predefiniowanego kodu z upływem czasu nauka staje się coraz łatwiejsza.
Będący częścią platformy .NET program narzędziowy ildasm.exe (Intermediate Language Disassembler) umożliwia wczytanie i zbadanie zawartości dowolnego pakietu .NET, w tym jego manifestu, kodu CIL i metadanych typów. Narzędzie to daje programiście możliwość szczegółowego zapoznania się z tym, w jaki sposób jego kod C# został przekształcony na CIL, a w ostateczności pozwala zrozumieć wewnętrzne mechanizmy działania platformy .NET. Co prawda używanie ildasm.exe do tego, aby zostać efektywnym programistą .NET, nigdy nie jest konieczne, ale gorąco zachęcam do uruchomienia tego narzędzia od czasu do czasu, aby lepiej zrozumieć zależności między kodem C# a działaniem środowiska uruchomieniowego.
Uwaga. Aby uruchomić ildasm.exe, wystarczy w wierszu poleceń Visual Studio wpisać ildasm, a następnie nacisnąć Enter.
Po otwarciu programu z menu Open wybierz polecenie File i przejdź do pakietu, który chcesz analizować. Dla przykładu na rysunku 1.6 jest przedstawiony pakiet Calc.exe wygenerowany na podstawie omawianego wcześniej w tym rozdziale pliku Calc.cs. Program ildasm.exe pokazuje strukturę pakietu w dobrze znanym widoku drzewa.
Rysunek 1.6. Program ildasm.exe umożliwia zapoznanie się z kodem CIL, manifestem i metadanymi pakietu .NET
Przeglądanie kodu CIL
Oprócz przestrzeni nazw, typów i składowych znajdujących się w danym pakiecie przy użyciu ildasm.exe można też podejrzeć instrukcje CIL danej składowej. Jeśli na przykład dwukrotnie klikniesz metodę Main() klasy Program, w osobnym oknie wyświetlony zostanie odpowiedni kod CIL (patrz rys. 1.7).
Rysunek 1.7. Przeglądanie bazowego kodu CIL
Przeglądanie metadanych typu
Jeśli chcesz zobaczyć metadane typów z aktualnie załadowanego pakietu, naciśnij Ctrl+M. Na rysunku 1.8 są pokazane metadane metody Calc.Add().
Rysunek 1.8. Przeglądanie metadanych typów w programie ildasm.exe
Przeglądanie metadanych pakietu (manifestu)
A jeśli chcesz zobaczyć zawartość manifestu pakietu, wystarczy w głównym oknie programu ildasm dwukrotnie kliknąć ikonę MANIFEST (patrz rys. 1.9).
Zapewniam, że ildasm.exe ma znacznie więcej opcji niż te przedstawione na rysunku 1.9. Będziemy je wprowadzać w odpowiednich momentach w dalszej części tekstu.
Rysunek 1.9. Przeglądanie zawartości manifestu w programie ildasm.exe
Niezależność .NET od platformy systemowej
Na zakończenie rozdziału pokrótce skomentuję niezależność technologii .NET od platformy systemowej. Ku zaskoczeniu wielu programistów pakiety .NET można tworzyć i wykonywać nie tylko w systemach operacyjnych Microsoftu. Może to być zarówno Mac OS X, różne dystrybucje Linuksa, Solaris, jak i urządzenie Apple iPhone (dzięki API MonoTouch). Aby zrozumieć, jak to w ogóle możliwe, trzeba zapoznać się z jeszcze jednym akronimem ze świata .NET: CLI (Common Language Infrastructure, wspólna infrastruktura języka).
Wydaniu przez Microsoft języka C# oraz platformy .NET towarzyszyło udostępnienie zbioru formalnych dokumentów, w których opisano składnię i semantykę języków C# i CIL, format pakietu .NET, podstawowe przestrzenie nazw .NET oraz mechanikę hipotetycznego silnika uruchomieniowego .NET (znanego pod nazwą Virtual Execution System, VES). Co ważniejsze, dokumenty te zostały wysłane do organizacji Ecma International (http://www.ecma-international.org), a następnie ratyfikowane przez nią jako międzynarodowy standard. Specyfikacje, o których mowa, to:
ECMA-334: The C# Language Specification,
ECMA-335: The Common Language Infrastructure (CLI).
Ich znaczenie staje się jasne, gdy zdasz sobie sprawę, że umożliwiają one niezależnym producentom tworzenie dystrybucji platformy .NET na dowolne systemy operacyjne i procesory. Z wymienionej dwójki bardziej treściwa jest ta druga, a treści tej jest aż tyle, że trzeba było podzielić ją na sześć części (patrz tab. 1.4).
Tabela 1.4. Części specyfikacji CLI
Części specyfikacji ECMA-335
Znaczenie w praktyce
Część I: Pojęcia i architektura
Opis ogólnej architektury CLI, w tym reguł specyfikacji CTS i CLS, oraz mechaniki silnika uruchomieniowego .NET
Część II: Definicja metadanych i semantyka
Szczegółowy opis metadanych .NET i formatu pakietów
Część III: Zbiór instrukcji CIL
Opis składni i semantyki kodu CIL
Część IV: Profile i biblioteki
Ogólny przegląd minimalnego i kompletnego zbioru bibliotek klas, jaki musi obsługiwać każda dystrybucja .NET
Część V: Formaty binarne
Opis standardowego sposobu wymiany informacji o debugowaniu między producentami i odbiorcami CIL
Część VI: Załączniki
Zbiór różności, w tym wskazówki dotyczące projektowania bibliotek klas i szczegóły implementacyjne kompilatora CIL
Należy zwrócić uwagę, że w części IV zdefiniowano jedynie minimalny zbiór przestrzeni nazw, których można wymagać od dystrybucji CLI. Reprezentują one podstawowe usługi, jak na przykład kolekcje, konsolowe operacje we/wy, plikowe operacje we/wy, wątki, mechanizm refleksji, dostęp do sieci, podstawowe zabezpieczenia, używanie danych XML. W CLI nie zdefiniowano przestrzeni nazw umożliwiających tworzenie aplikacji internetowych (ASP.NET), związanych z dostępem do baz danych (ADO.NET) ani z tworzeniem aplikacji z graficznym interfejsem użytkownika (Windows Forms/Windows Presentation Foundation).
Mam też dobrą wiadomość: w najważniejszych dystrybucjach .NET oprócz bibliotek CLI znajdują się również kompatybilne z produktami Microsoftu odpowiedniki implementacji ASP.NET, implementacji ADO.NET i różnych implementacji GUI do aplikacji okienkowych, dzięki którym są to pełnowartościowe produkcyjne platformy programistyczne. Obecnie oprócz przeznaczonej dla Windows platformy .NET Microsoftu istnieją dwie główne implementacje CLI. Patrz tabela 1.5.
Tabela 1.5. Dystrybucje .NET o charakterze open source
Dystrybucja
Znaczenie w praktyce
Mono Project
Projekt Mono to opensource’owa dystrybucja CLI przeznaczona na różne dystrybucje Linuksa (np. SuSe, Fedora), system Mac OS X, urządzenia z systemem iOS (iPad, iPhone), urządzenia z systemem Android i (niespodzianka!) Windows.
.NET Core 5
Oprócz platformy .NET przeznaczonej specjalnie dla systemu Windows Microsoft udostępnia również wieloplatformową wersję .NET, która koncentruje się na tworzeniu bibliotek kodu i biznesowych aplikacji internetowych.
Projekt Mono
Projekt Mono to doskonały wybór, jeśli chcesz tworzyć oprogramowanie .NET, które może być uruchamiane w różnych systemach operacyjnych. Oprócz wszystkich kluczowych przestrzeni nazw .NET w projekcie Mono są dostępne dodatkowe biblioteki umożliwiające tworzenie oprogramowania użytkowego z graficznym interfejsem użytkownika, internetowych aplikacji ASP.NET oraz programów na urządzenia przenośne (iPad, iPhone i Android). Dystrybucję Mono można pobrać pod następujacym adresem URL:
www.mono-project.com/
Sam projekt Mono składa się z kilku narzędzi działających w wierszu poleceń i wszystkich potrzebnych bibliotek z kodem. Jak jednak zobaczysz w rozdziale 2, z Mono zwykle używa się zawierającego wszystkie istotne funkcję graficznego IDE o nazwie Xamarin Studio. Poza tym projekty z Microsoft Visual Studio można otwierać w projektach Xamarin Studio i na odwrót. Więcej informacji znajduje się w rozdziale 2, ale zachęcam do zapoznania się ze szczegółami na witrynie Xamarin:
http://xamarin.com/
Uwaga. W załączniku B (w folderze z kodem do pobrania) można znaleźć omówienie platformy Mono.
Microsoft .NET Core
Kolejna wieloplatformowa dystrybucja .NET pochodzi z Microsoft Corporation. Już w roku 2014 Microsoft zapowiedział wariant pełnej wersji, z otwartym kodem źródłowym (dla systemu Windows), platformy .NET 4.6 o nazwie .NET Core. Dystrybucja .NET Core nie jest pełną, wierną kopią platformy .NET 4.6. .NET Core dotyczy przede wszystkim tworzenia internetowych aplikacji ASP.NET, które można uruchamiać na Linuksie, Mac OS X i Windows. Tym samym w gruncie rzeczy można uznać .NET Core za podzbiór pełnego zbioru technologii .NET. Ciekawe porównanie pełnej wersji platformy .NET z wersją .NET Core można znaleźć w artykule na blogu MSDN .NET. Poniżej odnośnik (a jeśli się zmieni, poszukaj w Internecie hasła .NET Core is Open Source):
http://blogs.msdn.com/b/dotnet/archive/2014/11/12/net-core-is-open-source.aspx
Traf chciał, że w .NET Core można znaleźć wszystkie elementy języka C#, jak również wiele kluczowych bibliotek. Dlatego też większa część tego tekstu będzie odnosić się bezpośrednio do tej dystrybucji. Należy jednak pamiętać, że .NET Core jest przeznaczona przede wszystkim do pisania aplikacji internetowych i nie zawiera implementacji API do programów z GUI (takich jak WPF czy Windows Forms). Jeśli musisz tworzyć wieloplatformowe aplikacje użytkowe z GUI, lepszym wyborem jest projekt Mono.
Warto też dodać, że Microsoft, aby ułatwić programowanie w .NET Core, udostępnił bezpłatnie lekki i wieloplatformowy edytor kodu. Jego nazwa to po prostu Visual Studio Code. Choć rzecz jasna nie ma on takich możliwości jak Microsoft Visual Studio ani jak Xamarin Studio, przydaje się do edycji kodu C# przeznaczonego na wiele platform. W tym tekście nie będziemy posługiwali się Visual Studio Code, ale więcej na temat tego narzędzia można znaleźć na stronie:
https://code.visualstudio.com/
Podsumowanie
Celem tego rozdziału było położenie pojęciowego fundamentu pod materiał będący treścią tej książki. Zacząłem od omówienia kilku ograniczeń i złożoności cechujących technologie poprzedzające .NET, a następnie wyjaśniłem, w jaki sposób platforma .NET i język C# uprościły zastany stan rzeczy.
Platforma .NET w gruncie rzeczy sprowadza się do silnika uruchomieniowego (mscoree.dll) i bibliotek klas bazowych (mscorlib.dll oraz pokrewne). Wspólne środowisko uruchomieniowe (CLR) może hostować dowolny plik binarny .NET (zwany pakietem), zgodny z regułami zarządzanego kodu. Jak już wiesz, pakiety oprócz metadanych i manifestu zawierają instrukcje CIL, które są kompilowane do instrukcji konkretnej platformy sprzętowej przez kompilator JIT (just-in-time). Po omówieniu roli wspólnej specyfikacji języka (CLS) i wspólnego systemu typów (CTS) przedstawiłem program narzędziowy do przeglądania obiektów, ildasm.exe.
W kolejnym rozdziale poznasz najpopularniejsze zintegrowane środowiska programistyczne (IDE), w których możesz tworzyć projekty C#. Z radością informuję, że w tej książce używam całkowicie bezpłatnego (i mającego ogromne możliwości) IDE, więc możesz rozpocząć podróż po wszechświecie .NET bez żadnego obciążenia dla kieszeni.
Rozdział 2
Tworzenie aplikacji w języku C#
Jako programista C# masz do wyboru wiele narzędzi, w których możesz tworzyć aplikacje .NET. Twój wybór będzie się opierał przede wszystkim na trzech kryteriach: koszt, system operacyjny, w którym pracujesz, oraz docelowe platformy sprzętowe. Celem tego rozdziału jest przegląd najpopularniejszych zintegrowanych środowisk programistycznych (IDE) obsługujących język C#. Należy podkreślić, że w rozdziale tym nie omawiam każdego szczegółu każdego IDE; zawiera on informacje wystarczające do dokonania wyboru środowiska programistycznego na potrzeby pracy z tym tekstem oraz podstawę do dalszych działań.
W pierwszej części tego rozdziału przedstawiam zbiór IDE z Microsoftu, które pozwalają tworzyć aplikacje .NET w systemie operacyjnym Windows (7, 8.x i 10). Jak się przekonasz, niektórych z nich można używać wyłącznie do tworzenia aplikacji dla systemu Windows, inne natomiast pozwalają pisać aplikacje C# przeznaczone na alternatywne systemy operacyjne i urządzenia (jak np. Mac OS X, Linux i Android). W ostatniej części rozdziału omawiam kilka IDE, które działają w systemach innych niż Windows. Dzięki nim programiści mogą pisać programy C# zarówno na komputerach Apple, jak i w dystrybucjach Linuksa.
Uwaga. W tym rozdziale zajmę się kilkoma IDE. Zakładam jednak, że używasz (całkowicie bezpłatnego) Visual Studio Community Edition. Jeśli chcesz tworzyć swoje aplikacje w innym systemie operacyjnym (Mac OS X lub Linux), rozdział ten poprowadzi cię we właściwym kierunku, jednak w swoim IDE nie zobaczysz dokładnie tego, co jest na zrzutach ekranowych ilustrujących ten tekst.
Tworzenie aplikacji C# w systemie operacyjnym Windows
Jak przekonasz się, czytając ten rozdział, chcąc tworzyć aplikacje C#, masz do wyboru całą gamę środowisk IDE: niektóre z nich są produktami Microsoftu, a inne nie (wiele z nich ma charakter open source). I wbrew temu, co może się wydawać, niektóre IDE z Microsoftu są całkowicie bezpłatne. Dlatego też, jeżeli twoim podstawowym celem jest tworzenie oprogramowania .NET w systemie operacyjnym Windows (7, 8.x lub 10), masz do wyboru:
Visual Studio Express,
Visual Studio Community,
Visual Studio Professional (lub wyżej).
Wymienione IDE mają podobny zbiór możliwości, ale różnią się przede wszystkim liczbą ułatwień związanych z działalnością biznesową i liczbą obsługiwanych typów projektów. Na przykład w edycjach Express brakuje kilku zaawansowanych narzędzi do integracji z bazami danych oraz specjalistycznych szablonów projektów dla alternatywnych języków .NET (takich jak F# czy Python), które można znaleźć w Visual Studio 2015 Professional. Visual Studio Community Edition obsługuje te same typy projektów co Visual Studio 2015 Professional, ale brakuje w nim kilku funkcji, które przydają się w programowaniu zespołowym (np. pełna integracja z Team Foundation Server). Na szczęście w każdym z tych IDE dostępne są zaawansowane edytory kodu, podstawowe moduły projektowania baz danych, zintegrowane, działające w trybie graficznym debugery, narzędzia do projektowania GUI dla aplikacji okienkowych oraz internetowych i tak dalej. Zaczniemy od omówienia roli rodziny Express.
Rodzina IDE: Visual Studio Express
Rodzina Visual Studio Express jest całkowicie bezpłatna. We wcześniejszych wersjach platformy .NET kryterium podziału był język: Microsoft oferował narzędzia o nazwach C# Express, VB Express, Web Developer Express i C++ Express. Jednak niedawno wprowadzono zmianę polegającą na podziale w zależności wyłącznie od typu tworzonych aplikacji (internetowe, okienkowe itd.). W szczególności, do rodziny Visual Studio Express należą:
Express for Windows Desktop: obsługuje tworzenie konsolowych i graficznych aplikacji okienkowych (Windows Forms i Windows Presentation Foundation) dla systemu operacyjnego Windows; obsługiwane języki to C#, VB i C++;
Express for Windows 10: obsługuje tworzenie bazowego oprogramowania przeznaczonego dla „uniwersalnego” typu aplikacji Windows 10, które można uruchamiać na wielu produktach Microsoftu (system operacyjny Windows, Xbox, urządzenia z Windows Mobile, HoloLens itd.); obsługiwane języki to C#, VB i C++;
Express for Web: pozwala tworzyć internetowe aplikacje ASP.NET, aplikacje wykorzystujące chmurę Azure oraz aplikacje Microsoft Silverlight; obsługiwane języki to C#, VB i C++;
Team Foundation Server 2015 Express: ta wersja rodziny Express to przede wszystkim graficzny interfejs do numeracji wersji kodu, tworzenia i przekształcania historii i zadań oraz umożliwienia współpracy między zespołami programistów. W tym wydaniu zbiór narzędzi programistycznych jest ograniczony, więc nie będziemy więcej na ten temat pisać.
Uwaga. Produkty Express można pobrać spod adresu https://www.visualstudio.com/products/visual-studio-express-vs (albo po prostu wyszukując w Internecie Visual Studio Express).
Narzędzia Express są przydatne dla stawiających pierwsze kroki na platformie .NET w tym sensie, że można w nich znaleźć wszystkie najważniejsze funkcje (projektowanie GUI w trybie graficznym, debugery, edytory kodu z wieloma funkcjami itd.), ale nie przytłaczają dziesiątkami dodatkowych i zaawansowanych funkcji, które początkującym tylko by zawadzały. Z całą pewnością narzędzia Express to doskonały zestaw dla programistów amatorów i każdego, kto chce używać „minimalnego, a zarazem pełnego” IDE.
Jeśli chcesz, możesz pobrać IDE Express for Windows Desktop i Express for Web i z powodzeniem przerobić materiał zwarty w tej książce. Z grubsza rzecz biorąc, rozdziały 2–30 można przerobić, używając Express for Windows Desktop, ponieważ będę się koncentrował na aplikacjach konsolowych, WPF i – okazjonalnie – Windows Forms. Do pozostałych rozdziałów, w których jest omówione tworzenie aplikacji internetowych (od 31 do końca), można używać Express for Web albo Visual Studio Community (które zostanie omówione w dalszej części tego rozdziału).
Pamiętaj, że zarówno IDE z rodziny Express, jak i Visual Studio 2015 Community oraz Visual Studio 2015 Professional mają wspólny zbiór podstawowych funkcji. Dlatego też dobra wiadomość jest taka, że po przejściu z jednego środowiska do innego nie ma większych problemów z wykonywaniem podstawowych operacji. A skoro tak, przyjrzyjmy się trochę bliżej kilku IDE z rodziny Express (które, powtórzę, są całkowicie bezpłatne).
Krótkie omówienie Express for Windows Desktop
Ta wersja rodziny Express umożliwia tworzenie aplikacji stacjonarnych, które działają bezpośrednio w systemie operacyjnym Windows (wersje 7, 8.x lub 10). Zaczniemy od napisania prostej aplikacji C#, używając Express for Windows Desktop, ale pamiętaj, że omawiane zagadnienia odnoszą się do wszystkich IDE Microsoftu.
Okno dialogowe New Project i edytor kodu C#
Zakładam, że to IDE masz już pobrane i zainstalowane. Z menu File (plik) wybierz polecenie New Project (nowy projekt). Jak widzisz na rysunku 2.1, w tym IDE można tworzyć aplikacje konsolowe, aplikacje WPF/Windows Forms, a także kilka typów niskopoziomowych projektów C++. Aby rozpocząć, utwórz nowy projekt C# Console Application (aplikacja konsolowa) i nazwij go SimpleCSharpConsoleApp.
Rysunek 2.1. Okno dialogowe New Project
Po utworzeniu projektu zobaczysz plik z początkowym kodem C# (o nazwie Program.cs), otwarty w edytorze kodu. Do metody Main() dodaj poniższy kod C#. Wpisując go, zauważysz, że zaraz po wpisaniu operatora kropkowego aktywuje się funkcja IntelliSense.
static void Main(string[] args)
{
// Konfiguracja interfejsu użytkownika programu konsolowego
Console.Title = "My Rocking App";
Console.ForegroundColor = ConsoleColor.Yellow;
Console.BackgroundColor = ConsoleColor.Blue;
Console.WriteLine("*************************************");
Console.WriteLine("***** Welcome to My Rocking App *****");
Console.WriteLine("*************************************");
Console.BackgroundColor = ConsoleColor.Black;
// Zaczekaj na naciśnięcie klawisza ENTER.
Console.ReadLine();
}
W tym przykładzie używasz klasy Console zdefiniowanej w przestrzeni nazw System. Ponieważ przestrzeń ta została automatycznie uwzględniona na początku pliku za pomocą instrukcji using, nie musisz podawać jej pełnej nazwy przed nazwą klasy (np. System.Console.WriteLine()). Program ten nie robi nic ciekawego. Zwróć jednak uwagę na znajdujące się na końcu wywołanie Console.ReadLine(). Znalazło się tam ono po to, aby uzależnić zakończenie aplikacji od naciśnięcia przez użytkownika klawisza. Bez niego program zniknąłby prawie natychmiast po uruchomieniu!
Uruchamianie i debugowanie projektu
Aby teraz uruchomić program i zobaczyć wyniki, naciśnij po prostu klawisze Ctrl+F5 (polecenie to jest również dostępne w menu Debug). Gdy to zrobisz, na ekranie pojawi się okno konsoli Windows, wyświetlające niestandardowy (i barwny) komunikat. Pamiętaj, że „uruchamiając” program, pomijasz zintegrowany debuger.
Jeśli musisz zdebugować swój kod (co z pewnością będzie miało znaczenie w trakcie tworzenia większych programów), pierwszym krokiem jest ustawienie punktów przerwania w tych instrukcjach kodu, które chcesz zbadać. W tym przykładzie kodu jest co prawda niewiele, ale ustaw punkt przerwania, klikając szary pasek znajdujący się po lewej stronie edytora kodu (punkty przerwania są oznaczane ikoną z czerwoną kropką; patrz rys. 2.2).
Rysunek 2.2. Ustawianie punktów przerwania
Jeśli teraz naciśniesz klawisz F5 (albo użyjesz menu Debug), program będzie zatrzymywał się w każdym punkcie przerwania. Oczywiście, z debugerem można komunikować się za pomocą różnych przycisków na pasku narzędzi i opcji z menu IDE. Po sprawdzeniu wszystkich punktów przerwania aplikacja zakończy działanie po zakończeniu metody Main().
Uwaga. W środowiskach IDE Microsoftu dostępne są zaawansowane debugery, a w kolejnych rozdziałach poznasz różne techniki sprawdzania błędów. W tej chwili zapamiętaj, że gdy trwa sesja debugowania, w menu Debug pojawia się wiele przydatnych opcji. Znajdź chwilę czasu, aby się z nimi zapoznać.
Solution Explorer
Po prawej stronie IDE widzisz okno o nazwie Solution Explorer, w którym możesz znaleźć kilka ważnych rzeczy. Po pierwsze, IDE utworzyło rozwiązanie z jednym projektem (patrz rys. 2.3). W pierwszej chwili może to być mylące, ponieważ jedno i drugie ma taką samą nazwę (SimpleCSharpConsoleApp). Chodzi o to, że „rozwiązanie” (ang. solution) może zawierać wiele powiązanych ze sobą projektów. Na przykład, w swoim rozwiązaniu możesz mieć trzy biblioteki klas, jedną aplikację WPF i jedną usługę WCF. W początkowych rozdziałach tej książki zawsze będzie tylko jeden projekt; jednak tworząc bardziej złożone przykłady, dowiesz się, jak do początkowego rozwiązania dodawać nowe projekty.
Uwaga. Pamiętaj, że gdy wybierzesz w oknie Solution Explorer rozwiązanie na najwyższym poziomie, w systemie menu IDE będzie inny zbiór opcji niż w przypadku wybrania projektu. Jeżeli kiedykolwiek zaczniesz zastanawiać się, gdzie podziała się jakaś opcja z menu, upewnij się, czy przez pomyłkę nie został wybrany niewłaściwy węzeł.
Rysunek 2.3. Solution Explorer
Zobaczysz również ikonę References (odwołania). Węzła tego możesz użyć, gdy aplikacja oprócz tych bibliotek, które znajdują się domyślnie w projekcie określonego typu, do działania potrzebuje jeszcze dodatkowych. Ponieważ tworzysz projekt C# Console Application, kilka bibliotek zostało dodanych automatycznie, w tym System.dll, System.Core.dll, System.Data.dll i tak dalej (na liście pod węzłem References nie jest pokazywane rozszerzenie pliku, .dll). Już wkrótce dowiesz się, jak dodawać biblioteki do projektu.
Uwaga. Przypomnę z rozdziału 1, że wszystkie projekty .NET mają dostęp do kluczowej biblioteki platformy o nazwie mscorlib.dll. Ta biblioteka jest tak niezbędna, że nie jest nawet wymieniana w Solution Explorerze.
Przeglądarka obiektów
Dwukrotnie klikając dowolną bibliotekę pod węzłem References, otworzysz zintegrowaną przeglądarkę obiektów (Object Browser). Możesz w tym celu użyć również menu View (widok). Narzędzie to pozwala zobaczyć różne przestrzenie nazw w pakiecie, typy w przestrzeniach nazw oraz składowe każdego typu. Na rysunku 2.4 pokazano kilka przestrzeni nazw z zawsze obecnego pakietu mscorlib.dll.
Rysunek 2.4. Object Browser
Narzędzie to jest przydatne, gdy chcesz zapoznać się z wewnętrzną organizacją biblioteki .NET, jak również wtedy, gdy potrzebny jest zwięzły opis danego elementu. Zwróć również uwagę na pasek wyszukiwania (<Search>) znajdujący się w górnej części okna. Przydaje się, gdy znasz nazwę typu, którego chcesz użyć, ale nie masz pojęcia, gdzie może się znajdować. Nawiasem mówiąc, pamiętaj, że domyślne przeszukiwane są jedynie biblioteki znajdujące się w bieżącym rozwiązaniu (aby przeszukiwać całą platformę .NET, należy wybrać odpowiednią opcję w rozwijanym polu Browse).
Odwołania do dodatkowych pakietów
Kontynuując ten przykład, dodamy jakiś pakiet (czyli bibliotekę kodu), który nie jest automatycznie załączany do projektu Console Application. W tym celu kliknij prawym przyciskiem myszy zakładkę References w oknie Solution Explorer i wybierz Add Reference (albo z menu Project wybierz polecenie Add Reference). Następnie w oknie dialogowym znajdź bibliotekę o nazwie System.Windows.Forms.dll (w tym przypadku też nie zobaczysz rozszerzenia pliku) i zaznacz pole wyboru (patrz rys. 2.5).
Po kliknięciu przycisku OK ta nowa biblioteka zostanie dodana do zbioru odwołań (pojawi się na liście pod węzłem References). Jednak jak wyjaśniłem w rozdziale 1, odwołanie się do biblioteki to tylko pierwszy krok. Aby używać typów w danym pliku z kodem C#, trzeba zdefiniować instrukcję using. W pliku z kodem do dyrektyw using dodaj poniższy wiersz:
using System.Windows.Forms;
A następnie, bezpośrednio po wywoływaniu Console.ReadLine() w metodzie Main(), dodaj poniższy wiersz kodu:
MessageBox.Show("All done!");
Rysunek 2.5. Okno dialogowe Add Reference
Gdy ponownie uruchomisz program albo rozpoczniesz jego debugowanie, przed zakończeniem programu na ekranie pojawi się prosta ramka z komunikatem.
Przeglądanie właściwości projektu
Zwróć teraz uwagę w Solution Explorerze na ikonę o nazwie Properties (właściwości). Po dwukrotnym kliknięciu tego elementu na ekranie pojawi się zaawansowany edytor konfiguracji projektu. Jako przykład na rysunku 2.6 jest pokazane, w jaki sposób można zmienić docelową dla danego rozwiązania wersję platformy .NET.
Na kolejnych stronach tej książki zapoznasz się z różnymi aspektami okna Project Properties. Jednak jeśli znajdziesz trochę czasu na to, by samodzielnie się z nim zapoznać, zauważysz, na przykład, że można tutaj znaleźć różne ustawienia związane z bezpieczeństwem, podać silną nazwę dla pakietu (patrz rozdz. 4), zainstalować aplikację, wstawić zasoby, a także skonfigurować zdarzenia w procesie kompilacji (pre- i post-build; wstępne i finalne).
Na tym kończę ten krótki przegląd Express for Windows Desktop. Oczywiście narzędzie to ma znacznie więcej dostępnych funkcji niż te, które przedstawiłem w tym punkcie. Przypominam, że wydanie Visual Studio Community ma taki sam podstawowy graficzny interfejs użytkownika co Express for Windows Desktop. W dalszej części książki poznasz inne funkcje, których można używać w jednym i drugim IDE, ale warto znaleźć czas na zapoznanie się z opcjami menu, oknami dialogowymi i ustawieniami właściwości.
Kod źródłowy. Projekt SimpleCSharpConsoleApp znajduje się w podkatalogu Chapter 2.
Rysunek 2.6. Okno dialogowe Project Properties
Krótki przegląd środowiska Express for Web
Jeśli chcesz tworzyć na platformie .NET aplikacje internetowe za pomocą zbioru narzędzi Express, możesz pobrać i zainstalować bezpłatne IDE o nazwie Express for Web. W ostatnich rozdziałach tej książki (31–34) znajdziesz omówienie wielu istotnych szczegółów związanych z tworzeniem aplikacji internetowych na platformie .NET, a na razie wystarczy, że spojrzysz na rysunek 2.7, na którym są pokazane typy projektów, jakie można tworzyć w tym IDE. Okno to otworzysz, wybierając z menu New polecenie Project.
Rysunek 2.7. Okno Project Properties w Express for Web
Jak widzisz, Express for Web pozwala tworzyć internetowe aplikacje ASP.NET, internetowe GUI Silverlight oraz usługi w chmurze Microsoft Azure. Oprócz tego dostępny jest szablon WCF, który pozwala tworzyć rozproszone rozwiązania oparte na usługach. Powtórzę: ASP.NET jest omówione w ostatnich rozdziałach tej książki.
Na tym kończymy szybki przegląd wybranych członków rodziny Express. Jak wspomniałem, przekonasz się, że zbiór funkcji w tych narzędziach jest „w sam raz wystarczający” do realizacji twoich przedsięwzięć związanych z kodowaniem. Zajmijmy się teraz rolą Visual Studio Community.
Środowisko Visual Studio Community Edition
Każde z narzędzi Express jest ograniczone w tym sensie, że pozwala tworzyć oprogramowanie .NET, które będzie działać jedynie w systemie operacyjnym Windows (7, 8.x lub 10). Jednak jak wspomniałem w rozdziale 1, platforma .NET działa na wielu systemach operacyjnych i urządzeniach. Dlatego też, jeżeli musisz napisać program .NET, który ma działać (na przykład) na Androidzie albo produkcie Apple, produkty Express nie będą zbyt pomocne. Na szczęście Microsoft oferuje kolejne, zupełnie darmowe IDE, pozwalające tworzyć znacznie szerszą gamę typów projektów i to w dużej liczbie języków .NET. Mowa o IDE o nazwie Visual Studio Community.
Uwaga. Visual Studio Community możesz pobrać spod adresu https://www.visualstudio.com/products/visual-studio-community-vs.
Pierwszą rzeczą, jaką należy wiedzieć o Visual Studio Community, jest to, że jest środowiskiem do tworzenia zarówno aplikacji stacjonarnych, jak i internetowych (jak również „uniwersalnych” aplikacji .NET). Dlatego też, w przeciwieństwie do rodziny Express, wystarczy pobrać i zainstalować jeden produkt.
Poza tym to środowisko IDE obsługuje wiele dodatkowych języków programowania (F#, Python i JavaScript) oraz typów projektów. Znajdziesz w nim nie tylko bardziej specjalistyczne typy projektów dla systemu operacyjnego Windows, ale też przeznaczone na platformy niepochodzące od Microsoftu. Oto kilka wartych wzmianki przykładów:
projekty przeznaczone do aplikacji Windows Phone i Windows 8.x;
projekty przeznaczone na urządzenia z Androidem;
projekty przeznaczone dla rodziny iOS (iPad, iPhone i Apple Watch);
projekty związane z używaniem niskopoziomowych API C++, takich jak MFC i ATL;
kilka typów projektów przeznaczonych do tworzenia gier wideo na różne urządzenia;
projekty pozwalające rozszerzyć Visual Studio Community (jak również Visual Studio Professional) o nowe funkcje za pomocą wtyczek;
projekty do tworzenia niestandardowych skryptów PowerShell.
Aby lepiej zdać sobie sprawę z języków i typów projektów dostępnych w Visual Studio Community, spójrz na rysunek 2.8, który pokazuje okno dialogowe New Project w tym środowisku IDE.
Projektant klas – Class Designer
Visual Studio Community daje również możliwość projektowania klas i innych typów (takich jak interfejsy i delegaty) w trybie graficznym (tej możliwości nie ma w IDE z rodziny Express). Program narzędziowy Class Designer umożliwia przeglądanie i modyfikowanie relacji między znajdującymi się w projekcie typami (klasy, interfejsy, struktury, wyliczenia i delegaty). Za pomocą tego narzędzia możesz w trybie graficznym dodawać (lub usuwać) składowe danego typu, a modyfikacje te zostaną uwzględnione w odpowiednim pliku C#. I na odwrót, modyfikacje w pliku C# są odzwierciedlane na diagramie klas.
Jeśli masz zainstalowane Visual Studio Community, utwórz nowy projekt Console Application i nazwij go VisualTypeDesignerApp. Aby móc korzystać z narzędzi do projektowania typów w trybie graficznym, najpierw należy wstawić nowy plik z diagramem klasy. W tym celu wybierz kolejno Project ► Add New Item i znajdź typ Class Diagram (patrz rys. 2.9).
Rysunek 2.8. Okno dialogowe New Project w Visual Studio Community
Rysunek 2.9. Wstawianie do bieżącego projektu pliku z diagramem klas
Rysunek 2.10. Przeglądarka Class Diagram
Początkowo okno to będzie puste; można do niego przenosić metodą przeciągnij i upuść pliki z Solution Explorera. Na przykład, po przeciągnięciu Program.cs pojawi się wizualna reprezentacja klasy Program. Jeśli klikniesz ikonę ze strzałką dla danego typu, możesz pokazywać albo ukrywać składowe tego typu (patrz rys. 2.10).
Uwaga. Za pomocą paska narzędzi Class Designer możesz precyzyjnie dostosować opcje wyświetlania na powierzchni okna projektowania.
Program narzędziowy Class Designer działa w połączeniu z dwoma innymi elementami Visual Studio: oknem Class Details (otwieramy je z menu View ► Other Windows) i Class Designer Toolbox (z menu View należy wybrać pozycję Toolbox [przybornik]). W oknie Class Details widzimy nie tylko szczegółowe informacje na temat elementu zaznaczonego w danym momencie na diagramie, ale również możemy w locie modyfikować istniejące składowe i wstawiać nowe (patrz rys. 2.11).
Rysunek 2.11. Okno Class Details
Class Designer Toolbox, które również można otworzyć z menu View, pozwala wstawiać do projektu nowe typy (i tworzyć relacje między nimi) w trybie graficznym (patrz rys. 2.12). (Pamiętaj, że przybornik ten jest widoczny tylko wtedy, gdy aktywnym oknem jest diagram klas.) Gdy używasz tego programu, IDE automatycznie, na drugim planie, tworzy nowe definicje typów C#.
Aby zobaczyć przykład, przeciągnij nową klasę z przybornika do okna Class Designer. W oknie dialogowym nadaj tej klasie nazwę Car. Skutkiem będzie utworzenie nowego pliku C# o nazwie Car.cs, który zostanie automatycznie dodany do projektu. A teraz, za pomocą okna Class Details, dodaj publiczne pole typu string o nazwie petName (patrz rys. 2.13).
Rysunek 2.12. Class Designer Toolbox
Rysunek 2.13. Dodawanie pola za pomocą okna Class Details
Jeśli teraz zajrzysz do kodu C# i definicji klasy Car, zobaczysz, że została odpowiednio zaktualizowana (nie licząc dodatkowego widocznego tutaj komentarza).
public class Car
{
// Dane publiczne to zwykle kiepski pomysł;
// tutaj jednak chodzi o to, by przykład był prosty.
public string petName;
}
Teraz ponownie aktywuj plik projektanta, przeciągnij do okna nową klasę i nadaj jej nazwę SportsCar. W oknie Class Designer Toolbox wybierz ikonę Inheritance (dziedziczenie) i kliknij w górnej części ikony SportsCar. Następnie kliknij w górnej części ikony klasy Car. Jeśli kroki te zostały wykonane prawidłowo, to właśnie z klasy Car została wyprowadzona klasa SportsCar (patrz rys. 2.14).
Rysunek 2.14. Wyprowadzanie klasy potomnej w trybie graficznym
Uwaga. Pojęcie dziedziczenia jest omówione wyczerpująco w rozdziale 6.
Aby zakończyć ten przykład, zaktualizuj wygenerowaną klasę SportsCar, dodając do niej publiczną metodę o nazwie GetPetName():
public class SportsCar : Car
{
public string GetPetName()
{
petName = "Fred";
return petName;
}
}
Oczywiście, wizualny projektant typów to tylko jedna z wielu funkcji dostępnych w środowisku Visual Studio Community. Jak wspomniałem, w tym wydaniu książki zakładam, że to właśnie ono jest wybranym przez ciebie IDE. W kolejnych rozdziałach dowiesz się o wielu kolejnych możliwościach tego narzędzia.
Kod źródłowy. Projekt VisualTypeDesignerApp znajduje się w podkatalogu Chapter 2.
Środowisko Visual Studio 2015 Professional
Aby zakończyć omawianie IDE, które działają wyłącznie w systemach operacyjnych Windows, zajmijmy się na krótko Visual Studio 2015 Professional. Jeśli pracujesz na stanowisku inżyniera ds. oprogramowania, to całkiem możliwe, że firma kupiła dla ciebie kopię tego właśnie IDE. Visual Studio 2015 Professional ma wszystkie funkcje dostępne w Visual Studio Community (te same typy projektów, te same języki i te same narzędzia do projektowania w trybie graficznym). Oprócz tego w tym IDE jest kilka funkcji nakierowanych na zespołowe programowanie na poziomie korporacji. Przykładowo, w Visual Studio Professional znajdziesz:
integrację z TFS (Team Foundation Server), aby zarządzać tablicami w metodach Agile i Kanban;
narzędzia do tworzenia i zarządzania historiami, zadaniami i epikami;
integrację z SharePoint i pokoje do rozmów między programistami;
narzędzia do zarządzania planowaniem sprintu.
Oczywiście, zagłębianie się w szczegóły cyklu życia tworzenia oprogramowania wychodzi poza zakres tej książki. Z tego powodu nie będę się rozpisywał na temat Visual Studio 2015 Professional. Jeśli chcesz używać właśnie tego IDE, to może tak oczywiście być. Pamiętaj, że nie licząc narzędzi przeznaczonych do pracy zespołowej, możliwości Community i Professional są identyczne.
Uwaga. Porównawcze zestawienie wersji Professional i Community można znaleźć na stronie https://www.visualstudio.com/products/compare-visual-studio-2015-products-vs.
System dokumentacji .NET Framework
Ostatnim aspektem Visual Studio, z którym musisz się oswoić od samego początku, jest w pełni zintegrowany system pomocy. Dokumentacja platformy .NET jest naprawdę świetna, bardzo czytelna i pełna przydatnych informacji. Z uwagi na ogromną liczbę predefiniowanych typów .NET (są ich tysiące) musisz przygotować się na długie godziny spędzone na przeczesywaniu tego zbioru informacji. Jeśli tego nie zrobisz, to twój żywot programisty .NET będzie naznaczony frustracją i ciągłym bólem głowy.
Jeśli masz połączenie z Internetem, dokumentację .NET Framework SDK możesz przeglądać w trybie online pod adresem
http://msdn.microsoft.com/library.
Po otwarciu tej strony znajdź na niej sekcję Development Tools and Languages i kliknij „.NET Framework class library”. Następnie kliknij odnośnik odpowiadający używanej wersji platformy (zakładam, że będzie to 4.6). Od tego momentu, używając nawigacji w formacie drzewa, możesz zapoznawać się z informacjami na temat każdej przestrzeni nazw, typu i składowej platformy. Na rysunku 2.15 jest pokazane dla przykładu przeglądanie typów z przestrzeni nazw System.
Rysunek 2.15. Przeglądanie dokumentacji .NET Framework w trybie online
Uwaga. Nie zdziw się, jeśli któregoś dnia Microsoft zmieni adres internetowej dokumentacji .NET Framework Class Library. Jeśli tak się stanie, do internetowej wyszukiwarki wpisz po prostu .NET Framework Class Library documentation. W ten sposób uda się zapewne szybko rozwiązać ten problem.
Oprócz dokumentacji online Visual Studio pozwala również zainstalować ten sam system pomocy lokalnie na komputerze (co może się przydać w przypadku braku aktywnego połączenia z Internetem). Jeśli chcesz wykonać lokalną instalację systemu pomocy po zainstalowaniu Visual Studio Community, wybierz kolejno Help ► Add and Remove Help Content. Następnie możesz wybrać każdy z systemów pomocy, które chcesz zainstalować lokalnie (jeśli masz miejsce na dysku, to zalecam zainstalowanie całości). Patrz rysunek 2.16.
Rysunek 2.16. Instalowanie lokalnego systemu pomocy
Po zainstalowaniu całej dokumentacji z menu Help możesz uruchomić aplikację do przeglądania pomocy (jak również ustawić, w jaki sposób Visual Studio ma wyświetlać pomoc: lokalnie czy online). Jeszcze łatwiejszy sposób używania dokumentacji polega na podświetleniu słowa kluczowego C#, wpisaniu nazwy typu lub nazwy składowej w oknie z kodem Visual Studio i naciśnięciu klawisza F1. W ten sposób automatycznie otworzy się okno z dokumentacją dotyczącą wybranego elementu. Na przykład, w definicji klasy Car wybierz słowo kluczowe string. Po naciśnięciu F1 zobaczysz stronę z pomocą dla typu string.
Kolejnym przydatnym elementem dokumentacji jest zakładka Search. Tutaj możesz wpisać nazwę dowolnej przestrzeni nazw, typu albo składowej, a następnie przejść pod odpowiedni adres. Możesz na przykład wyszukać przestrzeń nazw System.Reflection, a następnie zapoznać się ze szczegółami na jej temat, zbadać zawarte w niej typy, zobaczyć przykładowy kod i tak dalej.
Uwaga. Nazwij mnie zdartą płytą, ale powtórzę, że umiejętność korzystania z dokumentacji .NET Framework SDK jest nie do przecenienia. W żadnej książce, nieważne jakiej objętości, nie da się omówić każdego aspektu platformy .NET. Wygospodaruj koniecznie trochę czasu na oswojenie się z systemem pomocy – później będziesz sobie za to dziękować.
Tworzenie aplikacji .NET w innych systemach operacyjnych
Rodzina Microsoft Visual Studio to produkty dość zaawansowane i o ogromnych możliwościach. I jeśli twoim głównym (albo jedynym) zamiarem jest pisanie programów .NET na komputerze z Windows po to, aby uruchamiać je na komputerach z Windows, to Visual Studio może być jedynym IDE, jakiego potrzebujesz. Jednak jak wspomniałem w rozdziale 1, platforma .NET może działać na wielu systemach operacyjnych. Z tej przyczyny chcę wspomnieć o najważniejszym wieloplatformowym środowisku IDE.
Xamarin Studio
W rozdziale 1 wspomniałem o platformie Mono. Przypomnę, że ta wieloplatformowa implementacja technologii .NET jest wyposażona w wiele programistycznych narzędzi działających w wierszu poleceń. Ale choć za pomocą prostego edytora tekstu i konsolowego kompilatora C# można napisać pełnowartościowy program .NET, to zajęcie takie szybko staje się męczące.
Xamarin Studio to bezpłatne środowisko IDE dla platformy .NET działające w systemach operacyjnych Windows, Mac OS X i Linux. Jest podobne do Visual Studio Community w tym sensie, że obsługuje różne języki programowania (w tym C#), jak również pozwala używać bogatych w funkcje edytorów kodu oraz debugerów i narzędzi do projektowania GUI w trybie graficznym. Jeśli chcesz tworzyć oprogramowanie .NET w systemie operacyjnym innym niż te od Microsoftu, to z pewnością wybierzesz właśnie to środowisko IDE. Możesz je pobrać (i poczytać sobie o nim) pod adresem:
http://xamarin.com/.
Uwaga. Nie ma żadnych przeciwskazań, aby zainstalować Xamarin Studio na komputerze, na którym zainstalowano wcześniej Visual Studio Community. Jednak aby instalacja odbyła się płynnie, należy wcześniej zamknąć Visual Studio.
Po zainstalowaniu tego IDE nowy projekt utworzysz, wybierając z menu File kolejno New i Solution. Możesz zdecydować się na jeden z wielu szablonów oraz wybrać język programowania. Na rysunku 2.17 jest pokazana znana nam już aplikacja konsolowa C#.
Rysunek 2.17. Tworzenie nowego rozwiązania w Xamarin Studio
Jeśli znasz podstawy używania Visual Studio, to po utworzeniu nowego projektu nie będziesz mieć większych problemów z używaniem tego nowego IDE. Na rysunku 2.18 widzisz znane ci już IntelliSense i podobny schemat nawigacji w obrębie projektu.
Rysunek 2.18. Edytor kodu w Xamarin Studio
Ponieważ Xamarin Studio można uruchamiać w systemach operacyjnych innych niż te od Microsoftu, nie powinno nikogo dziwić, że w systemie Mac OS X lub Linux będzie ono używać środowiska uruchomieniowego i zbioru narzędzi Mono. Ale Xamarin Studio z takim samym powodzeniem można używać w systemie Windows. Wówczas kod C# można skompilować albo na platformę .NET Microsoftu, albo na platformę Mono (zakładając, że Mono jest zainstalowane). Aby wybrać docelową platformę, z menu Tools wybierz kolejno Tools i Options, a następnie kategorię .NET Runtimes (patrz rys. 2.19).
Rysunek 2.19. Wybór środowiska uruchomieniowego .NET w Xamarin Studio
Pomimo tego, że w książce tej nie będę używał Xamarin Studio, mam dla ciebie dobrą wiadomość: większość przykładowych projektów będzie doskonale działać w systemach niepochodzących z Microsoftu. Dlatego też, jeśli chcesz przerobić ten materiał na komputerze z systemem Mac OS X lub Linux, możesz to zrobić bez większych problemów. Pamiętaj jednak, że platforma Mono nie obsługuje API WPF (Windows Presentation Foundation), więc w innych systemach operacyjnych nie będziesz mieć możliwości pisania aplikacji WPF. Mono obsługuje jednak większość API używanych w tej książce (nie będziesz mieć problemów, przerabiając rozdziały 3–24 i większość materiału na temat aplikacji internetowych).
Uwaga. Pełne informacje na temat tego, które aspekty Microsoft .NET są obsługiwane przez Mono, można znaleźć w dokumentacji Mono na stronie www.mono-project.com/docs/.
Podsumowanie
Jak widzisz, masz do swojej dyspozycji wiele nowych zabawek! Celem tego rozdziału było przybliżenie najważniejszych narzędzi, których w trakcie tworzenia aplikacji może używać programista C#. Jak wspomniałem, jeśli interesuje cię jedynie pisanie programów .NET na komputerze z systemem Windows, najlepszym wyborem jest pobranie wersji Visual Studio Community. Poza tym, na tym właśnie środowisku IDE będą opierały się przykłady w tym wydaniu książki. Dlatego też, omawiając wszystkie kolejne zrzuty ekranu, opcje menu i narzędzia do projektowania w trybie graficznym, zakładam, że używasz Visual Studio Community.
Jeśli chcesz tworzyć aplikacje .NET, używając platformy Mono, albo pisać oprogramowanie w systemie operacyjnym innym niż Windows, najlepszym wyborem będzie Xamarin Studio. To IDE nie jest identyczne jak Visual Studio Community, ale z używaniem go w trakcie lektury nie powinno być większych problemów. A w rozdziale 3 rozpoczyna się omówienie języka programowania C#.
Część II
Podstawy programowania w języku C#
Rozdział 3
Podstawowe konstrukcje programistyczne języka C#, część 1
W tym rozdziale rozpoczyna się formalne poznawanie języka programowania C#. Jest tu przedstawiona pewna liczba krótkich, niezależnych zagadnień, z którymi musisz się oswoić, aby z powodzeniem używać technologii .NET. W pierwszej kolejności dowiesz się, jak utworzyć w swoich programach obiekt aplikacji oraz jak jest skonstruowany punkt wejścia takiego programu, czyli metoda Main(). Następnie poznasz podstawowe typy danych języka C# (oraz odpowiadające im typy z przestrzeni nazw System), a w szczególności klasy System.String i System.Text.StringBuilder.
Po bliższym zaznajomieniu się z podstawowymi typami danych .NET poznasz kilka technik przekształcania (konwersji) typów danych, w tym operacje zawężania i poszerzania, a także dowiesz się, jak używać słów kluczowych checked i unchecked.
W rozdziale tym opisuję również rolę słowa kluczowego var, które pozwala jawnie zdefiniować zmienną lokalną. Jak się przekonasz w dalszej części książki, jawna typizacja jest bardzo przydatna, a nawet obowiązkowa, gdy pracujemy ze zbiorem technologii LINQ. Rozdział zamyka krótki przegląd słów kluczowych i operatorów języka C#, które umożliwiają sterowanie przepływem aplikacji za pomocą różnych konstrukcji iteracyjnych i decyzyjnych.
Anatomia prostego programu C#
Język C# wymaga, aby całe działanie programu było zawarte w definicjach typów (jak wspomniałem w rozdz. 1, typ to określenie ogólne, odnoszące się do jednego z elementów zbioru {klasa, interfejs, struktura, wyliczenie, delegat}). W odróżnieniu od wielu innych języków w C# nie można tworzyć ani globalnych funkcji, ani globalnych punktów danych. Wszystkie składowe danych i wszystkie metody muszą być zawarte w definicji typu. Aby rozpocząć, utwórz nowy projekt Console Application i nazwij go SimpleCSharpApp. Zgodzisz się chyba, że w kodzie początkowego pliku Program.cs nie dzieje się nic ciekawego.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SimpleCSharpApp
{
class Program
{
static void Main(string[] args)
{
}
}
}
A teraz w klasie Program zmodyfikuj metodę Main(), wprowadzając do niej poniższe instrukcje:
class Program
{
static void Main(string[] args)
{
// Wyświetl użytkownikowi prosty komunikat.
Console.WriteLine("***** My First C# App *****");
Console.WriteLine("Hello World!");
Console.WriteLine();
// Przed zamknięciem zaczekaj na naciśnięcie klawisza ENTER.
Console.ReadLine();
}
}
Uwaga. W języku C# wielkość liter ma znaczenie. Dlatego też Main i main to coś innego, podobnie jak Readline i readLine. Zapamiętaj, że wszystkie słowa kluczowe języka C# są pisane małymi literami (np. public, lock, class, dynamic), natomiast nazwy przestrzeni nazw, typów i składowych (zgodnie z konwencją) zaczynają się dużą literą, podobnie jak wszystkie słowa zagnieżdżone (np. Console.WriteLine, System.Windows.Forms.MessageBox, System.Data.SqlClient). W praktyce, jeśli kompilator zgłosi błąd mówiący o „niezdefiniowanych symbolach” („undefined symbols”), w pierwszej kolejności należy sprawdzić pisownię i wielkość liter!
Powyższy kod zawiera definicję klasy, która obsługuje jedną metodę o nazwie Main(). W Visual Studio domyślną nazwą klasy, w której zdefiniowano metodę Main(), jest Program, jednak zawsze możesz ją zmienić. Każda aplikacja C# (program konsolowy, okienkowy program Windows, czy też usługa Windows) musi zawierać klasę ze zdefiniowaną metodą Main(), która odgrywa rolę punktu wejścia programu.
Formalnie klasa, w której zdefiniowano metodę Main(), nazywana jest obiektem aplikacji (ang. application object). Jedna wykonywalna aplikacja może mieć więcej niż jeden taki obiekt (co może się przydać przy wykonywaniu testów jednostkowych), ale w takiej sytuacji trzeba poinformować kompilator, którą z metod Main() należy potraktować jako punkt wejścia. Można to zrobić za pomocą opcji /main w wierszu poleceń kompilatora albo korzystając z rozwijanej listy Startup Object znajdującej się na karcie Application w edytorze właściwości projektu Visual Studio (patrz rozdz. 2).
Zwróć uwagę, że w sygnaturze metody Main() znajduje się słowo kluczowe static, które zostanie szczegółowo omówione w rozdziale 5. W tej chwili wystarczy wiedzieć, że statyczne składowe mają zasięg ograniczony do klasy (a nie obiektu), a tym samym można je wywoływać bez konieczności wcześniejszego tworzenia nowej instancji klasy.
Oprócz słowa kluczowego static, ta metoda Main() ma jeden parametr, którym tutaj akurat jest tablica łańcuchów (string[] args). Pomimo tego, że w tej chwili nie musisz się zajmować przetwarzaniem tej tablicy, wspomnę tylko, że parametr ten może zawierać dowolną liczbę argumentów wejściowych (za moment dowiesz się, jak ich używać). Poza tym, w tej metodzie Main()została skonfigurowana wartość zwracana void, a to oznacza, że przed wyjściem z zasięgu metody nie definiujesz jawnie wartości zwracanej za pomocą słowa kluczowego return.
Treść klasy Program znajduje się w obrębie metody Main(). W tym przypadku używasz klasy Console, która została zdefiniowana w przestrzeni nazw System. Do jej składowych należy statyczna metoda WriteLine(), która, jak się łatwo domyślić, wysyła do standardowego wyjścia łańcuch znaków i znak powrotu karetki (nowego wiersza). Możesz również wywołać metodę Console.ReadLine(), dzięki czemu zgłoszenie wiersza poleceń uruchomione przez Visual Studio pozostanie widoczne w trakcie sesji debugowania aż do naciśnięcia klawisza Enter. (Jeśli nie dodasz tego wiersza, aplikacja zakończy się natychmiastowo po sesji debugowania i nie zdążysz przeczytać wyników!) Więcej na temat klasy System.Console dowiesz się już wkrótce.
Warianty metody Main( )
Wartością zwracaną przez wygenerowaną domyślnie przez Visual Studio metodę Main() jest void, a jej jedynym parametrem wejściowym jest tablica typów string. Nie jest to jednak jedyna dopuszczalna postać metody Main(). Dozwolone jest konstruowanie punktu wejścia aplikacji zgodnie z jedną z poniższych sygnatur (zakładając, że znajduje się on w definicji klasy bądź struktury C#):
// typ zwracany - int, parametrem tablica łańcuchów.
static int Main(string[] args)
{
// Musi zwrócić wartość przed wyjściem!
return 0;
}
// Brak typu zwracanego, brak parametrów.
static void Main()
{
}
// typ zwracany – int, brak parametrów.
static int Main()
{
// Musi zwrócić wartość przed wyjściem!
return 0;
}
Uwaga. Metodę Main() można również zdefiniować nie jako prywatną, ale jako publiczną. Jeśli nie podasz konkretnego modyfikatora dostępu, Visual Studio automatycznie zdefiniuje metodę Main() programu jako niejawnie prywatną.
Oczywiście wybór rodzaju metody Main() będzie wynikał z odpowiedzi na dwa pytania. Po pierwsze: czy po wykonaniu metody Main() i zakończeniu programu chcesz zwrócić do systemu jakąś wartość? Jeśli tak, to zamiast zwracać void musisz zwracać int. Po drugie: czy mają być przetwarzane jakieś parametry podane przez użytkownika w wierszu poleceń? Jeśli tak, to zostaną one zapisane w tablicy typów string. Omówmy powyższe opcje bardziej szczegółowo.
Określanie kodu błędu aplikacji
Pomimo tego że wartością zwracaną w zdecydowanej większości metod Main() będzie void, dzięki możliwości zwracania typu int język C# zachowuje spójność z innymi językami opartymi na C. Zgodnie z konwencją zwrócenie wartości 0 oznacza, że program zakończył się pomyślnie, natomiast inna wartość (np. -1) mówi o wystąpieniu błędu (należy wiedzieć, że wartość 0 jest zwracana automatycznie, nawet jeśli konstruujemy metodę Main() zwracającą void).
W systemie operacyjnym Windows wartość zwracana przez aplikację jest zapisywana w systemowej zmiennej środowiskowej o nazwie %ERRORLEVEL%. Jeśli chcesz utworzyć aplikację, której kod uruchamia inną aplikację (temat ten jest omówiony w rozdz. 18), wartość zmiennej %ERRORLEVEL% możesz uzyskać za pomocą statycznej właściwości System.Diagnostics.Process.ExitCode.
Ponieważ wartość zwracana przez aplikację jest przekazywana do systemu w momencie, gdy aplikacja kończy działanie, aplikacja w trakcie działania oczywiście nie potrafi uzyskać i wyświetlić swojego ostatecznego kodu błędu. Jednak aby zobaczyć, jak poznać ten kod błędu po zakończeniu programu, zacznij od zmodyfikowania metody Main():
// Tym razem zamiast void zwracamy int.
static int Main(string[] args)
{
// Wyświetl komunikat i zaczekaj na naciśnięcie klawisza ENTER.
Console.WriteLine("***** My First C# App *****");
Console.WriteLine("Hello World!");
Console.WriteLine();
Console.ReadLine();
// Zwróć arbitralny kod błędu.
return -1;
}
Przechwyćmy teraz wartość zwracaną metody Main() za pomocą pliku wsadowego. Otwórz Eksploratora Windows i przejdź do folderu zawierającego skompilowaną aplikację (np. C:\SimpleCSharpApp\bin\Debug). W folderze Debug utwórz nowy plik tekstowy o nazwie SimpleCSharpApp.bat i zapisz w nim poniższe instrukcje (jeśli pierwszy raz w życiu tworzysz plik *.bat, nie rozpraszaj się szczegółami. To tylko test...):
@echo off
rem Plik wsadowy do programu SimpleCSharpApp.exe
rem przechwytujący zwracaną przez niego wartość.
SimpleCSharpApp
@if "%ERRORLEVEL%" == "0" goto success
:fail
echo This application has failed!
echo return value = %ERRORLEVEL%
goto end
:success
echo This application has succeeded!
echo return value = %ERRORLEVEL%
goto end
:end
echo All Done.
Teraz otwórz wiersz poleceń Visual Studio i przejdź do folderu zawierającego plik wykonywalny oraz nowy plik *.bat. Wykonaj plik wsadowy wpisując jego nazwę i naciskając ENTER. Ponieważ w tym przypadku metoda Main() zwraca wartość -1, na ekranie powinno pojawić się to co poniżej. Gdyby metoda Main() zwracała 0, na konsoli wyświetlony zostałby komunikat „This application has succeeded!”.
***** My First C# App *****
Hello World!
This application has failed!
return value = -1
All Done.
Powtórzę, że w zdecydowanej większości aplikacji C# wartością zwracaną z metody Main() będzie void, która, przypomnę, oznacza niejawne zwrócenie kod błędu 0. Dlatego też metody Main() w tej książce (oprócz bieżącego przykładu) będą faktycznie zwracały void (a w pozostałych projektach z całą pewnością nie będzie trzeba używać plików wsadowych do przechwytywania zwracanych wartości).
Przetwarzanie argumentów z wiersza poleceń
Po tym omówieniu wartości zwracanych metody Main() przyjrzyjmy się wejściowej tablicy danych typu string. Załóżmy, że chcesz zmodyfikować aplikację w ten sposób, aby przetwarzała wszystkie możliwe argumenty podane w wierszu poleceń. Jednym ze sposobów na zrobienie tego w języku C# jest pętla for. (Konstrukcje iteracyjne C# są omówione bardziej szczegółowo pod koniec tego rozdziału.)
static int Main(string[] args)
{
...
// Przetwórz wszystkie argumenty wejściowe.
for(int i = 0; i < args.Length; i++)
Console.WriteLine("Arg: {0}", args[i]);
Console.ReadLine();
return -1;
}
W tym przypadku za pomocą właściwości Length typu System.Array sprawdzasz, czy tablica typów string zawiera jakąś liczbę elementów. Jak przekonasz się w rozdziale 4, wszystkie tablice C# są tak naprawdę aliasem typu System.Array, a tym samym korzystają ze wspólnego zbioru składowych. W trakcie wykonywania pętli przez wszystkie elementy tablicy ich wartości są wyświetlane w oknie konsoli. Podawanie argumentów w wierszu poleceń jest równie proste:
C:\SimpleCSharpApp\bin\Debug>SimpleCSharpApp.exe /arg1 -arg2
***** My First C# App *****
Hello World!
Arg: /arg1
Arg: -arg2
Alternatywą dla standardowej pętli for jest iteracja po wejściowej tablicy typów string za pomocą słowa kluczowego foreach. Poniżej przykład (i powtórzę, że konkrety na temat konstrukcji iteracyjnych są w dalszej części rozdziału):
// Używając foreach, nie musisz sprawdzać rozmiaru tablicy.
static int Main(string[] args)
{
...
// Przetwórz wszystkie argumenty wejściowe używając foreach.
foreach(string arg in args)
Console.WriteLine("Arg: {0}", arg);
Console.ReadLine();
return -1;
}
Poza tym dostęp do argumentów wiersza poleceń możesz uzyskać również za pomocą statycznej metody GetCommandLineArgs() z typu System.Environment. Wartością przez nią zwracaną jest tablica typów string. Pod pierwszym indeksem jest nazwa samej aplikacji, natomiast pozostałe elementy tablicy zawierają poszczególne argumenty wiersza poleceń. Zwróć uwagę, że stosując ten sposób, nie trzeba już w metodzie Main() deklarować tablicy typów string jako parametru wejściowego (ale też i nie szkodzi tak zrobić).
static int Main(string[] args)
{
...
// Wczytaj argumenty za pomocą System.Environment.
string[] theArgs = Environment.GetCommandLineArgs();
foreach(string arg in theArgs)
Console.WriteLine("Arg: {0}", arg);
Console.ReadLine();
return -1;
}
Oczywiście to ty decydujesz, na jakie argumenty wiersza poleceń program będzie odpowiadał (i czy w ogóle będzie taka opcja) i jaki ma być ich format (np. z prefiksem – lub /). W tym przypadku przesłałem po prostu ciąg opcji, które zostały wyświetlone bezpośrednio w wierszu poleceń. Załóżmy jednak, że tworzysz nową grę wideo i chcesz, aby w aplikacji tej dostępna była opcja –godmode. Jeśli użytkownik uruchomi aplikację z tą flagą, to będziesz wiedzieć, że to w rzeczywistości oszust, a wtedy będzie można podjąć stosowne kroki.
Podawanie argumentów wiersza poleceń w Visual Studio
W programach z prawdziwego zdarzenia użytkownik końcowy może podawać argumenty wiersza poleceń w momencie uruchamiania programu. W trakcie tworzenia aplikacji możesz natomiast używać flag w wierszu poleceń do przeprowadzania testów. Aby to zrobić w Visual Studio, dwukrotnie kliknij ikonę Properties w Solution Explorerze i po lewej stronie wybierz kartę Debug. Następnie w polu tekstowym Command line arguments podaj wartości (patrz rys. 3.1) i zapisz zmiany.
Rysunek 3.1. Ustawianie argumentów polecenia w Visual Studio
Po ustaleniu argumentów wiersza poleceń w taki sposób będą one automatycznie przekazane do metody Main() w czasie debugowania albo uruchamiania aplikacji w Visual Studio.
Ciekawostka: kilka kolejnych składowych klasy System.Environment
Klasa Environment oprócz GetCommandLineArgs() udostępnia kilka bardzo pomocnych metod. W szczególności klasa ta za pomocą kilku statycznych składowych pozwala uzyskać kilka informacji o systemie operacyjnym, w którym w danej chwili działa aplikacja .NET. Aby przekonać się o przydatności tej klasy, zmodyfikuj metodę Main(), aby wywoływała metodę pomocniczą o nazwie ShowEnvironmentDetails().
static int Main(string[] args)
{
...
// Metoda pomocnicza w klasie Program.
ShowEnvironmentDetails();
Console.ReadLine();
return -1;
}
Zaimplementuj tę metodę w klasie Program, aby wywoływała różne składowe typu Environment:
static void ShowEnvironmentDetails()
{
// Wyświetl informacje o dyskach w tym komputerze
// i inne ciekawe szczegóły.
foreach (string drive in Environment.GetLogicalDrives())
Console.WriteLine("Drive: {0}", drive);
Console.WriteLine("OS: {0}", Environment.OSVersion);
Console.WriteLine("Number of processors: {0}",
Environment.ProcessorCount);
Console.WriteLine(".NET Version: {0}",
Environment.Version);
}
Poniżej wynik testowego wywołania tej metody. Oczywiście, jeżeli nie podasz argumentów wiersza poleceń na karcie Debug w Visual Studio, to w konsoli ich nie zobaczysz.
***** My First C# App *****
Hello World!
Arg: -godmode
Arg: -arg1
Arg: /arg2
Drive: C:\
Drive: D:\
OS: Microsoft Windows NT 6.2.9200.0
Number of processors: 8
.NET Version: 4.0.30319.42000
W typie Environment oprócz składowych z powyższego przykładu zdefiniowano jeszcze inne. W tabeli 3.1 wymieniono kilka interesujących właściwości, pamiętaj jednak, że wyczerpujące informacje znajdziesz w dokumentacji .NET Framework 4.6 SDK.
Tabela 3.1. Wybrane właściwości klasy System.Environment
Właściwość
Znaczenie w praktyce
ExitCode
Pobiera albo ustawia kod wyjścia aplikacji.
Is64BitOperatingSystem
Zwraca wartość logiczną informującą, czy na tym komputerze jest 64-bitowy system operacyjny.
MachineName
Odczytuje nazwę bieżącego komputera.
NewLine
Odczytuje symbol nowego wiersza w bieżącym środowisku.
SystemDirectory
Zwraca pełną ścieżkę do katalogu systemowego.
UserName
Zwraca nazwę użytkownika, który uruchomił tę aplikację.
Version
Zwraca obiekt Version, reprezentujący wersję platformy .NET.
Kod źródłowy. Projekt SimpleCSharpApp znajduje się w podkatalogu Chapter 3.
Klasa System.Console
W prawie wszystkich przykładowych aplikacjach tworzonych w początkowych rozdziałach tej książki bardzo często używamy klasy System.Console. I chociaż prawdą jest, że interfejs użytkownika konsoli (CUI) nie jest tak atrakcyjny jak graficzny interfejs użytkownika (GUI) ani jak aplikacja internetowa, dzięki ograniczeniu się w początkowych przykładach do programów konsolowych łatwiej będzie ci skoncentrować się na składni języka C# i podstawowych aspektach platformy .NET. Bardziej złożonymi zagadnieniami tworzenia interfejsów graficznych czy też witryn internetowych zajmiemy się później.
Zgodnie ze swoją nazwą klasa Console odpowiada za operacje wejścia, wyjścia i strumienia błędów w aplikacjach konsolowych. W tabeli 3.2 są przedstawione wybrane (w żadnym wypadku nie wszystkie) ważne składowe. Jak widzisz, w klasie Console znajduje się kilka składowych, za pomocą których można uatrakcyjnić prostą aplikację konsolową, na przykład zmieniając kolory pierwszego i drugiego planu lub emitując sygnały dźwiękowe (o różnych częstotliwościach!).
Tabela 3.2. Wybrane składowe klasy System.Console
Składowa
Znaczenie w praktyce
Beep()
Ta metoda nakazuje wyemitować sygnał dźwiękowy o określonej częstotliwości i czasie trwania.
BackgroundColor
ForegroundColor
Te właściwości służą do ustawiania pierwszo- i drugoplanowego koloru bieżących wyników. Można do nich przypisać dowolne składowe z wyliczenia ConsoleColor.
BufferHeight
BufferWidth
Te właściwości służą do ustawiania wysokości i szerokości obszaru buforowania konsoli.
Title
Ta właściwość pozwala pobrać albo ustawić tytuł bieżącej konsoli.
WindowHeight
WindowWidth
WindowTop
WindowLeft
Te właściwości odpowiadają za wymiary konsoli względem ustalonego bufora.
Clear()
Ta metoda czyści ustalony bufor i obszar wyświetlania konsoli.
Podstawowe operacje we/wy z klasą Console
Oprócz składowych z tabeli 3.2 w typie Console zdefiniowano zbiór metod do przechwytywania wejścia i wyjścia. Wszystkie te metody są statyczne, a tym samym wywołuje się je, poprzedzając nazwę metody nazwą klasy (Console). Jak już wiesz, metoda WriteLine() umieszcza w strumieniu wyjściowym łańcuch tekstowy wraz ze znakiem powrotu karetki, a metoda Write() – tekst bez tego znaku. Metoda ReadLine() pozwala odbierać informacje ze strumienia wejściowego aż do naciśnięcia klawisza Enter, natomiast metoda Read() służy do przechwytywania pojedynczego znaku ze strumienia wejściowego.
Aby zobaczyć przykład podstawowych operacji we/wy z użyciem klasy Console, utwórz nowy projekt Console Application, nadaj mu nazwę BasicConsoleIO i zmodyfikuj metodę Main(), by wywoływała metodę pomocniczą o nazwie GetUserData():
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Basic Console I/O *****");
GetUserData();
Console.ReadLine();
}
private static void GetUserData()
{
}
}
Uwaga. W Visual Studio dostępnych jest wiele tzw. snippetów („wycinków”), które służą do automatycznego uzupełniania kodu. W trakcie przerabiania pierwszych rozdziałów tego tekstu szczególnie przydatny jest snippet o nazwie cw, który wstawia do kodu Console.WriteLine()! Aby się o tym przekonać, wpisz cw w obrębie metody Main() i dwukrotnie naciśnij klawisz Tab (niestety, nie ma takiego ułatwienia dla metody Console.ReadLine()). Wszystkie dostępne snippety zobaczysz, klikając prawym przyciskiem myszy w obrębie pliku z kodem C# i z menu wybierając opcję Insert Snippet.
Zaimplementuj tę metodę w klasie Program wraz z kodem pytającym użytkownika o kilka informacji i wyświetlającym każdy wprowadzony element w standardowym strumieniu wyjściowym. Możesz na przykład zapytać użytkownika o imię i wiek (które dla uproszczenia potraktujemy jak wartość tekstową, a nie zwyczajowo jak wartość liczbową):
static void GetUserData()
{
// Pobierz imię i wiek.
Console.Write("Please enter your name: ");
string userName = Console.ReadLine();
Console.Write("Please enter your age: ");
string userAge = Console.ReadLine();
// Tak dla zabawy zmień kolor tekstu.
ConsoleColor prevColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
// Wyświetl tekst w konsoli.
Console.WriteLine("Hello {0}! You are {1} years old.",
userName, userAge);
// Przywróć poprzedni kolor.
Console.ForegroundColor = prevColor;
}
I nie ma nic dziwnego w tym, że po uruchomieniu tej aplikacji, dane wejściowe są wyświetlane na konsoli (i to na dodatek w niestandardowym kolorze!).
Formatowanie wyników na konsoli
W pierwszych przykładach często pojawiają się symbole takie jak {0} i {1} osadzone w różnych literałach znakowych. Formatowanie łańcuchów na platformie .NET przypomina trochę znaną z języka C instrukcję printf(). Mówiąc najprościej, gdy definiujesz literał łańcuchowy zawierający segmenty danych, których wartość nie jest znana do czasu wykonania programu, używając tej składni z klamrami możesz podać symbol zastępczy (wypełniacz). W czasie działania programu, w miejsca te zostaną podstawione wartości przesłane do metody Console.WriteLine().
Pierwszy parametr metody WriteLine() reprezentuje literał łańcuchowy, który zawiera opcjonalne wypełniacze oznakowane symbolami {0}, {1}, {2} itd. Należy koniecznie zapamiętać, że pierwsza liczba porządkowa w tych klamrach to zawsze 0. Pozostałe parametry metody WriteLine() to po prostu wartości, które zostaną wstawione w odpowiednie miejsca.
Uwaga. Jeżeli unikatowo ponumerowanych klamrowych wypełniaczy będzie więcej niż podanych argumentów, to w czasie działania programu zostanie zgłoszony wyjątek formatowania. Jeśli natomiast więcej będzie argumentów, to te nadmiarowe zostaną zignorowane.
Dozwolone jest powtarzanie się danego wypełniacza w danym łańcuchu. Jeśli na przykład jesteś fanem The Beatles i chcesz utworzyć łańcuch "9, number 9, number 9", to możesz napisać:
// John mówi...
Console.WriteLine("{0}, Number {0}, Number {0}", 9);
Poza tym warto wiedzieć, że każdy wypełniacz można umieścić w dowolnym miejscu literału łańcuchowego, a kolejność nie musi być rosnąca. Spójrz na poniższy fragment kodu:
// Wyświetla: 20, 10, 30
Console.WriteLine("{1}, {0}, {2}", 10, 20, 30);
Formatowanie danych liczbowych
Możliwe jest też bardziej wyszukane formatowanie danych liczbowych, gdyż każdy wypełniacz może opcjonalnie zawierać różne znaki formatujące. W tabeli 3.3 wymieniono najczęściej używane opcje formatowania.
Tabela 3.3. Znaki do formatowania danych liczbowych .NET
Znak formatowania łańcucha
Znaczenie w praktyce
C lub c
Do formatowania waluty. Domyślnie, flaga poprzedza lokalny symbol waluty (znak dolara [$] w amerykańskim angielskim).
D lub d
Do formatowania liczb dziesiętnych. Flaga ta może również określać minimalną liczbę cyfr używanych do wypełnienia wartości.
E lub e
Do zapisu wykładniczego. Stałą wykładniczą można wyświetlić dużą (E) albo małą (e) litery.
F lub f
Do formatowania stałoprzecinkowego. Flaga ta pozwala określić również minimalną liczbę cyfr służących do wypełnienia wartości.
G lub g
Od ang. wyrazu general (ogólny). Znaku tego możemy użyć do sformatowania liczby do formatu stałego albo wykładniczego.
N lub n
Do podstawowego formatowania liczbowego (z przecinkami).
X lub x
Do formatowania szesnastkowego. Jeśli użyjesz dużej litery X, format szesnastkowy również będzie zawierał duże litery.
Te znaki formatujące umieszcza się jako sufiks danego wypełniacza za pomocą dwukropka (np. {0:C}, {1:d}, {2:X}). Dla przykładu zmodyfikuj metodę Main(), aby wywoływała nową funkcję pomocniczą o nazwie FormatNumericalData(). Zaimplementuj ją w klasie Program, aby na kilka sposobów sformatować stałą wartość liczbową.
// Użyj kilku znaczników formatowania.
static void FormatNumericalData()
{
Console.WriteLine("The value 99999 in various formats:");
Console.WriteLine("c format: {0:c}", 99999);
Console.WriteLine("d9 format: {0:d9}", 99999);
Console.WriteLine("f3 format: {0:f3}", 99999);
Console.WriteLine("n format: {0:n}", 99999);
// W przypadku wartości szesnastkowych można
// określić wielkość liter.
Console.WriteLine("E format: {0:E}", 99999);
Console.WriteLine("e format: {0:e}", 99999);
Console.WriteLine("X format: {0:X}", 99999);
Console.WriteLine("x format: {0:x}", 99999);
}
Poniżej widać wyniki wywołania metody FormatNumericalData():
The value 99999 in various formats:
c format: $99,999.00
d9 format: 000099999
f3 format: 99999.000
n format: 99,999.00
E format: 9.999900E+004
e format: 9.999900e+004
X format: 1869F
x format: 1869f
Dodatkowe przykłady z formatowaniem zobaczysz w odpowiednich miejscach w dalszej części tekstu. Jeśli jednak chcesz dowiedzieć się więcej na temat formatowania łańcuchów na platformie .NET, w dokumentacji .NET Framework 4.6 SDK poszukaj tematu „Formatting Types”.
Kod źródłowy. Projekt BasicConsoleIO znajduje się w podkatalogu Chapter 3.
Formatowanie danych liczbowych poza aplikacjami konsolowymi
Na zakończenie tego punktu warto wspomnieć, że stosowanie przedstawionych znaków formatowania łańcuchów .NET nie ogranicza się do programów konsolowych. Tej samej składni formatowania możesz użyć, wywołując statyczną metodę string.Format(). Może się ona przydać, kiedy zechcesz skonfigurować dane tekstowe w czasie działania dowolnego programu (np. aplikacji z interfejsem użytkownika, internetowej aplikacji ASP.NET, sieciowej usługi XML).
Metoda string.Format() zwraca nowy obiekt string sformatowany zgodnie z podanymi flagami. Od tego momentu możesz używać danych tekstowych tak jak to uznasz za stosowne. Na przykład załóżmy, że tworzysz graficzną okienkową aplikację WPF i musisz sformatować łańcuch, który zostanie wyświetlony w oknie z komunikatem. Poniższy kod pokazuje, jak to można zrobić, ale pamiętaj, aby przed kompilacją umieścić w projekcie odwołania do pakietu PresentationFramework.dll (informacje na temat odwoływania się do bibliotek zewnętrznych w Visual Studio znajdują się w rozdz. 2).
static void DisplayMessage()
{
// Używanie string.Format() do formatowania literału łańcuchowego.
string userMessage = string.Format("100000 in hex is {0:x}", 100000);
// Aby skompilować ten wiersz kodu, należy
// odwołać się do biblioteki PresentationFramework.dll!
System.Windows.MessageBox.Show(userMessage);
}
Uwaga. W wydaniu .NET 4.6 wprowadzono alternatywę dla klamrowych wypełniaczy. Składnia ta to interpolacja łańcuchów, a więcej na jej temat można znaleźć w dalszej części rozdziału.
Systemowe typy danych i odpowiadające im słowa kluczowe C#
Tak jak w każdym języku programowania, tak i w C# zdefiniowano wewnętrzny zbiór podstawowych typów danych, które służą do przedstawiania zmiennych lokalnych, zmiennych składowych, wartości zwracanych i parametrów. Jednak w przeciwieństwie do innych języków programowania te słowa kluczowe to coś więcej niż proste leksemy rozpoznawane przez kompilator. Słowa kluczowe dla typów danych C# są raczej skrótowym zapisem pełnowartościowych typów z przestrzeni nazw System. W tabeli 3.4 są podane wszystkie typy systemowe, ich zakresy, odpowiadające im słowa kluczowe w języku C# oraz zgodność typów ze specyfikacją CLS.
Tabela 3.4. Wbudowane typy danych C#
Skrót w C#
Zgodny z CLS?
Podklasa System
Zakres
Znaczenie w praktyce
bool
tak
System.Boolean
true lub false
Reprezentuje prawdę lub fałsz
sbyte
nie
System.SByte
od –128 do 127
8-bitowa liczba ze znakiem
byte
tak
System.Byte
od 0 do 255
8-bitowa liczba bez znaku
short
tak
System.Int16
od –32 768 do 32 767
16-bitowa liczba ze znakiem
ushort
nie
System.UInt16
od 0 do 65 535
16-bitowa liczba bez znaku
int
tak
System.Int32
od –2 147 483 648 do 2 147 483 647
32-bitowa liczba ze znakiem
uint
nie
System.UInt32
od 0 do 4 294 967 295
32-bitowa liczba bez znaku
long
tak
System.Int64
od –9 223 372 036 854 775 808 do 9 223 372 036 854 775 807
64-bitowa liczba ze znakiem
ulong
nie
System.UInt64
od 0 do 18 446 744 073 709 551 615
64-bitowa liczba bez znaku
char
tak
System.Char
U+0000 do U+ffff
Jeden 16-bitowy znak Unicode
float
tak
System.Single
od –3,4·1038 do +3,4·1038
32-bitowa liczba zmiennoprzecinkowa
double
tak
System.Double
od ±5,0·10–324 do ±1,7·10308
64-bitowa liczba zmiennoprzecinkowa
decimal
tak
System.Decimal
(od –7,9·1028 do 7,9·1028)/(100 do 28)
128-bitowa liczba ze znakiem
string
tak
System.String
Ograniczony wielkością pamięci systemowej
Reprezentuje zbiór znaków Unicode
Object
tak
System.Object
Może przechowywać dowolny typ danych w zmiennej obiektowej
Klasa bazowa wszystkich typów w świecie .NET
Uwaga. Przypomnij sobie z rozdziału 1, że kod .NET zgodny ze specyfikacją CLS może być wykorzystywany przez wszystkie zarządzane języki programowania. Jeśli w swoich programach udostępniasz dane niezgodne z CLS, to inne języki .NET mogą nie potrafić ich używać.
Domyślnie liczba zmiennoprzecinkowa jest traktowana jako typ double. Aby zadeklarować zmienną typu float, należy użyć sufiksu f lub F przy zwykłej wartości liczbowej (np. 5.3F), a żeby zadeklarować wartość dziesiętną, liczbę zmiennoprzecinkową zapisujemy z sufiksem m lub M (np. 300.5M). Poza tym, niedookreślone liczby całkowite domyślnie są typu int. Aby ustawić bazowy typ danych na long, używa się przyrostka l lub L (4L).
Deklarowanie i inicjalizowanie zmiennych
Gdy deklarujesz zmienną lokalną (tzn. zmienną w zasięgu składowej), podajesz typ danych, a po nim nazwę zmiennej. Na początek utwórz nowy projekt Console Application i nazwij go BasicDataTypes. Zmodyfikuj klasę Program, wprowadzając do niej wywoływaną z metody Main() poniższą metodę pomocniczą:
static void LocalVarDeclarations()
{
Console.WriteLine("=> Data Declarations:");
// Zmienne lokalne deklarujemy tak:
// typDanych nazwaZmiennej;
int myInt;
string myString;
Console.WriteLine();
}
Należy pamiętać, że użycie zmiennej lokalnej przed przypisaniem wartości początkowej spowoduje, że kompilator zgłosi błąd. Dlatego też dobrym zwyczajem jest przypisywanie początkowej wartości lokalnym punktom danych w czasie deklarowania. Można to zrobić albo w jednym wierszu, albo dzieląc deklarację i przypisanie na dwie instrukcje:
static void LocalVarDeclarations()
{
Console.WriteLine("=> Data Declarations:");
// Zmienne lokalne deklarujemy i inicjalizujemy tak:
// typDanych nazwaZmiennej = początkowaWartość;
int myInt = 0;
// Deklarację i przypisanie można wykonać również w dwóch wierszach.
string myString;
myString = "This is my character data";
Console.WriteLine();
}
Dozwolone jest również deklarowanie w jednym wierszu wielu zmiennych tego samego typu. Poniżej przykład z trzema zmiennymi typu bool:
static void LocalVarDeclarations()
{
Console.WriteLine("=> Data Declarations:");
int myInt = 0;
string myString;
myString = "This is my character data";
// Zadeklaruj trzy wartości bool w jednym wierszu.
bool b1 = true, b2 = false, b3 = b1;
Console.WriteLine();
}
Ponieważ słowo kluczowe bool języka C# to po prostu skrót oznaczający strukturę System.Boolean, możliwa jest również alokacja dowolnego typu danych za pomocą jego pełnej nazwy (oczywiście tak samo jest z każdym słowem kluczowym reprezentującym typ danych C#). Poniżej ostateczna implementacja metody LocalVarDeclarations() będąca ilustracją różnych sposobów deklarowania zmiennej lokalnej:
static void LocalVarDeclarations()
{
Console.WriteLine("=> Data Declarations:");
// Zmienne lokalne deklarujemy i inicjalizujemy tak:
// typDanych nazwaZmiennej = początkowaWartość;
int myInt = 0;
string myString;
myString = "This is my character data";
// Zadeklaruj 3 wartości bool w jednym wierszu.
bool b1 = true, b2 = false, b3 = b1;
// Użyj typu danych System.Boolean do zadeklarowania wartości bool.
System.Boolean b4 = false;
Console.WriteLine("Your data: {0}, {1}, {2}, {3}, {4}, {5}",
myInt, myString, b1, b2, b3, b4);
Console.WriteLine();
}
Wbudowane typy danych i operator new
Wszystkie wbudowane typy danych obsługują tzw. konstruktor domyślny (ang. default constructor; patrz rozdz. 5). Daje on możliwość tworzenia zmiennych za pomocą słowa kluczowego new, które automatycznie ustawia zmienną na jej wartość domyślną.
Zmienne typu bool otrzymują wartość false.
Dane liczbowe są ustawiane na 0 (albo 0.0 w przypadku zmiennoprzecinkowych typów danych).
Zmienne typu char są ustawiane na jeden pusty znak.
Zmienne typu BigInteger są ustawiane na 0.
Zmienne typu DateTime są ustawiane na 1/1/0001 12:00:00 AM.
Referencje do obiektów (w tym string) są ustawiane na null.
Uwaga. O wymienionym powyżej typie danych BigInteger będzie mowa już za moment.
Mimo że używanie słowa kluczowego new przy tworzeniu zmiennych podstawowych typów danych jest niewygodne, poniżej dla przykładu przedstawiam prawidłowo zbudowany pod kątem składni kod w języku C#:
static void NewingDataTypes()
{
Console.WriteLine("=> Using new to create variables:");
bool b = new bool(); // Ustawia na false.
int i = new int(); // Ustawia na 0.
double d = new double(); // Ustawia na 0.
DateTime dt = new DateTime(); // Ustawia na 1/1/0001 12:00:00 AM
Console.WriteLine("{0}, {1}, {2}, {3}", b, i, d, dt);
Console.WriteLine();
}
Hierarchia klas typów danych
Co ciekawe, nawet najprostsze typy danych .NET są uporządkowane w hierarchię klas. Jeśli nie masz pojęcia, na czym polega dziedziczenie w świecie programowania, o szczegółach dowiesz się w rozdziale 6. W tej chwili wystarczy zrozumieć, że typy znajdujące się na szczycie hierarchii klas mają pewne domyślne własności, które są przyznawane typom pochodnym. Zależności między tymi najważniejszymi typami systemowymi zostały przedstawione na rysunku 3.2.
Rysunek 3.2. Hierarchia klas typów systemowych
Zwróć uwagę, że każdy z tych typów ostatecznie wywodzi się z klasy System.Object, w której zdefiniowano zbiór metod (np. ToString(), Equals(), GetHashCode()) wspólnych dla wszystkich typów z bibliotek klas bazowych .NET (metody te zostały wyczerpująco omówione w rozdz. 6.).
Zwróć również uwagę na to, że wiele liczbowych typów danych wywodzi się z klasy o nazwie System.ValueType. Typy wywodzące się z klasy ValueType są automatycznie alokowane na stosie, a tym samym mają przewidywalny czas życia i są dość wydajne. Z drugiej strony, typy, które w swoim łańcuchu dziedziczenia nie mają System.ValueType (np. System.Type, System.String, System.Exception i System.Delegate) nie są alokowane na stosie, ale na stercie z automatycznym zarządzaniem pamięcią. (Więcej informacji na temat tej różnicy znajdziesz w rozdz. 4.)
Nie wnikając w szczegóły klas System.Object i System.ValueType, zapamiętaj, że ponieważ słowo kluczowe C# (takie jak int) jest tylko skrótowym zapisem odnoszącym się do typu systemowego (w tym przypadku System.Int32), poniższa składnia jest jak najbardziej dozwolona. Skoro System.Int32 (int w C#) ostatecznie wywodzi się z System.Object, to może wywoływać dowolną z jego publicznych składowych i to właśnie pokazuje ta dodatkowa funkcja pomocnicza:
static void ObjectFunctionality()
{
Console.WriteLine("=> System.Object Functionality:");
// W języku C# int to tak naprawdę skrótowy zapis System.Int32,
// który dziedziczy poniższe składowe z System.Object.
Console.WriteLine("12.GetHashCode() = {0}", 12.GetHashCode());
Console.WriteLine("12.Equals(23) = {0}", 12.Equals(23));
Console.WriteLine("12.ToString() = {0}", 12.ToString());
Console.WriteLine("12.GetType() = {0}", 12.GetType());
Console.WriteLine();
}
Jeśli wywołasz tę metodę z metody Main(), otrzymasz następujące wyniki:
=> System.Object Functionality:
12.GetHashCode() = 12
12.Equals(23) = False
12.ToString() = 12
12.GetType() = System.Int32
Składowe liczbowych typów danych
Kontynuując eksperymenty z wbudowanymi typami danych C#, zauważ, że liczbowe typy .NET obsługują właściwości MaxValue i MinValue, które podają informacje o zakresie dozwolonych dla danego typu wartości. Oprócz właściwości MinValue/MaxValue w danym systemowym typie liczbowym mogą być zdefiniowane inne przydatne składowe. Na przykład typ System.Double pozwala uzyskać wartości epsilon (najmniejsza dodatnia wartość większa od zera) i nieskończoności (które mogą się przydać pasjonatom matematyki). Dla przykładu przyjrzyj się poniższej funkcji pomocniczej:
static void DataTypeFunctionality()
{
Console.WriteLine("=> Data type Functionality:");
Console.WriteLine("Max of int: {0}", int.MaxValue);
Console.WriteLine("Min of int: {0}", int.MinValue);
Console.WriteLine("Max of double: {0}", double.MaxValue);
Console.WriteLine("Min of double: {0}", double.MinValue);
Console.WriteLine("double.Epsilon: {0}", double.Epsilon);
Console.WriteLine("double.PositiveInfinity: {0}",
double.PositiveInfinity);
Console.WriteLine("double.NegativeInfinity: {0}",
double.NegativeInfinity);
Console.WriteLine();
}
Składowe typu System.Boolean
Przyjrzyjmy się teraz typowi danych System.Boolean. Jedyną prawidłową wartością, jaką w języku C# można przypisać do typu bool, jest element ze zbioru {true | false}. Dlatego jest zrozumiałe, że typ System.Boolean zamiast zbioru właściwości MinValue/MaxValue obsługuje parę TrueString/FalseString (które generują odpowiednio łańcuchy "True" lub "False"). Oto przykład:
Console.WriteLine("bool.FalseString: {0}", bool.FalseString);
Console.WriteLine("bool.TrueString: {0}", bool.TrueString);
Składowe typu System.Char
Dane tekstowe w języku C# są reprezentowane przez słowa kluczowe string i char, będące prostym skrótowym zapisem klas System.String i System.Char. Pod maską oba te typy opierają się na Unicode. Być może już wiesz, że string reprezentuje ciągły zbiór znaków (np. "Hello"), a char może reprezentować jedno miejsce w łańcuchu znaków (np. 'H').
Możliwości typu System.Char są znacznie większe niż przechowywanie jednego punktu danych znakowych. Używając statycznych metod tego typu, możesz ustalić, czy dany znak jest cyfrą, liczbą, znakiem przestankowym, czy jeszcze czymś innym. Spójrz na poniższą metodę:
static void CharFunctionality()
{
Console.WriteLine("=> char type Functionality:");
char myChar = 'a';
Console.WriteLine("char.IsDigit('a'): {0}", char.IsDigit(myChar));
Console.WriteLine("char.IsLetter('a'): {0}", char.IsLetter(myChar));
Console.WriteLine("char.IsWhiteSpace('Hello There', 5): {0}",
char.IsWhiteSpace("Hello There", 5));
Console.WriteLine("char.IsWhiteSpace('Hello There', 6): {0}",
char.IsWhiteSpace("Hello There", 6));
Console.WriteLine("char.IsPunctuation('?'): {0}",
char.IsPunctuation('?'));
Console.WriteLine();
}
Metoda ta pokazuje, że wiele składowych System.Char można wywoływać na dwa sposoby: albo podając pojedynczy znak, albo łańcuch z liczbowym indeksem wskazującym miejsce znaku do sprawdzenia.
Analiza składniowa (parsing) wartości z danych łańcuchowych
Typy danych .NET dają możliwość wygenerowania zmiennej typu bazowego na podstawie tekstowego odpowiednika (tzn. parsing). Technika ta jest szczególnie przydatna, gdy chcesz przekształcić jakieś dane wprowadzone przez użytkownika (np. opcja wybrana z rozwijanego menu) na wartość liczbową. Spójrz na poniższą analizę składniową w metodzie o nazwie ParseFromStrings():
static void ParseFromStrings()
{
Console.WriteLine("=> Data type parsing:");
bool b = bool.Parse("True");
Console.WriteLine("Value of b: {0}", b);
double d = double.Parse("99.884");
Console.WriteLine("Value of d: {0}", d);
int i = int.Parse("8");
Console.WriteLine("Value of i: {0}", i);
char c = Char.Parse("w");
Console.WriteLine("Value of c: {0}", c);
Console.WriteLine();
}
Typy System.DateTime i System.TimeSpan
W przestrzeni nazw System zdefiniowano kilka przydatnych typów danych, którym nie odpowiadają żadne skrótowe słowa kluczowe języka C#. Są to na przykład struktury DateTime i TimeSpan. (Zbadanie System.Guid i System.Void z rys. 3.2 pozostawiam zainteresowanym czytelnikom, ale warto zauważyć, że te dwa typy z przestrzeni nazw System są rzadko przydatne w większości aplikacji.)
Typ DateTime zawiera dane reprezentujące konkretną datę (miesiąc, dzień, rok) i czas. Obie te wartości można formatować na wiele sposobów za pomocą wbudowanych składowych. Struktura TimeSpan pozwala z łatwością definiować i przekształcać jednostki czasu za pomocą różnych składowych.
static void UseDatesAndTimes()
{
Console.WriteLine("=> Dates and Times:");
// Ten konstruktor przyjmuje (rok, miesiąc, dzień).
DateTime dt = new DateTime(2015, 10, 17);
// Jaki to dzień miesiąca?
Console.WriteLine("The day of {0} is {1}", dt.Date, dt.DayOfWeek);
// Teraz mamy grudzień.
dt = dt.AddMonths(2);
Console.WriteLine("Daylight savings: {0}", dt.IsDaylightSavingTime());
// Ten konstruktor przyjmuje (godziny, minuty, sekundy).
TimeSpan ts = new TimeSpan(4, 30, 0);
Console.WriteLine(ts);
// Odejmij 15 minut od bieżącej wartości TimeSpan i wyświetl wyniki.
Console.WriteLine(ts.Subtract(new TimeSpan(0, 15, 0)));
}
Pakiet System.Numerics.dll
W przestrzeni nazw System.Numerics zdefiniowano strukturę o nazwie BigInteger. Służy ona, nomen omen, do reprezentacji ogromniastych wartości liczbowych, które nie mają stałej górnej ani dolnej granicy.
Uwaga. W przestrzeni nazw System.Numerics zdefiniowano drugą strukturę o nazwie Complex, która umożliwia modelowanie matematyczne złożonych danych liczbowych (np. liczb zespolonych o częściach rzeczywistych i urojonych, tangensa hipoerbolicznego). Jeśli cię to pasjonuje, poszukaj informacji w dokumentacji .NET Framework 4.6 SDK.
Pomimo tego że w większości aplikacji .NET nie będziesz używać tej struktury, to gdyby jednak zaszła potrzeba zdefiniowania ogromnej wartości liczbowej, w pierwszej kolejności musisz w projekcie odwołać się do pakietu System.Numerics.dll. Pozostając przy bieżącym przykładzie, wykonaj co następuje:
W Visual Studio wybierz kolejno Project ► Add Reference.
Z listy bibliotek znajdującej się na karcie Framework po lewej stronie wybierz pakiet System.Numerics.dll.
Kliknij przycisk OK.
Następnie do pliku, który będzie używał typu danych BigInteger, wstaw poniższą dyrektywę using:
// Tutaj mieszka BigInteger!
using System.Numerics;
Teraz zmienną typu BigInteger możesz utworzyć za pomocą operatora new. W konstruktorze możesz podać wartość liczbową, również zmiennoprzecinkową . Jednak pamiętaj, że gdy definiujesz literał całkowitoliczbowy (np. 500), środowisko uruchomieniowe domyślnie zmieni typ danych na int. I podobnie, literał zmiennoprzecinkowy (np. 55.333) będzie domyślnie typu double. Jak w takim razie ustawić ogromną wartość liczbową BigInteger, nie doprowadzając do przepełnienia typów danych używanych do zwykłych wartości liczbowych?
Najprostszy sposób polega na podaniu ogromnej wartości liczbowej jako literału tekstowego, który można przekształcić na zmienną typu BitInteger za pomocą statycznej metody Parse(). Jeśli będzie to konieczne, możesz również przesłać tablicę bajtów bezpośrednio do konstruktora klasy BigInteger.
Uwaga. Wartości przypisanej do zmiennej BigInteger nie można zmienić, ponieważ dane te są niemodyfikowalne. Jednak w klasie BigInteger zdefiniowano wiele składowych, które zwrócą nowe obiekty BigInteger po modyfikacji (np. statyczna metoda Multiply(), której użyjemy w kolejnym przykładzie).
Tak czy inaczej zauważysz, że składowe klasy BigInteger są bardzo podobne do składowych innych wbudowanych typów danych C# (np. float czy int). Dodatkowo w klasie BigInteger zdefiniowano kilka statycznych składowych, które umożliwiają wykonywanie na zmiennych BigInteger podstawowych działań matematycznych (takich jak dodawanie i mnożenie). Poniżej przykład użycia klasy BigInteger.
static void UseBigInteger()
{
Console.WriteLine("=> Use BigInteger:");
BigInteger biggy =
BigInteger.Parse("9999999999999999999999999999999999999999999999");
Console.WriteLine("Value of biggy is {0}", biggy);
Console.WriteLine("Is biggy an even value?: {0}", biggy.IsEven);
Console.WriteLine("Is biggy a power of two?: {0}", biggy.IsPowerOfTwo);
BigInteger reallyBig = BigInteger.Multiply(biggy,
BigInteger.Parse("8888888888888888888888888888888888888888888"));
Console.WriteLine("Value of reallyBig is {0}", reallyBig);
}
Należy również zauważyć, że typu danych BigInteger można używać z wbudowanymi matematycznymi operatorami C#, takimi jak +, - i *. Dlatego też zamiast wywoływać metodę BigInteger.Multiply() w celu pomnożenia dwóch wielkich liczb, możesz napisać poniższy kod:
BigInteger reallyBig2 = biggy * reallyBig;
Mam nadzieję, że w tym momencie jest już jasne, że słowom kluczowym języka C# reprezentującym podstawowe typy danych odpowiadają typy z bibliotek klas bazowych .NET, a każdy z nich udostępnia stały zbiór funkcji. Co prawda nie omówiłem szczegółowo każdej składowej tych typów danych, ale masz solidne podstawy do tego, aby na własną rękę szukać dalszych informacji. Pełne informacje na temat różnych typów danych .NET znajdziesz w dokumentacji .NET Framework 4.6 SDK – prawdopodobnie zaskoczy cię ogrom wbudowanych możliwości.
Kod źródłowy. Projekt BasicDataTypes znajduje się w podkatalogu Chapter 3.
Używanie danych łańcuchowych
System.String, jak przystało na taką klasę narzędziową, udostępnia wiele metod, w tym metody zwracające długość danych znakowych, znajdujące podłańcuchy w bieżącym łańcuchu oraz przekształcające małe litery na duże i na odwrót. W tabeli 3.5 zebrano najciekawsze (na pewno nie wszystkie) składowe.
Tabela 3.5. Wybrane składowe typu System.String
Składowa typu String
Znaczenie w praktyce
Length
Ta właściwość zwraca długość bieżącego łańcucha.
Compare()
Ta metoda statyczna porównuje dwa łańcuchy.
Contains()
Ta metoda ustala, czy łańcuch znaków zawiera podany podłańcuch.
Equals()
Ta metoda sprawdza, czy dwa obiekty łańcuchowe zawierają identyczne dane znakowe.
Format()
Ta statyczna metoda formatuje łańcuch używając innych typów prostych (np. danych liczbowych albo innych łańcuchów) i omówionej wcześniej w tym rozdziale notacji z klamrami ({0}).
Insert()
Ta metoda wstawia do danego łańcucha inny łańcuch.
PadLeft()
PadRight()
Te metody służą do wypełniania łańcucha jakimiś znakami.
Remove()
Replace()
Te metody pozwalają uzyskać kopię łańcucha po modyfikacjach (po usunięciu albo zastąpieniu znaków).
Split()
Ta metoda zwraca tablicę typów string, zawierającą podłańcuchy, które w tej instancji były oddzielone elementami z podanej tablicy typów char albo string.
Trim()
Ta metoda usuwa wszystkie wystąpienia zbioru podanych znaków z początku i końca bieżącego łańcucha.
ToUpper()
ToLower()
Te metody tworzą kopię bieżącego łańcucha po sformatowaniu go z użyciem odpowiednio dużych lub małych liter.
Podstawowe operacje na łańcuchach
Używanie składowych System.String jest bardzo proste. Wystarczy zadeklarować zmienną typu string i użyć jednej z wbudowanych funkcji za pomocą operatora kropkowego. Miej na uwadze, że kilka składowych System.String to składowe statyczne, a tym samym należy je wywoływać na poziomie klasy, a nie na poziomie obiektu. Utwórz nowy projekt Console Application, nadaj mu nazwę FunWithStrings, a następnie napisz poniższą metodę i wywołaj ją z metody Main():
static void BasicStringFunctionality()
{
Console.WriteLine("=> Basic String functionality:");
string firstName = "Freddy";
Console.WriteLine("Value of firstName: {0}", firstName);
Console.WriteLine("firstName has {0} characters.", firstName.Length);
Console.WriteLine("firstName in uppercase: {0}", firstName.ToUpper());
Console.WriteLine("firstName in lowercase: {0}", firstName.ToLower());
Console.WriteLine("firstName contains the letter y?: {0}",
firstName.Contains("y"));
Console.WriteLine("firstName after replace: {0}",
firstName.Replace("dy", ""));
Console.WriteLine();
}
Nie trzeba tu zbyt wiele wyjaśniać, ponieważ metoda ta po prostu wywołuje względem zmiennej lokalnej typu string różne składowe, takie jak ToUpper() i Contains(), wykonując operacje związane z formatowaniem i przekształcaniem. Poniżej początkowe wyniki:
***** Fun with Strings *****
=> Basic String functionality:
Value of firstName: Freddy
firstName has 6 characters.
firstName in uppercase: FREDDY
firstName in lowercase: freddy
firstName contains the letter y?: True
firstName after replace: Fred
W wynikach tych zastanawiający jest jedynie nieco mylący efekt wywołania metody Replace(). Tak naprawdę wartość zmiennej firstName w ogóle nie uległa zmianie – zamiast tego otrzymaliśmy nową zmienną typu string w zmodyfikowanym formacie. Więcej na temat tego, że łańcuchy są niemodyfikowalne, już za moment.
Konkatenacja łańcuchów
W języku C# zmienne typu string można łączyć za pomocą operatora +, tworząc w ten sposób dłuższe zmienne tego samego typu. Być może wiesz, że formalna nazwa tej techniki to konkatenacja łańcuchów (ang. string concatenation, potocznie łączenie lub sklejanie łańcuchów). Oto nowa funkcja pomocnicza:
static void StringConcatenation()
{
Console.WriteLine("=> String concatenation:");
string s1 = "Programming the ";
string s2 = "PsychoDrill (PTP)";
string s3 = s1 + s2;
Console.WriteLine(s3);
Console.WriteLine();
}
Co ciekawe, kompilator C# przetwarza symbol + na wywołanie statycznej metody String.Concat(). Dlatego też możliwe jest wykonanie konkatenacji łańcuchów poprzez bezpośrednie wywołanie metody String.Concat() (ale nic w ten sposób nie zyskasz – prawdę mówiąc, będziesz mieć tylko więcej pisania!).
static void StringConcatenation()
{
Console.WriteLine("=> String concatenation:");
string s1 = "Programming the ";
string s2 = "PsychoDrill (PTP)";
string s3 = String.Concat(s1, s2);
Console.WriteLine(s3);
Console.WriteLine();
}
Znaki ucieczki
Tak jak w innych językach opartych na C literały znakowe C# mogą zawierać różne znaki ucieczki (ang. escape characters, inaczej: znaki modyfikacji), za pomocą których można określić sposób wyświetlania danych znakowych w strumieniu wyjściowym. Każdy znak ucieczki zaczyna się od lewego ukośnika, a po nim występuje konkretny symbol. Dla tych, którzy dawno już nie używali znaków ucieczki, najważniejsze przypomniano w tabeli 3.6.
Tabela 3.6. Znaki ucieczki dla literałów znakowych
Znak ucieczki
Znaczenie w praktyce
\'
Wstawia do literału znakowego apostrof.
\"
Wstawia do literału znakowego cudzysłów.
\\
Wstawia do literału znakowego lewy ukośnik. Przydatny przy podawaniu ścieżki do pliku albo adresu sieciowego.
\a
Wyzwala alert systemowy (sygnał dźwiękowy). W programach konsolowych można to wykorzystać jako dźwiękową wskazówkę dla użytkownika.
\n
Wstawia nowy wiersz (na platformach Windows).
\r
Wstawia znak powrotu karetki.
\t
Wstawia do literału znakowego tabulację.
Aby na przykład wyświetlić łańcuch zawierający między każdym słowem tabulację, możesz użyć znaku ucieczki \t. A jeśli chcesz utworzyć kolejno: literał zawierający cudzysłów, literał zawierający ścieżkę do katalogu oraz literał łańcuchowy, który wstawia trzy puste wiersze po wyświetleniu danych znakowych w taki sposób, aby kompilator nie zgłosił żadnego błędu, musisz użyć znaków ucieczki \", \\ i \n. A żeby zirytować wszystkie osoby, znajdujące się w promieniu 3 metrów od komputera, w każdym literale łańcuchowym osadziłem alarm (sygnał dźwiękowy). Spójrz na poniższy kod:
static void EscapeChars()
{
Console.WriteLine("=> Escape characters:\a");
string strWithTabs = "Model\tColor\tSpeed\tPet Name\a ";
Console.WriteLine(strWithTabs);
Console.WriteLine("Everyone loves \"Hello World\"\a ");
Console.WriteLine("C:\\MyApp\\bin\\Debug\a ");
// Dodaje w sumie 4 puste wiersze (i znów brzęczy!).
Console.WriteLine("All finished.\n\n\n\a ");
Console.WriteLine();
}
Definiowanie łańcuchów dosłownych
Gdy literał łańcuchowy poprzedzisz symbolem @, utworzysz tak zwany łańcuch dosłowny (ang. verbatim string). Używając łańcuchów dosłownych, rezygnujemy z przetwarzania znaków ucieczki i wyświetlamy dane typu string, tak jak je stworzył programista. Jest to najbardziej przydatne przy używaniu łańcuchów do wyświetlania ścieżek i adresów sieciowych. Zamiast używać znaku ucieczki \\, możesz napisać po prostu poniższy kod:
// Poniższy łańcuch zostanie wyświetlony dosłownie,
// wraz ze wszystkimi znakami ucieczki.
Console.WriteLine(@"C:\MyApp\bin\Debug");
Zwróć przy okazji uwagę, że łańcuchów dosłownych można też użyć do zachowania odstępów w przypadku łańcuchów wielowierszowych.
// Łańcuchy dosłowne pozwalają zachować odstępy.
string myLongString = @"This is a very
very
very
long string";
Console.WriteLine(myLongString);
Gdy używasz łańcuchów dosłownych, możesz do literału łańcuchowego bezpośrednio wstawić cudzysłów, podwajając symbol ":
Console.WriteLine(@"Cerebus said ""Darrr! Pret-ty sun-sets""");
Porównywanie łańcuchów
Jak dokładnie wyjaśniam w rozdziale 4, typ referencyjny (ang. reference type) to obiekt alokowany na zarządzanej stercie z odzyskiwaniem pamięci. Jeśli porównywane obiekty referencyjne (w języku C# służą do tego operatory == i !=) wskazują na ten sam obiekt w pamięci, domyślnie zwracana jest wartość true. Jednak pomimo faktu, że string to tak naprawdę referencyjny typ danych, operatory porównania zostały przedefiniowane, aby porównywać wartości obiektów string, a nie obiekt w pamięci, do którego się odwołują.
static void StringEquality()
{
Console.WriteLine("=> String equality:");
string s1 = "Hello!";
string s2 = "Yo!";
Console.WriteLine("s1 = {0}", s1);
Console.WriteLine("s2 = {0}", s2);
Console.WriteLine();
// Porównaj poniższe łańcuchy.
Console.WriteLine("s1 == s2: {0}", s1 == s2);
Console.WriteLine("s1 == Hello!: {0}", s1 == "Hello!");
Console.WriteLine("s1 == HELLO!: {0}", s1 == "HELLO!");
Console.WriteLine("s1 == hello!: {0}", s1 == "hello!");
Console.WriteLine("s1.Equals(s2): {0}", s1.Equals(s2));
Console.WriteLine("Yo.Equals(s2): {0}", "Yo!".Equals(s2));
Console.WriteLine();
}
W języku C# obiekty typu string są porównywane znak po znaku, z uwzględnieniem wielkości liter. Dlatego też "Hello!" różni się od "HELLO!", a to ostatnie od "hello!". Poza tym przypomnij sobie o związku string z typu System.String i zauważ, że do porównania możesz użyć zarówno metody Equals() typu String, jak i predefiniowanych operatorów. Oprócz tego, ponieważ każdy literał łańcuchowy (jak np. "Yo") to prawidłowa instancja klasy System.String, możesz używać funkcji związanych z łańcuchami na bazie stałej sekwencji znaków.
Łańcuchów nie można modyfikować
Jedną z najciekawszych własności typu System.String jest to, że po przypisaniu początkowej wartości do obiektu string danych znakowych nie można zmienić. Brzmi to jak bezczelne kłamstwo: przecież nieustannie przypisujemy nowe wartości, a w typie System.String zdefiniowano wiele metod, które w taki czy inny sposób modyfikują dane znakowe (np. zmieniając wielkość liter). Jeśli jednak zajrzysz pod maskę, okaże się, że metody typu string tak naprawdę zwracają zupełnie nowy obiekt string w zmodyfikowanym formacie.
static void StringsAreImmutable()
{
// Ustaw początkową wartość łańcucha.
string s1 = "This is my string.";
Console.WriteLine("s1 = {0}", s1);
// Zmienić wartość zmiennej s1 na duże litery?
string upperString = s1.ToUpper();
Console.WriteLine("upperString = {0}", upperString);
// Nie! s1 ma ten sam format!
Console.WriteLine("s1 = {0}", s1);
}
Poniższe wyniki potwierdzają, że początkowy obiekt string (czyli s1) nie został przekształcony na duże litery po wywołaniu metody ToUpper(), ale została zwrócona kopia obiektu string w zmodyfikowanym formacie.
s1 = This is my string.
upperString = THIS IS MY STRING.
s1 = This is my string.
To samo prawo niemodyfikowalności obowiązuje przy używaniu operatora przypisania języka C#. Dla przykładu zaimplementuj następującą metodę StringsAreImmutable2():
static void StringsAreImmutable2()
{
string s2 = "My other string";
s2 = "New string value";
}
A teraz skompiluj aplikację i otwórz pakiet w programie ildasm.exe (patrz rozdz. 1). Poniższe wyniki znajdziesz w kodzie CIL dla metody StringsAreImmutable2().
.method private hidebysig static void StringsAreImmutable2() cil managed
{
// Code size 14 (0xe)
.maxstack 1
.locals init ([0] string s2)
IL_0000: nop
IL_0001: ldstr "My other string"
IL_0006: stloc.0
IL_0007: ldstr "New string value"
IL_000c: stloc.0
IL_000d: ret
} // end of method Program::StringAreImmutable2
Omówienie niskopoziomowych szczegółów CIL jeszcze przed nami, ale już teraz warto zwrócić uwagę na liczne wywołania kodu operacyjnego ldstr (load string). Mówiąc najprościej, kod operacyjny ldstr w CIL ładuje na zarządzaną stertę nowy obiekt string. Poprzedni obiekt string, który zawierał wartość "My other string", zostanie ostatecznie usunięty w trakcie odzyskiwania pamięci.
A co z tego wszystkiego wynika? Mówiąc krótko, niewłaściwe używanie klasy string może być niewydajne i przyczyniać się do zasobożerności, zwłaszcza przy wykonywaniu konkatenacji łańcuchów czy też pracy z ogromną ilością danych tekstowych. Gdy potrzebujesz reprezentacji prostych danych znakowych, takich jak numer PESEL, imię, nazwisko albo krótkie teksty wyświetlane w aplikacji, klasa string to wybór idealny.
Jeśli jednak tworzysz aplikację, w której duże ilości danych tekstowych odgrywają główną rolę (np. procesor tekstu), używanie obiektów string do przetwarzania tekstu to fatalny pomysł, ponieważ najprawdopodobniej skończy się to tworzeniem (często niebezpośrednio) zbędnych kopii danych łańcuchowych. Cóż więc ma w takiej sytuacji zrobić programista? To bardzo dobre pytanie.
Typ System.Text.StringBuilder
Z uwagi na problemy wynikające z nonszalanckiego używania typu string w bibliotekach klas bazowych .NET udostępniono przestrzeń nazw System.Text. W tej stosunkowo niewielkiej przestrzeni mieszka sobie klasa o nazwie StringBuilder. Podobnie jak w klasie System.String w klasie StringBuilder zdefiniowano metody umożliwiające na przykład zastępowanie lub formatowanie fragmentów tekstu. Gdy chcesz użyć tego typu w plikach z kodem C#, na samym początku musisz pamiętać o zaimportowaniu do pliku poniższej przestrzeni nazw (byłoby ideałem, aby sprawdzanie, czy zaimportowano wszystkie potrzebne przestrzenie nazw, weszło ci w nawyk przy tworzeniu wszystkich nowych projektów w Visual Studio):
// Tutaj mieszka StringBuilder!
using System.Text;
Unikatową cechą typu StringBuilder jest to, że wywołując jego składowe, zamiast uzyskiwać kopię danych w zmodyfikowanym formacie, bezpośrednio modyfikujesz wewnętrzne dane znakowe obiektu (co jest bardziej wydajne). Gdy tworzysz instancję typu StringBuilder, wartości początkowe tego obiektu możesz podać za pomocą jednego z wielu konstruktorów. Jeśli nie wiesz jeszcze, do czego służą konstruktory, to w tej chwili zapamiętaj tylko, że pozwalają one za pomocą słowa kluczowego new utworzyć obiekt z początkowym stanem. Spójrz na poniższe użycie typu StringBuilder:
static void FunWithStringBuilder()
{
Console.WriteLine("=> Using the StringBuilder:");
StringBuilder sb = new StringBuilder("**** Fantastic Games ****");
sb.Append("\n");
sb.AppendLine("Half Life");
sb.AppendLine("Morrowind");
sb.AppendLine("Deus Ex" + "2");
sb.AppendLine("System Shock");
Console.WriteLine(sb.ToString());
sb.Replace("2", " Invisible War");
Console.WriteLine(sb.ToString());
Console.WriteLine("sb has {0} chars.", sb.Length);
Console.WriteLine();
}
Powyżej utworzyłem obiekt StringBuilder z początkową wartością ustawioną na "**** Fantastic Games ****". Jak widzisz, dane załączam do wewnętrznego bufora, co daje mi możliwość zastępowania lub usuwania znaków wedle własnego uznania. Domyślnie StringBuilder może początkowo przechowywać łańcuch o maksymalnej długości 16 znaków (ale w razie konieczności automatycznie się rozwinie). Można jednak zmienić tę domyślną wartość początkową za pomocą dodatkowego argumentu konstruktora:
// Utwórz obiekt StringBuilder o początkowym rozmiarze 256.
StringBuilder sb = new StringBuilder("**** Fantastic Games ****", 256);
Jeśli liczba załączonych znaków przekroczy podany limit, obiekt StringBuilder skopiuje swoje dane do nowej instancji i odpowiednio zwiększy rozmiar bufora.
Interpolacja łańcuchów
Przedstawiona w tym rozdziale składnia z klamrami ({0}, {1} itd.) istnieje na platformie .NET już od wersji 1.0. Począwszy od bieżącego wydania programiści C# mogą używać składni alternatywnej do tworzenia literałów łańcuchowych zawierających wypełniacze dla zmiennych. Formalna nazwa tej techniki to interpolacja łańcuchów (ang. string interpolation). Wynik tej operacji jest identyczny jak w przypadku tradycyjnej składni formatowania łańcuchów, jednak ten nowy sposób pozwala bezpośrednio osadzać same zmienne, zamiast załączania ich na liście z przecinkiem jako separatorem.
Spójrz na poniższą dodatkową metodę w klasie Program. Jej nazwa to StringInterpolation(), a ma ona za zadanie utworzenie zmiennej łańcuchowej na dwa wspomniane sposoby:
static void StringInterpolation()
{
// Kilka zmiennych lokalnych, których użyjemy w dłuższym łańcuchu.
int age = 4;
string name = "Soren";
// Składnia z klamrami.
string greeting = string.Format("Hello {0} you are {1} years old.",
name, age);
// Interpolacja łańcuchów.
string greeting2 = $"Hello {name} you are {age} years old.";
}
Spójrz na zmienną greeting2; konstruowany łańcuch zaczyna się przedrostkiem w postaci znaku dolara ($). Zwróć też uwagę, że nadal używane są klamry z wypełniaczami, jednak zamiast znacznika liczbowego zmienną możesz umieścić bezpośrednio w zasięgu. Zaletą tej nowej składni formatowania jest czytanie liniowe (z lewej do prawej), ponieważ nie trzeba „przeskakiwać na koniec” do listy wartości używanych w trakcie wykonywania programu.
Jest jeszcze jeden ciekawy aspekt tej nowej składni: klamry używane w interpolacji łańcuchów są w dozwolonym zasięgu. Dlatego też można modyfikować stan zmiennych za pomocą notacji kropkowej. Spójrz na przeróbkę poniższych zmiennych string:
string greeting = string.Format("Hello {0} you are {1} years old.",
name.ToUpper(), age);
string greeting2 = $"Hello {name.ToUpper()} you are {age} years old.";
W tym przykładzie zmieniłem wielkość liter wartości zmiennej name, wywołując ToUpper(). Zwróć uwagę, że stosując interpolację łańcuchów, wywołania tej metody nie kończysz średnikiem. Dlatego też zasięgu klamr nie możesz traktować jako pełnowartościowego zasięgu metody zawierającego wiele wierszy wykonywalnego kodu. Możesz za to wywołać jedną składową względem obiektu, używając operatora kropkowego, jak również definiować proste wyrażenia ogólne, takie jak {age += 1}.
Warto też zauważyć, że używając tej nowej składni, nadal można używać znaków ucieczki w literałach łańcuchowych. I jeśli chcesz wstawić znak tabulacji, możesz w taki oto sposób użyć znaku ucieczki \t jako prefiksu:
string greeting = string.Format("\tHello {0} you are {1} years old.",
name.ToUpper(), age);
string greeting2 = $"\tHello {name.ToUpper()} you are {age} years old.";
Oczywiście wybór sposobu konstruowania zmiennych typu string w locie należy do ciebie. Pamiętaj jednak, że jeśli używasz jednej z wcześniejszych wersji platformy .NET, składnia z interpolacją łańcuchów spowoduje, że kompilator zgłosi błąd. Dlatego też, jeśli chcesz mieć pewność, że twój kod C# zostanie skompilowany przez różne wersje kompilatora C#, bezpieczniej będzie pozostać przy tradycyjnym sposobie z wypełniaczami liczbowymi.
Kod źródłowy. Projekt FunWithStrings znajduje się w podkatalogu Chapter 3.
Zawężające i rozszerzające konwersje typów danych
Wiedząc już, jak używać wbudowanych typów danych C#, możemy przejść do pokrewnego tematu, mianowicie przekształcania (konwersji) typów danych. Załóżmy, że masz nowy projekt Console Application o nazwie TypeConversions, w którym zdefiniowano następującą klasę:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with type conversions *****");
// Dodaj dwie zmienne typu short i wyświetl wynik.
short numb1 = 9, numb2 = 10;
Console.WriteLine("{0} + {1} = {2}",
numb1, numb2, Add(numb1, numb2));
Console.ReadLine();
}
static int Add(int x, int y)
{
return x + y;
}
}
Zwróć uwagę, że metoda Add() oczekuje na przesłanie dwóch parametrów typu int, natomiast metoda Main() przekazuje tak naprawdę dwie zmienne typu short. Mimo że pozornie wygląda to na kompletne pomieszanie typów danych, program można skompilować i uruchomić bez błędów, a zwracany jest prawidłowy wynik: 19.
Kompilator traktuje ten kod tak, jakby był on składniowo prawidłowy, ponieważ nie ma możliwości utraty danych. Maksymalna wartość typu short (32 767) bez problemu mieści się w limicie przewidzianym dla typu int (2 147 483 647), więc kompilator niejawnie rozszerza każdy obiekt typu short do obiektu typu int. Mówiąc formalnie, rozszerzanie (ang. widening, inaczej: promocja) to określenie niejawnego rzutowania w górę (ang. upward cast), które nie kończy się utratą danych.
Uwaga. Więcej informacji na temat dozwolonego rozszerzania (i zawężania, o tym za chwilę) różnych typów danych C#, znajdziesz w dokumentacji .NET Framework 4.6 SDK, wyszukując hasło „Type Conversion Tables”.
Mimo tego, że w powyższym przykładzie to niejawne rozszerzanie zadziałało na twoją korzyść, innym razem może być źródłem błędów w trakcie kompilacji. Na przykład załóżmy, że ustawiono takie wartości zmiennych numb1 i numb2, które po dodaniu przekraczają maksymalną dopuszczalną wartość typu short. Załóżmy też, że wartość zwracana przez metodę Add() nie jest wyświetlana bezpośrednio na konsoli, ale zostaje zapisana w nowej zmiennej lokalnej typu short.
static void Main(string[] args)
{
Console.WriteLine("***** Fun with type conversions *****");
// Poniższy fragment spowoduje błąd w trakcie kompilacji!
short numb1 = 30000, numb2 = 30000;
short answer = Add(numb1, numb2);
Console.WriteLine("{0} + {1} = {2}",
numb1, numb2, answer);
Console.ReadLine();
}
W tym przypadku kompilator zgłasza poniższy błąd:
Cannot implicitly convert type 'int' to 'short'.
An explicit conversion exists (are you missing a cast?)
Problem w tym, że chociaż metoda Add() potrafi zwrócić typ int o wartości 60 000 (ponieważ wielkość ta mieści się w zakresie przewidzianym dla System.Int32), wartość ta nie może zostać zapisana w zmiennej typu short, ponieważ przekracza granice tego typu. Formalnie rzecz biorąc, środowisko CLR nie mogło zastosować konwersji zawężającej (ang. narrowing operation). Jak się łatwo domyślić, zawężanie to logiczna odwrotność rozszerzania, to znaczy większa wartość jest zapisywana w zmiennej mniejszego typu danych.
Należy podkreślić, że wszystkie operacje zawężania kończą się błędami w trakcie kompilacji, nawet jeśli rozsądek podpowiada, że operacja zawężania powinna zakończyć się powodzeniem. Na przykład poniższy kod również spowoduje błąd w czasie kompilacji:
// Kolejny błąd w czasie kompilacji!
static void NarrowingAttempt()
{
byte myByte = 0;
int myInt = 200;
myByte = myInt;
Console.WriteLine("Value of myByte: {0}", myByte);
}
W tym przypadku wartość zawarta w zmiennej typu int (myInt) mieści się bez problemu w zakresie przewidzianym dla typu byte i dlatego liczymy na to, że operacja zawężania nie spowoduje błędu w czasie kompilacji. Jednak ponieważ język C# powstawał z myślą o bezpieczeństwie typów, kompilator zgłasza błąd.
Gdy chcesz poinformować kompilator, że liczysz się z możliwością utraty danych w wyniku operacji zawężania, musisz zastosować jawne rzutowanie (ang. explicit cast). W języku C# służy do tego operator rzutowania: (). Spójrz na następującą modyfikację typu Program:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with type conversions *****");
short numb1 = 30000, numb2 = 30000;
// Jawne rzutowanie typu int na short (ze zgodą na utratę danych).
short answer = (short)Add(numb1, numb2);
Console.WriteLine("{0} + {1} = {2}",
numb1, numb2, answer);
NarrowingAttempt();
Console.ReadLine();
}
static int Add(int x, int y)
{
return x + y;
}
static void NarrowingAttempt()
{
byte myByte = 0;
int myInt = 200;
// Jawne rzutowanie typu int na byte (bez utraty danych).
myByte = (byte)myInt;
Console.WriteLine("Value of myByte: {0}", myByte);
}
}
W tej sytuacji kod zostanie skompilowany, jednak wynik tego dodawania jest kompletnie bezsensowny:
***** Fun with type conversions *****
30000 + 30000 = -5536
Value of myByte: 200
I to jest dowód na to, że stosując jawne rzutowanie, możesz zmusić kompilator do konwersji zawężającej, nawet za cenę utraty danych. W przypadku metody NarrowingAttempt() nie było z tym problemu, ponieważ wartość 200 bez problemu mieści się w zakresie typu byte. Jednak wynik dodawania dwóch wartości typu short w metodzie Main() jest absolutnie nie do zaakceptowania (30 000 + 30 000 = –5536?).
Gdy piszesz aplikację, w której utrata danych nigdy nie jest dopuszczalna, możesz użyć słów kluczowych checked i unchecked, aby zapewnić, że taka sytuacja zawsze zostanie zauważona.
Słowo kluczowe checked
Zacznijmy od roli słowa kluczowego checked. Załóżmy, że w klasie Program masz nową metodę, która próbuje dodać dwie wartości typu byte. Każdej z nich przypisano bezpieczną wartość, nieprzekraczającą dozwolonej granicy (255). Dodając wartości tych typów (rzutując zwracany typ int na typ byte), zakładasz, że w wyniku otrzymasz dokładną sumę każdej składowej.
static void ProcessBytes()
{
byte b1 = 100;
byte b2 = 250;
byte sum = (byte)Add(b1, b2);
// Zmienna sum powinna mieć wartość 350, ale jest 94!
Console.WriteLine("sum = {0}", sum);
}
Ale spoglądając na wyniki tej aplikacji, z zaskoczeniem zauważasz, że zmienna sum ma wartość 94, zamiast oczekiwanej wartości 350. Przyczyna jest prosta. Ponieważ typ System.Byte może przechowywać jedynie wartości z zakresu od 0 do 255 (włącznie, co razem daje 256 możliwości), zmienna sum zawiera teraz wartość nadmiarową (bo 350 – 256 = 94). Domyślnie, jeśli nie podejmiesz żadnych działań, aby temu zapobiec, błędy nadmiaru i niedomiaru nie zostaną zgłoszone.
Błędami nadmiaru i niedomiaru w swoich aplikacjach możesz zająć się na dwa sposoby. Pierwszy polega na wykorzystaniu własnej pomysłowości i umiejętności programistycznych. Oczywiście wadą tej techniki jest prosty fakt, ze jesteś tylko człowiekiem i pomimo najszczerszych chęci możesz coś przeoczyć.
Na szczęście w języku C# jest słowo kluczowe checked. Kiedy instrukcję (albo i blok instrukcji) umieścisz w zasięgu tego słowa, kompilator C# wygeneruje dodatkowe instrukcje CIL, które sprawdzają, czy przy dodawaniu, mnożeniu, odejmowaniu lub dzieleniu dwóch liczbowych typów danych nie wystąpił problem z nadmiarem.
Jeśli wystąpił, zostanie zgłoszony wyjątek wykonywania: System.OverflowException. W rozdziale 7 szczegółowo jest omówiona strukturalna obsługa wyjątków oraz używanie słów kluczowych try i catch. Nie zajmując się w tym momencie szczegółami, przyjrzyj się poniższej aktualizacji:
static void ProcessBytes()
{
byte b1 = 100;
byte b2 = 250;
// Tym razem nakażemy kompilatorowi wstawienie kodu CIL,
// który zgłosi wyjątek,
// jeśli wystąpi problem z nadmiarem/niedomiarem.
try
{
byte sum = checked((byte)Add(b1, b2));
Console.WriteLine("sum = {0}", sum);
}
catch (OverflowException ex)
{
Console.WriteLine(ex.Message);
}
}
Zwróć uwagę, że wartość zwracana przez metodę Add() znajduje się w zasięgu słowa kluczowego checked. Ponieważ wartość zmiennej typu sum przekracza limit typu byte, zostaje zgłoszony wyjątek w czasie wykonywania programu. Zwróć uwagę na komunikat o błędzie wyświetlony za pomocą właściwości Message (działanie arytmetyczne zakończyło się błędem nadmiaru):
Arithmetic operation resulted in an overflow.
Jeżeli chcesz, aby sprawdzanie nadmiaru odbywało się dla całego bloku instrukcji, możesz zdefiniować „sprawdzany zakres” w taki oto sposób:
try
{
checked
{
byte sum = (byte)Add(b1, b2);
Console.WriteLine("sum = {0}", sum);
}
}
catch (OverflowException ex)
{
Console.WriteLine(ex.Message);
}
W obu przypadkach kod zostanie automatycznie sprawdzony pod kątem występowania problemu nadmiaru i jeśli taki się wydarzy, zostanie zgłoszony odpowiedni wyjątek.
Ustawianie sprawdzania nadmiaru w całym projekcie
Jeśli w swojej aplikacji w żadnym momencie nie możesz pozwolić na niewykrycie nadmiaru, czeka cię niewdzięczne zadanie wielokrotnego wstawiania fragmentów kodu w objęcia słowa kluczowego checked. Alternatywnym sposobem jest obsługiwana przez kompilator C# flaga /checked. Po jej włączeniu wszystkie działania arytmetyczne zostaną sprawdzone pod kątem występowania nadmiaru, bez konieczności używania słowa kluczowego checked. Po wykryciu nadmiaru arytmetycznego i tak zostanie zgłoszony wyjątek wykonywania.
Aby włączyć tę flagę w środowisku Visual Studio, otwórz stronę z właściwościami projektu i na karcie Build kliknij przycisk Advanced. W wyświetlonym oknie dialogowym zaznacz pole wyboru „Check for arithmetic overflow/underflow” (patrz rys. 3.3).
Rysunek 3.3. Włączanie sprawdzania danych pod kątem nadmiaru/niedomiaru arytmetycznego w całym projekcie
Włączenie tego ustawienia może być bardzo przydatne przy tworzeniu wersji do debugowania. Po pozbyciu się wszystkich wyjątków nadmiaru z bazy kodu w kolejnych wersjach możesz wyłączyć flagę /checked (zwiększając wydajność aplikacji).
Słowo kluczowe unchecked
Załóżmy teraz, że to ustawienie zostało włączone dla całego projektu, ale masz jakiś blok kodu, w którym utrata danych jest dopuszczalna. Co można zrobić? Wiadomo, że flaga /checked sprawdza całą arytmetykę kodu, ale w języku C# dostępne jest też słowo kluczowe unchecked, które pozwala wyłączyć zgłaszanie wyjątku nadmiaru w konkretnych przypadkach. Słowa tego używa się identycznie jak słowa checked: można wskazać pojedynczą instrukcję albo cały blok instrukcji.
// Zakładając, że flaga /checked jest włączona,
// kod w tym bloku nie spowoduje zgłoszenia
// wyjątku wykonywania.
unchecked
{
byte sum = (byte)(b1 + b2);
Console.WriteLine("sum = {0} ", sum);
}
Podsumowując temat słów kluczowych checked i unchecked: zapamiętaj, że środowisko uruchomieniowe .NET domyślnie ignoruje błędy nadmiaru/niedomiaru arytmetycznego. Kiedy chcesz wybiórczo posprawdzać instrukcje, używasz słowa kluczowego checked. Aby przechwytywać błędy nadmiaru w całej aplikacji, włączasz flagę /checked. A jeśli w jakimś bloku kodu nadmiar jest dopuszczalny (i nie trzeba zgłaszać wyjątku przepełnienia), to możesz ten blok wziąć w objęcia słowa kluczowego unchecked.
Kod źródłowy. Projekt TypeConversions znajduje się w podkatalogu Chapter 3.
Zmienne lokalne z typizacją niejawną
Jak dotąd, definiując w tym rozdziale zmienne lokalne, jawnie podawaliśmy bazowy typ danych każdej deklarowanej zmiennej.
static void DeclareExplicitVars()
{
// Zmienne lokalne z jawną typizacją
// deklarujemy w następujący sposób:
// typDanych nazwaZmiennej = początkowaWartość;
int myInt = 0;
bool myBool = true;
string myString = "Time, marches on...";
}
Choć wiele osób (w tym skromny ja sam) uważa, że zawsze warto jawnie określać typ danych każdej zmiennej, język C# daje możliwość niejawnej typizacji (ang. implicitly typing; w literaturze pisze się też o typach domniemanych) zmiennych lokalnych za pomocą słowa kluczowego var. Można go użyć zamiast konkretnego typu danych (jak np. int, bool albo string), a kompilator automatycznie wywnioskuje, jaki jest bazowy typ danych na podstawie początkowej wartości użytej do zainicjalizowania lokalnego punktu danych.
Aby pokazać niejawną typizację na przykładzie, utwórz nowy projekt Console Application i nazwij go ImplicitlyTypedLocalVars. Zwróć uwagę, że zmienne lokalne z poprzedniej metody można teraz zadeklarować w taki oto sposób:
static void DeclareImplicitVars()
{
// Niejawną typizację zmiennych lokalnych
// deklarujemy w następujący sposób:
// var nazwaZmiennej = wartośćPoczątkowa;
var myInt = 0;
var myBool = true;
var myString = "Time, marches on...";
}
Uwaga. Ściśle rzecz biorąc, var nie jest słowem kluczowym języka C#. Dozwolone jest deklarowanie zmiennych, parametrów i pól o nazwie var i nie powoduje to błędów w trakcie kompilacji. Jednak po użyciu słowa var jako typu danych kompilator zrozumie kontekst i potraktuje je jak słowo kluczowe.
W tym przypadku kompilator na podstawie początkowo przypisanej wartości potrafi ustalić, że myInt to w rzeczywistości System.Int32, myBool to System.Boolean, a myString jest typu System.String. Możesz się o tym przekonać, wyświetlając nazwę typu za pomocą refleksji. W rozdziale 15 zostanie dokładnie wyjaśnione, że refleksja to ustalanie budowy typu w trakcie działania programu. Można w ten sposób na przykład ustalić typ danych zmiennej zadeklarowanej na zasadzie niejawnej typizacji. Zmodyfikuj swoją metodę, wprowadzając do niej następujące instrukcje:
static void DeclareImplicitVars()
{
// Niejawna typizacja zmiennych lokalnych.
var myInt = 0;
var myBool = true;
var myString = "Time, marches on...";
// Wyświetl typ bazowy.
Console.WriteLine("myInt is a: {0}", myInt.GetType().Name);
Console.WriteLine("myBool is a: {0}", myBool.GetType().Name);
Console.WriteLine("myString is a: {0}", myString.GetType().Name);
}
Uwaga. Pamiętaj, że niejawnej typizacji możesz używać dla dowolnego typu, w tym tablic, typów generycznych (patrz rozdz. 9) i typów niestandardowych. Kolejne przykłady niejawnej typizacji znajdziesz na kolejnych stronach książki.
Po wywołaniu metody DeclareImplicitVars() z metody Main() zobaczysz poniższe wyniki:
***** Fun with Implicit Typing *****
myInt is a: Int32
myBool is a: Boolean
myString is a: String
Ograniczenia niejawnej typizacji zmiennych
Używanie słowa kluczowego var ma swoje ograniczenia. Przede wszystkim niejawna typizacja dotyczy wyłącznie zmiennych lokalnych w zasięgu metody albo właściwości. Niedozwolone jest używanie słowa kluczowego var w definicjach wartości zwracanych, parametrów czy też pól danych niestandardowego typu. Na przykład, poniższa definicja klasy spowoduje zgłoszenie kilku błędów w czasie kompilacji:
class ThisWillNeverCompile
{
// Błąd! Słowa var nie można użyć jako pola danych!
private var myInt = 10;
// Błąd! Słowa var nie można użyć jako typu
// wartości zwracanej ani parametru!
public var MyMethod(var x, var y){}
}
Poza tym zmiennym lokalnym deklarowanym za pomocą słowa kluczowego var trzeba przypisywać wartość początkową dokładnie w momencie deklaracji, a tą wartością nie może być null. Przyczyną tego ostatniego wymogu jest oczywisty fakt, że na podstawie samej wartości null kompilator nie jest w stanie wywnioskować, jaki ma być typ danych zmiennej.
// Błąd! Trzeba przypisać wartość!
var myData;
// Błąd! Wartość trzeba przypisać dokładnie w momencie deklaracji!
var myInt;
myInt = 0;
// Błąd! Początkową wartością nie może być null!
var myObj = null;
Dozwolone jest za to ustawienie zmiennej lokalnej na wartość null w kolejnym przypisaniu (pod warunkiem, że jest to typ referencyjny).
// OK, jeśli SportsCar to typ referencyjny!
var myCar = new SportsCar();
myCar = null;
Poza tym dozwolone jest również przypisywanie zmiennej lokalnej z niejawną typizacją wartości innych zmiennych, niezależnie od tego, czy ich typ został określony w taki sam sposób, czy nie.
// Również OK!
var myInt = 0;
var anotherInt = myInt;
string myString = "Wake up!";
var myData = myString;
Wreszcie, dozwolone jest zwracanie zmiennej lokalnej z niejawną typizacją do procedury wywołującej, pod warunkiem że wartość zwracana przez metodę ma taki sam typ bazowy co punkt danych zdefiniowany za pomocą słowa kluczowego var.
static int GetAnInt()
{
var retVal = 9;
return retVal;
}
Dane z niejawną typizacją to dane z silną typizacją
Zapamiętaj, że w rezultacie niejawnej typizacji zmiennych lokalnych otrzymujesz dane z silną typizacją (ang. strongly typed data; inaczej: dane ze ścisłą kontrolą typu). Tym samym używanie słowa kluczowego var różni się od technik stosowanych w językach skryptowych (np. JavaScript lub Perl) oraz typu danych Variant w modelu COM, gdzie zmienna w trakcie swojego życia w programie może przechowywać wartości różnego typu (co często nazywane jest typizacją dynamiczną).
Uwaga. Język C# umożliwia typizację dynamiczną za sprawą słowa kluczowego o nazwie – niespodzianka! – dynamic. Więcej na ten temat w rozdziale 16.
Tutaj ustalanie typu odbywa się z zachowaniem zasady silnej typizacji i ma wpływ jedynie na deklarację zmiennych w czasie kompilacji. Później punkt danych jest traktowany tak, jakby został zadeklarowany w określonym typie; przypisanie wartości innego typu spowoduje błąd w czasie kompilacji.
static void ImplicitTypingIsStrongTyping()
{
// Kompilator wie, że "s" to System.String.
var s = "This variable can only hold string data!";
s = "This is fine...";
// Można wywołać dowolną składową bazowego typu.
string upper = s.ToUpper();
// Błąd! Nie można przypisać danych liczbowych do łańcucha!
s = 44;
}
Przydatność zmiennych lokalnych z niejawną typizacją
Po omówieniu składni deklarowania zmiennych lokalnych z typizacją niejawną na usta ciśnie się pytanie: kiedy używać tej konstrukcji? Przede wszystkim używanie słowa kluczowego var do deklarowania zmiennych lokalnych dla samej sztuki niewiele daje. Na dodatek może wprowadzać w błąd inne osoby czytające kod, ponieważ utrudnia szybkie ustalenie bazowego typu danych, a tym samym zrozumienie ogólnego przeznaczenia zmiennej. Jeśli wiesz, że potrzebny jest typ int, to zadeklaruj int!
Jednak jak przekonasz się, poczynając od rozdziału 12, zbiór technologii LINQ wykorzystuje wyrażenia z zapytaniami (ang. query expressions), które potrafią generować dynamicznie tworzone zbiory wyników na podstawie formatu samego zapytania. W takich sytuacjach typizacja niejawna jest bardzo przydatna, ponieważ nie trzeba jawnie definiować typu, który może zostać zwrócony przez zapytanie, a czasami jest to po prostu niewykonalne. Nie wnikając w poniższy przykładowy kod LINQ, spróbuj odgadnąć bazowy typ danych zmiennej subset:
static void LinqQueryOverInts()
{
int[] numbers = { 10, 20, 30, 40, 1, 2, 3, 8 };
// Zapytanie LINQ!
var subset = from i in numbers where i < 10 select i;
Console.Write("Values in subset: ");
foreach (var i in subset)
{
Console.Write("{0} ", i);
}
Console.WriteLine();
// Hmm... jakiego typu jest zmienna subset?
Console.WriteLine("subset is a: {0}", subset.GetType().Name);
Console.WriteLine("subset is defined in: {0}",
subset.GetType().Namespace);
}
Można by założyć, że typem danych zmiennej subset jest tablica całkowitoliczbowa. Tak się wydaje, ale w rzeczywistości jest to niskopoziomowy typ danych LINQ, który poznasz dopiero po dłuższym używaniu tej technologii albo otwierając skompilowany kod w programie ildasm.exe. Dobra wiadomość jest taka, że używając LINQ (bardzo) rzadko musisz zajmować się bazowym typem wartości zwracanej przez zapytanie; po prostu przypisujesz wartość do zmiennej lokalnej, używając niejawnej typizacji.
Na dobrą sprawę można powiedzieć, że jedynym przypadkiem, gdy używamy słowa kluczowego var, jest definiowanie danych zwracanych przez zapytanie LINQ. Pamiętaj: jeśli wiesz, że potrzebny jest typ int, po prostu zadeklaruj int! Nadużywanie typizacji niejawnej (ze słowem kluczowym var) w kodzie produkcyjnym jest przez większość programistów odbierane jako oznaka złego stylu.
Kod źródłowy. Projekt ImplicitlyTypedLocalVars znajduje się w podkatalogu Chapter 3.
Konstrukcje iteracyjne w języku C#
We wszystkich językach programowania istnieją jakieś sposoby na powtarzanie bloków kodu, aż do spełnienia jakiegoś warunku. Niezależnie od tego, jakie języki programowania już znasz, instrukcje iteracyjne C# nie powinny być dla ciebie zaskoczeniem i nie wymagają wielu wyjaśnień. W języku C# dostępne są cztery poniższe konstrukcje iteracyjne:
pętla for,
pętla foreach/in,
pętla while,
pętla do/while.
Aby omówić po kolei każdą z nich, utwórz nowy projekt Console Application o nazwie IterationsAndDecisions.
Uwaga. Ostatni punkt tego rozdziału będzie krótki i bardzo konkretny, ponieważ zakładam, że masz doświadczenie w używaniu podobnych słów kluczowych (if, for, switch itd.) w swoim bieżącym języku programowania. Jeśli potrzebujesz dodatkowych informacji, w dokumentacji .NET Framework 4.6 SDK poszukaj tematów „Iteration Statements (C# Reference),” „Jump Statements (C# Reference)” oraz „Selection Statements (C# Reference)”.
Pętla for
Gdy musisz wykonać określoną liczbę razy iterację po bloku kodu, dużo swobody daje ci pętla for. Najkrócej rzecz ujmując, podajesz, ile razy należy powtórzyć blok kodu i warunek końcowy. Zamiast rozpisywać się na ten temat, przejdźmy do przykładu:
// Prosta pętla for.
static void ForLoopExample()
{
// Uwaga! Zmienna "i" jest widoczna tylko w zasięgu pętli for.
for(int i = 0; i < 4; i++)
{
Console.WriteLine("Number is: {0} ", i);
}
// Tutaj zmienna "i" nie jest widoczna.
}
Pisząc instrukcje for w języku C#, możesz stosować wszystkie swoje sztuczki sprawdzone w językach C, C++ i Java. Możesz tworzyć złożone warunki końcowe, nieskończone pętle, pętle wykonywane wstecz (za pomocą operatora --), a także używać słów kluczowych goto, continue i break.
Pętla foreach
Używając słowa kluczowego foreach, możesz iterować po wszystkich elementach w kontenerze bez konieczności sprawdzania górnego limitu. Jednak w przeciwieństwie do pętli for pętla foreach umożliwia jedynie iterację liniową, czyli n + 1 (nie można przejść przez elementy wstecz, sprawdzać tylko co trzeci element itp.).
Jeśli jednak musisz po prostu przejść po kolei przez wszystkie elementy kontenera, pętla foreach to doskonały wybór. Poniżej dwa przykłady jej użycia; w pierwszym iteracja odbywa się po tablicy łańcuchów, a w drugim – po tablicy całkowitoliczbowej. Zwróć uwagę, że typ danych przed słowem kluczowym in odnosi się do typu danych w kontenerze.
// Iteracja przez elementy tablicy za pomocą pętli foreach.
static void ForEachLoopExample()
{
string[] carTypes = {"Ford", "BMW", "Yugo", "Honda" };
foreach (string c in carTypes)
Console.WriteLine(c);
int[] myInts = { 10, 20, 30, 40 };
foreach (int i in myInts)
Console.WriteLine(i);
}
Elementem po słowie kluczowym in może być prosta tablica (tak jak tutaj) lub, mówiąc bardziej konkretnie, dowolna klasa implementująca interfejs IEnumerable. Jak przekonasz się w rozdziale 9, w bibliotekach klas bazowych .NET jest wiele kolekcji, które zawierają implementacje popularnych abstrakcyjnych typów danych (ang. abstract data types, ADT). Każdego z tych elementów (np. generycznego List<T>) można użyć w pętli foreach.
Stosowanie niejawnej typizacji w konstrukcjach foreach
W konstrukcji foreach można też użyć niejawnej typizacji. Jak się można spodziewać, to kompilator prawidłowo domyśli się, jaki jest „typ typu”. Przypomnij sobie pokazaną wcześniej w tym rozdziale przykładową metodę LINQ. Ponieważ nie znasz bazowego typu danych zmiennej subset, iterację po zbiorze wyników możesz wykonać, używając typizacji niejawnej.
static void LinqQueryOverInts()
{
int[] numbers = { 10, 20, 30, 40, 1, 2, 3, 8 };
// Zapytanie LINQ!
var subset = from i in numbers where i < 10 select i;
Console.Write("Values in subset: ");
foreach (var i in subset)
{
Console.Write("{0} ", i);
}
}
Pętle while i do/while
Pętla while przydaje się do wykonywania bloku instrukcji do momentu spełnienia jakiegoś warunku końcowego. Musisz zadbać o to, aby ten warunek został faktycznie spełniony w zasięgu pętli while, bo w przeciwnym razie powstanie pętla nieskończona. Poniższy przykład będzie wyświetlał komunikat "In while loop" aż do momentu, gdy użytkownik zakończy pętlę, wpisując w wierszu poleceń yes:
static void WhileLoopExample()
{
string userIsDone = "";
// test na kopii łańcucha z małych liter.
while(userIsDone.ToLower() != "yes")
{
Console.WriteLine("In while loop");
Console.Write("Are you done? [yes] [no]: ");
userIsDone = Console.ReadLine();
}
}
Spokrewniona z pętlą while jest instrukcja do/while. Podobnie jak tej pierwszej instrukcji do/while używa się, gdy trzeba wykonać jakieś działanie nieokreśloną liczbę razy. Różnica polega na tym, że w przypadku pętli do/while gwarantowane jest przynajmniej jednorazowe wykonanie bloku kodu. W przypadku zwykłej pętli while jest na odwrót: jeśli warunek jest fałszywy na samym początku, to nigdy nie zostanie wykonana.
static void DoWhileLoopExample()
{
string userIsDone = "";
do
{
Console.WriteLine("In do/while loop");
Console.Write("Are you done? [yes] [no]: ");
userIsDone = Console.ReadLine();
}while(userIsDone.ToLower() != "yes"); // Zwróć uwagę na średnik!
}
Konstrukcje decyzyjne i operatory porównania/równości
Wiesz już, jak wykonać iterację po bloku instrukcji, możemy więc przejść do podobnego tematu, czyli sterowania przepływem wykonywania programu. W języku C# istnieją dwie proste konstrukcje pozwalające zmienić przepływ programu w zależności od okoliczności:
instrukcja if/else,
instrukcja switch.
Instrukcja if/else
Zacznijmy od instrukcji if/else. W języku C#, inaczej niż w językach C/C++, działa ona tylko z wyrażeniami logicznymi, a nie z wartościami doraźnymi, takimi jak -1 czy 0.
Operatory równości i relacyjne
W celu uzyskania wartości logicznej w instrukcjach if/else w języku C# używa się operatorów wymienionych w tabeli 3.7.
Tabela 3.7. Operatory równości i relacyjne w języku C#
Operator równości/porównania w C#
Przykład użycia
Znaczenie w praktyce
==
if(age == 30)
Zwraca true tylko wtedy, gdy oba wyrażenia są takie same.
!=
if("Foo" != myStr)
Zwraca true tylko wtedy, gdy wyrażenia są inne.
<
>
<=
>=
if(bonus < 2000)
if(bonus > 2000)
if(bonus <= 2000)
if(bonus >= 2000)
Zwraca true, jeżeli wyrażenie A (bonus) jest kolejno: mniejsze, większe, mniejsze lub równe albo większe lub równe niż wyrażenie B (2000).
Wypada w tym momencie ostrzec programistów C i C++, że w C# sztuczka polegająca na sprawdzaniu warunku dla wartości różnej od zera nie zadziała. Załóżmy, że chcesz sprawdzić, czy wartość zmiennej typu string jest dłuższa niż zero znaków. Aż się prosi o napisanie takiego kodu:
static void IfElseExample()
{
// Tak nie można, ponieważ Length zwraca int, a nie bool.
string stringData = "My textual data";
if(stringData.Length)
{
Console.WriteLine("string is greater than 0 characters");
}
}
Jeśli chcesz użyć właściwości String.Length, aby ustalić prawdziwość bądź fałszywość warunku, musisz zmodyfikować wyrażenie warunkowe, aby zwracana była wartość logiczna:
// Tak można, bo wyznaczana wartość to true lub false.
if(stringData.Length > 0)
{
Console.WriteLine("string is greater than 0 characters");
}
Operatory warunkowe
Instrukcja if może składać się ze złożonych wyrażeń, może również zawierać dodatkowe instrukcje else. Składnia jest taka sama jak w językach C(++) i Java. Oczywiście w języku C# dostępny jest zbiór warunkowych operatorów logicznych do konstruowania takich wyrażeń (patrz tab. 3.8).
Tabela 3.8. Operatory warunkowe C#
Operator
Przykład
Znaczenie w praktyce
&&
if(age == 30 && name == "Fred")
Operator AND. Zwraca true, jeśli wszystkie wyrażenia mają wartość true.
||
if(age == 30 || name == "Fred")
Operator OR. Zwraca true, jeżeli przynajmniej jedno z wyrażeń ma wartość true.
!
if(!myBool)
Operator NOT. Zwraca true, jeśli wartość wyrażenia to false, albo false, jeśli wartość wyrażenia to true.
Uwaga. Gdy zachodzi taka konieczność, operatory && i || skracają procedurę wyznaczania wartości. Oznacza to, że jeżeli złożone wyrażenie zostanie uznane za fałszywe, pozostałe wyrażenia podrzędne nie są już sprawdzane. Jeżeli pomimo to chcesz sprawdzić wszystkie wyrażenia, możesz użyć pokrewnych operatorów & i |.
Instrukcja switch
Kolejną prostą konstrukcją wyboru dostępną w języku C# jest instrukcja switch. Tak jak w innych językach opartych na C instrukcja switch umożliwia sterowanie przepływem programu na podstawie predefiniowanego zbioru opcji. Na przykład, poniższa metoda Main() wyświetla określony łańcuch komunikatu w zależności od wyboru jednej z dwóch przygotowanych opcji (nieprawidłowym wyborem zajmuje się instrukcja default):
// switch na wartościach liczbowych.
static void SwitchExample()
{
Console.WriteLine("1 [C#], 2 [VB]");
Console.Write("Please pick your language preference: ");
string langChoice = Console.ReadLine();
int n = int.Parse(langChoice);
switch (n)
{
case 1:
Console.WriteLine("Good choice, C# is a fine language.");
break;
case 2:
Console.WriteLine("VB: OOP, multithreading, and more!");
break;
default:
Console.WriteLine("Well...good luck with that!");
break;
}
}
Uwaga. W języku C# wymaga się, aby każdy przypadek (włączając default) zawierający instrukcje wykonywalne kończył się instrukcją break lub goto. Chodzi o to, aby uniknąć przechodzenia do instrukcji w następnym bloku case (tzw. fall-through).
Jedną z zalet instrukcji switch w języku C# jest możliwość wyznaczania wartości nie tylko danych liczbowych, ale również danych typu string. Poniższa instrukcja switch robi właśnie coś takiego (zwróć uwagę, że wykorzystując ten sposób, nie trzeba przekształcać danych podanych przez użytkownika na wartości liczbowe):
static void SwitchOnStringExample()
{
Console.WriteLine("C# or VB");
Console.Write("Please pick your language preference: ");
string langChoice = Console.ReadLine();
switch (langChoice)
{
case "C#":
Console.WriteLine("Good choice, C# is a fine language.");
break;
case "VB":
Console.WriteLine("VB: OOP, multithreading and more!");
break;
default:
Console.WriteLine("Well...good luck with that!");
break;
}
}
Możliwe jest również używanie instrukcji switch z wyliczeniowym typem danych. Jak zobaczysz w rozdziale 4, słowo kluczowe enum pozwala zdefiniować niestandardowy zbiór par nazwa/wartość. Aby zaostrzyć apetyt, poniżej ostatnia funkcja pomocnicza, w której instrukcji switch używamy z wyliczeniem System.DayOfWeek. Pewnych elementów składni jeszcze nie wyjaśniałem, ale w tej chwili skoncentruj się na iteracji po samym wyliczeniu; informacje zostaną uzupełnione w kolejnych rozdziałach.
static void SwitchOnEnumExample()
{
Console.Write("Enter your favorite day of the week: ");
DayOfWeek favDay;
try
{
favDay = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), Console.ReadLine());
}
catch (Exception)
{
Console.WriteLine("Bad input!");
return;
}
switch (favDay)
{
case DayOfWeek.Friday:
Console.WriteLine("Yes, Friday rules!");
break;
case DayOfWeek.Monday:
Console.WriteLine("Another day, another dollar");
break;
case DayOfWeek.Saturday:
Console.WriteLine("Great day indeed.");
break;
case DayOfWeek.Sunday:
Console.WriteLine("Football!!");
break;
case DayOfWeek.Thursday:
Console.WriteLine("Almost Friday...");
break;
case DayOfWeek.Tuesday:
Console.WriteLine("At least it is not Monday");
break;
case DayOfWeek.Wednesday:
Console.WriteLine("A fine day.");
break;
}
}
Kod źródłowy. Projekt IterationsAndDecisions znajduje się w podkatalogu Chapter 3.
Podsumowanie
Celem tego rozdziału było przedstawienie wielu podstawowych aspektów programowania w języku C#, w tym konstrukcji, których będziesz używać praktycznie w każdej aplikacji. Omówiłem rolę obiektu aplikacji, a następnie wyjaśniłem, że każdy wykonywalny program C# musi zawierać typ definiujący metodę Main(), która odgrywa rolę punktu wejścia programu. W zasięgu metody Main() możesz z reguły utworzyć dowolną liczbę obiektów, które współdziałając, tchną życie w aplikację.
Następnie, w trakcie omawiania wbudowanych typów danych C#, wyjaśniłem, że każde słowo kluczowe określające typ danych (np. int) to tak naprawdę skrótowy zapis pełnowartościowego typu z przestrzeni nazw System (w tym przypadku chodzi o System.Int32). Z tego powodu każdy typ danych C# ma pewną liczbę wbudowanych składowych. Przy okazji opowiedziałem o konwersji rozszerzającej i zawężającej, a także o roli słów kluczowych checked i unchecked.
Rozdział zakończyłem omówieniem niejawnej typizacji i słowa kluczowego var. Jak wspomniałem, technika ta jest najbardziej przydatna w modelu programowania LINQ. Na końcu zwięźle przedstawiłem różne konstrukcje iteracyjne i decyzyjne w języku C#.
Skoro masz już opanowane częściowo podstawy, w kolejnym rozdziale (rozdz. 4) zakończymy omawianie najważniejszych własności języka. Zdobędziesz w ten sposób solidne podstawy do nauki o obiektowych cechach języka C#, zaczynając od rozdziału 5.