Spis treści Poprzednia strona: Klasycznie Następna strona: Stałe
W tym rozdziale dowiesz się:
- Jak przygotować środowisko pracy programistycznej?
- Z czego składa się program w języku C#?
- Jaka jest zależność między folderami a rozwiązaniami, projektami i przestrzeniami nazw?
- Jaka jest różnica między assembly a biblioteką?
- Jak korzystać z konsoli do wyprowadzania tekstów na ekran?
- Jak skompilować i uruchomić program?
Aby napisać pierwszy program potrzebujemy kilku rzeczy:
- Komputer z klawiaturą (może być laptop)
- Edytor tekstu (notatnik odpada, jeśli już, to Notepad++, ale najlepiej zintegrowane środowisko Visual Studio lub SharpDevelop)
- Środowisko .NET Framework do kompilowania i uruchamiania programów.
Jeśli zdecydujesz się na Visual Studio (mądra decyzja, bo w tym się pisze zawodowo, a .NET Framework jest od razu w pakiecie), to możesz zacząć od wersji testowej na 90-dni, albo od darmowej tzw. Community (dawniej Express) albo za darmo albo na 30 dni (po tym okresie wymaga darmowej rejestracji i wpisania kodu uzyskanej darmowej licencji). Wersja offline instalatora Express to obraz płyty w formacie ISO rozmiaru około 700 MB. Polecam jednak instalator sieciowy vcs_web.exe (wymaga internetu), bo zwykle ściąga mniej (tylko to, czego w systemie brakuje). Ten instalator może trochę czasu chodzić i wymagać restartu komputera.
Nie wiesz co to MB? MB to megabajty, jednostka rozmiaru zajmowanej pamięci komputerowej (dyskowej lub operacyjnej). 1 MB = 1024 kB (kilobajtów), 1 kB = 1024 B (bajtów), 1 B = 8 b (bitów), 1 b to jeden bit, czyli najmniejsza jednostka informacji – zero lub jedynka.
Możesz oczywiście skorzystać z darmowego środowiska SharpDevelop (znacznie mniejsze, bo instalator ma rozmiar około 19 MB), które w zupełności wystarczy (plus do tego .Net Framework 4, którego Standalone Installer to niecałe 50 MB). Jeśli będziemy mówić o czymś specyficznym dla Visual Studio, to poprzedzimy to etykietą „VS:”, a kwestie specyficzne dla SharpDevelop oznaczymy przez „SD:”. SD możesz mieć po polsku, a VS nie. Nie kieruj się tym jednak, bo nie wszystko jest przetłumaczone, a angielski i tak będzie potrzebny do korzystania z bibliotek klas. O tym co to są te biblioteki za chwilę.
Oczywiście wystarczy i notatnik, ale gdyby Egipcjanie budowali piramidy łyżeczką do herbaty… zaraz, zaraz, czy oni znali herbatę?
Acha. System operacyjny. Jeśli Windows XP, to co najmniej SP3, jeśli Vista, to SP2, a jeśli Windows 7 i więcej, to już jest OK, czyli w porządku.
Otwieramy nasze środowisko i tworzymy nowy projekt aplikacji konsolowej (VS: File > New project > Console Application, SD: File > New > Solution > Console Application) nadajemy mu nazwę HelloWorldApplication w polu Name i akceptujemy (klikamy VS: OK, SD: Create). Zostanie stworzone kilka folderów i plików składających się na tzw. Rozwiązanie [Solution], w którego skład wchodzi jeden projekt [project] o nazwie HelloWorldApplication. Otworzy się edytor z plikiem Program.cs (.cs to rozszerzenie nazwy pliku z kodem źródłowym w języku C Sharp, czyli C#) świadczący o tym, że będziemy tam pisali jakiś program.
Zamieńmy domyślną automatycznie wygenerowaną treść pliku Program.cs na taką:
using System; namespace HelloWorldApplication { /// <summary> /// This is an example class. /// </summary> class MyFirstClass { /// <summary> /// This is the static method where execution starts. /// </summary> /// <param name="args">Command line arguments</param> static void Main(string[] args) { Console.WriteLine("Hello World!"); Console.ReadKey(true); } } }
…i przeanalizujmy to teraz linijka po linijce:
- Używanie przestrzeni nazw System. Każdy plik to ma i mieć musi. Tutaj mogą być jeszcze inne linie z innymi przestrzeniami nazw. Oznacza to, że ten plik używa wszystkich klas [class], które znajdzie w wymienionych przestrzeniach nazw [namespaces].
- Linia odstępu (tylko dla czytelności, nie jest konieczna)
- Przestrzeń nazw HelloWorldApplication
- Początek definicji namespace
- Początek podsumowania [summary] w komentarzu XML-owym [XML comment]
- Treść podsumowania dotyczącego klasy MyFirstClass
- Koniec podsumowania i komentarza XML-owego
- Klasa MyFirstClass
- Początek definicji klasy MyFirstClass
- Początek podsumowania w komentarzu XML-owym
- Treść podsumowania dotyczącego metody Main
- Koniec podsumowania
- Opis parametru args
- Statyczna [static] metoda o nazwie Main nie zwracająca wartości (o tym mówi void), przyjmująca jeden parametr o nazwie args i o typie string[]
- Początek implementacji metody
- Początek treści metody Main. Wypisanie w jednej linii tekstu Hello World! na konsoli przy pomocy wywołania statycznej metody WriteLine należącej do klasy Console
- Każemy konsoli czekać na naciśnięcie dowolnego klawisza (metoda ReadKey) i nie pokazywanie na konsoli znaku odpowiadającego temu klawiszowi (to dzięki true)
- Koniec metody Main
- Koniec klasy MyFirstClass
- Koniec przestrzeni nazw HelloWorldApplication
Jak widać nawiasy klamrowe [curly brackets] albo krócej [braces] { i } oznaczają odpowiednio początek i koniec różnych rzeczy (tzw. bloków).
Należy się kilka wyjaśnień i postaraj się wszystkie te wyjaśnienia teraz zapamiętać.
W .NET mamy dwa poziomy abstrakcji: system plików i system nazw. Na poziomie systemu plików rozwiązania (tzw. solucje) zawierają projekty, a projekty zawierają pliki i foldery z plikami i folderami, itd. Na poziomie nazw przestrzenie nazw zawierają klasy. Klasy zawierają metody (i kilka innych rzeczy też). Metody zawierają instrukcje. Instrukcja to wyrażenie zakończone znakiem „;”, czyli średnikiem.
Komentarz rozpoczyna sekwencja znaków „//” (oczywiście bez tych cudzysłowów) i obowiązuje aż do końca linii, albo sekwencja „/*” i obowiązuje aż do sekwencji znaków „*/”. Komentarze nie są wykonywane, ale nie zawsze są całkowicie ignorowane. Komentarze XML-owe (to te zaczynające się od „///”) są przetwarzane na dokumentację.
Oprócz pliku Program.cs powstał plik AssemblyInfo.cs, w którym na razie nie grzebiemy. Nie jest to groźne, czy coś, ale dotyczy bardziej zaawansowanych zagadnień. I właśnie zaczyna się zabawa. Co to jest to Assembly? Najprościej rzecz ujmując assembly to jest plik, który powstaje w wyniku kompilacji projektu. W naszym przypadku (projekt typu Console Application) będzie to plik wykonywalny o rozszerzeniu nazwy .exe, a nazwa jest domyślnie taka sama jak nazwa projektu (tego zachowania się raczej nie zmienia, chociaż można). Taki plik .exe nie jest „samodzielny”, tzn. można go zabrać do kolegi, ale da się go tam uruchomić tylko jeśli kolega ma zainstalowaną taką samą wersję .NET Framework dla której skompilowaliśmy nasze assembly [target framework].
A co z solucją? Solucja ma swój plik o rozszerzeniu .sln i zwykle ma też swój folder (VS przy zapisie nowego projektu pyta czy utworzyć folder dla solucji, czy nie – zwykle wybieramy domyślne tak). W pliku .sln nie grzebiemy (chyba że zmieniliśmy ręcznie lokalizacje lub nazwy podfolderów z projektami), a w folderze solucji znajdują się podfoldery projektów. W każdym folderze projektu znajduje się plik projektu o rozszerzeniu .csproj i w nim też nie grzebiemy (chyba że wiemy jak i musimy go naprawić, bo ktoś inny nam go zepsuł – patrz programowanie zespołowe). To są dane potrzebne środowisku VS do zapamiętania co wchodzi w skład projektów, a co nie (tak, mogą być w tych folderach czy katalogach pliki, które nie wchodzą w skład projektu, i są w pewnym sensie ukryte, nie przejmujmy się nimi na razie). Strukturę rozwiązania widzimy w VS w oknie o nazwie Solution Explorer (SD: Projects).
Widzimy, że nasza przestrzeń nazw [namespace] też ma taką samą nazwę jak projekt: HelloWorldApplication. To też reguła, ale już częściej spotkamy się z jej łamaniem (namespace jest zwykle dłuższy i może nawet zawierać nazwę firmy i zespołu w którym pracujemy). W jednym projekcie może być wiele przestrzeni nazw i ta sama przestrzeń nazw może być w różnych projektach. Projekty służą do tworzenia assembly. Program to assembly, które odwołuje się [references] do innych assembly i łączy przestrzenie nazw w nich zawarte z własną przestrzenią nazw po to, aby odnaleźć wszystkie klasy potrzebne do swojego działania. Stąd właśnie nasz program wie gdzie jest klasa Console, z której korzysta: jest ona w przestrzeni nazw System w assembly o nazwie mscorlib.dll (tak, .dll a nie .exe, co oznacza że to nie jest program wykonywalny, tylko tzw. biblioteka klas [Class Library] i w oknie tworzenia nowego projektu obok aplikacji konsolowej [Console Application] jest też inny typ projektu, który mogliśmy wybrać: Class Library właśnie).
Plik mscorlib.dll to podstawowa biblioteka klas, dostępna w każdym programie. Zawiera podstawowe klasy z około 60 przestrzeni nazw, z których główne to:
- System – podstawowe typy, konwertery i właśnie konsola
- System.Collections – kolekcje, czyli większe zbiory danych
- System.Collections.Generic – kolekcje generyczne (tak ważne narzędzie, że VS dodaje tą przestrzeń nazw domyślnie do nowo tworzonych plików, podobnie dodaje też domyślnie System.Text i od wersji 3.5 także System.Linq z innej biblioteki System.Core.dll)
- System.Globalization – coś żeby programy były międzynarodowe
- System.IO – Input/Output (wejście/wyjście) pliki i strumienie danych
- System.Resources – zasoby dołączane do assembly (np. grafika)
- System.Security – bezpieczeństwo, ochrona dostępu, szyfrowanie
- System.Text – przetwarzanie tekstów różnych formatów (np. UTF-8)
- System.Threading – wątki, znane z programowania równoległego
Podczas programowania w .NET korzystamy z klas .NET Framework. Klasy te są zgrupowane w przestrzeniach nazw. Przestrzenie nazw są podzielone między biblioteki klas, czyli assembly w plikach dll. Jeżeli chcemy skorzystać z jakiejś klasy (np. Console), to albo używamy danej przestrzeni nazw, w której ta klasa się znajduje (jak w przykładzie using System) albo stosujemy pełną nazwę klasy razem z przestrzenią nazw: System.Console albo jednocześnie tak i tak, bo jeden sposób nie wyklucza drugiego dopóki nie ma konfliktu nazw (tzn. dopóki w naszej przestrzeni nazw HelloWorldApplication nie stworzyliśmy klasy też o nazwie Console). Dobry programista .NET wie (z praktyki) w jakich przestrzeniach nazw i w jakich bibliotekach są najważniejsze klasy, z których korzysta. Nie polecam nauki tego na pamięć, to samo przyjdzie z czasem i wcale nie jest konieczne. Każdą klasę można znaleźć na przykład przy pomocy narzędzia Object Browser w VS. W wersji Express też jest, ale nie widać go w menu, bo Express startuje w trybie Basic. Jeśli przełączymy VS w tryb Expert (Tools > Settings > Expert Settings – i radzę to zawsze ustawiać), to pojawi się pod View > Other Windows > Object Browser.
Jeśli do odpowiedniego assembly jest już odwołanie w naszym projekcie (pod węzłem References w oknie Solution Explorer), to ustawiając kursor (tzw. karetkę, czyli kursor do pisania [caret], a nie wskaźnik myszy [cursor]) na nazwie jakiejś klasy z tego assembly gdzieś w edytorze, możemy nacisnąć F12 (opcja Go To Definition), a otworzy się okienko edytora w miejscu, gdzie została zdefiniowana ta klasa. Jeśli tym miejscem jest plik .cs w naszym projekcie, to zostanie on otwarty, ale jeśli jest to gdzieś w assembly, to deklaracja (nie definicja, czyli nie będzie treści metod) całej klasy zostanie odczytana z metadanych [metadata] zawartych w assembly i pokazana w oknie tylko do odczytu (na samej górze mamy wtedy nazwę odpowiedniego assembly). Oczywiście możemy także podejrzeć treść metod zawartych w jakimś assembly (tak, już po jego kompilacji), bo kod pośredni (tzw. IL – Intermediate Language) da się przetłumaczyć z powrotem na kod źródłowy. Służy do tego narzędzie do dekompilacji o nazwie Reflector, który od lutego 2011 nie jest już darmowy, ale istnieją darmowe alternatywy (np. ILSpy). Zabawa z tymi narzędziami nie wymaga wielkiego zaawansowania, a możliwość „podglądania frameworka”, czyli „jak oni to w tym Microsoft-cie zrobili?” może się przydać nawet początkującym programistom, bo nie ma to jak zobaczyć czyjś kod i zrozumieć o co w nim chodzi.
Nazwa przestrzeni nazw może składać się z kilku wyrazów oddzielonych kropkami. Po co? Bo przestrzenie nazw mogą tworzyć (i zwykle tworzą) strukturę hierarchiczną. Im więcej kropek i wyrazów tym głębiej w hierarchii. Na przykład System.Collections.Generic jest zawarta w System.Collections a ta jest zawarta w System. Nie wszystko zaczyna się od słowa System. Kiedy piszemy swoje klasy, pierwszym słowem w przestrzeni nazw jest zwykle nazwa naszej firmy, potem nazwa projektu, a potem nazwa tzw. warstwy. Kropki oddzielają wszystko: poziomy przestrzeni nazw od siebie, przestrzeń nazw od nazwy klasy, nazwę klasy od nazwy członka [member] klasy (np. metody, pola, właściwości, zdarzenia, a nawet innej klasy zagnieżdżonej (tzn. też zawartej w jakiejś klasie) – tak, to rzadko spotykane, ale klasy też mogą tworzyć struktury hierarchiczne).
Jeśli do projektu dodamy folder, to klasy dodawane do tego foldera automatycznie lądują w przestrzeni nazw, która zawiera nazwę tego foldera. Spróbuj to zrobić w oknie Solution Explorer i zobacz jak wygląda przestrzeń nazw w pliku z klasą. Główną przestrzeń nazw projektu ustalamy we właściwościach projektu [properties] (zwykle tylko wciskamy na początek nazwę naszej firmy). Folder solucji nie bierze udziału w automatycznym przydzielaniu przestrzeni nazw (co nie oznacza, że nie można tego zrobić ręcznie). Można też mieć hierarchię solucji. Myślisz, że to już przesada? Poczekaj aż zobaczysz projekty, które mają po 8 tysięcy klas i kompilują się tak długo, że zdążysz iść na obiad i z niego wrócić, a dalej będzie się kompilowało.
Zwykle przyjmuje się zasadę: jeden plik na jedną klasę. Jak każda z tego typu zasad i ta może być złamana. W jednym pliku może być więcej klas i wtedy nazwa pliku przestaje być taka sama jak nazwa klasy w nim zawartej. Jedna klasa może być nawet w wielu plikach. Są to tzw klasy częściowe [partial]. To rozwiązanie przydaje się na przykład do ukrywania części klasy w innym pliku. Zwykle chodzi tu o wyodrębnienie automatycznie generowanej części klasy, której nie należy zmieniać. Jeśli nie rozumiesz czym są klasy – nie przejmuj się. Na razie będzie tylko jedna. O tym jak zadecydować kiedy stworzyć nową klasę, a kiedy rozszerzać istniejącą będzie w rozdziale o programowaniu obiektowym. Cierpliwości.
Kiedy mamy już napisany nasz pierwszy program przyszedł czas, żeby go uruchomić. Przed uruchomieniem musimy go jednak skompilować (nie musimy, ale należy się przyzwyczajać, żeby tak robić). W oknie VS: Solution Explorer (SD: Projects) klikamy prawym przyciskiem myszy na nazwie projektu i wybieramy z rozwiniętego menu pierwszą opcję o nazwie Build (dlatego czasem słyszy się o „bildowaniu”). Jeśli są błędy, to pojawiają się w oknie VS: Error list (SD: Errors). Kliknięcie w błąd przenosi nas do odpowiedniej linii kodu źródłowego, w której ten błąd wystąpił (chyba że błąd nie dotyczy kodu źródłowego). Poprawiamy wszystkie błędy i kompilujemy jeszcze raz. Jeśli nie było błędów, to zróbmy je na chwilę (np. zmień Console na Konsole), tylko po to, żeby zobaczyć jak działa ten mechanizm. Po bezbłędnej kompilacji uruchamiamy program poprzez klawisz F5, albo z menu VS: Debug > Start Debugging (SD: Debug > Run). Gdyby nie 17-ta linia programu, nie moglibyśmy zobaczyć wyniku jego wykonania (okna konsoli), bo od razu by się zamknęło (sprawdź to wykomentowując linię 17-tą, użyj komentarza liniowego // albo blokowego /* … */), a teraz czeka na naciśnięcie dowolnego klawisza i dopiero po tym się zamyka. Uruchamianie samo wykonuje kompilację jeśli zaszły zmiany w kodzie źródłowym od ostatniej kompilacji, więc po wykonaniu zmian wystarczy nacisnąć F5. Kompilację osobno robi się, kiedy chcemy tylko sprawdzić błędy, a nie chcemy uruchamiać albo nie możemy uruchamiać (np. jeśli projekt jest biblioteką, a nie aplikacją, albo skądinąd wiemy, że uruchomienie nic nie da – tzn. da błąd wykonania, bo czegoś jeszcze brakuje). Taka jest różnica między błędem kompilacji, a błędem wykonania: błąd wykonania występuje dopiero po uruchomieniu aplikacji (np. próba otwarcia do czytania nieistniejącego pliku). Uruchamianie przez F5 to tzw. uruchamianie z debugowaniem. Debugowanie [debugging] to w tym przypadku śledzenie miejsca wystąpienia błędów wykonania i wracanie do edytora w tym miejscu podobnie jak dzieje się to w przypadku błędów kompilacji. Aby uruchomić bez debugowania używamy kombinacji klawiszy Ctrl+F5 (przytrzymujemy na dół Ctrl, naciskamy i puszczamy F5 i potem puszczamy do góry Ctrl). Teraz wystąpienie błędu wykonania spowoduje, że nasz program się zepsuje i zostanie na siłę zakończony przez system operacyjny. Zobaczymy ten efekt i poznamy jego przykre konsekwencje nieco później. Teraz należy wiedzieć, że z debugowaniem uruchamia programy tylko programista. Gotowy program należy dostarczyć klientowi bez debugowania (i musi on działać bez debugowania).
Zadania
- Stwórz projekt aplikacji konsolowej w nowym rozwiązaniu i dodaj nowy projekt biblioteki klas to tego rozwiązania. Następnie dodaj do projektu aplikacji odwołanie do projektu biblioteki i pokaż, że aplikacja ma dostęp do klas z tej biblioteki.
- Poeksperymentuj z uzyskiwaniem dostępu do poszczególnych przestrzeni nazw, klas i składowych klas przy pomocy notacji z kropką.
- Zmień przestrzeń nazw biblioteki. Jaki ma to wpływ na sposób korzystania z tej klasy w aplikacji? Skorzystaj ze słowa kluczowego using.
- Zapoznaj się z metodami klasy Console, które pojawiają się w przewijanym okienku podpowiedzi [intellisense] po wpisaniu kropki po słowie Console.
- Niech główną przestrzenią nazw aplikacji będzie Moje.Konsole, a biblioteki Moje.Biblioteki. Czy musi istnieć folder o nazwie „Moje”?
- Skompiluj projekt. Gdzie powstał plik assembly? Dlaczego? Jak go zobaczyć w oknie Solution Explorer? Jak zmienić jego automatycznie nadawaną nazwę?
- Skompiluj rozwiązanie. Czy można skompilować jeden projekt rozwiązania, kiedy drugi zawiera błędy? Który i dlaczego?
Przykładowa aplikacja
Tak, jak było obiecane, każdy rozdział kończy dyskusja i rozwijanie tej samej przykładowej aplikacji do rozwiązywania Sudoku. Sudoku to wpisywanie cyfr do diagramu, więc chcemy mieć ten diagram, tzn. narysować go.
using System; namespace ConsoleSudoku { class Program { static void Main(string[] args) { Console.SetCursorPosition(35, 6); Console.Write("┌───┬───┬───┐"); Console.SetCursorPosition(35, 7); Console.Write("│ │ │ │"); Console.SetCursorPosition(35, 8); Console.Write("│ │ │ │"); Console.SetCursorPosition(35, 9); Console.Write("│ │ │ │"); Console.SetCursorPosition(35,10); Console.Write("├───┼───┼───┤"); Console.SetCursorPosition(35,11); Console.Write("│ │ │ │"); Console.SetCursorPosition(35,12); Console.Write("│ │ │ │"); Console.SetCursorPosition(35,13); Console.Write("│ │ │ │"); Console.SetCursorPosition(35,14); Console.Write("├───┼───┼───┤"); Console.SetCursorPosition(35,15); Console.Write("│ │ │ │"); Console.SetCursorPosition(35,16); Console.Write("│ │ │ │"); Console.SetCursorPosition(35,17); Console.Write("│ │ │ │"); Console.SetCursorPosition(35,18); Console.Write("└───┴───┴───┘"); Console.ReadKey(true); } } }
Metoda Console.SetCursorPosition(x,y) ustawia początek wypisywania tekstu na ekranie dla metody Console.Write i Console.WriteLine. Metoda Console.Write nie przenosi kursora do pierwszej pozycji w nowej linii pod spodem wypisanego tekstu, tak jak Console.WriteLine. W rzeczywistości Console.WriteLine(„A kuku”) to to samo, co Console.Write(„A kuku\r\n”). Teksty w cudzysłowach to tzw. łańcuchy znaków i jest o nich osobny rozdział.
Spis treści Poprzednia strona: Klasycznie Następna strona: Stałe