Dane oryginału

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·10³⁸ do +3,4·10³⁸

32-bitowa liczba zmiennoprzecinkowa

double

tak

System.Double

od ±5,0·10–324 do ±1,7·10³⁰⁸

64-bitowa liczba zmiennoprzecinkowa

decimal

tak

System.Decimal

(od –7,9·10²⁸ do 7,9·10²⁸)/(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.