Tematem niniejszego artykułu są standardowe operacje wejścia
– wyjścia. Już wielokrotnie spotkaliśmy się z podstawowymi operacjami zarówno
wejściowymi jak i wyjściowymi na łamach portalu CentrumXP, niemniej jednak dzisiejszy
tekst będzie utrwaleniem oraz poszerzeniem zdobytej już wiedzy na temat
sposobów komunikowania się z użytkownikiem za pomocą języka C# 2.0.
Jak wiemy wykonanie jakiegokolwiek programu przez komputer
polega na wprowadzaniu odpowiednich danych do pamięci operacyjnej oraz
wykonaniu odpowiednich instrukcji, które te dane będą w odpowiedni sposób
przetwarzać. W końcu, wykonanie programu to wyprowadzenie przez komputer
(wyświetlenie) uzyskanych wyników. Do wprowadzania i wyprowadzania danych służą
między innymi: klawiatura, myszka, dyski, ekran czy drukarka. W niniejszym
artykule posłużą nam jedynie klawiatura (do wprowadzania danych) oraz ekran (na
którym wyświetlane będą wyniki, jakie otrzymamy w wyniku działania naszych
programów).
Pierwszym punktem naszego artykułu będzie zapoznanie się z
operacjami konsolowymi wejścia – wyjścia, jakich dostarcza nam język C# 2.0.
Warto zapamiętać: język C# obsługuje operacje wejścia –
wyjścia za pomocą strumieni. Kiedy programista chce użyć jakiejś zmiennej
bądź obiektu, to korzysta z jego nazwy i gotowe. Jednak, gdy chce zapisać dane
do np. pliku lub odczytać dane z tego pliku poprzez np. internet, musi je
umieścić we wspomnianym właśnie strumieniu.
Napiszmy przykład, który prezentuje sposób używania operacji
konsolowych w języku C# 2.0:
using System;
namespace CentrumXP_18
{
class
MojaKlasa
{
static void Main()
{
int mojaLiczba;
string mojaLiterka;
string mojZnak;
Console.WriteLine("Podaj
dowolną liczbę:");
mojaLiczba = int.Parse(Console.ReadLine());
Console.WriteLine("Podaj dowolną literkę:");
mojaLiterka = Console.ReadLine();
Console.WriteLine("Podaj dowolny znak:");
mojZnak = Console.ReadLine();
Console.WriteLine("Oto moje wyniki:");
Console.WriteLine("Moja liczba to: {0}.", mojaLiczba);
Console.WriteLine("Moja litera to: {0}.", mojaLiterka);
Console.WriteLine("Moje znaki to: {0}.", mojZnak);
}
}
}
W powyższym przykładzie godną uwagi jest metoda ReadLine(),
dzięki której możliwe było odczytanie kolejno wprowadzonej przez użytkownika
liczby, litery oraz dowolnego znaku.
Wyjaśnienia wymaga również następujący fragment kodu:
mojaLiczba = int.Parse(Console.ReadLine());
W powyższej linii użyliśmy metody Parse(), która
udostępnia sposób zamiany wartości numerycznej (odczytanej jako łańcuch
wprowadzony przez użytkownika przy pomocy klawiatury) na wewnętrzny format (w
naszym przypadku tym formatem jest intiger). A więc po odczytywaniu
łańcucha znaku wpisanego przez użytkownika i użyciu metody ReadLine()
skorzystano następnie z metody Parse(), dzięki której dokonano zamiany
łańcucha na liczbę całkowitą.
Po uruchomieniu powyższego przykałdu otrzymamy na ekranie
naszego monitora następujące wyniki:

Powyższy przykład jest bardzo przydatny, aby zapamiętać 3
ważne informacje:
- w języku C# parametr wejściowy musi być typu łańcuchowego.
Do jego wczytania służy metoda ReadLine().
- metoda Parse() umożliwia zamianę wartości
numerycznej (odczytanej jako łańcuch np. z klawiatury) na wewnętrzny
format
- do wyprowadzenia zmiennych (wyświetlenia ich) na ekranie
komputera służy metoda WriteLine().
Prześledźmy jeszcze inny prosty przykład prezentujący sposób
używania operacji wejścia – wyjścia:
using System;
namespace CentrumXP_18
{
class
MojaKlasa
{
public enum Oceny
{
Jedynka = 1,
Dwojka = 2,
Trojka = 3,
Czworka = 4,
Piatka = 5,
Szostka = 6
}
static void Main()
{
int
mojaOcena;
Console.WriteLine("Podaj ocenę jaką dzisiaj dostałeś w szkole:");
mojaOcena = int.Parse(Console.ReadLine());
switch (mojaOcena)
{
case
((int)Oceny.Jedynka):
Console.WriteLine("Niestety dostałeś dzisiaj jedynke. Popraw się!");
break;
case
((int)Oceny.Dwojka):
Console.WriteLine("Niestety dostałeś dzisiaj słabą ocenę. Popraw
się!");
break;
case
((int)Oceny.Trojka):
Console.WriteLine("Niestety trójka jest słabą oceną. Postaraj sie o
wyżej!");
break;
case
((int)Oceny.Czworka):
Console.WriteLine("Czwórka jest dobrą oceną...");
break;
case ((int)Oceny.Piatka):
Console.WriteLine("Gratuluję! Pięć to bardzo dobry stopień.");
break;
case ((int)Oceny.Szostka):
Console.WriteLine("Gratuluję! Szóstka to wspaniała ocena! Wiecej
ich.");
break;
default:
Console.WriteLine("Niestety podałeś nieprawidłowy stopień:-(");
break;
}
}
}
}
W powyższym przykładzie, obok
użycia metod ReadLine() oraz WriteLine() godną uwagi jest
instrukcja switch. Jest ona ulubioną instrukcją programistów stanowiącą
alternatywę dla zagnieżdżonych instrukcji if. Dlatego też warto napisać
parę słów na jej temat na łamach portalu CentrumXP. Jak większość z nas się
domyśla, zagnieżdżone instrukcje if są mało czytelne i w czasie ich
pisanie nietrudno o pomyłkę. Stąd twórcy języka C# utowrzyli alternatywę dla
pętli if, którą to definiujemy w następujący sposób:
switch (wyrażenie)
{
case
(stałe_wyrażenie):
instrukcja
instrukcja
skoku
[default: instrukcja]
}
Wyrażenie warunkowe znajduje się
w nawiasach w nagłówku instrukcji switch (podobnie jak w
instrukcji if). Każda instrukcja case wymaga
stałego wyrażenia (np. wyliczenia, które użyliśmy w powyższym przykładzie).
Jeśli dana instrukcja case pasuje do wyrażenia, to zostaną
wykonane instrukcje z nim związane, a następnie – instrukcja skoku. Zwykle
instrukcją skoku jest break, który powoduje wyjście programu z
instrukcji switch.
W powyższym przykładzie
zdefiniowaliśmy sobie wyliczenie przechowujące stopnie, jakie każdy uczeń może
dostać w szkole. Następnie za pomocą metody ReadLine() program wczytuje
ocenę, jaką wpisał za pomocą klawiatury uczeń. Kolejnym krokiem jest wykonanie
się instrukcji switch, która w zależności od podanej oceny będzie
wykonywać odpowiednią instrukcją wraz z odpowiadającą mu metodą WriteLine().
Po uruchomieniu powyższego
przykładu otrzymamy następujące wyniki:

A co będzie, jak uczeń wpisze
inną cyfrę niż z zakresu: 1-6? Nasz program jest na to przygotowany - a
mianowicie instrukcja switch, która zawiera instrukcję: default.
Instrukcja ta jest wykonywana w momencie, gdy żadna z instrukcji case
nie pasuje do danego wyrażenia (podanej przez użytkownika oceny) znajdującego
się w nawiasach w nagłówku switch. Innymi słowy, jeśli uczeń w
powyższym programie poda inną liczbę niż z przedziału: 1-6, wówczas żadna z
instrukcji case nie będzie na to „przygotowana” i wykona się
instrukcja default wraz z metodą WriteLine(), dzięki
której na ekranie otrzymamy napis informujący o tym, że podano błędną ocenę:

Drugim punktem niniejszego
artykułu są operacje wejścia – wyjścia na plikach. Jak wiemy, do tej pory
wykonywaliśmy operacje na danych, które były przechowywane wyłącznie w pamięci
operacyjnej komputera. Jednak język C# umożliwia nam zapis i odczyt do/z
pamięci zewnętrznej. Przechowywanie uporządkowanych danych w pamięci
zewnętrznej komputera odbywa się w plikach. W zależności od dostępu do pliku
mamy do czynienia z dwoma rodzajami klas plików:
- pliki o dostępie
sekwencyjnym – dostęp do danego elementu wymaga przejrzenia wszystkich
elementów poprzedzających go w pliku
- pliki o dostępie swobodnym
– dostęp do danego elementu nie zależy od innych elementów w pliku.
Jak każdy język obiektowy, tak i
C# 2.0 dostarcza nam szereg klas, które pozwalają nam na zapis danych do pliku
oraz ich odczyt z pliku. Plik otwieramy za pośrednictwem połączenia go ze
strumieniem. Język C# definiuje zarówno klasy strumieni bajtowych jak i
znakowych. Wszystkie klasy strumieni są zdefiniowane wewnątrz przestrzeni nazw System.IO.
Strumienie znakowe umożliwiają
zapis do plików tekstowych i ich odczyt. Do przeprowadzenia takich operacji
możemy wykorzystać strumień FileStream oraz klasy StreamReader
(do odczytu) oraz StreamWriter (do zapisu). Klasy te
automatycznie konwertują strumień bajtów na strumień znaków (i oczywiście na
odwrót).
Aby zdefiniować strumień bajtów
wraz z plikiem musimy zbudować obiekt typu FileStream, który
będzie między innymi miał konstruktor przyjmujący 2 parametry (nazwa pliku,
który chcemy otworzyć oraz tryb otwarcia tegoż pliku):
FileStream fs = new FileStream("C:\\moj_plik.txt", FileMode.CreateNew);
W powyższej linii kodu
zdefiniowaliśmy obiekt fs klasy FileStream, który
definiuje konstruktor przyjmujący wspomniane dwa parametry: plik: mój_plik.txt,
który chcemy otworzyć oraz tryb określający tryb otwarcia pliku (w
naszym przypadku tworzymy nowy plik). Poniżej prezentujemy wszystkie rodzaje
trybów otwarcia pliku:
Tryb
|
Opis
|
FileMode.Append
|
Dopisywanie danych do końca
pliku
|
FileMode.CreateNew
|
Utworzenie nowego pliku
|
FileMode.Open
|
Otwarcie pliku, który już
istnieje
|
FileMode.OpenOrCreate
|
Otwarcie pliku, któru już
istnieje lub utworzenie pliku, który nie istniał
|
FileMode.Truncate
|
Otwarcie pliku, który już
istnieje, redukując jego wartość do zera
|
Aby utworzyć wyjściowy strumień
znakowy, musimy umieścić obiekt typu Stream (np. FileStream)
wewnątrz klasy StreamWriter. Klasa ta oferuje kilka
konstruktorów, z których najczęściej używanym jest:
StreamWriter(Stream
nazwa_otwartego_strumienia);
Poniżej napiszemy prosty
program, który zapisuje do pliku (mojPlik.txt) znajdującego się na dycku
C wszystkie dane odczytane z klawiatury:
using System;
using System.IO;
namespace CentrumXP_18
{
class
MojaKlasa
{
static void Main(string[] args)
{
//zmienne
string
mojeDaneZapis;
FileStream
fsZapis;
//tworzymy
obiekt typu FileStream
fsZapis = new FileStream("C://mojPlik.txt", FileMode.CreateNew);
//tworzymy
strumien typu StreamWriter
StreamWriter sw = new StreamWriter(fsZapis);
//pobieramy dane
od uzytkownika
Console.WriteLine("Podaj dane do pliku.");
Console.WriteLine("Podaj imię i nazwisko:");
mojeDaneZapis = Console.ReadLine();
//zapisujemy
lancuch tekstu do pliku
sw.WriteLine(mojeDaneZapis);
//pobieramy
dane
Console.WriteLine("Podaj szczęśliwą liczbę:");
mojeDaneZapis = Console.ReadLine();
//zapisujemy
lancuch tekstu do pliku
sw.WriteLine(mojeDaneZapis);
//zamykamy
zapis
sw.Close();
}
}
}
W powyższym przykładzie utworzyliśmy
obiekt fsZapis typu FileStream wewnątrz klasy StreamWriter:
StreamWriter sw = new StreamWriter(fsZapis);
Następnie zbieramy wszystkie
dane, jakie zostały wpisane z klawiatury i za pomocą metody WriteLine()
wywołanej na obiekcie sw (obiekt klasy StreamWriter) zapisujemy
je do pliku mojPlik.txt, który został wcześniej utworzony dzięki klasie FileStream
(i trybowi FileMode.CreateNew).
Przy zapisywaniu danych do pliku
i używaniu przy tym obiektów klasy StreamWriter nie możemy zapomnieć o
metodzie Close() (metoda wywoływana właśnie na tych obiektach), dzięki
której zamykamy możliwość dalszego zapisywania łańcuchów znaków do naszego
pliku tekstowego.
Po skompilowaniu i uruchomieniu
powyższego przykładu otrzymamy następujące wyniki, które następnie w odpowiedni
sposób są zapisane do pliku mojPlik.txt stworzonego na dysku C:

Umiemy już zapisywać dane do
pliku. A więc czas na przykład prezentujący sposób odczytu tych danych z pliku:
mojPlik.txt:
using System;
using System.IO;
namespace CentrumXP_18
{
class
MojaKlasa
{
static void Main(string[] args)
{
//zmienne
string
mojeDaneOdczyt;
FileStream
fsOdczyt;
//tworzymy
obiekt typu FileStream
fsOdczyt = new FileStream("C://mojPlik.txt", FileMode.Open);
//tworzymy
strumien typu StreamReader
StreamReader sr = new StreamReader(fsOdczyt);
//odczyt danych
z pliku: mojPlik.txt
Console.WriteLine("Odczytujemy dane z pliku.");
while
((mojeDaneOdczyt = sr.ReadLine()) != null)
{
Console.WriteLine(mojeDaneOdczyt);
}
//zamykamy plik
sr.Close();
Console.ReadLine();
}
}
}
Aby odczytać dane z pliku musimy
utworzyć strumień wejściowy (w naszym przykładzie obiekt fsOdczyt klasy FileStream)
bazujący na znakach, który musi znajdować się wewnątrz klasy StreamReader:
StreamReader sr = new StreamReader(fsOdczyt);
Wartym podkreślenia jest
następujący fragment kodu:
while ((mojeDaneOdczyt =
sr.ReadLine()) != null
Powyższa linijka kodu umożliwia
odczyt danych z pliku, aż zostanie osiągnięty koniec tego pliku. Wszystkie te
dane zostaną następnie w odpowiedni sposób wyświetlone na ekranie. Podobnie jak
przy zapisywaniu, tak i przy odczycie ważną metodą jest metoda Close(),
która musi zostać wywołana na obiekcie typu StreamReader, aby dany plik
został w odpowiedni sposób zamknięty.
Jeśli nasz program zostanie
uruchominy, to na ekranie otrzymamy następujące wyniki:

Tematem niniejszego artykułu
było przedstawienie podstawowych operacji wejścia-wyjścia, jakie oferuje nam
język C# 2.0. Opowiedzieliśmy sobie o podstawowych metodach służących do
prawidłowej komunikacji użytkownika z komputerem (poznaliśmy znaczenie metod WriteLine()
oraz ReadLine()). Nauczylismy się również zapisywać dane do pliku
tekstowego i z niego je odczytywać. Przy okazji jednego z przykładów tego
artykułu zdefiniowaliśmy instrukcję switch, której znaczenie jest z
punktu widzenia programistów bardzo ważne.
Za tydzień będziemy kontynuować ten temat. Powiemy sobie
między innymi szerzej o sposobach zapisu i odczytu danych, a także o sposobach
tworzenia plików i katalogów na naszym dysku.