Główna C# 6.0 w pigułce

C# 6.0 w pigułce

,
0 / 0
Jak bardzo podobała Ci się ta książka?
Jaka jest jakość pobranego pliku?
Pobierz książkę, aby ocenić jej jakość
Jaka jest jakość pobranych plików?
C# jest jednym z najlepszych projektów firmy Microsoft. Język ten został od podstaw zaprojektowany jako obiektowy. Charakteryzuje się niezwykłą elastycznością i wszechstronnością. Udostępnia wysokopoziomowe abstrakcje, takie jak wyrażenia, zapytania i kontynuacje asynchroniczne, a równocześnie pozwala na korzystanie z niskopoziomowych mechanizmów, jak własne typy wartościowe programisty czy opcjonalne wskaźniki. C# w wersji 6.0 jest kolejną istotną aktualizacją języka. Programista piszący w C# powinien konsekwentnie poznawać te zmiany.

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

Najważniejsze zagadnienia ujęte w książce:
składnia, typy oraz zmienne C#
bezpieczeństwo kodu i dyrektywy preprocesora
rdzenne technologie i techniki platformy .NET Framework, w tym LINQ, XML, kolekcje, kontrakty kodu, zarządzanie pamięcią, refleksja, programowanie dynamiczne
kompilator Roslyn, w tym jego architektura, struktura drzewa składni i model semantyczny
C# jest szybki, efektywny, wygodny — to narzędzie w sam raz dla Ciebie!

Joseph Albahari jest autorem kilku książek dotyczących C# oraz LINQ. Napisał też LINQPad, popularny program do testowania zapytań LINQ.
Ben Albahari — były kierownik programowy w Microsofcie; współtworzył takie projekty, jak .NET Compact Framework i ADO.NET. Jeden z założycieli firmy Genamics zajmującej się produkcją narzędzi dla programistów C# i J++ oraz oprogramowania do analizy DNA i sekwencjonowania białek. Jest autorem i współautorem kilku książek dotyczących C#.
Rok:
2016
Wydanie:
VI
Wydawnictwo:
Helion
Język:
polish
Plik:
RAR, 15,96 MB
Ściągnij (rar, 15,96 MB)

Możesz być zainteresowany Powered by Rec2Me

 

Najbardziej popularne frazy

 
0 comments
 

To post a review, please sign in or sign up
Możesz zostawić recenzję książki i podzielić się swoimi doświadczeniami. Inni czytelnicy będą zainteresowani Twoją opinią na temat przeczytanych książek. Niezależnie od tego, czy książka ci się podoba, czy nie, jeśli powiesz im szczerze i szczegółowo, ludzie będą mogli znaleźć dla siebie nowe książki, które ich zainteresują.
1

Introduction to Social Problems

Rok:
2015
Język:
english
Plik:
PDF, 15,15 MB
0 / 0
2

C# 6.0. Kompletny przewodnik dla praktyków

Rok:
2016
Język:
polish
Plik:
RAR, 13,33 MB
0 / 0
 

Joseph Albahari, Ben Albahari - C# 6.0 w pigułce. Wydanie VI [2016 - HQ - Helion].epub
  [image: ]






  Joseph Albahari, Ben Albahari


  C# 6.0 w pigułce


  Wydanie VI






  Tytuł oryginału: C# 6.0 in a Nutshell, 6th Edition


  Tłumaczenie: Łukasz Piwko (wstęp, rozdz. 1 – 11, 20 – 24)
  Robert Górczyński (12 – 18, 26 – 27)
  Jakub Hubisz (rozdz. 19, 25)


  ISBN: 978-83-283-2424-4


  © 2016 Helion SA


  Authorized Polish translation of the English edition of C# 6.0 in a Nutshell, 6th Edition ISBN9781491927069 © 2016 Joseph Albahari, Ben Albahari.


  This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same.


  All rights reserved. No part of this book may be reproduced or transmitted in any form or by anymeans, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher.


  Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji.


  Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ichwłaścicieli.


  Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane ztym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nieponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce.


  Wydawnictwo HELION

  ul. Kościuszki 1c, 44-100 GLIWICE
  tel. 32 231 22 19, 32 230 98 63
  e-mail: helion@helion.pl
  WWW: http://helion.pl (księgarnia internetowa, katalog książek)


  Drogi Czytelniku!

 ;  Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres

  http://helion.pl/user/opinie/c6pig6_ebook

  Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.


  Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/c6pig6.zip


  
    
      	Poleć książkę


      	Kup w wersji papierowej


      	Oceń książkę

    

  


  
    
      	Księgarnia internetowa


      	Lubię to! » nasza społeczność

    

  

[image: byRARE2016]




  
    [image: ]


    Wstęp


    C# 6.0 to piąta poważna aktualizacja flagowego języka programowania firmy Microsoft, który odznacza się niezwykłą elastycznością i wszechstronnością. Z jednej strony zapewnia wysokopoziomowe abstrakcje, takie jak wyrażenia zapytania i kontynuacje asynchroniczne, a z drugiej udostępnia niskopoziomowe mechanizmy pozwalające osiągnąć maksymalną wydajność aplikacji dzięki wykorzystaniu takich konstrukcji, jak własne typy wartościowe programisty czy opcjonalne wskaźniki.


    Ceną za ten rozwój jest to, że programista musi nauczyć się wielu nowych rzeczy. I choć takie narzędzia, jak funkcja IntelliSense Microsoftu oraz podręczniki internetowe stanowią nieocenioną pomoc w pracy, to jednak by z nich skutecznie korzystać, trzeba mieć pewne ogólne rozeznanie w technologii. Ta książka zawiera właśnie taki zbiór wiedzy przedstawiony w zwięzły i spójny sposób, bez zbędnych szczegółów i przydługich wprowadzeń.


    Tak jak w trzech poprzednich wydaniach podstawę książki C# 6.0 w pigułce stanowią opisy pojęć ikonkretne przypadki użycia, dzięki czemu tekst można bez przeszkód czytać zarówno od deski do deski, jak i na wyrywki. Ponadto mimo że opisywane zagadnienia są traktowane dogłębnie, do zrozumienia wywodów wystarczy podstawowa wiedza z zakresu programowania. Dzięki temu publikacja ta jest przystępna zarówno dla średniozaawansowanych, jak i zaawansowanych czytelników.


    Tematami tej książki są C#, CLR oraz rdzenne zestawy platformy Framework. Dokonaliśmy tego wyboru tematów, aby móc poświęcić nieco uwagi takim trudnym tematom, jak współbieżność, bezpieczeństwo i domeny aplikacji, nie zaniedbując jednocześnie kwestii czytelności i nie spłycając informacji. Nowe składniki języka C# 6.0 i związanej z nim platformy specjalnie oznaczono, dzięki czemu ta książka może też służyć jako podręcznik do C# 5.0.


    Adresaci książki


    Niniejsza książka jest przeznaczona dla średniozaawansowanych i zaawansowanych programistów. Nie musisz być programistą języka C#, ale bez znajomości pewnych podstawowych zagadnień programistycznych może być Ci trudno. Uwaga dla początkujących: tę pozycję należy traktować jako uzupełnienie, a nie zamiennik dla normalnego kursu wprowadzającego do programowania.


    Jeśli znasz język C# 5.0, to znajdziesz tu informacje o nowych składnikach języka oraz nowy rozdział okompilatorze Roslyn zwanym przez firmę Microsoft kompilatorem usługowym (ang. compiler as a service).


    Książka ta jest doskonałym uzupełnieniem wszystkich innych pozycji na temat technologii stosowanych, takich jak WPF, ASP.NET czy WCF. To, co w tych książkach jest traktowane pobieżnie lub pomijane, w książce C# 6.0 w pigułce opisano szczegółowo i vice versa.


    Jeżeli szukasz tylko pobieżnego opisu różnych technologii .NET Framework, to trafiłeś pod niewłaściwy adres. Książka ta nie przyda się też nikomu, kto chce poznać interfejsy API do programowania na tablety lub system Windows Phone.


    Struktura książki


    Trzy pierwsze rozdziały po wprowadzeniu dotyczą wyłącznie języka C# i zawierają opis podstaw składni, typów oraz zmiennych, a także bardziej zaawansowanych tematów, takich jak bezpieczeństwo kodu i dyrektywy preprocesora. Czytelnicy nieznający języka C# powinni przeczytać te rozdziały po kolei.


    W pozostałych rozdziałach opisaliśmy rdzenne technologie i techniki platformy .NET Framework, awięc np.: LINQ, XML, kolekcje, kontrakty kodu, współbieżność, wejście i wyjście oraz praca wsieci, zarządzenie pamięcią, refleksja, programowanie dynamiczne, atrybuty, bezpieczeństwo, domeny aplikacji oraz macierzyste mechanizmy współdziałania. Większość tych rozdziałów można przeczytać w dowolnej kolejności. Wyjątek stanowią rozdziały 6. i 7., które zawierają informacje wprowadzające do dalszych tematów. Trzy rozdziały o LINQ także najlepiej jest czytać po kolei. Ponadto do zrozumienia niektórych rozdziałów przydatna jest znajomość zagadnień dotyczących współbieżności, o których jest mowa w rozdziale 14.


    Co jest potrzebne


    Do wypróbowania przykładów kodu z tej książki potrzebny jest kompilator C# 6.0 i platforma Microsoft .NET Framework 4.6. Ponadto warto mieć w zasięgu ręki dokumentację .NET (dostępna winternecie), aby móc w niej sprawdzać właściwości różnych typów i składowych.


    Choć kod źródłowy można pisać nawet w Notatniku, a kompilator uruchamiać z poziomu wiersza poleceń, o wiele efektywniejszym rozwiązaniem jest skorzystanie ze specjalnego brudnopisu (ang. code scratchpad) do szybkiego testowania fragmentów kodu oraz ze zintegrowanego środowiska programistycznego (IDE) do tworzenia plików wykonywalnych i bibliotek.


    Jako brudnopis może posłużyć program LINQPad 5 lub nowszy ze strony: http://www.linqpad.net (darmowy). Program ten w pełni obsługuje język C# 6.0 i jest serwisowany przez jednego z autorów tej książki.


    Jeśli chodzi o zintegrowane środowisko programistyczne, to odpowiednie jest Microsoft Visual Studio 2015 w każdej wersji oprócz darmowej.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Wszystkie listingi kodu z rozdziałów od 2. do 10. oraz dotyczących współbieżności, programowania równoległego i dynamicznego są dostępne w interaktywnej (dającej się edytować) postaci próbek LINQPad. Można je pobrać z serwera FTP wydawnictwa Helion pod adresem: ftp://ftp.helion.pl/przyklady/c6pig6.zip.

          
        

      
    


    Konwencje typograficzne


    W tej książce relacje między typami przedstawiono za pomocą diagramów UML, jak pokazany na rysunku W.1. Równoległobok oznacza klasę abstrakcyjną, a kółko interfejs. Linia z pustym trójkątem reprezentuje dziedziczenie, a trójkąt wskazuje typ bazowy. Linia z grotem strzałki oznacza asocjację jednokierunkową. Natomiast linia bez grotu oznacza asocjację dwukierunkową.


    [image: ]


    Rysunek W.1. Przykładowy diagram


    Poniżej przedstawiono opis konwencji typograficznych zastosowanych w niniejszej książce:


    Pismo pogrubione


    Tego rodzaju pisma użyto do oznaczenia nowych, ważnych pojęć.


    Pismo pochyłe


    Tego rodzaju pisma użyto do oznaczenia identyfikatorów URI, nazw plików oraz katalogów.


    Pismo o stałej szerokości znaków


    W ten sposób zapisane są wszystkie fragmenty kodu źródłowego, słowa kluczowe i identyfikatory oraz dane zwracane przez programy.


    Pismo pochyłe o stałej szerokości znaków


    Wyróżnia tekst, w miejsce którego należy wstawić rzeczywiste wartości.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            W ten sposób oznaczamy wskazówki i sugestie.

          
        

      
    


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            W ten sposób oznaczamy ogólne uwagi.

          
        

      
    


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            W ten sposób oznaczamy ostrzeżenia.

          
        

      
    


    Sposób użycia przykładów kodu


    Materiały pomocnicze (przykłady kodu, ćwiczenia) można pobrać z serwera FTP wydawnictwa Helion pod adresem: ftp://ftp.helion.pl/przyklady/c6pig6.zip.


    Ta książka ma Ci pomóc w pracy. Ogólnie rzecz biorąc, kodu znajdującego się w tej książce możesz używać we własnych programach i dokumentacjach bez proszenia kogokolwiek o zgodę, chyba że wykorzystasz duże fragmenty. Jeśli np. w pisanym programie użyjesz kilku fragmentów kodu z tej książki, nie musisz pytać o pozwolenie. Aby sprzedawać i rozprowadzać płyty CD-ROM zprzykładami z książek wydawnictwa Helion, trzeba mieć zezwolenie. Aby odpowiedzieć komuś na pytanie, cytując fragment tej książki wraz z kodem źródłowym, nie trzeba mieć zezwolenia. Aby wykorzystać dużą ilość kodu źródłowego z tej książki w dokumentacji własnego produktu, trzeba mieć pozwolenie.


    Informacje o źródle użytych fragmentów są mile widziane, ale niewymagane. Notka powinna zawierać nazwisko autora, tytuł publikacji oraz dane wydawcy, np. Joseph Albahari i Ben Albahari, C# 6.0 w pigułce. Wydanie VI, Helion, Gliwice 2016.


    Jeśli chcesz się upewnić, że wykorzystujesz przykłady kodu, nie łamiąc naszych zasad, wyślij pytanie na adres: helion@helion.com.


    Podziękowania


    Joseph Albahari


    Przede wszystkim dziękuję mojemu bratu Benowi Albahari za namówienie mnie do napisania książki C# 3.0 in a Nutshell, której powodzenie sprawiło, że powstały jeszcze trzy kolejne wydania. Ben podobnie jak ja lubi kwestionować powszechnie przyjętą wiedzę i ma żyłkę do rozbierania rzeczy na czynniki pierwsze, aby dociec, jak naprawdę działają.


    Wielkim zaszczytem dla mnie było współpracować ze świetnymi redaktorami merytorycznymi. W tym wydaniu bezcennych porad udzieliły nam następujące osoby: Jared Parsons, Stephen Toub, Matthew Groves, Dixin Yan, Lee Coward, Bonnie DeWitt, Wonseok Chae, Lori Lalonde oraz James Montemagno.


    Niniejsza książka powstała na bazie poprzedniego wydania, przy którym również pracowało wielu redaktorów merytorycznych zasługujących na uhonorowanie — są to: Eric Lippert, Jon Skeet, Stephen Toub, Nicholas Paldino, Chris Burrows, Shawn Farkas, Brian Grunkemeyer, Maoni Stephens, David DeWinter, Mike Barnett, Melitta Andersen, Mitch Wheat, Brian Peek, Krzysztof Cwalina, Matt Warren, Joel Pobar, Glyn Griffiths, Ion Vasilian, Brad Abrams, Sam Gentile oraz Adam Nathan.


    Cieszę się, że wiele z tych osób to znakomici przedstawiciele Microsoftu i dziękuję im wszystkim za wzniesienie tej książki na wyższy poziom jakości.


    Ponadto chciałbym podziękować pracownikom wydawnictwa O’Reilly, m.in. mojemu najlepszemu redaktorowi Brianowi MacDonaldowi. Osobiście dziękuję też Miri i Soni.


    Ben Albahari


    Ponieważ podziękowania mojego brata znajdują się powyżej, wystarczy, że się pod nimi podpiszę :). Obaj programujemy od dziecka (mieliśmy wspólny komputer Apple Ile; on pisał swój własny system operacyjny, a ja pisałem grę w wisielca), więc fajnie, że teraz razem piszemy książki. Mam nadzieję, że tak jak dla nas wzbogacającym doświadczeniem było pisanie tej książki, tak dla czytelników równie owocne będzie jej czytanie.


    Chciałbym też podziękować moim kolegom z Microsoftu. Pracuje tam wielu bardzo inteligentnych ludzi i mam na myśli nie tylko ich możliwości intelektualne, ale szersze cechy emocjonalne. Brakuje mi tej pracy. Szczególnie dużo nauczyłem się od Briana Beckmana, któremu jestem bardzo wdzięczny.

  






  
    [image: ]


    Rozdział 1.

    Wprowadzenie do C# i .NET Framework


    C# to obiektowy język programowania ogólnego przeznaczenia z bezpiecznymi typami. Jednym z jego podstawowych założeń jest możliwość szybkiego pisania programów. Dlatego też projektanci języka starali się zachować balans między prostotą, ekspresyjnością i wydajnością. Głównym architektem trzymającym pieczę nad tym językiem od samego początku jest Anders Hejlsberg (twórca Turbo Pascala i architekt Delphi). Język C# jest neutralny, jeśli chodzi o platformę, ale został szczególnie dobrze przystosowany do współpracy z platformą Microsoft .NET Framework.


    Obiektowość


    Język C# implementuje szeroki wachlarz technik obiektowych, takich jak hermetyzacja, dziedziczenie ipolimorfizm. Hermetyzacja to technika polegająca na tworzeniu ograniczonych obiektów, których zewnętrzne (publiczne) mechanizmy są oddzielone od wewnętrznych (prywatnych) szczegółów implementacyjnych. Cechy wyróżniające język C# w odniesieniu do cech obiektowych to:


    Jednolity system typów


    Podstawowym blokiem budowy programu w C# jest hermetyczna jednostka danych i funkcji zwana typem. Język C# ma jednolity system typów, w którym wszystkie typy mają jeden wspólny typ bazowy. Oznacza to, że wszystkie typy — czy to reprezentujące obiekty biznesowe, czy wartości podstawowe, takie jak liczby — mają wspólny zbiór elementów funkcjonalności. Na przykład egzemplarz każdego typu można przekonwertować na łańcuch, używając metody ToString.


    Klasy i interfejsy


    W tradycyjnym modelu obiektowości jedynym rodzajem typów jest klasa. W C# występują jeszcze inne rodzaje typów, z których jednym jest interfejs. Interfejs jest podobny do klasy, ale różni się od niej tym, że tylko opisuje składowe. Implementacja tych składowych znajduje się w typach implementujących ten interfejs. Najbardziej przydatne interfejsy są w przypadkach, gdy trzeba skorzystać z wielodziedziczenia (C# w odróżnieniu od takich języków, jak C++ i Eiffel nie pozwala na to, by jedna klasa dziedziczyła po więcej niż jednej innej klasie).


    Własności, metody i zdarzenia


    W czystym paradygmacie obiektowym wszystkie funkcje są metodami (tak jest w języku Smalltalk). W C# metody są tylko jednym rodzajem składowych funkcyjnych, do których zaliczają się też własności i zdarzenia (są też inne). Własności to składowe funkcyjne reprezentujące fragment stanu obiektu, np. kolor przycisku albo tekst etykiety. Natomiast zdarzenia to składowe funkcyjne ułatwiające podejmowanie działań w reakcji na zmiany stanu obiektów.


    Choć język C# jest głównie obiektowy, można w nim też znaleźć pewne cechy zaczerpnięte z paradygmatu programowania funkcyjnego, a konkretnie:


    Funkcje można traktować jako wartości


    Dzięki delegatom w języku C# funkcje można przekazywać jako wartości do innych funkcji iz nich.


    C# umożliwia czyste programowanie funkcyjne


    Podstawą programowania funkcyjnego jest unikanie używania zmiennych, których wartości się zmieniają, na rzecz stosowania technik deklaratywnych. Język C# ma kluczowe elementy funkcjonalności umożliwiające stosowanie tych technik (zalicza się do nich m.in. pisanie nienazwanych funkcji w locie), które „przechwytują” zmienne (wyrażenia lambda) oraz możliwość programowania listowego (ang. list programming) lub reaktywnego (ang. reactive programming) za pomocą wyrażeń zapytań (ang. query expressions). Ponadto język C# 6.0 zawiera automatyczne własności tylko do odczytu pomocne przy pisaniu typów niezmiennych.


    Bezpieczeństwo typów


    Język C# jest bezpieczny pod względem typów, tzn. egzemplarze różnych typów mogą wchodzić w interakcje tylko poprzez zdefiniowane przez siebie protokoły, co zapewnia ich wewnętrzną spójność. Na przykład w języku C# nie można posługiwać się typem łańcuchowym tak, jakby był typem całkowitoliczbowym.


    Mówiąc dokładniej: w języku C# zastosowano statyczną kontrolę typów (ang. static typing), tzn. typy danych użytych w programie są sprawdzane w czasie kompilacji. Jest to dodatek do kontroli typów prowadzonej także w czasie działania programu.


    Statyczna kontrola typów pozwala wyeliminować wiele błędów, zanim program zostanie w ogóle uruchomiony. Przenosi ciężar odpowiedzialności za ich wykrywanie z testów jednostkowych prowadzonych w czasie działania programu na kompilator, który musi sprawdzić, czy wszystkie typy zostały użyte zgodnie z ich przeznaczeniem. To bardzo ułatwia zapanowanie nad dużymi programami, sprawia, że łatwiej przewidzieć ich zachowanie oraz zapewnia większą niezawodność. Ponadto dzięki statycznej kontroli typów mogą działać takie narzędzia pomocnicze, jak funkcja IntelliSense wśrodowisku Visual Studio, która zna typy wszystkich zmiennych, więc jest w stanie określić zestaw metod, jakie na nich można wywoływać.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Część kodu w języku C# może też być poddana dynamicznej kontroli typów za pomocą słowa kluczowego dynamic (wprowadzonego w C# 4.0). Mimo to podstawą tego języka jest statyczna kontrola typów.

          
        

      
    


    C# można też określić jako język o ścisłej kontroli typów (ang. strongly typed language), ponieważ obecne w nim zasady dotyczące sposobu używania typów (nieważne, czy egzekwowane statycznie, czy dynamicznie) są bardzo ścisłe. Na przykład nie można wywołać funkcji przyjmującej jako argument liczby całkowitej z argumentem zmiennoprzecinkowym, chyba że uprzednio jawnie przekonwertuje się tę liczbę zmiennoprzecinkową na typ całkowitoliczbowy. To pozwala uniknąć wielu błędów.


    Ponadto ścisła kontrola typów odgrywa ważną rolę w uruchamianiu kodu C# w piaskownicy (ang. sandbox), czyli w środowisku, w którym wszystkie aspekty bezpieczeństwa znajdują się pod kontrolą hosta. W piaskownicy ważne jest, aby nie dało się w dowolny sposób uszkodzić stanu obiektu przez obejście reguł kontroli typów.


    Zarządzanie pamięcią


    W języku C# pamięcią automatycznie zarządza system wykonawczy. Środowisko uruchomieniowe CLR (ang. Common Language Runtime) zawiera moduł usuwania nieużywanych obiektów (ang. garbage collector), który jest wykonywany w ramach programu w celu odzyskania pamięci zajmowanej przez już nieużywane obiekty. To uwalnia programistę od konieczności pamiętania owłasnoręcznej dezalokacji pamięci oraz eliminuje problemy związane z nieprawidłowym użyciem wskaźników, które to problemy nękają m.in. programistów języka C++.


    W języku C# nie zrezygnowano całkowicie z wskaźników, tylko sprawiono, że są rzadko potrzebne. Czasami używa się ich w miejscach o ostrych wymaganiach wydajnościowych i w celu zapewnienia jak najlepszej współpracy między pewnymi składnikami. Jednak posługiwanie się nimi jest dozwolone tylko w blokach oznaczonych jako niebezpieczne (unsafe).


    Platformy


    Kiedyś w języku C# pisało się w zasadzie tylko programy przeznaczone do używania w systemach Windows. Ostatnio jednak firma Microsoft wraz z innymi firmami postanowiła zainwestować w inne platformy, m.in. Mac OS X i iOS oraz Android. Xamarin™ umożliwia programowanie w języku C# na różne platformy urządzeń przenośnych, a biblioteki PCL (ang. Portable Class Libraries) zyskują coraz szersze uznanie. ASP.NET 5 firmy Microsoft to nowy zbiór technologii do tworzenia aplikacji internetowych, które mogą działać na platformie .NET Framework lub .NET Core — nowym niewielkim, szybkim i otwartym wieloplatformowym środowisku wykonawczym.


    Powiązania C# z CLR


    Język C# jest zależny od systemu wykonawczego wyposażonego w wiele funkcji, takich jak automatyczne zarządzanie pamięcią czy obsługa wyjątków. Budową ściśle przypomina budowę systemu CLR Microsoftu, który zapewnia wymienione elementy funkcjonalności (choć technicznie język C# jest niezależny od CLR). Ponadto system typów C# dokładnie odpowiada systemowi typów CLR (np. mają wspólne definicje typów predefiniowanych).


    CLR i .NET Framework


    Platforma .NET Framework składa się z systemu CLR i dużego zbioru bibliotek. Biblioteki dzielą się na rdzenne (im poświęcona jest ta książka) i stosowane, które zależą od rdzennych. Rysunek 1.1 przedstawia ogólny przegląd tych bibliotek (i stanowi drogowskaz do zawartości tej książki).


    [image: ]


    Rysunek 1.1. Tematy opisane w tej książce i rozdziały, w których te opisy się znajdują. Nieporuszane zagadnienia znajdują się poza obwodem największego koła


    CLR to system wykonawczy służący do wykonywania kodu zarządzanego. C# jest jednym z kilku języków zarządzanych podlegających kompilacji do postaci kodu zarządzanego. Kod zarządzany jest zapisywany w tzw. zestawach (ang. assembly) pod postacią plików wykonywalnych (.exe) lub bibliotek (.dll) wraz z informacjami o typie zwanymi metadanymi.


    Kod zarządzany jest reprezentowany w języku pośrednim IL (ang. intermediate language). Gdy CLR wczytuje zestaw, konwertuje zawarty w nim kod IL na macierzysty kod maszynowy, np. x86. Konwersja ta jest przeprowadzana przez kompilator JIT (ang. just-in-time) systemu wykonawczego CLR. Zestaw zachowuje prawie wszystkie pierwotne konstrukcje językowe, dzięki czemu łatwo można go przeglądać, a nawet dynamicznie generować.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Zawartość zestawu IL można przejrzeć i zdekompilować za pomocą specjalnych narzędzi, np.: ILSpy, dotPeek (JetBrains) lub Reflector (Red Gate).

          
        

      
    


    Twórcy aplikacji do sklepu Windows Store mogą też bezpośrednio generować kod macierzysty (macierzysty w przypadku platformy .NET). Umożliwia to szybsze uruchamianie programów oraz lepsze zarządzanie pamięcią (co jest szczególnie ważne w urządzeniach przenośnych), a także przyspiesza działania aplikacji dzięki statycznej konsolidacji i innym technikom optymalizacyjnym.


    W środowisku CLR działa wiele usług, np. moduł zarządzania pamięcią, system ładowania bibliotek oraz usługi zabezpieczające. Środowisko CLR jest neutralne językowo, a więc można dla niego tworzyć programy w różnych językach, np. C#, F#, Visual Basic .NET czy zarządzany C++.


    Platforma .NET Framework zawiera biblioteki umożliwiające utworzenie w zasadzie każdej aplikacji na system Windows i sieciowej. W rozdziale 5. znajduje się przegląd bibliotek .NET Framework.


    C# i środowisko wykonawcze systemu Windows


    Język C# współpracuje też ze środowiskiem wykonawczym systemu Windows (ang. Windows Runtime — WinRT). WinRT to interfejs wykonawczy i środowisko wykonawcze umożliwiające korzystanie z obiektowych bibliotek w sposób niezależny od języka. Środowisko to jest obecne w systemie Windows 8 i nowszych oraz (częściowo) jest ulepszoną wersją modelu COM (ang. Component Object Model) Microsoftu (zob. rozdział 25.).


    System Windows 8 i nowsze zawierają zestaw niezarządzanych bibliotek WinRT służących jako platforma do budowy aplikacji z obsługą ekranów dotykowych oferowanych w sklepie aplikacji firmy Microsoft. (Termin WinRT odnosi się także do tych bibliotek). Dzięki temu, że są częścią WinRT, biblioteki te mogą być z łatwością wykorzystywane nie tylko w programach C# i VB, ale iC++ oraz JavaScript.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Niektóre biblioteki WinRT mogą być wykorzystywane także w zwykłych aplikacjach nieprzeznaczonych na tablety. Jednak zależność od WinRT automatycznie sprawia, że program może być uruchamiany tylko w systemach nie starszych od Windows 8.

          
        

      
    


    Biblioteki WinRT obsługują „nowoczesny” interfejs użytkownika (do pisania immersyjnych aplikacji dotykowych), funkcje specjalne urządzeń przenośnych (czujniki, wiadomości tekstowe itd.) oraz szeroki wybór rdzennych funkcji pokrywających się częściowo z platformą .NET Framework. Ze względu na to pokrywanie się elementów funkcjonalności w Visual Studio wbudowano profil referencyjny (zbiór zestawów referencyjnych .NET) dla projektów Windows Store — profil chowa te części platformy .NET Framework, które pokrywają się z WinRT. Profil ukrywa także duże części platformy .NET Framework uważane za niepotrzebne w aplikacjach przenośnych (np. funkcje dostępu do bazy danych). Sklep aplikacji Microsoftu, który kontroluje przekazywanie programów na urządzenia użytkowników, odrzuca wszelkie programy próbujące korzystać z ukrytych typów.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Zestaw referencyjny (ang. reference assembly) służy tylko do kompilowania aplikacji imoże mieć ograniczony zbiór typów i składowych. Umożliwia to programistom instalowanie kompletnej platformy .NET Framework na swoich komputerach i programowanie tak, jakby zainstalowali tylko jej określoną część. Rzeczywista funkcjonalność podczas działania programu pochodzi od zestawów z globalnego bufora zestawów (ang. global assembly cache — zob. rozdział 18.), który może zastępować zestawy referencyjne.

          
        

      
    


    Ukrycie znacznej części platformy .NET ułatwia naukę programistom nieobeznanym jeszcze z platformą Microsoftu, choć są dwa ważniejsze powody, dla których się to robi:


    
      	Ograniczanie aplikacji do piaskownicy (redukcja funkcjonalności w celu zmniejszenia ewentualnych szkód spowodowanych przez szkodliwe oprogramowanie). Na przykład zabroniony jest dostęp do przypadkowych plików, a możliwości komunikacji z innymi programami na komputerze są bardzo ograniczone.


      	Możliwość instalowania w tabletach o niewielkiej mocy obliczeniowej z systemem Windows RT okrojonej wersji platformy .NET Framework, co pozwala zmniejszyć ilość miejsca zajmowanego przez system operacyjny.

    


    Tym, co odróżnia WinRT od COM, jest to, że WinRT rzutuje swoje biblioteki na różne języki, C#, VB, C++ i JavaScript, tak że w każdym z nich typy WinRT wydają się (prawie) jak napisane specjalnie dla niego. Na przykład WinRT dostosowuje reguły stosowania dużych liter do standardów języka docelowego, a nawet remapuje niektóre funkcje i interfejsy. Zestawy WinRT zawierają dodatkowo pliki metadanych .winmd, które mają taki sam format, jak pliki zestawów .NET, co pozwala na korzystanie z nich bez specjalnego przetwarzania. W istocie można nawet nie wiedzieć, że korzysta się z typów WinRT, a nie .NET, jeśli nie bierze się pod uwagę różnic w przestrzeniach nazw. Ponadto typy WinRT podlegają ograniczeniom w stylu COM, np. w ograniczonym stopniu obsługują dziedziczenie i typy generyczne.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            WinRT nie zastępuje całkowicie platformy .NET Framework, która wciąż jest zalecana (i niezbędna) do programowania standardowych aplikacji na komputery osobiste i na serwery oraz ma następujące zalety:


            
              	Działanie programów nie jest ograniczone do piaskownicy.


              	Programy mogą wykorzystywać całą platformę .NET i dowolne biblioteki zewnętrzne.


              	Dystrybucja programów nie musi się odbywać poprzez sklep Windows Store.


              	Aplikacje mogą korzystać z najnowszej wersji platformy bez konieczności posiadania przez użytkowników najnowszej wersji systemu operacyjnego.

            

          
        

      
    


    Co nowego w C# 6.0


    Największą nowością w C# 6.0 jest kompilator napisany całkowicie od nowa w C#. Ten znany pod nazwą Roslyn nowy kompilator udostępnia programiście poprzez biblioteki cały proces kompilacji, dzięki czemu można wykonywać analizę kodu w przypadku dowolnego kodu źródłowego (zob. rozdział 27.). Kompilator ma otwarty kod źródłowy, który można znaleźć pod adresem: github.com/dotnet/roslyn.


    Ponadto w C# 6.0 wprowadzono kilka drobniejszych, ale istotnych poprawek mających przede wszystkim pozwolić lepiej uporządkować kod.


    Operator warunkowy null (Elvis) (zob. „Operatory null” w rozdziale 2.) eliminuje konieczność jawnego sprawdzania wartości null przed wywołaniem metody lub użyciem składowej typu. Wynikiem poniższego przykładu będzie wartość null w zmiennej result, a nie błąd NullReferenceException:

    System.Text.StringBuilder sb = null;


    
      string result = sb?.ToString(); // wynik to null

    


    Funkcje wyrażeniowe (ang. expression-bodied functions — zob. „Metody” w rozdziale 3.) to bardziej zwięzły sposób zapisu metod, własności, operatorów i indeksatorów zawierających tylko jedno wyrażenie w stylu wyrażeń lambda:

    public int TimesTwo (int x) => x * 2;


    
      public string SomeProperty => "Wartość własności";

    


    Inicjalizatory własności (rozdział 3.) umożliwiają przypisywanie wartości początkowej własnościom automatycznym:

    public DateTime Created { get; set; } = DateTime.Now;


    Zainicjalizowane własności mogą być też tylko do odczytu:

    public DateTime Created { get; } = DateTime.Now;


    Własności tylko do odczytu można także ustawiać w konstruktorze, co ułatwia tworzenie typów niezmiennych (tylko do odczytu).


    Inicjalizatory indeksów (rozdział 4.) umożliwiają jednoetapową inicjalizację każdego typu udostępniającego indeksator:

    new Dictionary<int,string>()


    
      {

    


    
        [3] = "trzy",

    


    
        [10] = "dziesięć"

    


    
      }

    


    Interpolacja łańcuchów (zob. „Typ string” w rozdziale 2.) stanowi zwięzłą alternatywę wobec metody string.Format:

    string s = $"Dziś jest {DateTime.Now.DayOfWeek}";


    Filtry wyjątków (zob. „Instrukcje try i wyjątki” w rozdziale 4.) umożliwiają stosowanie warunków do bloków catch:

    try


    
      {

    


    
        new WebClient().DownloadString("http://asef");

    


    
      }

    


    
      catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)

    


    
      {

    


    
        ...

    


    
      }

    


    Dyrektywa using static (zob. „Przestrzenie nazw” w rozdziale 2.) umożliwia zaimportowanie wszystkich statycznych składowych typu, dzięki czemu można ich używać bez kwalifikatorów:

    using static System.Console;


    
      ...

    


    
      WriteLine ("Witaj, świecie!"); // WriteLine zamiast Console.WriteLine

    


    Operator nameof (rozdział 3.) zwraca nazwę zmiennej, typu lub innego symbolu w postaci łańcucha. Pozwala to uniknąć awarii w kodzie, gdy zmieni się nazwę symbolu w Visual Studio:

    int capacity = 123;


    
      string x = nameof (capacity); // x to "capacity"

    


    
      string y = nameof (Uri.Host); // y to "Host"

    


    I w końcu nie można używać słowa kluczowego await w blokach catch i finally.


    Co było nowego w C# 5.0


    Największą nowością w C# 5.0 była obsługa funkcji asynchronicznych za pomocą dwóch nowych słów kluczowych: async i await. Funkcje te umożliwiają tworzenie asynchronicznych kontynuacji, które ułatwiają pisanie interaktywnych i bezpiecznych wątkowo bogatych funkcjonalnie aplikacji. Ponadto dzięki nim łatwiej się pisze współbieżne i wydajne korzystające z wejścia i wyjścia aplikacje niewiążące zasobów wątkowych z operacjami.


    Szerzej o funkcjach asynchronicznych piszemy w rozdziale 14.


    Co było nowego w C# 4.0


    W C# 4.0 dodano następujące nowości:


    
      	wiązanie dynamiczne,


      	parametry opcjonalne i argumenty nazwane,


      	wariancja typów z generycznymi interfejsami i delegatami,


      	udoskonalenia w zakresie współdziałania z COM.

    


    Wiązanie dynamiczne (rozdziały 4. i 20.) to technika polegająca na odłożeniu wiązania — procesu rozpoznawania typów i składowych — z czasu kompilacji do czasu działania programu. Jej zastosowanie pozwala uniknąć konieczności używania skomplikowanych rozwiązań z wykorzystaniem refleksji. Ponadto wiązanie dynamiczne jest przydatne przy współdziałaniu z językami dynamicznymi i komponentami COM.


    Parametry opcjonalne (rozdział 2.) umożliwiają funkcjom definiowanie domyślnych wartości parametrów, dzięki czemu w wywołaniu można pominąć niektóre argumenty. Natomiast argumenty nazwane (ang. named arguments) pozwalają na identyfikowanie argumentów wywołania funkcji za pomocą nazw, a nie pozycji na liście argumentów.


    Reguły wariancji typów (ang. type variance) zostały w C# 4.0 rozluźnione (rozdziały 3. i 4.), tak że parametry typów w interfejsach i delegatach generycznych mogą być oznaczane jako kowariantne lub kontrawariantne, co pozwala na bardziej naturalne konwersje typów.


    Techniki współdziałania z COM (rozdział 25.) zostały w C# 4.0 rozszerzone na trzy sposoby. Po pierwsze, argumenty można przekazywać przez referencję bez użycia słowa kluczowego ref (jest to szczególnie przydatne w połączeniu z parametrami opcjonalnymi). Po drugie, zestawy zawierające typy interoperacyjne COM można dołączać, a nie tylko się do nich odnosić. Dołączone typy interoperacyjne obsługują ekwiwalencję typów, dzięki czemu nie jest konieczne korzystanie z podstawowych zestawów międzyoperacyjnych (ang. Primary Interop Assemblies) i tym skończyła się era problemów z wersjonowaniem i wdrażaniem. Po trzecie, funkcje zwracające różne rodzaje typów COM z połączonych typów interoperacyjnych są mapowane na typ dynamic, a nie object, co eliminuje konieczność rzutowania.


    Co było nowego w C# 3.0


    Większość nowości dodanych w C# 3.0 dotyczyła technologii LINQ (ang. Language Integrated Query). Technologia ta umożliwia pisanie zapytań bezpośrednio w programach C# i statyczne sprawdzanie ich poprawności. Przy jej użyciu można wysyłać zapytania zarówno do kolekcji lokalnych (np. list lub dokumentów XML), jak i zdalnych źródeł danych (np. baz danych). Wśród udogodnień dodanych w C# do pracy z LINQ znajdują się: niejawnie typowane zmienne lokalne, typy anonimowe, inicjalizatory obiektów, wyrażenia lambda, metody rozszerzające, wyrażenia zapytań oraz drzewa wyrażeń.


    Niejawnie typowane zmienne lokalne (słowo kluczowe var, rozdział 2.) umożliwiają opuszczenie deklaracji typu zmiennej, dzięki czemu zostanie on wydedukowany przez kompilator. W ten sposób redukuje się bałagan oraz powstaje możliwość posługiwania się typami anonimowymi (rozdział 4.), które są prostymi klasami tworzonymi w locie często wykorzystywanymi w wynikach zapytań LINQ. Tablice także mogą być typowane niejawnie (rozdział 2.).


    Inicjalizatory obiektów (rozdział 3.) upraszczają proces konstruowania obiektów przez umożliwienie nadawania wartości własnościom na miejscu za wywołaniem konstruktora. Inicjalizatory obiektów działają zarówno z nazwanymi, jak i anonimowymi typami.


    Wyrażenia lambda (rozdział 4.) to miniaturowe funkcje tworzone przez kompilator w locie. Są szczególnie przydatne przy tworzeniu „płynnych” zapytań LINQ (rozdział 8.).


    Metody rozszerzeń (rozdział 4.) rozszerzają istniejące typy o nowe metody (bez zmieniania ich definicji), sprawiając że metody statyczne zachowują się jak metody egzemplarzowe. Metodami rozszerzeń są m.in. operatory zapytań LINQ.


    Wyrażenia zapytań (rozdział 8.) to wysokopoziomowa składnia do pisania zapytań LINQ, które mogą być znacznie prostsze do pracy z licznymi sekwencjami lub zmiennymi zakresowymi.


    Drzewa wyrażeń (rozdział 8.) to miniaturowe struktury DOM (ang. Document Object Model — obiektowy model dokumentu) kodu opisujące wyrażenia lambda przypisane do specjalnego typu Expression<TDelegate>. Drzewa wyrażeń umożliwiają zdalne wykonywanie zapytań LINQ (np. do serwera baz danych), ponieważ ułatwiają introspekcję i translację w czasie działania programu (np. na instrukcję SQL).


    Ponadto w C# 3.0 dodano własności automatyczne i metody częściowe.


    Własności automatyczne (rozdział 3.) eliminują konieczność pisania własności ustawiających i pobierających prywatne pola, ponieważ kompilator generuje je za programistę. Natomiast metody częściowe (rozdział 3.) to mechanizm wykorzystywany w automatycznie generowanych klasach do dostarczania punktów zaczepienia dla programisty do ręcznej implementacji, które „zanikają”, jeśli nie są używane.

  






  
    [image: ]


    Rozdział 2.

    Podstawy języka C#


    W rozdziale tym znajduje się wprowadzenie do języka C#.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Wszystkie programy i przykłady kodu przedstawione w tym i w dwóch następnych rozdziałach można pobrać w postaci interaktywnych przykładów programu LINQPad. Jednoczesne analizowanie tych przykładów i czytanie książki przyspiesza proces nauki, ponieważ można zmieniać kod i od razu widzieć efekty tych modyfikacji bez potrzeby tworzenia całych projektów i rozwiązań w środowisku Visual Studio.


            Pliki z kodem źródłowym można pobrać z serwera FTP wydawnictwa Helion pod adresem: ftp://ftp.helion.pl/przyklady/c6pig6.zip.

          
        

      
    


    Pierwszy program w języku C#


    Przedstawiamy program wykonujący mnożenie wartości 12 przez 30 i drukujący wynik, 360, na ekranie. Podwójny ukośnik oznacza, że dalsza część wiersza jest komentarzem:

    using System; // import przestrzeni nazw


    
      class Test // deklaracja klasy

    


    
      {

    


    
        static void Main() // deklaracja metody

    


    
        {

    


    
          int x = 12 * 30; // instrukcja 1

    


    
          Console.WriteLine (x); // instrukcja 2

    


    
        } // koniec metody

    


    
      } // koniec klasy

    


    Serce tego programu stanowią dwie poniższe instrukcje:

    int x = 12 * 30;


    
      Console.WriteLine (x);

    


    W języku C# instrukcje są wykonywane po kolei i każda z nich musi być zakończona średnikiem (lub, jak się przekonasz później, blokiem kodu). Pierwsza instrukcja oblicza wartość wyrażenia 12 * 30 i zapisuje wynik w zmiennej lokalnej o nazwie x, która jest typu całkowitoliczbowego. Druga instrukcja wywołuje metodę WriteLine klasy Console w celu wydrukowania wartości zmiennej x w oknie tekstowym na ekranie.


    Metoda działa przez wykonywanie serii instrukcji zwanych blokiem instrukcji — jest nim klamra zawierająca zero lub więcej instrukcji. Zdefiniowaliśmy jedną metodę o nazwie Main:

    static void Main()


    
      {

    


    
        ...

    


    
      }

    


    Program można uprościć przez napisanie funkcji wyższego poziomu wywołujących funkcje niższego poziomu. Powyższy program możemy więc poddać refaktoryzacji w celu zdefiniowania metody mnożącej dowolną liczbę całkowitą przez 12:

    using System;


    
      class Test

    


    
      {

    


    
        static void Main()

    


    
        {

    


    
          Console.WriteLine (FeetToInches (30)); // 360

    


    
          Console.WriteLine (FeetToInches (100)); // 1200

    


    
        }

    


    
        static int FeetToInches (int feet)

    


    
        {

    


    
          int inches = feet * 12;

    


    
          return inches;

    


    
        }

    


    
      }

    


    Wywołujący może przekazać do wywoływanej metody dane w parametrach oraz odebrać wynik obliczeń za pomocą typu zwrotnego. W tym przykładzie zdefiniowaliśmy metodę o nazwie FeetToInches pobierającą w parametrze liczbę sto i zwracającą liczbę cali:

    static int FeetToInches (int feet ) {...}


    Literały 30 i 100 to argumenty przekazane do metody FeetToInches. Nawias obok nazwy metody Main w tym przykładzie jest pusty, ponieważ ta metoda nie ma żadnych parametrów. Natomiast słowo kluczowe void oznacza, że metoda nie zwraca do wywołującego żadnej wartości:

    static void Main()


    W języku C# metoda o nazwie Main stanowi domyślny początek wykonywania programu. Może ona zwracać liczbę całkowitą (zamiast void), aby przekazać informacje do środowiska wykonawczego (dla którego każda inna wartość niż zero z reguły oznacza błąd). Metoda Main może też przyjmować jako parametr tablicę łańcuchów (do której zostaną wstawione argumenty przekazane do pliku wykonywalnego). Na przykład:

    static int Main (string[] args) {...}


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Tablica (np. string[]) reprezentuje pewną ustaloną liczbę elementów określonego typu. Tablice definiuje się przez wstawienie kwadratowego nawiasu za nazwą typu definiowanej konstrukcji. Bardziej szczegółowy opis tablic znajduje się w podrozdziale „Tablice”.

          
        

      
    


    Metody to jeden z rodzajów funkcji dostępnych w języku C#. Drugi rodzaj, którego użyliśmy wnaszym przykładowym programie, to operator mnożenia *. Pozostałe rodzaje to: konstruktory, własności, zdarzenia, indeksatory oraz finalizatory.


    W naszym przykładzie obie metody znajdują się w jednej klasie. Klasa to konstrukcja zawierająca składowe, którymi mogą być funkcje i pola danych. Klasy są jednym z elementów obiektowości. Klasa Console zawiera składowe do obsługi operacji wejścia i wyjścia, np. metodę WriteLine. Zdefiniowana przez nas klasa Test zawiera dwie metody — Main i FeetToInches. Klasa jest więc rodzajem typu, ale typami zajmujemy się bardziej szczegółowo w podrozdziale „Podstawy typów”.


    W najszerszym ujęciu typy w programie są objęte przestrzeniami nazw. W przedstawionej aplikacji za pomocą dyrektywy using udostępniliśmy sobie zawartość przestrzeni System, do której należy m.in. klasa Console. Wszystkie nasze klasy moglibyśmy zdefiniować w przestrzeni nazw TestPrograms, jak pokazano poniżej:

    using System;


    
      namespace TestPrograms

    


    
      {

    


    
        class Test {...}

    


    
        class Test2 {...}

    


    
      }

    


    Organizacja platformy .NET Framework dzieli się na zagnieżdżone przestrzenie nazw. Poniżej np. pokazujemy przestrzeń nazw zawierającą typy do pracy z tekstem:

    using System.Text;


    Dyrektywa using jest tylko udogodnieniem, tzn. do typów można odnosić się także przy użyciu ich pełnych nazw składających się z nazwy właściwej typu poprzedzonej nazwą przestrzeni nazw, do której ten typ należy, np. System.Text.StringBuilder.


    Kompilacja


    Kompilator C# kompiluje kod źródłowy znajdujący się w plikach o rozszerzeniu cs do postaci zestawu (ang. assembly). Zestaw w .NET to jednostka pakowania i wdrażania. Może reprezentować zarówno aplikację, jak i bibliotekę. Każda normalna aplikacja konsolowa lub systemu Windows zawiera metodę Main i występuje w postaci pliku z rozszerzeniem exe. Natomiast biblioteka to plik .dll, który od pliku .exe różni się tym, że nie ma zdefiniowanego punktu początkowego. Biblioteki są przeznaczone do wykorzystywania przez aplikacje lub inne biblioteki, które mogą się do nich odnosić. Platforma .NET Framework jest właśnie zbiorem takich bibliotek.


    Kompilator języka C# nazywa się csc.exe. Programy można kompilować za pomocą środowiska IDE, np. Visual Studio, lub przez ręczne uruchamianie kompilatora w wierszu poleceń, używając polecenia csc. (Ponadto kompilator występuje też w postaci biblioteki — zob. rozdział 27.). Aby skompilować program ręcznie, należy zapisać go w pliku, np. PierwszyProgram.cs, a następnie wwierszu poleceń wywołać kompilator csc (znajdujący się w katalogu %ProgramFiles(X86)%\ msbuild\14.0\bin) w następujący sposób:

    csc PierwszyProgram.cs


    W efekcie powstanie aplikacja o nazwie PierwszyProgram.exe.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Co dziwne, .NET Framework 4.6 zawiera kompilator C# 5. Jeśli więc ktoś chce korzystać z kompilatora wiersza poleceń C# 6.0, musi zainstalować Visual Studio lub MSBuild 14.

          
        

      
    


    Aby utworzyć bibliotekę (plik .dll), należy wykonać następujące polecenie:

    csc /target:library PierwszyProgram.cs


    Zestawy szczegółowo opisujemy w rozdziale 18.


    Składnia


    Składnia języka C# jest podobna do składni języków C i C++. W tej sekcji opisujemy elementy składni języka C# na przykładzie poniższego programu:

    using System;


    
      class Test

    


    
      {

    


    
        static void Main()

    


    
        {

    


    
          int x = 12 * 30;

    


    
          Console.WriteLine (x);

    


    
        }

    


    
      }

    


    Identyfikatory i słowa kluczowe


    Identyfikatory to nazwy nadawane przez programistów klasom, metodom, zmiennym itd. Poniżej znajduje się lista identyfikatorów z powyższego programu w kolejności występowania w kodzie źródłowym:

    System Test Main x Console WriteLine


    Identyfikator nie może zawierać przerw, może składać się ze znaków Unicode oraz musi zaczynać się od znaku podkreślenia lub litery. W języku C# wielkość liter w identyfikatorach ma znaczenie. Umownie nazwy parametrów, zmiennych lokalnych i pól prywatnych zapisuje się w tzw. notacji wielbłądziej (np. mojaZmienna), a wszystkie inne — w notacji Pascala (np. MojaMetoda).


    Słowa kluczowe to nazwy o specjalnym znaczeniu dla kompilatora. W naszym programie znalazły się następujące słowa kluczowe:

    using class static void int


    Większość słów kluczowych jest jednocześnie słowami zarezerwowanymi, tzn. takimi, których nie można używać jako identyfikatorów. Poniżej znajduje się lista wszystkich słów zarezerwowanych języka C#:

    abstract


    
      as

    


    
      base

    


    
      bool

    


    
      break

    


    
      byte

    


    
      case

    


    
      catch

    


    
      char

    


    
      checked

    


    
      class

    


    
      const

    


    
      continue

    


    
      decimal

    


    
      default

    


    
      delegate

    


    
      do

    


    
      double

    


    
      else

    


    
      enum

    


    
      event

    


    
      explicit

    


    
      extern

    


    
      false

    


    
      finally

    


    
      fixed

    


    
      float

    


    
      for

    


    
      foreach

    


    
      goto

    


    
      if

    


    
      implicit

    


    
      in

    


    
      int

    


    
      interface

    


    
      internal

    


    
      is

    


    
      lock

    


    
      longnamespace

    


    
      new

    


    
      null

    


    
      object

    


    
      operator

    


    
      out

    


    
      override

    


    
      params

    


    
      private

    


    
      protected

    


    
      public

    


    
      readonly

    


    
      ref

    


    
      return

    


    
      sbyte

    


    
      sealed

    


    
      short

    


    
      sizeof

    


    
      stackalloc

    


    
      static

    


    
      string

    


    
      struct

    


    
      switch

    


    
      this

    


    
      throw

    


    
      true

    


    
      try

    


    
      typeof

    


    
      uint

    


    
      ulong

    


    
      unchecked

    


    
      unsafe

    


    
      ushort

    


    
      using

    


    
      virtual

    


    
      void

    


    
      volatile

    


    
      while

    


    Unikanie konfliktów nazw


    Jeśli konieczne jest użycie identyfikatora figurującego na liście słów zarezerwowanych, można go opatrzyć przedrostkiem @, np.:

    class class {...} // źle


    
      class @class {...} // dobrze

    


    Symbol @ nie należy do identyfikatora, a więc nazwa @mojaZmienna jest równoznaczna z mojaZmienna.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Przedrostek @ jest przydatny przy korzystaniu z bibliotek napisanych w innych językach .NET, w których zarezerwowane są inne słowa kluczowe.

          
        

      
    


    Kontekstowe słowa kluczowe


    Niektóre słowa kluczowe są kontekstowe, tzn. można ich używać w roli identyfikatorów bez przedrostka @. Oto lista tych słów:


    
      
        
        
        
        
        
      

      
        
          	
            add

          

          	
            dynamic

          

          	
            in

          

          	
            orderby

          

          	
            var

          
        


        
          	
            ascending

          

          	
            equals

          

          	
            into

          

          	
            partial

          

          	
            when

          
        


        
          	
            async

          

          	
            from

          

          	
            join

          

          	
            remove

          

          	
            where

          
        


        
          	
            await

          

          	
            get

          

          	
            let

          

          	
            select

          

          	
            yield

          
        


        
          	
            by

          

          	
            global

          

          	
            nameof

          

          	
            set

          

          	
        


        
          	
            descending

          

          	
            group

          

          	
            on

          

          	
            value

          

          	
        

      
    


    W przypadku tych słów niejednoznaczność nie może wystąpić w kontekście, w którym są używane.


    Literały, znaki interpunkcyjne i operatory


    Literały to podstawowe porcje danych wprowadzone wprost do kodu źródłowego programu. W naszym przykładowym programie literałami są wartości 12 i 30.


    Znaki interpunkcyjne służą do określania struktury programu. W przykładowym programie użyliśmy następujących znaków interpunkcyjnych:

    { } ;


    Klamry służą do grupowania instrukcji w blokach instrukcji.


    Średnik oznacza koniec instrukcji. (Ale bloki instrukcji nie muszą być zakończone średnikiem). Instrukcje mogą się rozciągać na wiele wierszy:

    Console.WriteLine


    
        (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10);

    


    Operator przekształca wyrażenia lub tworzy ich kombinacje. Większość operatorów w języku C# ma postać pojedynczego znaku, np. operator mnożenia jest gwiazdką *. Szerzej na temat operatorów piszemy w dalszej części rozdziału. W przykładowym programie użyliśmy następujących operatorów:

    . () * =


    Kropka oznacza składową czegoś (lub w literałach liczbowych znak wartości dziesiętnej). Nawiasy służą do deklarowania i wywoływania metod. Pusty nawias oznacza, że metoda nie przyjmuje żadnych argumentów wywołania. (Nawiasów używa się także do innych celów, o czym jest mowa w dalszej części tego rozdziału). Znak równości to operator przypisania. (Z kolei podwójny znak równości, ==, to operator porównywania, o którym szerzej piszemy w dalszej części rozdziału).


    Komentarze


    W języku C# dokumentację kodu źródłowego można zapisywać w dwóch rodzajach komentarzy: jednowierszowych i wielowierszowych. Komentarz jednowierszowy zaczyna się od podwójnego ukośnika i obejmuje wszystko do końca wiersza, np.:

    int x = 3; // komentarz o przypisaniu wartości 3 do zmiennej x


    Początek komentarza wielowierszowego wyznaczają znaki /*, a koniec — znaki */, np.:

    int x = 3; /* to jest komentarz


    
                   obejmujący dwa wiersze */

    


    W komentarzach można używać znaczników dokumentacyjnych XML, o których szerzej piszemy w podrozdziale „Dokumentacja XML” w rozdziale 4.


    Podstawy typów


    Typ to definicja szablonu wartości. W przykładowym programie użyliśmy dwóch literałów typu int o wartościach 12 i 30. Ponadto zadeklarowaliśmy zmienną typu int o nazwie x:

    static void Main()


    
      {

    


    
        int x = 12 * 30;

    


    
        Console.WriteLine (x);

    


    
      }

    


    Zmienna oznacza miejsce w pamięci, w którym można przechowywać różne wartości. Natomiast stała zawsze reprezentuje tę samą wartość (ten temat rozwijamy nieco dalej):

    const int y = 360;


    Wszystkie wartości w języku C# są egzemplarzami pewnego typu. To od niego zależy znaczenie oraz zbiór potencjalnych wartości zmiennej.


    Przykłady typów predefiniowanych


    Typy predefiniowane to takie, które są specjalnie traktowane przez kompilator. Do tej grupy należy np. typ int reprezentujący zbiór liczb całkowitych mieszczących się w 32 bitach pamięci, a więc z przedziału od −231 do 231−1. Jest to też domyślny typ literałów numerycznych mieszczących się w tym przedziale. Na egzemplarzach typu int można wykonywać działania arytmetyczne w następujący sposób:

    int x = 12 * 30;


    Innym typem predefiniowanym w języku C# jest string. Reprezentuje on ciąg znaków, np. ".NET" lub "http://helion.pl". Praca z łańcuchami polega na wywoływaniu na nich funkcji, np.:

    string message = "Witaj, świecie";


    
      string upperMessage = message.ToUpper();

    


    
      Console.WriteLine (upperMessage); // WITAJ, ŚWIECIE

    


    
      int x = 2015;

    


    
      message = message + x.ToString();

    


    
      Console.WriteLine (message); // Witaj, świecie2015

    


    Predefiniowany typ bool reprezentuje tylko dwie potencjalne wartości: true (prawda) i false (fałsz). Wartości tego typu najczęściej używa się w konstrukcjach rozgałęziających program opartych na instrukcji warunkowej if, np.:

    bool simpleVar = false;


    
      if (simpleVar)

    


    
        Console.WriteLine ("To nie będzie wydrukowane.");

    


    
      int x = 5000;

    


    
      bool lessThanAMile = x < 5280;

    


    
      if (lessThanAMile)

    


    
        Console.WriteLine ("To zostanie wydrukowane.");

    


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            W języku C# typy predefiniowane (zwane też typami wbudowanymi) oznacza się za pomocą specjalnych słów kluczowych. Ale w przestrzeni nazw System platformy .NET Framework znajduje się wiele ważnych typów, które nie są predefiniowane w C# (np. DateTime).

          
        

      
    


    Przykłady definiowania typów przez programistę


    Tak jak z prostych funkcji można tworzyć bardziej złożone funkcje, z typów prostych można składać bardziej złożone typy. W poniższym przykładzie zdefiniowaliśmy własny typ o nazwie UnitConverter. Jest to klasa służąca jako szablon do przeprowadzania konwersji jednostek:

    using System;


    
      public class UnitConverter

    


    
      {

    


    
        int ratio; // pole

    


    
        public UnitConverter (int unitRatio) {ratio = unitRatio; } // konstruktor

    


    
        public int Convert (int unit) {return unit * ratio; } // metoda

    


    
      }

    


    
      class Test

    


    
      {

    


    
        static void Main()

    


    
        {

    


    
          UnitConverter feetToInchesConverter = new UnitConverter (12);

    


    
          UnitConverter milesToFeetConverter = new UnitConverter (5280);

    


    
          Console.WriteLine (feetToInchesConverter.Convert(30)); // 360

    


    
          Console.WriteLine (feetToInchesConverter.Convert(100)); // 1200

    


    
          Console.WriteLine (feetToInchesConverter.Convert(

    


    
                                milesToFeetConverter.Convert(1))); // 63360

    


    
        }

    


    
      }

    


    Składowe typu


    Każdy typ zawiera dwa rodzaje składowych: dane i funkcje. W klasie UnitConverter składową reprezentującą dane jest pole o nazwie ratio. Natomiast składowe funkcyjne tej klasy to metoda Convert i konstruktor UnitConverter.


    Symetria typów predefiniowanych i typy własne


    Piękną cechą języka C# jest to, że typy predefiniowane tylko nieznacznie różnią się od typów własnych programisty. Predefiniowany typ int jest szablonem reprezentującym liczby całkowite. Umożliwia przechowywanie danych — 32 bity — oraz zapewnia funkcje składowe operujące na tych danych, np. ToString. Analogicznie nasz typ UnitConverter jest szablonem reprezentującym konwersję jednostek. Przechowuje dane — współczynnik — i zapewnia funkcje składowe służące do pracy na tych danych.


    Konstruktory i tworzenie egzemplarzy


    Dane tworzy się przez utworzenie egzemplarza (ang. instantiation) wybranego typu. Egzemplarze typów predefiniowanych można tworzyć przy użyciu literałów, np. 12 albo "Witaj, świecie". Do tworzenia egzemplarzy typów własnych programisty służy operator new. W przykładowym programie utworzyliśmy i zadeklarowaliśmy egzemplarz typu UnitConverter za pomocą poniższej instrukcji:

    UnitConverter feetToInchesConverter = new UnitConverter (12);


    W chwili gdy operator new utworzy nowy obiekt, zostaje wywołany konstruktor tego obiektu w celu zainicjalizowania jego stanu. Konstruktor definiuje się tak samo jak metodę, tylko w miejscu nazwy metody i typu zwrotnego podaje się nazwę typu nadrzędnego:

    public class UnitConverter


    
      {

    


    
        ...

    


    
        public UnitConverter (int unitRatio) { ratio = unitRatio; }

    


    
        ...

    


    
      }

    


    Składowe egzemplarza a składowe statyczne


    Składowe danych i funkcyjne operujące na egzemplarzu typu nazywają się składowymi egzemplarza. Przykładami takich składowych są metoda Convert typu UnitConverter i metoda ToString typu int. Domyślnie wszystkie składowe należą do egzemplarza.


    Składowe danych i funkcyjne nieoperujące na egzemplarzu typu, tylko na samym typie, muszą być oznaczone słowem kluczowym static. Przykładami metod statycznych są Test.Main i Console.Write. Tak naprawdę, to Console jest klasą statyczną, co oznacza, że wszystkie jej składowe również są statyczne. Nigdy nie tworzy się egzemplarzy tej klasy — wszystkie składniki aplikacji korzystają z jednej wspólnej konsoli.


    Porównajmy składowe egzemplarza i statyczne. W poniższym kodzie pole egzemplarza Name odnosi się do konkretnego egzemplarza typu Panda, a Population — do zbioru wszystkich egzemplarzy tego typu:

    public class Panda


    
      {

    


    
        public string Name; // pole egzemplarza

    


    
        public static int Population; // pole statyczne

    


    
        public Panda (string n) // konstruktor

    


    
        {

    


    
          Name = n; // przypisanie wartości do pola egzemplarza

    


    
          Population = Population + 1; // zwiększenie wartości statycznego pola Population

    


    
        }

    


    
      }

    


    W poniższym przykładzie tworzymy dwa egzemplarze typu Panda oraz drukujemy ich nazwy i liczebność całej populacji:

    using System;


    
      class Test

    


    
      {

    


    
        static void Main()

    


    
        {

    


    
          Panda p1 = new Panda ("Pan Dee");

    


    
          Panda p2 = new Panda ("Pan Dah");

    


    
          Console.WriteLine (p1.Name); // Pan Dee

    


    
          Console.WriteLine (p2.Name); // Pan Dah

    


    
          Console.WriteLine (Panda.Population); // 2

    


    
        }

    


    
      }

    


    Próba sprawdzenia wartości p1.Population lub Panda.Name spowoduje błąd kompilacji.


    Słowo kluczowe public


    Słowo kluczowe public udostępnia składowe innym klasom. W tym przykładzie gdyby pole Name w typie Panda nie było oznaczone słowem public, byłoby prywatne i niedostępne dla klasy Test. Oznaczenie składowej słowem kluczowym public to w istocie następująca informacja: „To jest składowa, którą mają widzieć inne typy, a cała reszta to prywatne szczegóły implementacyjne”. W obiektowości mówi się, że składowe publiczne hermetyzują składowe prywatne klasy.


    Konwersje


    W języku C# można konwertować zgodne ze sobą typy. Wynikiem konwersji zawsze jest nowa wartość utworzona z istniejącej wartości. Konwersja może być niejawna lub jawna. Pierwsze odbywają się automatycznie, a drugie wymagają rzutowania. W poniższym przykładzie niejawnie konwertujemy typ int na long (który ma dwa razy większą pojemność bitową od typu int) oraz jawnie rzutujemy typ int na short (który ma dwa razy mniejszą pojemność bitową od typu int):

    int x = 12345; // int to 32-bitowa liczba całkowita


    
      long y = x; // niejawna konwersja na 64-bitowy typ całkowitoliczbowy

    


    
      short z = (short)x; // jawna konwersja na 16-bitowy typ całkowitoliczbowy

    


    Konwersja niejawna jest dozwolona, gdy spełnione są dwa następujące warunki:


    
      	kompilator ma gwarancję, że operacja zawsze się powiedzie;


      	w wyniku operacji nie dojdzie do utraty informacji[1].

    


    Natomiast konwersja jawna jest konieczna, gdy spełniony jest jeden z poniższych warunków:


    
      	kompilator nie ma gwarancji, że operacja zawsze się powiedzie;


      	w wyniku konwersji może dojść do utraty informacji.

    


    (Jeżeli kompilator stwierdza, że konwersja nigdy się nie powiedzie, zabronione są oba rodzaje konwersji. Także konwersje typów generycznych mogą się nie udać w pewnych sytuacjach — zob. podrozdział „Parametry typów i konwersje” w rozdziale 3.).


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Pokazane konwersje liczbowe to operacje wbudowane w język. Ponadto C# obsługuje też konwersje obejmujące referencje (ang. reference conversion) i pakowanie (ang. boxing conversion) — rozdział 3. — oraz własne konwersje programisty (zob. podrozdział „Przeciążanie operatorów” w rozdziale 4.). W odniesieniu do konwersji własnych programisty kompilator nie egzekwuje wymienionych powyżej zasad, więc istnieje ryzyko, że nieprawidłowo zaprojektowane typy będą się zachowywać niepoprawnie.

          
        

      
    


    Typy wartościowe a typy referencyjne


    Wszystkie typy w języku C# zaliczają się do następujących kategorii:


    
      	typy wartościowe,


      	typy referencyjne,


      	parametry typów generycznych,


      	typy wskaźnikowe.

    


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            W tym podrozdziale opisujemy typy wartościowe i referencyjne. Parametry typów generycznych opisujemy w podrozdziale „Typy generyczne” w rozdziale 3., a typy wskaźnikowe w podrozdziale „Niebezpieczny kod i wskaźniki” w rozdziale 4.

          
        

      
    


    Do typów wartościowych zalicza się większość typów wbudowanych (wszystkie liczbowe, char i bool) oraz zdefiniowane przez programistę struktury (struct) i wyliczenia (enum).


    Do typów referencyjnych zaliczają się wszystkie klasy, tablice, delegacje oraz interfejsy (wśród nich znajduje się m.in. predefiniowany typ string).


    Podstawowa różnica między typami wartościowymi i referencyjnymi dotyczy sposobu przechowywania ich wartości w pamięci.


    Typy wartościowe


    Zawartością zmiennej lub stałej typu wartościowego jest po prostu wartość. Na przykład treścią wbudowanego typu int są 32 bity danych.


    Własny typ wartościowy można zdefiniować za pomocą słowa kluczowego struct (rysunek 2.1):

    public struct Point { public int X; public int Y; }


    [image: ]


    Rysunek 2.1. Egzemplarz typu wartościowego w pamięci


    Można to też zapisać bardziej zwięźle:

    public struct Point { public int X, Y; }


    Przypisanie egzemplarza typu wartościowego zawsze powoduje jego skopiowanie. Na przykład:

    static void Main()


    
      {

    


    
        Point p1 = new Point();

    


    
        p1.X = 7;

    


    
        Point p2 = p1; // przypisanie powoduje utworzenie kopii

    


    
        Console.WriteLine (p1.X); // 7

    


    
        Console.WriteLine (p2.X); // 7

    


    
        p1.X = 9; // zmiana p1.X

    


    
        Console.WriteLine (p1.X); // 9

    


    
        Console.WriteLine (p2.X); // 7

    


    
      }

    


    Na rysunku 2.2 pokazano, że wartości zmiennych p1 i p2 są przechowywane w sposób wzajemnie niezależny.


    [image: ]


    Rysunek 2.2. Przypisanie powoduje utworzenie kopii egzemplarza typu wartościowego


    Typy referencyjne


    Typ referencyjny jest bardziej skomplikowany niż typ wartościowy, ponieważ składa się z dwóch części: obiektu i referencji (odniesienia) do tego obiektu. Zawartość zmiennej lub stałej typu referencyjnego stanowi referencja do obiektu zawierającego wartość. Poniżej znajduje się typ Point z poprzedniego przykładu. Wcześniej był strukturą, a teraz przepisaliśmy go jako klasę (rysunek 2.3):

    public class Point { public int X, Y; }


    [image: ]


    Rysunek 2.3. Egzemplarz referencyjny w pamięci


    Przypisanie zmiennej typu referencyjnego powoduje skopiowanie referencji, a nie egzemplarza obiektu. Dzięki temu do jednego obiektu może się odnosić wiele zmiennych — u typów wartościowych normalnie jest to niemożliwe. Wracając do poprzedniego przykładu, zmieniając Point na klasę, teraz działania wykonywane na zmiennej p1 mają wpływ także na zmienną p2:

    static void Main()


    
      {

    


    
        Point p1 = new Point();

    


    
        p1.X = 7;

    


    
        Point p2 = p1; // kopiuje referencję p1

    


    
        Console.WriteLine (p1.X); // 7

    


    
        Console.WriteLine (p2.X); // 7

    


    
        p1.X = 9; // zmienia p1.X

    


    
        Console.WriteLine (p1.X); // 9

    


    
        Console.WriteLine (p2.X); // 9

    


    
      }

    


    Na rysunku 2.4 pokazano, że p1 i p2 są referencjami wskazującymi ten sam obiekt.


    [image: ]


    Rysunek 2.4. Przypisanie powoduje skopiowanie referencji


    Wartość null


    Zmiennej typu referencyjnego można przypisać literał null oznaczający, że dana referencja nie wskazuje żadnego obiektu:

    class Point {...}


    
      ...

    


    
      Point p = null;

    


    
      Console.WriteLine (p == null); // prawda

    


    
      // Poniższy wiersz kodu spowoduje błąd wykonawczy

    


    
      // NullReferenceException:

    


    
      Console.WriteLine (p.X);

    


    Natomiast typ wartościowy w normalnej sytuacji nie może mieć wartości null:

    struct Point {...}


    
      ...

    


    
      Point p = null; // błąd kompilacji

    


    
      int x = null; // błąd kompilacji

    


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            W języku C# istnieje też konstrukcja zwana typem dopuszczającym wartość null (ang. nullable type) służąca do reprezentowania pustych wartości typów wartościowych (zob. podrozdział „Typy wartościowe dopuszczające wartość null” w rozdziale 4.).

          
        

      
    


    Efektywność wykorzystania pamięci


    Egzemplarze typów wartościowych zajmują dokładnie tyle pamięci, ile potrzeba do przechowywania ich pól. Poniższa struktura Point zajmie więc osiem bajtów:

    struct Point


    
      {

    


    
        int x; // 4 bajty

    


    
        int y; // 4 bajty

    


    
      }

    


    Referencja i obiekt typu referencyjnego wymagają osobnej alokacji w pamięci. Rozmiar obiektu jest sumą rozmiarów jego pól powiększoną o pewną ilość miejsca potrzebną do celów obsługowych. Ilość tego dodatkowego miejsca zależy od implementacji systemu wykonawczego .NET, ale jest nie mniejsza niż osiem bajtów, w których zapisany zostaje klucz do typu obiektu oraz informacje tymczasowe, takie jak stan blokady dla wątków oraz flaga wskazująca, czy obiekt może być przenoszony przez system usuwania nieużywanych obiektów, czy nie. Każda referencja do obiektu potrzebuje dodatkowych czterech lub ośmiu bajtów, w zależności od tego, czy system wykonawczy .NET działa na platformie 32- czy 64-bitowej.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            System CLR wstawiają pola składowe typu pod adresami będącymi wielokrotnościami rozmiaru tych pól (maksymalnie do ośmiu bajtów). Dlatego poniższa struktura zajmie 16 bajtów pamięci (siedem bajtów za pierwszym polem będzie „zmarnowane”):

            struct A { byte b; long l; }


            Można to zmienić za pomocą atrybutu StructLayout (zob. podrozdział „Mapowanie struktury na pamięć niezarządzaną” w rozdziale 25.).

          
        

      
    


    Systematyka typów predefiniowanych


    W języku C# obecne są następujące typy predefiniowane:


    Typy wartościowe


    
      	Liczbowe

        
          	liczby całkowite ze znakiem (sbyte, short, int, long);


          	liczby całkowite bez znaku (byte, ushort, uint, ulong);


          	liczby rzeczywiste (float, double, decimal).

        

      


      	Logiczny (bool).


      	Znakowy (char).

    


    Typy referencyjne


    
      	Łańcuchowy (string).


      	Obiektowy (object).

    


    Nazwy typów predefiniowanych w języku C# są aliasami typów platformy z przestrzeni nazw System. Dwie poniższe instrukcje różnią się tylko składnią:

    int i = 5;


    
      System.Int32 i = 5;

    


    Predefiniowane typy wartościowych (nie licząc decimal) są w CLR tzw. typami prostymi (ang. primitive type). Ta nazwa wzięła się stąd, że typy te są obsługiwane bezpośrednio przez instrukcje w kodzie skompilowanym, co z kolei zazwyczaj przekłada się na bezpośrednią obsługę przez procesor. Na przykład:

                     // reprezentacja szesnastkowa


    
      int i = 7;       // 0x7

    


    
      bool b = true;   // 0x1

    


    
      char c = 'A';    // 0x41

    


    
      float f = 0.5f;  // kodowanie liczb zmiennoprzecinkowych zgodne ze standardem IEEE

    


    Typy System.IntPtr i System.UIntPtr to także typy proste (zob. rozdział 25.).


    Typy liczbowe


    W tabeli 2.1 przedstawiono zestawienie wszystkich predefiniowanych typów liczbowych języka C#.


    Tabela 2.1. Predefiniowane typy liczbowe języka C#


    
      
        
        
        
        
        
      

      
        
          	
            Typ C#

          

          	
            Typ systemu

          

          	
            Przyrostek

          

          	
            Rozmiar

          

          	
            Przedział wartości

          
        


        
          	
            Całkowitoliczbowe zeznakiem

          

          	

          	

          	

          	
        


        
          	
            sbyte

          

          	
            SByte

          

          	

          	
            8 bitów

          

          	
            –27 do 27–1

          
        


        
          	
            short

          

          	
            Int16

          

          	

          	
            16 bitów

          

          	
            –215 do 215–1

          
        


        
          	
            int

          

          	
            Int32

          

          	

          	
            32 bity

          

          	
            –231 do 231–1

          
        


        
          	
            long

          

          	
            Int64

          

          	
            L

          

          	
            64 bity

          

          	
            –263 do 263–1

          
        


        
          	
            Całkowitoliczbowe bezznaku

          

          	

          	

          	

          	
        


        
          	
            byte

          

          	
            Byte

          

          	

          	
            8 bitów

          

          	
            0 do 28–1

          
        


        
          	
            ushort

          

          	
            UInt16

          

          	

          	
            16 bitów

          

          	
            0 do 216–1

          
        


        
          	
            uint

          

          	
            UInt32

          

          	
            U

          

          	
            32 bity

          

          	
            0 do 232–1

          
        


        
          	
            ulong

          

          	
            UInt64

          

          	
            UL

          

          	
            64 bity

          

          	
            0 do 264–1

          
        


        
          	
            Liczby rzeczywiste

          

          	

          	

          	

          	
        


        
          	
            float

          

          	
            Single

          

          	
            F

          

          	
            32 bity

          

          	
            ± (~10–45 do 1038)

          
        


        
          	
            double

          

          	
            Double

          

          	
            D

          

          	
            64 bity

          

          	
            ± (~10–324 do 10308)

          
        


        
          	
            decimal

          

          	
            Decimal

          

          	
            M

          

          	
            128 bitów

          

          	
            ± (~10–28 do 1028)

          
        

      
    


    Z typów całkowitoliczbowych int i long to typy pierwszoklasowe (ang. first-type citizen) preferowane są zarówno w kodzie C#, jak i przez system wykonawczy. Pozostałe typy całkowitoliczbowe są zazwyczaj wykorzystywane w celu zapewnienia współdziałania komponentów lub w przypadkach, gdy najważniejsza jest efektywność.


    Wśród typów reprezentujących liczby rzeczywiste typy float i double są tzw. typami zmiennoprzecinkowymi (ang. floating-point type)[2]. Najczęściej wykorzystuje się je w obliczeniach naukowych igraficznych. Typ decimal najchętniej jest wykorzystywany w obliczeniach finansowych, w których kluczowe znaczenie ma dokładność działań na liczbach dziesiętnych.


    Literały liczbowe


    Literały całkowitoliczbowe można przedstawiać w notacji dziesiętnej i szesnastkowej. Liczby szesnastkowe oznacza się przedrostkiem 0x, np.:

    int x = 127;


    
      long y = 0x7F;

    


    Literały reprezentujące liczby rzeczywiste mogą być wyrażane w formacie dziesiętnym lub wykładniczym, np.:

    double d = 1.5;


    
      double million = 1E06;

    


    Inferencja typów literałów liczbowych


    Domyślnie kompilator stwierdza, że literał liczbowy jest typu double lub całkowitoliczbowego:


    
      	Jeżeli literał zawiera kropkę dziesiętną lub symbol wykładnika (E), to stwierdza, że wartość jest typu double.


      	W przeciwnym przypadku typem literału jest pierwszy typ z następującej listy, który odpowiada jego wartości: int, uint, long lub ulong.

    


    Na przykład:

    Console.WriteLine ( 1.0.GetType()); // Double (double)


    
      Console.WriteLine ( 1E06.GetType()); // Double (double)

    


    
      Console.WriteLine ( 1.GetType()); // Int32 (int)

    


    
      Console.WriteLine ( 0xF0000000.GetType()); // UInt32 (uint)

    


    
      Console.WriteLine (0x100000000.GetType()); // Int64 (long)

    


    Przyrostki literałów liczbowych


    Przyrostki literałów liczbowych bezpośrednio określają typ literału. Do ich zapisu można używać zarówno małych, jak i wielkich liter. W poniższej tabeli znajduje się wykaz wszystkich przyrostków.


    
      
        
        
        
      

      
        
          	
            Kategoria

          

          	
            Typ C#

          

          	
            Przykład

          
        


        
          	
            F

          

          	
            float

          

          	
            float f = 1.0F;

          
        


        
          	
            D

          

          	
            double

          

          	
            double d = 1D;

          
        


        
          	
            M

          

          	
            decimal

          

          	
            decimal d = 1.0M;

          
        


        
          	
            U

          

          	
            uint

          

          	
            uint i = 1U;

          
        


        
          	
            L

          

          	
            long

          

          	
            long i = 1L;

          
        


        
          	
            UL

          

          	
            ulong

          

          	
            ulong i = 1UL;

          
        

      
    


    Przyrostki U i L rzadko są potrzebne, ponieważ typy uint, long i ulong prawie zawsze mogą być wydedukowane lub niejawnie przekonwertowane z typu int:

    long i = 5; // niejawna bezstratna konwersja literału int na typ long


    Przyrostek D jest zbędny, ponieważ wszystkie literały z kropką dziesiętną są uznawane za typ double. A kropkę dziesiętną zawsze można dodać do literału liczbowego:

    double x = 4.0;


    Najbardziej przydatne są przyrostki F i M, które powinno się zawsze stosować do literałów typu float i decimal. Bez przyrostka F poniższa instrukcja nie przeszłaby kompilacji, ponieważ literał 4.5 zostałby uznany za typ double, który nie obsługuje niejawnej konwersji na float:

    float f = 4.5F;


    Ta sama zasada dotyczy literałów dziesiętnych:

    decimal d = -1.23M; // bez przyrostka M ta instrukcja nie przejdzie kompilacji


    Szczegółowy opis zasad semantycznych rządzących konwersjami liczbowymi zamieszczamy w następnej sekcji.


    Konwersje liczbowe


    Konwersje między typami całkowitoliczbowymi


    Konwersje między typami całkowitoliczbowymi odbywają się niejawnie, gdy typ docelowy może reprezentować każdą wartość typu źródłowego. W pozostałych przypadkach konieczne jest wykonanie konwersji jawnej, np.:

    int x = 12345; // int to 32-bitowy typ całkowitoliczbowy


    
      long y = x; // niejawna konwersja na 64-bitowy typ całkowitoliczbowy

    


    
      short z = (short)x; // jawna konwersja na 16-bitowy typ całkowitoliczbowy

    


    Konwersje między typami zmiennoprzecinkowymi


    Typ float można niejawnie przekonwertować na typ double, ponieważ za pomocą typu double można reprezentować wszystkie możliwe wartości typu float. Natomiast konwersja w drugą stronę musi być przeprowadzana jawnie.


    Konwersja typów zmiennoprzecinkowych na całkowitoliczbowe


    Wszystkie typy całkowitoliczbowe można niejawnie konwertować na wszystkie typy zmiennoprzecinkowe:

    int i = 1;


    
      float f = i;

    


    Natomiast konwersja w drugą stronę musi być jawna:

    int i2 = (int)f;


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Skutkiem rzutowania liczby zmiennoprzecinkowej na typ całkowitoliczbowy jest utrata części ułamkowej. Liczba nie jest zaokrąglana. Statyczna klasa System.Convert zawiera metody zaokrąglające liczby podczas wykonywania konwersji między różnymi typami liczbowymi (zob. rozdział 6.).

          
        

      
    


    W wyniku niejawnej konwersji dużej wartości typu całkowitoliczbowego na typ zmiennoprzecinkowy zostanie zachowany rząd wielkości, ale może nastąpić utrata precyzji. Jest to spowodowane tym, że typy zmiennoprzecinkowe zawsze mogą reprezentować większe wartości, ale mogą tracić na precyzji. Można to zademonstrować na podstawie poprzedniego przykładu z większymi wartościami:

    int i1 = 100000001;


    
      float f = i1; // rząd wielkości zachowany, utracona precyzja

    


    
      int i2 = (int)f; // 100000000

    


    Konwersje dziesiętne


    Wszystkie typy całkowitoliczbowe można niejawnie konwertować na typ dziesiętny, ponieważ ten typ reprezentuje wszystkie możliwe wartości całkowite języka C#. Pozostałe konwersje na typ dziesiętny i z tego typu muszą być wykonywane jawnie.


    Operatory arytmetyczne


    Operatory arytmetyczne (+, -, *, /, %) są zdefiniowane dla wszystkich typów liczbowych z wyjątkiem 8- i 16-bitowych typów całkowitoliczbowych:


    + dodawanie


    - odejmowanie


    * mnożenie


    / dzielenie


    % reszta z dzielenia


    Operatory inkrementacji i dekrementacji


    Operatory inkrementacji i dekrementacji (++ i --) zwiększają i zmniejszają wartości typów liczbowych o jeden. Mogą występować przed nazwą zmiennej i za nią, w zależności od tego, czy programista chce użyć wartości tej zmiennej przed, czy po wykonaniu inkrementacji lub dekrementacji. Na przykład:

    int x = 0, y = 0;


    
      Console.WriteLine (x++); // wynik: 0; x ma wartość 1

    


    
      Console.WriteLine (++y); // wynik 1; y ma wartość 1

    


    Specjalne działania całkowitoliczbowe


    Dzielenie całkowitoliczbowe


    Przy dzieleniu liczb typów całkowitoliczbowych zawsze opuszczana jest reszta z dzielenia (stosowane jest zaokrąglanie w dół). Dzielenie przez zmienną o wartości zero powoduje błąd wykonawczy (DivideByZeroException):

    int a = 2 / 3; // 0


    
      int b = 0;

    


    
      int c = 5 / b; // błąd DivideByZeroException

    


    Dzielenie przez literał lub stałą 0 powoduje błąd kompilacji.


    Przepełnienie całkowitoliczbowe


    Podczas wykonywania działań na wartościach całkowitoliczbowych może dojść do przepełnienia. Standardowo takie zdarzenie jest wyciszane — nie jest zgłaszany wyjątek, tylko dochodzi do „zawinięcia” wartości, tak jakby obliczenia wykonano na większym typie całkowitoliczbowym i odrzucono dodatkowy znaczący bit. Na przykład dekrementacja najmniejszej możliwej wartości typu int powoduje zwrócenie największej możliwej wartości tego typu:

    int a = int.MinValue;


    
      a--;

    


    
      Console.WriteLine (a == int.MaxValue); // prawda

    


    Operatory sprawdzania przepełnienia całkowitoliczbowego


    Operator checked nakazuje systemowi wykonawczemu generowanie wyjątku OverflowException zamiast niepostrzeżenie doprowadzać do przepełnienia, gdy wyrażenie lub instrukcja przekroczy arytmetyczny limit wartości danego typu. Operator ten działa na wyrażenia z operatorami ++, --, +, - (jedno i dwuargumentowy), *, / oraz operatorami jawnej konwersji działającymi na liczbach całkowitych.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Operator checked nie działa na typy double i float (których przepełnienie jest oznaczane specjalną wartością „nieskończoną”, o czym szerzej piszemy nieco dalej) ani decimal (który jest zawsze kontrolowany).

          
        

      
    


    Operator checked można zastosować zarówno do wyrażenia, jak i do bloku instrukcji, np.:

    int a = 1000000;


    
      int b = 1000000;

    


    
      int c = checked (a * b); // sprawdza tylko wyrażenie

    


    
      checked                 // sprawdza wszystkie wyrażenia

    


    
      {                       // w bloku instrukcji

    


    
        ...

    


    
        c = a * b;

    


    
        ...

    


    
      }

    


    W razie potrzeby można włączyć domyślną kontrolę przepełnień arytmetycznych we wszystkich wyrażeniach w programie. Należy w tym celu dodać przełącznik /checked+ do polecenia kompilacji (w Visual Studio odpowiednia opcja znajduje się w zaawansowanych ustawieniach kompilacji — Advanced Build Settings). Jeśli później trzeba wyłączyć tę funkcję tylko dla wybranych wyrażeń lub instrukcji, można się posłużyć operatorem unchecked. Na przykład poniższy kod nie spowoduje zgłoszenia wyjątków, nawet jeśli zostanie skompilowany z dodatkiem przełącznika /checked+:

    int x = int.MaxValue;


    
      int y = unchecked (x + 1);

    


    
      unchecked { int z = x + 1; }

    


    Sprawdzanie przepełnienia dla wyrażeń stałych


    Niezależnie od ustawień przełącznika kompilatora /checked wyrażenia obliczane w czasie kompilacji są zawsze sprawdzane pod kątem występowania przepełnień — chyba że programista zastosuje operator unchecked:

    int x = int.MaxValue + 1; // błąd kompilacji


    
      int y = unchecked (int.MaxValue + 1); // brak błędów

    


    Operatory bitowe


    W języku C# dostępne są wymienione poniżej operatory bitowe:


    
      
        
        
        
        
      

      
        
          	
            Operator

          

          	
            Opis

          

          	
            Przykładowe wyrażenie

          

          	
            Wynik

          
        


        
          	
            ~

          

          	
            dopełnienie

          

          	
            ~0xfU

          

          	
            0xfffffff0U

          
        


        
          	
            &

          

          	
            i

          

          	
            0xf0 & 0x33

          

          	
            0x30

          
        


        
          	
            |

          

          	
            lub

          

          	
            0xf0 | 0x33

          

          	
            0xf3

          
        


        
          	
            ^

          

          	
            lub wykluczające

          

          	
            0xff00 ^ 0x0ff0

          

          	
            0xf0f0

          
        


        
          	
            <<

          

          	
            przesunięcie w lewo

          

          	
            0x20 << 2

          

          	
            0x80

          
        


        
          	
            >>

          

          	
            przesunięcie w prawo

          

          	
            0x20 >> 1

          

          	
            0x10

          
        

      
    


    8- i 16-bitowe typy całkowitoliczbowe


    Do 8- i 16-bitowych typów całkowitoliczbowych zaliczają się byte, sbyte, short oraz ushort. Nie mają one własnych operatorów arytmetycznych, więc w razie potrzeby C# niejawnie konwertuje je na większe typy. Jeśli programista spróbuje przypisać wynik z powrotem do typu o małym przedziale wartości, może to spowodować błąd kompilacji:

    short x = 1, y = 1;


    
      short z = x + y; // błąd kompilacji

    


    W tym przypadku zmienne x i y zostały niejawnie przekonwertowane na typ int w celu ich zsumowania. W efekcie wynik też jest typu int, którego nie można niejawnie konwertować na typ short (ponieważ może to spowodować utratę danych). Dlatego aby skompilować ten kod, należy dodać operator rzutowania:

    short z = (short) (x + y); // OK


    Specjalne wartości typów float i double


    W odróżnieniu od typów całkowitoliczbowych typy zmiennoprzecinkowe mają pewne wartości, które są traktowane specjalnie przez niektóre operacje. Te wartości to NaN (nie liczba), +∞, −∞ oraz −0. Klasy float i double zawierają stałe reprezentujące NaN, +∞ i −∞ oraz inne wartości (MaxValue, MinValue oraz Epsilon). Na przykład:

    Console.WriteLine (double.NegativeInfinity); // -nieskończoność


    Poniższa tabela zawiera zestawienie stałych reprezentujących specjalne wartości typów float i double.


    
      
        
        
        
      

      
        
          	
            Wartość specjalna

          

          	
            Stała typu double

          

          	
            Stała typu float

          
        


        
          	
            NaN

          

          	
            double.NaN

          

          	
            float.NaN

          
        


        
          	
            +∞

          

          	
            double.PositiveInfinity

          

          	
            float.PositiveInfinity

          
        


        
          	
            −∞

          

          	
            double.NegativeInfinity

          

          	
            float.NegativeInfinity

          
        


        
          	
            −0

          

          	
            −0.0

          

          	
            −0.0f

          
        

      
    


    Wynikiem dzielenia niezerowej wartości przez zero jest nieskończoność. Na przykład:

    Console.WriteLine ( 1.0 / 0.0); // nieskończoność


    
      Console.WriteLine (−1.0 / 0.0); // -nieskończoność

    


    
      Console.WriteLine ( 1.0 / −0.0); // -nieskończoność

    


    
      Console.WriteLine (−1.0 / −0.0); // nieskończoność

    


    Wynikiem dzielenia zera przez zero i odejmowana nieskończoności od nieskończoności jest NaN. Na przykład:

    Console.WriteLine ( 0.0 / 0.0); // NaN


    
      Console.WriteLine ((1.0 / 0.0) − (1.0 / 0.0)); // NaN

    


    Przy porównywaniu za pomocą operatora == jedna wartość NaN nigdy nie jest równa innej wartości, nawet NaN:

    Console.WriteLine (0.0 / 0.0 == double.NaN); // fałsz


    Aby sprawdzić, czy ma się do czynienia z wartością NaN, należy się posłużyć metodami float.IsNaN lub double.IsNaN:

    Console.WriteLine (double.IsNaN (0.0 / 0.0)); // prawda


    Natomiast metoda object.Equals uznaje dwie wartości NaN za równe:

    Console.WriteLine (object.Equals (0.0 / 0.0, double.NaN)); // prawda


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Za pomocą wartości NaN można czasami reprezentować wartości specjalne. W WPF wartość double.NaN reprezentuje wielkość, której wartość jest „automatyczna”. Innym sposobem reprezentowania takiej wartości jest użycie typu dopuszczającego null (rozdział 4.), a jeszcze innym jest zdefiniowanie własnej struktury opakowującej typ liczbowy i z dodatkowym polem (rozdział 3.).

          
        

      
    


    Format typów float i double jest zgodny ze specyfikacją IEEE 754, którą obsługują prawie wszystkie procesory. Szczegółowe informacje na ten temat można znaleźć na stronie internetowej: http://www.ieee.org.


    Porównanie typów double i decimal


    Typ double jest przydatny przy wykonywaniu obliczeń naukowych (np. przy obliczaniu współrzędnych przestrzennych). Natomiast typ decimal znajduje zastosowanie raczej w obliczeniach finansowych i przy pracy z wartościami „stworzonymi przez człowieka” niż z wynikami pomiarów. W poniższej tabeli przedstawiamy podsumowanie różnic występujących między tymi typami.


    
      
        
        
        
      

      
        
          	
            Kategoria

          

          	
            double

          

          	
            decimal

          
        


        
          	
            Reprezentacja wewnętrzna

          

          	
            System dwójkowy

          

          	
            System dziesiętny

          
        


        
          	
            Precyzja dziesiętna

          

          	
            15 – 16 cyfr znaczących

          

          	
            28 – 29 cyfr znaczących

          
        


        
          	
            Zakres wartości

          

          	
            ±(~10−324 do ~10308)

          

          	
            ±(~10−28 do ~1028)

          
        


        
          	
            Wartości specjalne

          

          	
            +0, −0, +∞, −∞ oraz NaN

          

          	
            brak

          
        


        
          	
            Szybkość

          

          	
            Wartość obsługiwana bezpośrednio przez procesor

          

          	
            Wartość nieobsługiwana bezpośrednio przez procesor (ok. 10 razy wolniej niż double)

          
        

      
    


    Błędy zaokrąglania liczb rzeczywistych


    Typy float i double reprezentują liczby w systemie dwójkowym, więc precyzyjnie można przy ich użyciu wyrażać tylko wartości dające się dokładnie przedstawić w tym systemie. W praktyce oznacza to, że większość literałów z częścią ułamkową (które są wyrażone w systemie dziesiętnym) nie uzyska precyzyjnej reprezentacji. Na przykład:

    float tenth = 0.1f; // nie do końca 0,1


    
      float one = 1f;

    


    
      Console.WriteLine (one - tenth * 10f); // -1.490116E-08

    


    Dlatego właśnie typy float i double nie nadają się do wykonywania obliczeń finansowych. Natomiast typ decimal ma reprezentację dziesiętną, więc może precyzyjnie reprezentować wszystkie liczby z tego systemu (jak również systemów będących jego czynnikami, czyli dwójkowego i piątkowego). Jako że literały rzeczywiste są wyrażane w systemie dziesiętnym, typ decimal może dokładnie reprezentować takie liczby, jak 0.1. Natomiast ani typ double, ani decimal nie mogą precyzyjnie reprezentować ułamka, którego dziesiętna reprezentacja jest okresowa:

    decimal m = 1M / 6M; // 0.1666666666666666666666666667M


    
      double d = 1.0 / 6.0; // 0.16666666666666666

    


    To prowadzi do gromadzenia się błędów zaokrąglania:

    decimal notQuiteWholeM = m+m+m+m+m+m; // 1.0000000000000000000000000002M


    
      double notQuiteWholeD = d+d+d+d+d+d; // 0.99999999999999989

    


    W następstwie operatory porównawcze zwracają nieprawidłowe wartości:

    Console.WriteLine (notQuiteWholeM == 1M); // fałsz


    
      Console.WriteLine (notQuiteWholeD < 1.0); // prawda

    


    Typ logiczny i operatory


    Typ języka C# bool (będący aliasem typu System.Boolean) jest wartością logiczną, której można przypisać literał true lub false.


    Choć do przechowywania wartości typu logicznego (bool) potrzebny jest tylko jeden bit, system wykonawczy wykorzystuje jeden bajt, ponieważ jest to najmniejsza ilość pamięci, z jaką system wykonawczy i procesor mogą efektywnie pracować. Aby zapobiec marnotrawstwu pamięci w przypadku tablic, platforma udostępnia klasę BitArray w przestrzeni nazw System.Collections, która wykorzystuje tylko jeden bit do przechowywania wartości logicznej.


    Konwersje typu logicznego


    Typu logicznego nie można rzutować na żaden typ liczbowy i odwrotnie.


    Operatory porównywania i równości


    Operatory == i != sprawdzają równość i nierówność wartości każdego typu i zawsze zwracają wartość typu bool[3]. W odniesieniu do typów wartościowych pojęcie równości jest zazwyczaj bardzo proste:

    int x = 1;


    
      int y = 2;

    


    
      int z = 1;

    


    
      Console.WriteLine (x == y); // fałsz

    


    
      Console.WriteLine (x == z); // prawda

    


    W przypadku typów referencyjnych równość jest standardowo określana na podstawie referencji, a nie rzeczywistej wartości obiektu (szerzej na ten temat piszemy w rozdziale 6.):

    public class Dude


    
      {

    


    
        public string Name;

    


    
        public Dude (string n) { Name = n; }

    


    
      }

    


    
      ...

    


    
      Dude d1 = new Dude ("Jan");

    


    
      Dude d2 = new Dude ("Jan");

    


    
      Console.WriteLine (d1 == d2); // fałsz

    


    
      Dude d3 = d1;

    


    
      Console.WriteLine (d1 == d3); // prawda

    


    Operatory równości i porównywania, ==, !=, <, >, >= oraz <=, działają z wszystkimi typami liczbowymi, ale należy się nimi ostrożnie posługiwać przy pracy z liczbami rzeczywistymi (o czym była mowa w podrozdziale „Błędy zaokrąglania liczb rzeczywistych”). Operatory porównywania działają także na składowych typu enum i porównują odpowiadające im wartości całkowitoliczbowe. Szerzej na ten temat piszemy w rozdziale 3. w podrozdziale „Wyliczenia”.


    Bardziej szczegółowy opis operatorów równości i porównywania zamieszczamy w podrozdziale „Przeciążanie operatorów” w rozdziale 4. oraz w podrozdziałach „Sprawdzanie równości” i „Określanie kolejności” w rozdziale 6.


    Operatory warunkowe


    Operatory && i || reprezentują logiczne i oraz lub. Często używa się ich w połączeniu z operatorem ! reprezentującym negację. W poniższym przykładzie metoda UseUmbrella zwraca wartość true, jeśli pada deszcz lub świeci słońce (bierzemy parasol, aby chronić się przed deszczem lub słońcem), ale pod warunkiem że nie wieje silny wiatr (ponieważ wtedy parasol jest bezużyteczny):

    static bool UseUmbrella (bool rainy, bool sunny, bool windy)


    
      {

    


    
        return !windy && (rainy || sunny);

    


    
      }

    


    Jeśli jest taka możliwość, operatory && i || stosują skróconą metodę obliczania wartości. Wracając do powyższego przykładu, jeśli wieje wiatr, to wyrażenie (rainy || sunny) nie jest w ogóle wykonywane. Ten sposób wykonywania operacji jest kluczowy dla tego, aby takie wyrażenia jak poniższe nie powodowały wyjątku NullReferenceException:

    if (sb != null && sb.Length > 0) ...


    Operatory & i | również reprezentują warunki i oraz lub logicznego:

    return !windy & (rainy | sunny);


    Różnica polega na tym, że nie stosują skróconej metody wykonywania obliczeń. Dlatego nieczęsto są używane.


    
      
        
        
      

      
        
          	
            [image: ]

          

          	
            Inaczej niż w językach C i C++, operatory & i | wykonują logiczne operacje porównywania dla wyrażeń logicznych. Operacje bitowe wykonują tylko wtedy, gdy zostaną zastosowane do liczb.

          
        

      
    


    Operator warunkowy (trójargumentowy)


    Operator warunkowy (zwany też często operatorem trójargumentowym — ang. ternary operator — ponieważ jako jedyny spośród operatorów działa na trzech argumentach) ma postać q ? a : b. Jeśli warunek q jest spełniony, następuje wykonanie wyrażenia a, w przeciwnym przypadku wykonywane jest wyrażenie b. Na przykład:

    static int Max (int a, int b)


    
      {

    


    
        return (a > b) ? a : b;

    


    
      }

    


    Operator warunkowy znajduje zastosowanie szczególnie w zapytaniach LINQ (rozdział 8.).


    Łańcuchy znaków i pojedyncze znaki


    Typ C# char (alias typu System.Char) reprezentuje znak Unicode i zajmuje dwa bajty. Literały typu char zapisuje się w pojedynczych cudzysłowach:

    char c = 'A'; // pojedynczy znak


    Do reprezentacji znaków, których nie da się literalnie wpisać lub zinterpretować, służą tzw. sekwencje specjalne (ang. escape sequences). Początek sekwencji specjalnej wyznacza ukośnik wsteczny, po którym znajduje się znak o specjalnym znaczeniu. Na przykład:

    char newLine = '\n';


    
      char backSlash = '\\';

    


    W tabeli 2.2 zamieszczono wykaz niektórych dostępnych sekwencji specjalnych.


    Tabela 2.2. Sekwencje specjalne


    
      
        
        
        
      

      
        
          	
            Znak

          

          	
            Znaczenie

          

          	
            Wartość

          
        


        
          	
            \'

          

          	
            Pojedynczy cudzysłów

          

          	
            0x0027

          
        


        
          	
            \"

          

          	
            Podwójny cudzysłów prosty

          

          	
            0x0022

          
        


        
          	
            \\

          

          	
            Ukośnik wsteczny

          

          	
            0x005C

          
        


        
          	
            \0

          

          	
            Wartość null

          

          	
            0x0000

          
        


        
          	
            \a

          

          	
            Alert

          

          	
            0x0007

          
        


        
          	
            \b

          

          	
            Backspace

          

          	
            0x0008

          
        


        
          	
            \f

          

          	
            Wysuw strony

          

          	
            0x000C

          
        


        
          	
            \n

          

          	
            Nowy wiersz

          

          	
            0x000A

          
        


        
          	
            \r

          

          	
            Powrót karetki

          

          	
            0x000D

          
        


        
          	
            \t

          

          	
            Tabulator poziomy

          

          	
            0x0009

          
        


        
          	
            \v

          

          	
            Tabulator pionowy

          

          	
            0x000B

          
        

      
    


    Sekwencja specjalna \u (lub \x) służy do wyrażania dowolnego znaku Unicode za pomocą czterocyfrowego kodu szesnastkowego:

    char copyrightSymbol = '\u00A9';


    
      char omegaSymbol = '\u03A9';

    


    
      char newLine = '\u000A';

    


    Konwersje znaków


    Niejawną konwersję typu char na typ liczbowy można wykonać dla typów liczbowych mieszczących w swoim zakresie wartości typu short bez znaku. W przypadku pozostałych typów liczbowych konieczne jest stosowanie konwersji jawnej.


    Typ string


    Typ C# string (alias typu System.String szczegółowo opisanego w rozdziale 6.) reprezentuje niezmienną sekwencję znaków Unicode. Literały łańcuchowe przedstawia się w cudzysłowach podwójnych