Sunteți pe pagina 1din 112

Namespaces – Spatiile de nume

De ce a fost nevoie de namespaces? Practic, programele mari sunt impartite in module si sunt
dezvoltate separat de mai multe persoane. Din acest motiv, exista posibilitatea de a aparea
identificatori cu acelasi nume. Solutia la aceasta problema a fost introducerea cuvantului cheie
namespace, care, in teorie, se defineste ca o regiune declarativa ce ofera o modalitate de a separa
un set de nume de un altul. In concluzie, numele din cadrul unui spatiu nu va intra in conflict cu
acelasi nume declarat in alt spatiu.

Declarare namespace. Directiva using

Concret, am definita o clasa cu numele Exemplu. Aceasta ar putea intra in conflict cu o alta
clasa Exemplu folosita de programul nostru si care este dezvoltata de o alta persona. Folosind
spatiile de nume, evitam acest tip de problema. Forma generala pentru declararea unui spatiu de
nume este :

namespace Nume
{

//membri

Nume reprezinta numele spatiului. Toate entitatile definite in interiorul spatiului fac parte din
domeniul de valabilitate al acelui spatiu de nume ( se pot adauga clase, structuri, delegari,
enumerari, interfete si surpriza! chiar un alt spatiu de nume ).

Intr-un fisier sursa, se pot adauga oricat de multe spatii de nume, iar fiecare spatiu poate
contine oricat de multe entitati.
namespace A
{
public class Exemplu
{
//proprietate
public string Mesaj
{
get;
set;
}
}
}

Clasa Exemplu, care contine o proprietate numita Mesaj, este definita in domeniul de
valabilitate al spatiului de nume A.

Atunci cand e nevoie de crearea unei instanta a clasei, numele clasei trebuie sa fie calificat
cu numele spatiului. Acest mod este cunoscut ca “fully qualified name“.

A.Exemplu ex;
ex = new A.Exemplu();

Nu mai este necesar sa calific cu numele spatiului obiectul sau pe oricare din membrii sai. In
cazul nostru, proprietatea Mesaj, poate fi apelata direct.

ex.Mesaj;

Normal, situatia va deveni dificila cand programul va include referinte mai multe catre un
spatiu de nume si va trebui specificat numele de fiecare data. Solutia vine de la directiva using,
care va asigura vizibilitatea spatiului de nume.

using A;

Acum, declararea se poate face asa :

Exemplu ex;
ex = new Exemplu();

Compilatorul va cauta in mod automat in spatiul de nume A si in cazul in care va gasi clasa
Exemplu, o va folosi.

In cazul in care va gasi aceeasi clasa in doua spatii de nume diferite, va trebui specificata clasa si
spatiul de nume dorit.

Spatii de nume imbricate

Este posibil ca un spatiu de nume sa fie imbricat in interiorul altuia.


namespace A
{
namespace A1
{
public class ExempluA1
{
//
}
//
}
public class ExempluA
{
//
}
}

Acest lucru ajuta la divizarea in parti mai mici a codului pentru un mod de lucru mai organizat si
mai eficient.

Pentru a instantia clasa ExempluA1, trebuie neaparat sa folosesc modul “fully qualified name”.

A.A1.ExempluA1 exA1 = new A.A1.ExempluA1();

Folosirea “fully qualified name” nu pare foarte eficienta in scrierea codului , dar este
recomandata pentru usurinta identificarii spatiilor de nume, mai ales in lucru in echipa.

Spatii de nume aditive

Pot exista mai multe declaratii namespace cu acelasi nume. Acest lucru permite distribuirea unui
spatiu de nume in mai multe fisiere sau chiar separarea sa in cadrul aceluiasi fisier.

namespace B
{
public class Student
{
//
}
}
namespace B
{
public class Profesor
{
//
}
}

La compilare, continutul ambelor spatii de nume B va fi unit.

In cazul in care se redefineste o clasa, apare eroare la compilare.


Spatiile de nume constituie un instrument pentru organizarea programelor si cresterea
durabilitatii in mediile distribuite moderne.

Spatiile de nume reprezinta un concept fundamental in C# si orice program utilizeaza un spatiu


de nume intr-un fel sau altul.

Un exemplu de spatiu de nume folosit pe care il foloseste biblioteca arhitecturii .Net (implicit,
biblioteca limbajului c#) este System. Astfel, la inceputul oricarui program, este inclusa linia de
cod:

using System;

Acest spatiu reprezinta radacina pentru toate spatiile de nume din .Net. De asemea, el contine
si tipurile de date. De exemplu, se poate declara si initializa o variabila de tip int, astfel:

System.Int16 numar = 0;

Important de stiut este faptul ca acest spatiu contine clasa Object, care este radacina intregii
ierarhii de mostenire din Framework. Daca se defineste o clasa fara a se specifica in mod explicit
faptul ca aceasta mosteneste o anumita clasa, clasa Object va fi clasa implicita de baza. Ea
furnizeaza toate metodele si proprietatile pe care toate obiectele trebuie sa le suporte.

Exista multe alte spatii de nume subordonate lui System, care contin alte parti ale bibliotecii
limbajului c#.

O lista cu cele mai utilizate spatii de nume nu poate fi stabilita exact, dar printre cele mai
cunoscute sunt:

System.IO

Spatiul Input Output contine clase folosite pentru operatiile de intrare-iesire cu fisiere. De
exemplu, clasele File, StreamWriter, BinaryReader, BinaryWriter au functii ce folosesc la
accesarea unui fisier de pe disc. Acest namespace include si clase folosite in manipularea datelor
aflate in memoria aplicatiei: MemoryStream. Tot in acest namespace sunt definite clase folosite
in manipularea fisierelor si directorilor: DirectoryInfo, DriveInfo, FileInfo.
In acest namespace sunt definiti 3 delegates: ErrorEventHandler, FileSystemEventHandler si
RenamedEventHandler.

System.Collection

Permite dezvoltatorilor sa lucreze usor cu grupurile de obiecte. Exista colectii utile pentru
cresterea performantei in diferite scenarii (ArrayList, Queue, Stack, etc) si exista Dictionaries
(Hashtable, SortedList, StringDictionary, etc) cu principiul cheie-valoare.
System.Data

Pentru orice operatie cu o sursa de date externa, trebuie stabilita o conexiune cu acea sursa.
Acest spatiu de nume contine clase in mai multe subspatii (SqlClient, OleDb, etc) responsabile
cu: date referitoare la sursa de date, functii pentru deschiderea / inchiderea conexiunii, tranzactii
si multe altele.

System.Text

Contine clase care permit codarea si decodarea caracterelor, Encoder, Decoder. Foarte
utilizate sunt si sub-spatiul System.Text.RegularExpressions si clasa StringBuilder.

System.Diagnostics

E un spatiu de nume foarte util in “depanarea” aplicatiei (debug). Are clasa EventLog ce
permite interactiunea cu event log din Windows, clasa Process care furnizeaza o functionalitate
pentru monitorizarea proceselor sistem din retea, pornirea si oprirea proceselor sistem locale.
Clasa PerformanceCounter pentru monitorizarea performantei sistemului.

Sunt cazuri in care se adauga spatii de nume specifice tipului de proiect.

System.Web.Services – pentru crearea serviciilor web.

System.Web.UI – pentru crearea formelor web.

System.Window.Forms pentru crearea interfetei utilizator a unei aplicatii Windows.

O imagine de ansamblu asupra .Net Framework 3.5 si a spatiilor de nume organizate pe


tehnologia folosita, pe site-ul Microsoft!

Tipuri de date

Tipurile de date si operatorii stau la baza oricarui limbaj de programare. Ele stabilesc limitele
unui limbaj si determina tipurile de activitati pentru care poate fi utilizat.

Un program opereaza cu date. Daca un limbaj de programare nu furnizeaza un mod pentru


stocarea datelor, atunci el este inutil.

O variabila este o locatie de memorie cu nume, careia ii poate fi stocata o valoare. Valoare se
poate modifica pe parcursul executarii programului. Nu exista conceptul de variabila fara “tip”
pentru ca tipul unei valori determina in mod exact operatiile permise asupra sa (nu toate
operatiile sunt permise asupra tuturor tipurilor).

O constanta (literal), spre deosebire de variabila, desemneaza o valoare fixa. Pentru fiecare
tip de data, C# are un mod de declarare.
De ce tipurile de date sunt importante ?

C# este un limbaj puternic tipizat. Asta inseamna ca pentru toate operatiile, compilatorul
efectueaza verificari privind compatibilitatea tipurilor. Aceste verificari sunt necesare pentru
prevenirea erorilor si cresterea fiabilitatii programelor.

Pentru urmatoarele exemple, o sa adoptam urmatoarea conventie de scriere, numita conventia


Pascal : in cazul numelor compuse din mai multe cuvinte, fiecare cuvant este scris cu majuscula
(ex, AnFabricatie) – valabila pentru numele claselor, metodelor, proprietatilor, enumerarilor,
interfetelor, spatiilor de nume. In cazul variabilelor, primul cuvant incepe cu minuscula
(variabila anFabricatie).

Comentariile (foarte importante !) intr-un program C# se fac prin folosirea // pentru a


comenta un rand, iar pe mai multe randuri se folosesc /* */ .

Tipuri valorice in C#

Limbajul C# contine doua tipuri generale de tipuri predefinite.

Toate tipurile de date sunt derivate din tipul System.Object.

I – tip valoare.

Toate tipurile sunt derivate din clasa System.ValueType.

In cazul acestor tipuri de date, atunci cand se declara o variabila, va fi nevoie si de alocare de
spatiu pentru ea. Initial,variabilele contin valoarea implicita specifica tipului. Cand se face
atribuirea, are loc o copiere a datelor in variabila destinatie care nu mai este legata de variabila
initiala (transmitere prin valoare, ”value semantics”).

using System;
using System.Collections.Generic;
using System.Text;
namespace ExempluTipuriValoare
{
public struct Masina
{
//variabila instanta
public int anFabricatie;
}
class Program
{
static void Main(string[] args)
{
//creez obiectul masina, numit Seat
Masina seat = new Masina();
//atribui o valoare variabilei an a instantei seat
seat.anFabricatie = 2009;
Masina opel = seat;
// se initializeaza prin copiere variabila sb
Console.WriteLine("Masina Seat este din anul {0}.",
seat.anFabricatie);
Console.WriteLine("Masina Opel este din anul {0} prin
initializare.", opel.anFabricatie);
//schimb valoarea variabile an a instantei seat
seat.anFabricatie = 2010;
Console.WriteLine("Masina Seat este din anul {0}.",
seat.anFabricatie);
//valoare variabilei an a instantei opel ramane aceeasi
Console.WriteLine("Masina Opel este din anul este {0}.",
opel.anFabricatie);
Console.ReadLine();
}
}
}

II – tip referinta.

In cazul acestor tipuri de date, la declararea unei variabile nu are loc automat alocarea de
spatiu. Initial, referintele sunt null. E nevoie de alocare explicita de memorie pentru obiectele
propriu-zise, iar la atribuire este copiata referinta in destinatie, obiectul spre care indica
ramanand acelasi (“aliasing”, “reference semantics”).

Masina seat = new Masina();


seat.anFabricatie = 2009;
Masina opel = seat;

Console.WriteLine(seat.anFabricatie);
Console.WriteLine(opel.anFabricatie);
Console.Read();
Sistemul de operare si Common Language Runtime impart memoria folosita pentru stocarea
datelor in memorie stiva (stack) si memorie heap, fiecare functionand in mod diferit.

Memoria stack are o structura de tip FIFO (first in, last out) si este foarte eficienta. Ca un
exemplu practicStiva retine variabilele de tip valoare.

Heap retine variabilele create dinamic. Ca avantaj este faptul ca obiectele pot fi alocate si
sterse intr-o ordine aleatoare. Pentru a putea detine mai mult control si ordine, memoria heap are
nevoie de un “memory manager” si un “garbage collector”.

Tipurile de date reprezinta un subiect foarte vast. Vom continua cu tipurile valorice
fundamentale in C#.

Limbajul C# include doua categorii de tipuri de date : tipuri valorice si tipuri referinta.

Pentru ca C# respecta un domeniu de valori, iar fiecare tip valoric are un comportament,
limbajul asigura portabilitate. De exemplu, o variabila declarata de tip int, va ramane tot de tip
int, indiferent de mediul de executie. Asta ne ajuta la evitarea rescrierii codului pentru adaptarea
programului la o anumita platforma.

Conform MSDN, tipurile valoare se clasifica astfel :

I – Tipul structura – care cuprinde :

Tipuri numerice

Intregi

In C# sunt definite 9 tipuri de intregi : char, byte, sbyte, short, ushort, int, uint, long,
ulong. Cu exceptia lui char, toate sunt folosite la calcule numerice.

Variabilele de tip int se utilizeaza in controlul buclelor, indexarea tablourilor, aritmetica


normala cu numere intregi. Se recomanda ca atunci cand e nevoie de o valoare ( fara semn ) care
depaseste domeniul lui int,sa se foloseasca uint. In cazul valorilor mari este recomandat long, iar
la cele fara semn, ulong.
//declararea unui int cu valoare implicita 0
int aria;

//un alt mod de a declara un int


int latime = new int();

//atribuire valoare pentru variabila latime


latime = 30;

//declarare si initializare
int lungime = 40;

//CALCUL ARIA
aria = latime * lungime;

//Afisarea rezultatului in consola


Console.Write("Aria este {0} metri patrati.", aria);

In virgula mobila

Tipurile in virgula mobila, se utilizeaza pentru reprezentarea numerelor care au parte


fractionara. Exista doua tipuri : float, double. Acesta din urma, este cel mai intrebuintat.

//declararea unui double cu valoare implicita 0


double mediaAn;

//declarea si initializare
double mediaSemUnu = 8.45;

//un alt mod de a declara un tip double


double mediaSemDoi = new double();
//atribuire valoare pentru variabila mediaSemDoi
mediaSemDoi = 9.65;

//CALCUL MEDIA
mediaAn = (mediaSemUnu + mediaSemDoi) / 2;

//afisarea rezultatului in consola


Console.WriteLine("Media anuala este {0}", mediaAn);

Decimal

Tipul decimal este folosit in calculele monetare. El are avantajul ca elimina erorile de
rotunjire atunci cand se lucreaza cu valori fractionare, pentru ca poate reprezenta in mod precis
pana la 28 de pozitii zecimale. Tipul decimal nu exista in C, C++, Java.

//variabile de tip decimal,in declarare, trebuie urmate de m sau M


//declarea unei variabile decimal cu valoare implicita 0.0M
decimal sold = 1000.10m;
//un alt mod de a declara o variabila decimal
decimal dobanda = new decimal();

dobanda = 0.1m;

//CALCUL SOLD NOU


sold = sold + sold * dobanda;

//afisarea in consola a rezultatului


Console.WriteLine("Noul sold este {0} USD ", sold);

Tipuri Bool

Tipul bool retine valorile de adevar ( “true” ) si fals ( “false” ). Orice expresie de tipul bool
va lua una din aceste valori. Nu se poate converti. Teora

bool a;
a = false;
Console.WriteLine("a este {0}", a);
bool b;
b = true;
Console.WriteLine("b este {0}", b);

Caractere
In C#, pentru caractere, se utilizeaza modelul Unicode.

//declararea unei variabile char cu valoare implicita '\0'


char ch;

ch = 'b';
//afisarea valorii din standardul UNICODE pentru un caracter
//are loc conversia catre un int
Console.WriteLine("Valoarea lui {0} in standardul UNICODE este
{1}",ch,(int)ch);

II Structura – tip valoare propriu

Este asemantor cu cel din C++. Pentru ca este un tip valoric, va fi stocata in stiva. Atat timp
cat structura este rezonabila in ceea ce priveste marimea, nu vor fi probleme cu administrarea
memoriei.

O structura poate contine declaratii de constante, campuri, metode, proprietati, indexatori,


operatori, constructori.

Cele mai simple structuri, sunt System.Int32, System.Int64, System.Single, etc, pentru tipurile
de baza int, long, float.

struct Persoana
{

public string nume;


public string prenume;
public int varsta;
}

III – Tipul enumerare

Tipul enumerare se defineste de programator si se aseamana cu cel din C++. Acest tip permite
folosirea numelor carora li se atribuie o valoare.

Este recomandat sa declaram si sa definim o enumerare direct in interiorul unui spatiu de


nume, pentru a putea fi folosita de toate clasele.

Sintaxa pentru a defini o enumerare este :

[atribute] [modificatori] enum NumeEnum : [tip]


In cazul in care nu se specifica tipul enumerarii, acesta este considera implicit int. ( [] –
optional )

namespace tipulEnum
{
enum ZileSaptamana
{
Luni = 1,
Marti,
Miercuri,
Joi,
Vineri,
Sambata,
Duminica
}
}

Un exemplu de folosire a acestei enumerari este :

Console.WriteLine("Miercuri este a {0} zi din saptamana ",


(int)tipulEnum.ZileSaptamana.Miercuri);

Pentru a converti un tip string, de exemplu avem numele zilei, in Enum, se procedeaza in felul
urmator:

ZileSaptamana zi = (ZileSaptamana) Enum.Parse(typeof(ZileSaptamana),


"Miercuri");

Observatii:
Implicit, valoare primului membru din enumerare este 0. Pentru un exemplu simplu si practic,
initializat variabila ‘Luni’ cu 1; Apoi, fiecare variabila care va urma va avea o valoare implicita
mai mare decat precendenta, cu o unitate.

Daca nu foloseam conversia in exemplu ( int ), rezultatul ar fi aratat numele elementului.

Tipurile enum sunt derivate din clasa System.Enum, derivata din System.ValueType.

Tipurile nullable

Sunt tipuri valoare pentru care se pot memora valori posibile din aria tipurilor de baza,
inclusiv valoare null.

Valoarea unei variabile contine valoarea implicita a tipului, daca nu este initializata explicit.
Exista cazuri, in care se doreste ca valoarea implicita a variabilei sa nu fie definita.

Se foloseste structura System.Nullable <T>, T este un tip valoare , astfel :

Nullable<int> a ;
Un alt mod de a declara o variabila de acest tip este: int? a = null; iar verificarea se poate face in
felul urmator: Console.WriteLine(“valoarea variabilei este {0}”, a ?? “null”);

Tipuri repetitive

In limbajul c# exista mai multe posibilitati de a executa in mod repetat anumite blocuri de cod
(ciclu). Aceste posibilitati sunt:

• for
• while
• do…while
• foreach

Aceste instructiuni sunt specifice multor limbaje si intelegerea folosiri lor ar trebui sa fie usoara
pentru oriceine le-a mai folosit cu alte limbaje.

Le voi analiza pe rand, exemplificand utilizarea lor.

Cel mai simplu ciclu este ciclul for. Sintaxa generala este:

for (int i = 0; i <= 5; i++)


{
//executa ceva de 5 ori
}

O alta modalitate de scriere a unui bloc for este urmatoarea:

for (; ; )
{
// ...
}

care este un ciclu infinit. Acest bloc de cod ar trebui intrerupt (printr-o intructiune break, ca in
exemplul urmator:

int i = 0;
for (; ; )
{
// ...
if(i++ == 5) break; //intrerupe ciclul
}

O alta modalitate de a scrie un bloc ce se repeta la infinit este cu ajutorul keyword-ului while:

int i = 0;
while(true)
{
// ...
if(i++ == 5) break; //intrerupe ciclul
}

Acest bloc de cod este echivalent (functional) cu cel anterior (folosinf for).

Instructiunile do…while sunt o alta metoda de a executa inmod repetat anumite linii de cod:

int x = 0;
do
{
Console.WriteLine(x);
x++;
} while (x < 5);

Diferenta dintre un bloc while si un bloc do…while este ca instructiunile din do…while se
executa cel putin o data – conditia de iesire din loop se verifica dupa cel putin o executie. Codul
dintr-un ciclu while poate sa nu fie executat niciodata.

De exemplu:

int x = int.Parse(Console.ReadLine());
while (x % 5 != 0)
{
Console.WriteLine(x++);
}

Daca userul introduce un numar multiplu de 5, codul blocului while nu se executa nici macar o
data.

Am lasat la final foreach, deoarece este o metoda de a executa in mod repetitiv anumite operatii,
insa putin diferit de modurile cum lucreaza blocurile descrise anteorior. foreach executa blocul
de comenzi asociat o singura data pentru fiecare element dintr-un array sau colectie de obiecte –
conditia ca foreach sa poata fi aplicat colectiei este ca acea colectie sa implementeze interfata
IEnumerable (sau IEnumerable<T>).

Exemplu folosind un array:

int[] numbers = new int { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };


foreach (int n in numbers)
{
Console.WriteLine("{1} -> {2}", n, 2 * n);
}

Acest exemplu va afisa perechi de tipul n -> 2n pentru fiecare numar din array-ul de int folosit.

Conversia datelor in .NET


Inainte de a continua prezentarea tipurilor de date din C#, voi introduce un capitol care se leaga
strans de tipurile valorice numerice si anume conversiile.

Deocamda, cunoastem structura tipurilor de date astfel :

Definim expresie in modul urmator :

O expresie este o combinatie valida de operatori, literali si variabile.

O conversie permite ca o expresie de un anumit tip, sa fie tratata ca o expresie de un alt tip.

Exista doua tipuri de conversii numerice :

Conversii implicite
Conversia implicita se efectueaza automat doar daca nu este afectata valoarea convertita ( tipul
destinatie este mai cuprinzator decat tipul sursa ) si cele doua tipuri sunt compatibile. Daca sunt
indeplinite cele doua conditii, atunci valoarea din partea din dreapta este convertita la tipul din
partea stanga.

int i;
float f;

i = 10;

//atribuim unui int unui float


//valoarea lui i este intai convertita la tipul float
//apoi atribuita lui f
f = i;

Tipul float este suficient de cuprinzator pentru a memora orice valoare de tip int(conversie prin
largire ).

Chiar daca se poate efectua conversia automata din int in float, invers nu se poate efectua
automat.

int i;
float f;

f = 30.4F;
i = 10;

//conversia automata nu are loc


i = f;

Eroare la compilare: “Cannot implicitly convert type ‘float’ to ‘int’. An explicit conversion exists
(are you missing a cast?)“

Conversii explicite
Pentru a rezolva eroare de mai sus, este nevoie de folosirea unui cast (o directiva catre
compilator pentru convertirea unui tip in altul).

(tip-tinta) expr;

tip – tinta – tipul la care dorim sa convertim expresia expr;

int i;
float f;

f = 30.4F;
Console.WriteLine("Valoarea inainte de conversie {0} ", f);
//conversie cu trunchiere

i = (int)f;
Console.WriteLine("Valoarea dupa conversia de la float la int este {0} ", i);

Se observa ca partea fractionara se pierde, atunci cand valoarea in virgula mobila este
convertita la un tip intreg ( conversie prin restrangere ).

char ch;
byte b;
//codul ASCII pentru X
b = 88;
//cast intre tipuri incompatibile
ch = (char)b;
Console.WriteLine("ch : " + ch);

Una din cele mai intalnite conversii in programare este cea din tipul numeric in sir de
caractere. Aceasta se realizeaza cu metoda ToString din clasa Object.
int a = 2009; string s = a.ToString(); Console.WriteLine("{0} este {1}", a, a.GetType());
Console.WriteLine("{0} este {1}",s, s.GetType());

La fel de folosita este si conversia din caractere in numar, care se realizeaza cu metoda Parse
din clasa Object.

Daca sirul de caractere nu reprezinta un numar valid, conversia sirului la numar va esua.

int a = 2009;

string s = a.ToString();

Console.WriteLine("{0} este {1}", a, a.GetType());


Console.WriteLine("{0} este {1}",s, s.GetType());

Conversiile boxing si unboxing

Boxing (“impachetare”) permite ca tipurile valoare sa fie tratate ca obiecte. Boxing este o
conversie implicita. “Impachetarea” unei valori a unui tip valoare se realizeaza prin alocarea unui
obiect si copierea valorii in obiectul nou creat.

Rezultatul va fi crearea unui obiect 0, pe stiva,care va fi referinta catre o valoare de tip int, pe
stiva. Valoarea este o copie a valorii asignate variabilei a.

//declararea si initializarea unui tip valoare


int a = 2009;
//boxing asupra variabilei de tip valoare
object o = a;

//schimba valoarea
a = 2010;

Console.WriteLine("Valoarea tipului valoric este {0}", a);


Console.WriteLine("Valoarea tipului obiect este {0}", o);

“Unboxing” permite o conversie explicita de la tipul obiect la orice alta valoare de tip valoare.
O operatie de unboxing consta in verificarea faptului ca obiectul instantiaza o valoare “boxed” a
unui tip vlaoare, apoi copiaza valoare catre instanta.
int a = 2009;

//boxing

object o = a;

int j = 0;
//unboxing
j = (int)o;

//schimb valoarea pentru tipul valoare


a = 2010;
Console.WriteLine("Valoarea tipului valoric este {0}", a);
Console.WriteLine("Valoarea tipului obiect este {0}", o);
Console.WriteLine("Valoarea noului tip valoric, dupa unboxing este {0}", j);

O alta conversie pe care vreau sa o mentionez este cea din Enum in int si invers.
Daca avem enumeratia prezentata in exemplul din postarea anterioara, Tipuri de date in limbajul
c# – tipuri valorice, ZileSaptamana putem scrie:
int luni = (int)tipulEnum.ZileSaptamana.Luni; //luni va fi egal cu 1

sau putem scrie:


tipulEnum.ZileSaptamana luni = (tipulEnum.ZileSaptamana)1;

Tipurile referinta in .Net

Tipurile referinta permit accesul la datele necesare din locuri diferite ale programului.

Declararea tipurilor referinta nu implica automat alocarea de spatiu, ca in cazul tipurilor valoare.
E nevoie de alocare explicita de memorie pentru obiectele propriu-zise, iar la atribuire este
copiata referinta in destinatie, obiectul spre care indica ramanand acelasi (“aliasing”, “reference
semantics”). O imagine explicativa se gaseste in articolul Tipuri de date in C#.

Structura completa a tipurilor de date :


Tipurile referinta se clasifica astfel :

Tipul clasa – class

Clasa este unitatea de baza pe care este contruit limbajul C#. Clasele ofera baza pentru
programarea orientata pe obiect. Ele definesc natura obiectelor si reprezinta un sablon prin care
se defineste forma unui obiect. Intr-o clasa sunt definite datele si codul care prelucreaza acele
date. Pentru a stapani bine C#, elementul clasa este esential ( intreaga activitate a unui program
C# se desfasoara in interiorul unei clase).

Exemplu :

public class Student


{

public int nota;


public string nume;
public string prenume;

//metoda de afisare
public void Afiseaza()
{
Console.WriteLine("{0} {1} are nota {2}", nume, prenume, nota);
}
}

Tipurile clasa suporta mostenirea, un mecanism prin care o clasa ( derivata ) preia anumite
caracteristici ale unei alte clase ( de baza). Un exemplu concret va fi dezbatut in articolul despre
mostenire.

Tipul interfata – interface

O interfata defineste un set de metode care vor fi implementate de o clasa. Interfata poate fi
privita ca un “contract” iar clasa sau structura care o implementeaza, trebuie sa il respecte.

O interfata reprezinta o constructie pur abstracta si poate contine metode, proprietati,


evenimente, indecsi. O interfata poate mosteni mai multe interfete, iar o clasa poate implementa
mai multe interfete. Un exemplu concret va fi dezbatut in articolul despre interfete.

Tipul delegat – delegate

Prin cuvantul cheie delegate se defineste o delegare care nu inseamna decat o referinta catre o
metoda. Asadar, la crearea unui tip delegat se creaza un obiect care poate memora o referinta
catre o metoda, metoda care poate fi apelata prin intermediul referintei. Un exemplu concret va fi
dezbatut in articolul despre delegate.

Tipul obiect – object

Acest tip se bazeaza pe Sytem.Object din .Net Framework.

Pe langa exemplele din articolul Conversii despre boxing si unboxing unde se prezinta atribuirea
valorilor de orice fel variabilelor de tip object, adaug un exemplu in care se atribuie o valoare
referinta.

object a;

a = new Student();
Student student;
student = (Student)a;
Console.WriteLine(student.nota);

Tipul sir de caractere – string

Tipul string este unul din cele mai importante si utilizate tipuri de date. El defineste si
implementeaza sirurile de caractere. Spre deosebire de alte limbaje, unde tipul string este un
tablou de caractere, in C# tipul string este un obiect. Clasa string are o dimensiune relativ mare.

Exista doua tipuri de siruri


regulate – contine intre ghilimele zero sau mai multe caractere, inclusiv secvente escape
(reprezentarea caracterelor care nu au reprezentare grafica si a celor speciale).

string sir = "Limbajul C# este accesibil!";

“verbatim” – se folosesc cand se fac referiri la fisiere, la prelucrarea lor, la registri. Tipul acesta
de sir incepe cu “@” inainte ghilimelor.

string cale = @"c:\TipReferinta\string.cs";

Pentru concatenarea (unirea) sirurilor de caractere se mai foloseste operatorul “+”. Exemplu :

string sirConcatenat = "Limbajul " + "C#" + " " + "este accesibil";

Pentru comparare se folosesc “==” si “!=” – egalitate inseamna daca ambele sunt null sau daca
ambele au aceeasi lungime si pe fiecare pozitie au caractere respectiv identice. Un exemplu
complet in articolul despre clasa System.Text.

Despre ARRAYS

Un tablou reprezinta o colectie de variabile de acelasi tip, referite prin intermediul unui nume
comun. Tablourile se utilizeaza oriunde exista nevoia de a grupa mai multe variabile de acelasi
tip la un loc pentru a putea fi gestionate si sortate cu usurinta.Accesul se face printr-o variabila
referinta.

Diferenta dintre tablourile din alte limbaje si cele din C#, este ca, aici, tablourile sunt
implementate ca obiecte, fapt care conduce la colectarea automata a spatiului ocupat de
tablourile neutilizate.

In C#, tablourile pot avea mai multe dimensiuni.


Unidimensionale – intalnite foarte frecvent in programare.

La declarea unui tablou, se creeaza o instanta a clasei .Net, System.Array. Compilatorul va


traduce operatiile asupra tablourilor, apeland metode ale System.Array.

La declararea unui tablou folosim tipul variabilei, apoi avem nevoie de “[]” si la sfarsit
identificatorul.

Tip [] nume;

//declarea unui tablou unidimensional


//nu se aloca spatiu pentru memorie
int[] colectie;

//instantiere si alocare de memorie


//zona de memorie cu 4 elemente de tip int
colectie = new int[4];

Un mod in care se pot face in acelasi timp si operatiile de declarare, instantiere, initializare,
este :

//declarare, instantiere, initializare


int[] colectie = new int[] { 23, 23, 12,4 };

Numarul valorilor dintre acolade trebuie sa fie egal cu numarul care indica marimea
vectorului, altfel va aparea eroare la compilare.

Se poate omite declararea marimii vectorului si folosirea operatorului new. Compilatorul va


calcula marimea din numarul de initialzari.

int[] colectie = { 1, 3, 4, 5, 6 };

Un element din tablou poate fi accesat utilizand un index ( descrie pozitia elementului din cadrul
tabloului ). In C#, toate tablourile au indexul primului element egal cu zero.

//atribuim primului element o noua valoare


colectie[0] = 100;
Introducem instructiunea foreach, utilizata pentru iterarea elementelor unei colectii.

foreach ( tip nume-var in colectie ) instructiue;

tip nume-var specifica tipul si numele unei variabile de iterare, care va primi la fiecare iteratie
valoarea unui element din colectie. Trebuie ca tip sa fie compatibil cu tipul de baza al tabloului.
Variabila de iteratie nu poate fi modificata in timpul utilizarii ei in accesul la tablou.

Exemplu :

//utilizarea foreach pentru afisarea valorilor din vector


foreach (int i in colectie)
{
Console.WriteLine(i);
}

Incepand cu versiunea 3.0, C# introduce cuvantul cheie var. In articolul acesta, il vom folosi
pentru a creea vectori de tip implicit.

Exemplu :

//compilatorul identifica variabila nume ca un vector de string


var listaNume = new[] { "Elena", "Florin", "Andrei" };

Daca se adauga si alte variabile de alt tip, va aparea eroare la compilare.

Metoda Array.Sort permite sortarea elementelor dintr-un tablou, indiferent de tipul acestuia.

System.Array.Sort(listaNume);

Console.WriteLine("Sortare alfabetica :\n ");


foreach (string nume in listaNume)
{
Console.WriteLine("{0}", nume);
}

De remarcat ca Array.Sort sorteaza doar obiecte care implementeaza IComparable, interfata care
defineste relatie de ordine intre oricare doua elemente de tipul dat.
Un exemplu de sortare “custom” poate fi vazut pe programare.org: .Net IComparer.

Un avantaj al faptului ca, in C#, tablourile sunt implementate ca obiecte, este atribuirea pentru
fiecare tablou a proprietatii Length, care contine numarul de elemente pe care tabloul le poate
memora.

Exemplu :
Console.WriteLine("Numarul de elemente din vector este {0}",
listaNume.Length);

Cand incercam sa atribuim o valoare de tipul referinta la un tablou al unei altei valori de tip
referinta ( copiere ), se modifica doar obiectul referit de catre acea variabila. Nu se produce o
copiere a tabloului si nici continutul unui tablou nu se copiaza in celalalt.

int[] colectie1 = { 4, 4, 5, 5 };

int[] colectie2 = { 23, 23, 23, 23 };

Console.WriteLine("Elemente colectia1:");
foreach (int numar in colectie1)
{
Console.WriteLine(numar);
}

Console.WriteLine("Elemente colectia2 :");


foreach (int numar2 in colectie2)
{
Console.WriteLine(numar2);
}

//colectie2 refera pe colectie1


colectie2 = colectie1;
Console.WriteLine("Dupa atribuire :");

Console.WriteLine("Elemente colectia1:");

foreach (int numar in colectie1)


{
Console.WriteLine(numar);
}

Console.WriteLine("Elemente colectia2 :");


foreach (int numar2 in colectie2)
{
Console.WriteLine(numar2);
}

Tablourile multidimensionale

Tablourile multidimensionale au doua sau mai multe dimensiuni, iar un element oarecare
poate fi accesat utilizand combinatia a doi sau mai multi indici.

Se face distinctie intre tablouri regulate si tablouri neregulate (tablouri de tablouri – jagged
arrays ).

Cele mai simple tablouri multidimensionale regulate sunt cele bidimensionale. In cadrul
acestui tablou, pozitia unui anumit element este data de doi indici.

//dimensiune 2X2
int [,] matrice = new int[2,2];
//atribuim valoare elementului de pe linia 2 si coloana 1
matrice[1,0] = 230;

Tablourile reprezinta un raspuns la multe intrebari din programare si sunt utilizate cu succes
intr-o mare varietate de scopuri pentru modalitatea convenabila de grupare a variabilelor de
acelasi tip la un loc.

Despre operatori

Un operator este un simbol care indica o actiune. Operandul este valoarea asupra careia se
executa operatia. Operatorii alaturi de operanzi formeaza o expresie.

Clasificarea operatorilor

Operatorii sunt impartiti 3 categorii.

Operatori unari – actioneaza asupra unui singur operand.

Operatori binari – actioneaza intre doi operanzi.

Operatori ternari – actioneaza asupra a trei operanzi. Exista doar unul de acest tip, operatorul
conditional.

Operatorii aritmetici

In C#, exista urmatorii operatori aritmetici :

Operator Semnificatie
+ Adunare ( si plus unar )
- Scadere ( si minus unar )
* Inmultire
/ Impartire
% Impartire Modulo ( rest )
++ Incrementare
– Decrementare

Exemplu :

int numar = 5;
int numar2 = 13;

//Adunare
Console.WriteLine(numar + numar2);
//Scadere
Console.WriteLine(numar - numar2);
//Inmultire
Console.WriteLine(numar * numar2);
//Impartire
//restul este trunchiat
Console.WriteLine(numar2 / numar);
//conversie la tipul double, restul nu este trunchiat
Console.WriteLine((double)numar2 / numar);

//Impartire modulo
//returneaza restul impartirii
Console.WriteLine(numar2 % numar);

Exemplu incrementare :

//incrementare, cresterea cu o unitate a valorii


Console.WriteLine("INCREMENTARE PREFIXATA");
Console.WriteLine(++numar);
//sau folosind expresia x = x+1
numar2 = numar2 + 1;
Console.WriteLine(numar2);
Console.WriteLine("INCREMENTARE POSTFIXATA");
//operatia se va efectua inainte de a calcula valoarea operandului
Console.WriteLine(numar++);
Console.WriteLine(numar2++);

Exemplu decrementare

//decrementare, scaderea cu o unitate a valorii


Console.WriteLine("DECREMENTARE PREFIXATA");
Console.WriteLine(--numar);
//sau folosind expresia x = x-1
numar2 = numar2 - 1;
Console.WriteLine(numar2);

Console.WriteLine("DECREMENTARE POSTFIXATA");
Console.WriteLine(numar--);
Console.WriteLine(numar2--);

Operatorul "+" este folosit la concateanarea stringurilor :

Console.WriteLine("5"+"5");

Operatorii relationali

Operatorii relationali se refera la relatiile de ordine care pot exista intre doua valori. Deoarece
acesti operatori produc rezultate de tip adevarat sau fals, sunt folositi des cu operatorii logici.

Operator Semnificatie
== egal cu
!= diferit de
< mai mic
> mai mare
<= mai mic sau egal
>= mai mare sau egal

Exemplu

//obiecte diferite
object a = 1;
object b = 1;

Console.WriteLine(a == b);

int numar1 = 1;
int numar2 = 1;
Console.WriteLine(numar1 != numar2);

Operatorii logici

Pentru operatorii logici, operanzii trebuie sa fie de tipul bool, rezultatul fiind tot de acest tip.

Operator Semnificatie
! negatie logica
&& SI logic
|| SAU logic

Intr-o expresie logica ce are in componenta operatorul &&, daca primul operand are valoare
false, rezultatul va fi false, indiferent de valoarea celui de-al doilea operand, iar expresia nu va
mai fi evaluata.

Exemplu:

//Operatorul &&
Console.WriteLine(false && false);
Console.WriteLine(false && true);
Console.WriteLine(true && false);
Console.WriteLine(true && true);

Intr-o expresie logica ce are in componenta operatorul ||, daca primul operator are valoare
true, rezultatul va fi true, indiferent de valoarea celui de-al doilea operand, iar expresia nu va mai
fi evaluata.

Exemplu

//Operatorul ||
Console.WriteLine(true || false);
Console.WriteLine(false || true);
Console.WriteLine(false || false);
Console.WriteLine(true || true);

Operatorul de atribuire

Operatorul de atribuire “=” se foloseste intr-o constructie de forma variabila = expresie.


Dupa evaluarea expresiei, rezultatul va fi atribuit variabilei. Pentru atribuire se mai folosesc si
operatorii +=, –=, *=, /=, %=.

Exemplu :

int a = 5;
Console.WriteLine(a += 5);
//echivalent
Console.WriteLine(a = a + 5);

Operatorul conditional

Este de forma : expresie1? expresie2: expresie3 .

Daca expresie1 este adevarata, atunci va fi returnata valoarea lui expresie2. Daca expresie1 este
falsa, atunci va fi returnata valoarea lui expresie3.

Exemplu :

int x = 6;
int y = 10;
int z = 67;

//6<10 este true, returneaza valoarea lui y


Console.WriteLine(x < y ? y : z);

In C# sunt foarte multi operatori iar in cazul in care o expresie nu are paranteze, operatiile se
executa conform prioritatii operatorilor. Lista poate fi consultata aici.

Supraincarcarea operatorilor

Prin supraincarea operatorilor se redefineste semnificatia unui operator in contextul dat de o


anumita clasa creata. Prin supraincarea unui operator, se extinde aria de utilizare a acestuia la
clasa creata. Efectele operatorului pot diferi de la o clasa la alta.

Supraincarcarea operatorilor este strans legata de supraincarcarea metodelor. Desi ajuta la


sporirea expresivitatii codului, supraincarcarea operatorilor poate duce si la crearea confuziilor.

Despre polimorfism
“O singura interfata, mai multe metode” – sintagma pe care se bazeaza conceptul de
polimorfism. Se incearca stabilirea unei interfete generice pentru un intreg grup de activitati
asemanatoare.

Un obiect polimorfic este capabil sa ia mai multe forme, sa se afle in diferite stari, sa aiba
comportamente diferite.

Polimorfismul parametric

O metoda va prelua orice numar de parametri.

Cand cream o metoda, de regula se stie numarul parametrilor care vor fi transmisi. Sunt cazuri
in care nu se intampla acest lucru si va fi nevoie de un numar arbitrar de parametri. Se va recurge
la un tip special de parametru, de tipul params. Acesta va declara un tablou de parametri, care
poate memora zero sau mai multe elemente.

Exemplu :

//metoda va returna numarul minim


public int metoda(params int[] numere)
{

int minim;
//in cazul in care nu e transmis functiei
//nici un parametru, afiseaza un mesaj
if (numere.Length == 0)
{
Console.WriteLine("Nu sunt parametri");

return 0;
}
//initializam variabila
//cu primul element al tabloului
minim = numere[0];

//comparam fiecare element al tabloului


//cu valoarea minima initiala
foreach (int i in numere)

if (i < minim)
//atribuim valoarea minima variabilei minim
minim = i;
//inapoi la programul apelant al functiei
return minim;

Polimorfismul ad-hoc

Se mai numeste si supraincarcarea metodelor, una dintre cele mai interesante facilitati oferite de
limbaju C#. Cu ajutorul acesteia, se pot defini in cadrul unei clase mai multe metode, toate avand
acelasi nume, dar cu tipul si numarul parametrilor diferit. La compilare, se va apela functia dupa
numarul parametrilor folositi la apel.

Pentru a supraincarca o metoda, pur si simplu trebuie doar declararea unor versiuni diferite ale
sale. Nu este suficient insa, ca diferenta dintre doua metode sa fie facuta doar prin tipul valorii
returnate, ci e nevoie si de tipurile sau numarul parametrilor.

Exemplu:

class SupraincarcareMetoda
{

public void CalculeazaMedia()


{
Console.WriteLine("Nici un parametru");
}
//supraincarcam cu un parametru intreg
public void CalculeazaMedia(int nota)
{
Console.WriteLine("O nota:" + nota);
}
//supraincarcam cu doi parametri intregi
public void CalculeazaMedia(int nota1, int nota2)
{
Console.WriteLine("Doi parametri: " + nota1 + " " + nota2);
}
//supraincarcam cu parametri double
public void CalculeazaMedia(double nota)
{
Console.WriteLine("Un parametru double:" + nota);
}
//eroare, daca incerc sa suprascriu
//doar prin tipul de date returnat
public int CalculeazaMedia(double nota)
{
//
}
}

In metoda principala a programului :

//apelam toate versiunile lui CalculeazaMedia


SupraincarcareMetoda sup = new SupraincarcareMetoda();
sup.CalculeazaMedia();
sup.CalculeazaMedia(10);
sup.CalculeazaMedia(23, 23);
sup.CalculeazaMedia(34.34);

Polimorfismul de mostenire

Intr-o ierarhie de clase, se pune problema apelarii metodelor care au aceeasi lista de parametri,
dar care sunt in clase diferite.
Exemplu:

class Baza
{
public void Afiseaza()
{
Console.WriteLine("Apelul functiei Afiseaza din clasa de baza\n");
}

class Derivata : Baza


{
public void Afiseaza()
{
Console.WriteLine("Apelul functiei Afiseaza din clasa derivata");
}

La compilare se rezolva problema apelarii metode Afiseaza, pe baza tipului declarat al obiectelor
:

Derivata obiect2 = new Derivata();


//instantiem pe un obiect din clasa derivata
Baza obiect1 = obiect2;
//afiseaza functia din clasa de Baza
obiect1.Afiseaza();
obiect2.Afiseaza();

Modificatorii virtual si override

Virtual este folosit in declararea unei metode sau a unei proprietati. Acestea se vor numi
membri virtuali. Implementarea unui membru virtual poate fi schimbata prin suprascrierea
membrului intr-o clasa derivata.

Override se foloseste pentru a modifica o metoda sau o proprietate si furnizeaza o noua


implementare a unui membru mostenit dintr-o clasa de baza. Metoda de baza suprascrisa si
metoda de suprascriere trebuie sa aiba aceeasi signatura ( tip si numar de parametri ).

Implicit, metodele nu sunt virtuale. Nu se pot suprascrie metodele care nu sunt virtuale.

Exemplu:

class Baza
{
public virtual void Afiseaza()
{
Console.WriteLine("Apelul functiei Afiseaza din clasa de baza\n");
}
}
class Derivata : Baza
{
public override void Afiseaza()
{
Console.WriteLine("Apelul functiei Afiseaza din clasa derivata");
}

}
Derivata obiect2 = new Derivata();
//instantiem pe un obiect din clasa derivata
Baza obiect1 = obiect2;
//afiseaza functia din clasa de Baza
obiect1.Afiseaza();
obiect2.Afiseaza();

Polimorfismul ajuta la reducerea complexitatii pentru ca permite unei interfete sa fie folosita
de fiecare data pentru specificarea unei clase generice de actiuni. Programatorul nu va efectua
manual selectia. Selectia actiunii specifice (metoda) va fi facuta de compilator.

Programare orientata pe obiecte

Programarea orientata pe obiecte este notiunea de baza a limbajului C# si reprezinta o


metodologie puternica de abordare a programarii, pornind de la programarea nestructurata,
programarea procedurala si programarea modulara.

Programele orientate spre obiect sunt organizate in jurul datelor (ceea ce este afectat de executia
programului). Programele contin datele cat si metodele asociate crearii, prelucrarii si distrugerii
datelor.

Caracteristicile comune limbajelor care implementeaza programarea pe obiecte sunt:


incapsularea, polimorfism, mostenirea. Despre ultimele doua notiuni, vom discuta in urmatoarele
doua articole.

Incapsularea e un mecanism care combina codul si datele mentinandu-le integritatea in


timpul utilizarii. Din aceasta combinatie, se creeaza obiectul. Tot la nivelul incapsularii se
defineste nivelul de acces la datele unui obiect.

In cadrul unui obiect, codul si datele pot fi private sau public. Cand sunt private, ele sunt
vizibile si accesibile doar in interiorul obiectului. In cazul public, celelalte parti ale programului
le pot utiliza.

Forma unui obiect este definata de clasa. Datele care constituie o clasa sunt denumite
variabile membri. Codul care opereaza asupra datelor este numit metoda .

Metoda implementeaza o actiune, poate admite parametri si returna valori de tip predefinit, de
tip obiect sau tipul void (nimic). Un parametru sau argument este o valoare transmisa unei
metode, cu valabilitate in corpul functiei.

Exemplu :
public class Student
{
//declararea variabilelor membru
public double nota;
private int varsta;

//declarea unei metode


//care va returna true/false
public bool Admis()
{
//
}
}

Datorita modificatorului de acces public, clasa Student va fi vizibila peste tot si cu acces
nelimitat. In cazul in care nu adaugam un modificator in declarea clasei, ea ar primit implicit tot
acest modificator de acces.

modificator Explicatii
access
public access nelimitat
internal acces permis doar in clasa sau spatiul de nume
in care e cuprinsa
protected acces in clasa curenta sau in cele derivate
private implicit.Doar pentru clasele interioare
protected folosit pentru clasele interioare semnificand
internal accesul in clasa care-l contine sau in tipurile
derivate din clasa care-l contine

Atat datele cat si metodele pot avea modificatori de acces:

modificator access explicatie


public membrul accesibil de oriunde
internal accesbil doar intr-un bloc
functional al unei aplicatii .Net
protected accesibil oricarui membru al
clasei care-l contine si al
claselor derivate
private implicit. acces permis doar
pentru clasa care contine
membrul
protected internal accesibil oricarui membru al al
clasei care il contine si al
claselor derivate, precum si in
blocul functional

Definitia unei clase creeaza un nou tip de data (clasa este un tip de date referinta). Continuand
cu exemplu, vom creea un obiect de tipul Student.

//creeaza un obiect Student


Student Popescu = new Student();

Dupa executia instructiunii, Popescu va fi o instanta a clasei Student. La crearea unei instante,
se va crea un obiect care contine o copie proprie a fiecarei variabile membru din cadrul clasei.
Pentru a referi este variabile, se va utiliza operatorul “.” .

Exemplu:

//operatorul punct leaga


//numele unui obiect de numele
//unui membru al sau
Popescu.nota;

Constructorul

Constructorul este o metoda care face parte dintr-o clasa. Constructorul contine instructiuni
care se executa la crearea unui nou obiect al clasei. Daca o clasa nu are definit un constructor,
atunci se va atribui automat constructorul fara parametri al clasei de baza.

//constructor implicit
public Student()
{
nota = 0;
varsta = 0;

//constructor cu doi parametri


public Student(double nota, int varsta)
{
this.nota = nota;
this.varsta = varsta;
}

O clasa poate avea mai multi constructori.

La instantierea clasei, apelam constructorul parametrizat:

Student Ionescu = new Student(8.2, 23);


Console.WriteLine(Ionescu.nota);
Destructorul

Corpul destructorului este format din instructiuni care se executa la distrugerea unui obiect al
clasei. Destructorul nu este apelat in mod explicit, pentru ca, in mod normal, la procesul de
distrugere este invocat Garbage Collector .

Operatorul new

Operatorul new este folosit pentru crearea obiectelor si invocarea constructorilor (reamintim
ca si la tipurile de date se apela constructorul implicit: int variabila = new int ();).

Operatorul new nu poate fi supraincarcat.

Operatorul this

Acest operator se refera la instanta curenta pentru care a fost apelata o metoda, o proprietate.

Operatorul mai este folosit in situatia cand numele unui parametru sau al unei variabile locale
coincide cu numele unei variabile membru. In acest caz, numele local “va ascunde” variabila
membru. Prin intermediul lui this, se va putea referi la acea variabila membru. (exemplu mai sus,
la declararea constructorului cu parametri).

Pentru o aplicare cat mai buna a principiilor POO, sunt importante operatiile de identificare a
entitatilor, a datelor si operatiilor, a relatiilor dintre entitati si o posibila ierarhie a claselor.

Mostenirea este unul din principiile fundamentale ale programarii. Cum functioneaza? Definim o
clasa generala, clasa de baza, ce are caracteristici comune pentru o multime de elemente
asemanatoare. Apoi, clasa de baza va fi mostenita de alte clase, clase derivate, care isi vor
adauga pe langa caracteristicile mostenite, numai caracteristici care le identifica in mod unic. O
clasa derivata este o versiune specializata a unei clase de baza. O clasa derivata va mosteni toate
variabilele, metodele, proprietatile si indexarile definite in clasa de baza, la care va adauga
elemente proprii, unice.

Este nevoie de conceptul de mostenire pentru a evita repetitia definirii unor clase care au in
comun mai multe caracteristici. In programare, mostenirea se refera la clasificare, la o relatie
intre clase.

In C#, o clasa derivata poate mosteni de la o singura clasa de baza ( mostenire simpla ).

O clasa de baza poate fi derivata in mai multe clase derivate. O clasa derivata poate fi clasa de
baza pentru alta clasa derivata.

O clasa de baza impreuna cu toate clasele derivate, direct sau indirect, formeaza o ierarhie de
clase. Reamintim, toate clasele din C# mostenesc clasa Object.

Exemplu de mostenire :
//clasa de baza
class Forma
{
public double inaltime;
public double latime;
public void AfiseazaDimensiune()
{
Console.WriteLine("inaltime: {0}, latime: {1}",inaltime,latime);
}
}
//clasa Dreptunghi mosteneste
//clasa Forma
class Dreptunghi : Forma
{
public double Aria()
{
//clasa Dreptunghi poate
//referi membrii clasei Forma
//ca membri proprii
return inaltime * latime;
}
}

In metoda Main :

Dreptunghi d1 = new Dreptunghi();


d1.inaltime = 32;
d1.latime = 2;
Console.WriteLine("Aria: {0}", d1.Aria());

In ceea ce priveste sintaxa la mostenire, se observa ca numele clasei de baza urmeaza dupa
numele clasei derivate, separate de “:”.

Clasa de baza Forma poate fi utilizata independent, chiar daca din ea deriva alte clase.

Forma forma = new Forma();


forma.AfiseazaDimensiune();

Despre accesul la membri : in cazul in care in clasa de baza, declaram membri de tip private, (
private double inaltime; private double latime; ), clasa Dreptunghi nu i-ar mai fi putut accesa.
Membrii privati sunt accesibili doar pentru codul din interiorul clasei lor, deci, mostenirea NU va
anula restrictiile impuse.

Pentru a putea depasi aceasta problema si a nu fi nevoie sa declaram membri public ( vor fi
accesibili in tot restul codului ), una din solutii ar fi declararea membrilor ca protected. Acest
modificator de acces face ca membri protejati sa fie public in cadrul unei ierarhii de clase, dar
privati in afara ei.

O intrebare interesanta cand se discuta despre mostenire, apare cand clasele de baza si clasele
derivate dispun de constructori proprii : Ce constructor se utilizeaza pentru a construi un obiect
apartinand unei clase derivate? Raspuns : constructorul clasei de baza construieste portiunea
obiectului care apartine de clasa de baza, constructorul clasei de baza construieste portiunea care
apare de clasa derivata.

//clasa de baza
class Forma
{
//membri variabila
protected double _inaltime;
protected double _latime;
//constructor pentru clasa Forma
public Forma(double inaltime, double latime)
{
_inaltime = inaltime;
_latime = latime;
}
}
//clasa Dreptunghi mosteneste
//clasa Forma
class Dreptunghi : Forma
{
//membru variabila privat
string stil;

//utilizam base pentru


//a executa constructorul
//clasei de vaza
public Dreptunghi(string s, double inaltime, double latime)
: base(inaltime, latime)
{
stil = s;
}
}

Constructorul clasei Dreptunghi initializeaza si membrii mosteniti de la clasa Forma impreuna


cu campul propriu stil.

O alta situatie legata de mostenire este atunci cand intr-o clasa derivata definim un membru al
carui nume coincide cu numele unui membru din clasa de baza.

class A
{
public int a;

class B : A
{
int a;

In acest caz, la compilare va aparea mesajul :


‘Inheritance.B.a’ hides inherited member ‘Inheritance.A.a’. Use the new keyword if hiding was
intended. Daca se doreste ca membrul clasei de baza sa nu fie vizibil in clasa derivata, se
foloseste new.

//aceasta declarare a lui a


//il ascunde pe a din clasa A
new int a;

Chiar daca variabila membru i din B ascunde i din A, folosind base se permite accesul la
variabila definita in clasa de baza. Este valabil si pentru metode.

//descopera a
//din clasa A
base.a;

Modificatorul abstract

In unele cazuri, este necesar crearea unei clase de baza care stabileste un sablon general
comun pentru toate clasele derivate, fiecare completand detaliile caracteristice. O clasa care este
definita cu abstract, nu poate fi instantiata, poate contine metode abstract, iar o clasa derivata
dintr-o clasa abstract trebuie sa o implementeze in totalitate.

Modificatorul abstract poate fi aplicat si asupra metodelor, proprietatilor.

//aceasta clasa
//nu va putea fi mostenita
abstract class M
{
public abstract void Metoda();
}

Modificatorul sealed

Se foloseste in situatiile in care se doreste impiedicarea mostenirii.

//aceasta clasa
//nu va putea fi mostenita
sealed class M
{

Modificatorii sealed si abstract nu pot fi folositi impreuna pentru o clasa.


Pentru ca .Net nu permite derivarea multipla, o clasa nu poate deriva din doua clase; in loc de
folosirea claselor abstracte se pot folosi interfete, care se aseamana cu clasele abstract – definesc
un obiect fara a specifica implementarea concreta. O clasa pote implementa mai mult de o
interfata.
Mostenirea este un concept cheie in programarea orientata pe obiect. Alaturi de polimorfism si
incapsulare.

Vezi si articolul despre interfete

Interfete

Scriam in articolul despre mostenire (inheritance) ca mostenirea unei clase foarte importanta in
C#. Dar, un altfel de mostenire, cea a interfetelor, are o putere mult mai mare.

O interfata defineste un set de metode, proprietati, evenimente, indexatori care vor fi


implementate de o clasa. Atentie, interfetele NU implementeaza, ele sunt abstracte si doar isi
descriu membrii. Interfata precizeaza CE trebuie facut, dar NU si CUM trebuie facut.

Sintactic, interfetele sunt asematoare cu clasele abstract, cu mentiunea ca nici o metoda nu


poate avea corp. Se foloseste cuvantul cheie interface.

interface nume {
tip nume-metoda (param);
tip nume-metoda(param);
}

tip – tipul de date returnat de metoda,

param – signatura metodei, lista de parametri – numarul si tipul lor.

Toti membri declarati in interfata, sunt public in mod implicit.

public interface Interfata


{
void Metoda1();
void Metoda2();
}

Dupa definirea interfetei, o clasa o va putea implementa folosind aceeasi sintaxa ca la


mostenirea unei clase de baza, prin specificarea numelui interfetei dupa numele clasei, separate
de “:”.

Exemplu : selectam in Visual Studio numele interfetei si la click dreapta vom implementa
interfata :
Codul, avand continutul metodelor modificat :

//clasa A implementeaza interfata Interfata


class A : Interfata {

#region Interfata Members

public void Metoda1()


{
Console.WriteLine("Metoda1");
}

public void Metoda2()


{
Console.WriteLine("Metoda2");
}

#endregion
}

La crearea unei interfete, ca restrictii avem faptul ca nu putem defini un constructor, un


destructor, constante. Mai mult, o interfata nu poate mosteni o clasa sau o structura.

O interfata poate fi implementata de un numar infinit de clase.

O clasa poate implementa un numar infinit de interfete. Cand o clasa implementeaza o


interfata, va trebui sa implementeze toate metodele acesteia, care sunt de tip public.
Specificatorii de acces nu sunt permisi. O clasa nu poate alege pentru implementare doar anumite
metode.

Exemplu:

public interface I1
{
void Metoda1();
void Metoda2();
}

public interface I2
{
void Metoda3();
void Metoda4();
}
class A : I1, I2
{
#region I1 Members

public void Metoda1()


{
throw new NotImplementedException();
}

public void Metoda2()


{
throw new NotImplementedException();
}

#endregion

#region I2 Members

public void Metoda3()


{
throw new NotImplementedException();
}

public void Metoda4()


{
throw new NotImplementedException();
}

#endregion
}

In cazul in care o clasa implementeaza mai multe interfete, numele acestora vor fi separate
prin “,”. Este intalnita situatia cand o clasa va mosteni o clasa de baza si va implementa una sau
mai multe interfete – numele clasei de baza trebuie sa fie primul in lista separata prin virgule.

Mostenirea interfetelor

O interfata poate mosteni o alta interfata. Sintaxa este comuna cu cea folosita la mostenirea
claselelor. Daca o clasa mostenteste o interfata care mosteneste o alta interfata, clasa va trebui sa
contina implementari pentru toti membri definiti pe lantul de mostenire al interfetei.

Exemplu :

//interfata accesibila in tot programul


public interface A
{
void Metoda1();
void Metoda2();
}
//interfata B va include metoda1 si metoda2 plus metoda3
public interface B : A
{
void Metoda3();
}
//clasa Test va trebui sa implementeze toate mteodele din A si B
class Test : B
{

#region B Members

public void Metoda3()


{
Console.WriteLine("Metoda3");
}

#endregion

#region A Members

public void Metoda1()


{
Console.WriteLine("Metoda1");
}

public void Metoda2()


{
Console.WriteLine("Metoda2");
}

#endregion
}

Si in cadrul mostenirii interfetelor, poate aparea situatia cand un membru al interfetei derivate
are aceeasi signatura cu membrul cu acelasi nume din interfata de baza. Situatia va genera un
mesaj de atentionare si va fi rezolvata prin folosirea operatorului new, daca se doreste ca
membrul din interfata de baza sa fie ascuns. (la fel ca la mostenirea claselor).

O implementare explicita inseamna specificarea numelui complet, prefixand numele


membrului cu numele interfetei. O implementare explicita este necesara pentru eliminarea
ambiguitatii in cazul a doua interfete cu metode care au acelasi nume si aceasi signatura. Un alt
motiv pentru implementarea explicita ar fi ca metoda respectiva nu va fi vizibila pentru codul din
afara clasei.

Exemplu :

Pastram interfetele definite mai sus. Acum, selectam in Visual Studio numele interfetei si la click
dreapta vom implementa interfata in mod explicit
Doar la implementarea metodei2 vom aduce modificari, pentru a fi vizibila in afara clasei.

class Test : B
{
#region B Members

void B.Metoda3()
{
Console.WriteLine("Metoda3.Implementare explicita");
}

#endregion

#region A Members

void A.Metoda1()
{
Console.WriteLine("Metoda1.Implementare explicita");
}

//am adaugat public si am sters implementarea explicita


public void Metoda2()
{
Console.WriteLine("Metoda2");
}

#endregion

//metodele implementate explicit, nu vor fi vizibile


Test t = new Test();
t.Metoda2();

In .Net, printre cele mai folosite interfete se numara IDisposable, (folosita pentru eliberarea
spatiului de memorie), IComparable (folosita pentru sortare), IConvertible (pentru convertirea la
tipuri de baza), etc.

O interfata este o constructie pur abstracta. Pentru un programator, nu doar in C#, lucrul cu
interfetele trebuie stapanit foarte bine.

Despre delegates

In programare, exista situatii cand trebuie sa executam o anumita actiune, dar fara sa stim in
avans ce metoda sau ce obiect vom apela pentru executarea actiunii. Exemplu : la apasare, un
buton va sti ca trebuie sa notifice un anumit obiect , dar nu stie exact care. Solutia simpla consta
in conectarea butonului la un delegat si apoi acesta sa indice catre o anumita metoda.

Un delegat este un obiect care poate referi o metoda. Chiar daca nu este un obiect, o metoda
ocupa un loc in memorie, iar aici, la aceasta adresa, va fi transmis controlul la invocarea metodei.
Un delegat reprezinta modul prin care se realizeaza comunicarea intre obiecte. Un delegat este
un tip referinta si este este echivalentul unui pointer la functie din C++. Diferenta este ca
delegates sunt type-safe si ca sunt orientati pe obiect.

Un delegat se declara cu ajutorul cuvantului delegate.

delegate tip-rez nume (lista-parametri);

tip-rez – tipul valorii intoarse de metodele pe care delegatul le va apela.

nume –numele delegatului.

lista-parametri – lista de parametri necesari metodelor care vor fi apelate prin intermediul
delegatului.

Poate fi declarat in afara unei clase, sau in interior. In functie de cum se vrea vizibilitatea lui, ii
putem aplica modificatorii de acces public, private, protected,etc.

Dupa ce a fost declarat, un delegat poate apela doar metode cu acelasi tip returnat si aceeasi
lista de parametri.

Exemplu :

//declararea unui delegat


delegate int StringLengthDelegate(string str);
delegate string StringReverseDelegate(string str);

Intr-o clasa, construim functiile care vor fi apelate prin delegate.

class DelegateTest
{
//returneaza numarul de caractere al unui sir de caractere
public int StringLength(string str)
{
Console.WriteLine("Va returna numarul de caractere");
return str.Length;
}

//returneaza sirul inversat


public string StringReverse(string str)
{
string temp = "";
int i;
Console.WriteLine("Inverseaza sirul.");
//parcurgem sirul invers si concatenam
for (i = str.Length - 1; i >= 0; i--)
temp += str[i];
return temp;
}
}
In metoda Main :

DelegateTest test = new DelegateTest();

//construim delegat
StringLengthDelegate strLength = new
StringLengthDelegate(test.StringLength);
//sirul care va fi transmis functiilor
string str;
//apelul unei metode prin intermediul delegarii convertim de la numar la
string
//pentru ca functia returneaza int
str = strLength("Test").ToString() ;
Console.WriteLine(str);

//construim delegat
StringReverseDelegate strReverse = new
StringReverseDelegate(test.StringReverse);
//apelul unei metode prin intermediul delegarii
str = strReverse("Test");
Console.WriteLine(str);

Pe scurt: avem doua metode statice in clasa DelegateTest ale caror signaturi coincid cu
signaturile delegatilor. In Main, construim referinte de tipul StringLengthDelegate si
StringReverseDelegate, pe care le atribuim metodelor. Se mai observa ca invocarea delegatilor
determina apelul metodelor.Determinarea metodei apelate se rezolva la momentul executiei, nu
la compilare.

Multicasting

Multicasting-ul se defineste ca o facilitate a delegatilor si consta in capacitatea de a crea un


lant de metode care vor fi automat apelate la invocarea unui delegat. Delegarile multicast trebuie
sa returneze un rezultat de tip void, iar pentru crearea lor se folosesc operatorii += si –=, dupa
instantierea unui delegat.

Exemplu :

//declararea unui delegat multicast


delegate void MulticastDelegat(string str);
//construim delegatii
MulticastDelegat multiDel;
MulticastDelegate StringLength = new MulticastDelegat(test.StringLength);
MulticastDelegat StringReverse = new MulticastDelegat(test.StringReverse);
multiDel = StringLength;
//crearea unui delegat multicast
multiDel += StringReverse;

Datorita faptului ca la compilare, nu se cunosc metodele care urmeaza a fi executate, lucrul cu


delegate este intalnit in arhitecturile care permit adaugarea componentelor pe parcurs.
In articolul urmator, se va observa una din utilitatile delegatilor, anume implementarea
evenimentelor.

Despre evenimente

In stransa legatura cu facilitatea delegate a limbajului C#, prezentam evenimentele, events.

Cele mai multe evenimente sunt actiuni ale utilizatorilor (click, schimbarea unei liste,
introducere de text, sfarsitul unui calcul etc).

In .Net, obiectele cunoscute ca si event senders, declanseaza evenimente atunci cand au loc
actiuni. Event receivers se ocupa de aceste evenimente prin rularea unei metode numite event
handler. Pentru ca event sender-ul nu stie care metoda se va ocupa de eveniment, trebuie creat un
delegate care sa se comporte ca un pointer catre event handler.

Pentru a produce un eveniment, trebuie sa parcurgem 3 pasi : sa declaram un delegate, sa


construim un obiect eveniment, sa apelam delegatul intr-o metoda.

Pentru a raspunde la un eveniment, e nevoie de doi pasi: trebuie sa construim o metoda,


metoda care trebuie sa se potriveasca signaturii delegatului. Doi, e nevoie sa adauga un event
handler care sa indice care metoda trebuie sa primeasca evenimentele.

Declararea unui eveniment se face prin folosirea cuvantului cheie event.

event delegat-eveniment nume-obiect;

delegat-eveniment – numele delegatului folosit pentru tratarea evenimentului;

nume-obiect – numele instantei eveniment;

Exemplu:

//declaram un delegate
//pentru tratarea evenimentului
delegate void DelegatTratareEveniment();

Deoarece evenimentele au delegati multicast, tipul rezultatului va fi void. Pentru acest


exemplu, nu exista parametri, dar evenimentele pot accepta parametri.

Construim o clasa in care definim o instanta eveniment.

class Eveniment
{
//declaram evenimentul
public event DelegatTratareEveniment activat;
//metoda apelata la lansarea evenimentului
public void Actiune()
{
if (activat != null)
//lansam evenimentul
activat();
}
}

Metoda Actiune va fi apelata de program pentru a semnala un eveniment si apeleaza rutina de


tratare a evenimentului prin intermediul delegatului activat, daca si numai daca acesta nu este
null (verificam faptul ca delegatul nu este null pentru ca este posibil ca metoda Actiune sa fie
apelata inainte de inregistrarea rutinei de tratare).

In clasa Program a proiectului, construim rutina de tratare numita handler, care in acest
exemplu simplu doar va afisa un mesaj.

//rutina de tratare
static void handler()
{
Console.WriteLine("Eveniment produs");
}

In metoda Main, construim o instanta a clasei Eveniment, iar metoda handler este inregistrata ca
rutina de tratare a evenimentului.

//crearea instantei eveniment


Eveniment ev = new Eveniment();
//adaugam handler-ul la lista de evenimente
ev.activat += new DelegatTratareEveniment(handler);

Lansam evenimentul:

//lansam evenimentul
ev.Actiune();

Apelul metodei determina apelul rutine de tratare.

Multicast

Multicast in conceptul evenimentelor permite mai multor obiecte sa raspunda la instiintarea


aparitiei unui eveniment.

Exemplu:

Scriem acelasi cod pentru declararea unui delegat si pentru declararea clasei Eveniment.

Adaugam inca doua clase, a caror rutine de tratare nu sunt statice, deci va trebuie sa creeam
instante pentru clasele respective.

class A
{
public void AHandler()
{
Console.WriteLine("Eveniment primit de un obiect A");
}
}

class B
{
public void BHandler()
{
Console.WriteLine("Eveniment primit de un obiect B");
}
}

In clasa Program, adaugam doar in metoda Main:

//crearea instantelor
A a = new A();
B b = new B();

//adaugam rutinele de tratare la lisa de evenimente


ev.activat +=new DelegatTratareEveniment(a.AHandler);
ev.activat += new DelegatTratareEveniment(b.BHandler);

//lansam evenimentul
ev.Actiune();
//eliminarea unei rutine de tratare
ev.activat -= new DelegatTratareEveniment(a.AHandler);

Ca observatie valabila pentru evenimente in acest exemplu : evenimentele sunt destinate


instantelor si nu claselor in general.

In .Net, mai toate evenimentele sunt implementate folosind delegati multicast, care au doi
parametri (unul de tip object – care reprezinta obiectul care provoaca evenimentul, iar celelalt de
tip EventArgs care contine data utilizabile in tratarea evenimentului).

Putem construi evenimente in interiorul claselor, iar la declansarea lor ne putem da seama unde
s-a ajuns cu procesarea codului.

Suportul pentru delegate si evenimente permite gestionarea complexitatii programarii pe


obiecte intr-un mod mai usor.

Exemplul prezentat in acestarticol poate fi downloadat aici.

Despre threads

De multe ori, aplicatiile au nevoie de mult timp pentru a rezolva o sarcina (descarcarea unui
fisier, printare, generarea unui raport etc), timp in care programul nu poate raspunde unei alte
actiuni a utilizatorului. Pentru a face ca o aplicatie sa indeplineasca o sarcina si sa poata primi si
altele in acelasi timp, sunt folosite firele de executie multiple (multiple threads).
Intr-un program liniar se executa o singura linie de cod, se asteapta pentru completare si apoi
se continua cu urmatoarea linie de cod. Programul nu poate raspunde actiunii unui utilizator in
acest timp si chiar in cazul mai multor procesoare, va fi folosit doar unul singur, limitand
performanta, datorita programarii single-thread.

Un fir de executie (thread) este un program secvential, care poate fi executat concurent cu alte
fire. Un thread este o unitate de executie intr-un proces. Un proces poate avea mai multe fire de
executie, el numindu-se multithread. Daca un calculator are mai multe procesoare sau un
procesor cu mai multe nuclee, el poate executa mai multe fire de executie simultan.

Diferenta dintre un proces si un thread este ca procesele sunt izolate total unul de celalalt, in
timp ce thread-urile impart aceeasi memorie (heap) cu alte thread-uri care ruleaza in aceeasi
aplicatie (un thread poate prelua informatii noi, in timp ce un alt thread le prelucreaza pe cele
existente).

Folosirea firelor de executie este o solutie la imbunatatirea performantei. Se cere insa foarte
multa atentie la folosire. Scrierea unui cod multithread este complexa, iar problemele care pot
aparea pot fi foarte greu de rezolvat.

Un fir de executie, thread, poate avea mai multe stari.

Trecerea de la starea gata de executare la starea in curs de executare are loc cand un procesor
il alege pentru executare. La terminare, trece in starea terminat.

Un proces în curs de executare poate fi suspendat (amanat sau blocat), dupa care poate reveni
în substarea gata de executare.

– amânat: inseamna întreruperea executarii procesului un anumit timp, în urma apelarii


procedurii predefinite sleep; aceasta procedura are un singur parametru de tip întreg.

- blocat: inseamna întreruperea executarii procesului pe baza unei relatii (comunicari) cu alte
procese; procesul poate reveni ulterior în starea gata de executare pe o baza similara.Esential in
folosirea firelor de executie multiple este evitarea conflictelor de resurse, cand vor trebuie
blocate anumite resurse, pentru a putea fi folosite de catre un singur fir de executie, pe rand.

Cel mai simplu mod de a crea un thread este sa instantiem un obiect Thread, al carui
constructor va cere ca parametru un delegate de tipul ThreadStart. Delegatul va indica ce metoda
va rula in thread.

Adaugam namespace-ul, mai intai:

using System.Threading;

Exemplu pentru un simpu thread:

class Numara
{
//numara pana la 10
public void Zece()
{
Console.WriteLine("Simple Thread");
for (int i = 0; i <= 10; i++)
{
Console.WriteLine(i);
}
}
}

in Main:

Numara numara = new Numara();


//obiectul Thread
Thread thread1 = new Thread (new ThreadStart(numara.Zece));
threaUnu.Start();

Codul de mai sus nu reflecta puterea firelor de executie multiple, pentru ca avem doar unul. O
sa adaugam inca un thread, o sa setam numele pentru o identificare mai usoara si vom folosi
proprietatea CurrentThread care va returna firul de executie curent.

//obiectul Thread
Thread thread1 = new Thread (new ThreadStart(numara.Zece));
Thread thread2 = new Thread(new ThreadStart(numara.Zece));

thread1.Name = "Thread One";


thread2.Name = "Thread Two";

thread1.Start();
thread2.Start();

public void Zece()


{
for (int i = 0; i <= 10; i++)
{
Console.WriteLine("{0},numara :{1}",Thread.CurrentThread.Name,i);
}
}

Exemplul complet, de la sfarsitul articolului, va contine cod care va arata cum thread-urile
apeleaza metode diferite.

Cand trebuie sa declaram mai multe fire de executie, putem construi un vector de fire de
executie.

Thread[] threads =
{
new Thread(new ThreadStart(numara.Zece)),
new Thread(new ThreadStart(numara.Zece))
};

Metoda Sleep

Se foloseste atunci cand vrem sa suspendam un fir de executie, pentru o anumita perioada.

Exemplu:

Intr-una din metodele de mai sus adaugam :

Thread.Sleep(5000);

Metoda primeste ca parametri un integer care reprezinta numarul milisecundelor cat va fi


suspendat firul de executie.

Metoda Join

Presupunem ca suntem in situatia cand vrem ca un thread sa astepte un alt thread sa isi termine
executia, inainte ca thread-ul nostru curent sa continue.

//se va merge mai departe cu executia programului


//dupa terminararea celor trei fire de executie
thread1.Join();
thread2.Join();
thread3.Join();

Metoda Abort

Se foloseste atunci cand vrem sa oprim (“kill”) un fir de executie.

//thread2 a fost oprit


thread2.Abort();

Proprietatea Priority
Folosind aceasta proprietate, se poate determina ce timp de executie are un thread in
comparatie cu alte threaduri active din acelasi proces:

enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }

In lucrul cu multithreading-ul apare o problema majora, cea a sincronizarii: mai multe fire de
executii acceseaza acelasi obiect, simulan. Solutia vine de la “lock”: atunci cand primul thread
acceseaza obiectul, il va tine “ocupat” pana la incheiere.

Folosirea firelor de executie permite rularea proceselor simultan, dar acest lucru poate incetini
executia programului, daca nu sunt folosite cu atentie.

O aplicatie C# poate deveni multi-threading in doua moduri: fie explicit prin crearea si rularea
firelor de executie, fie prin folosirea unor caracteristici ale .Net care creeaza implicit thread-uri:
BackgroundWorker, thread pooling sau la construirea unui Web Service sau unei aplicatii
Asp.Net.

Codul folosit in exemplele de mai sus poate fi descarcat aici.

ArrayList

ArrayList este o colectie simpla care poate stoca orice tip de obiect.

Clasa ArrayList este continuta in spatiul de nume System.Collections. Aceasta clasa ne permite
sa construim un vector care sa creasca in marime, adaugand mereu elemente noi.

La declarare nu trebuie sa ii specificam marimea, fiind suficient doar :

//declare
ArrayList lista = new ArrayList();

//Folosind metoda Add adugam obiecte, indiferent de tipul lor.

//adaugam obiecte
lista.Add("String");
lista.Add(3);
lista.Add(5.65);

//folosim iteratia pentru


//parcurgerea listei
foreach (object o in lista)
{
Console.WriteLine(o);
}
//proprietatea count
//pentru a afla numarul de elemente
//din lista
Console.WriteLine("Lista are {0} obiecte", lista.Count);

Daca incercam o iteratie astfel, va fi aruncata o exceptie :


foreach (string s in lista)
{
Console.WriteLine(s);
}
Unable to cast object of type 'System.Int32' to type 'System.String'.

Construim o lista de tip string si vom folosi metoda Sort pentru a o sorta alfabetic si Reverse
pentru sortare inversa

ArrayList listaString = new ArrayList();


listaString.Add("def");
listaString.Add("ghi ");
listaString.Add("abc");
listaString.Add("xyz");
listaString.Add(" ");

//sortare in ordine alfabetica


listaString.Sort();
foreach (object o in listaString)
{
//convertim in string
Console.WriteLine(o.ToString());
}

Console.WriteLine("----");
//sortare in ordine inversa
listaString.Reverse();
foreach (object o in listaString)
{
Console.WriteLine(o.ToString());
}

Folosim metoda Remove pentru a sterge un obiect din lista.

//sterge un obiect
//din lista
lista.Remove("abc");

Pentru localizarea unui anumit element, apelam metoda BinarySearch si transmitem valoarea
obiectului pentru a fi returnata pozitia obiectului in lista.

Console.WriteLine(listaString.BinarySearch("def"));

Putem folosi si metoda Contains care va returna o valoare de tip boolean ( daca lista contine sau
nu contine elementul cautat).

Console.WriteLine(listaString.Contains("def"));

ArrayList si Array se aseamana foarte mult. Ambele permit stocarea unui numar mare de
elemente.

ArrayList cu referinte
Vom crea o clasa Cont, cu 3 metode. Apoi vom construi obiecte pe baza ei si vom adauga
referinte in ArrayList catre obiectele respective.

class Cont
{
private decimal sold = 0;
//scade o suma din sold
public bool RetrageSuma(decimal suma)
{
//daca nu sunt suficienti bani

if (suma > sold)


{
return false;
}
else
{
sold = sold - suma;
return true;
}
}

//adauga suma la debit


public void Depune(decimal suma)
{
sold = sold + suma;
}
//afiseaza soldul
public decimal Sold()
{
return sold;
}
}

In Main:

//initial putem dam un parametru


//dar poate pastra mai multe
//sau mai putine elemente
ArrayList conturi = new ArrayList(10);

//creem instante ale clasei cont


Cont student = new Cont();
Cont elev = new Cont();

//adaugam referinte
conturi.Add(student);
conturi.Add(elev);

Observatie : nu adaugam un Cont in lista, ci doar facem ca un element din lista sa indice catre
un Cont. Deci, nu se adauga obiectul insusi, ci o referinta.

Cum accesem elementele ? Daca incercam astfel, cum suntem obisnuiti, vom avea eroare la
compilare :
Cont c = conturi[0];
c.Sold();

Cannot implicitly convert type ‘object’ to ‘ContExemplu.Cont’. An explicit conversion exists (are
you missing a cast?)

Motivul este ca ArrayList este o lista de Objects. Vom rezolva problema convertind la tipul
Cont.

//facem conversie la Cont


Cont c = (Cont)conturi[0];
Console.WriteLine(c.Sold());

O lista cu membrii ArrayList se gaseste pe msdn.

Foarte asemanatoare cu ArrayList este clasa List, in plus avand avantajul de typesafe. Vom
scrie despre aceasta clasa in articolul viitor.Exista si StringCollection, o colectie tipizata.
Impreuna cu alte colectii din .Net care sunt strongly typed, aceste clase tipizate sunt usor de
folosit in dezvoltare datorita faptului ca Visual Studio poate face automat validarea si nu mai e
nevoie sa folosim conversia.

Codul folosit in exemplele anterioare:

• ContExemplu.rar
• ListaVectori.rar

Clase generice

Reamintesc problema din articolul despre ArrayList: cream o lista in care adaugam referinte
catre obiecte iar pentru a le accesa trebuia sa convertim explicit la tipul obiectului, altfel
primeam eroare la compilare, de tipul: “Cannot implicitly convert type ‘object’ to
‘ContExemplu.Cont’. An explicit conversion exists (are you missing a cast?)”.

C# este un limbaj type-safe, lucru care permite ca unele eventualele erori sa fie returnate la
compilare (acest lucru face ca programul sa fie stabil, in sensul ca mare parte dintre errorile
datorate tipurilor de date folosite sunt detectate la compilare), si nu la executie (run-time).

Am expus aceasta problema, pentru a intelege mai bine termenul de generic. Clasele sau
functiile generice sunt cele care primesc ca parametru tipul datelor manipulate si sunt foarte
folosite in implementarea colectiilor sau algoritmilor care actioneaza asupra unor tipuri variate
de date.

Notiunea de cod generic poate parea cunoscuta celor care programeaza in C++, doar ca
implementarea si regulile de utilizare difera in .Net.

Conceptul de generics a aparut in versiunea 2.0 a limbajului C#.


Dupa cum se va observa in cazul clasei List, la declarare, clasele generice vor contine intre “<”
“>” tipul parametrului.

public class Exemplu<T>

Acum, putem construi o clasa pentru int si una pentru double, foarte simplu :

Exemplu<int> ex = new Exemplu<int>();


Exemplu<double> ex2 = new Exemplu<double>();

Clasa List se gaseste in spatiul de nume System.Collections.Generic, astfel ca, inainte de a o


folosi, vom adauga :

using System.Collections.Generic;

Folosind aceeasi clasa Cont din articolul ArrayList, construim o lista in care vom retine
referinte catre obiecte de tip Cont.

List<Cont> conturi = new List<Cont>();

Lista conturi “va sti” exact ce tip de obiecte sa stocheze, astfel vom putea adauga un obiect de
tip Cont si vom putea accesa metodele lui, astfel:

Cont student = new Cont();


conturi.Add(student);

conturi[0].Depune(40);
Console.WriteLine(conturi[0].Sold());

Daca incercam sa adauga un o varabila de un alt tip in afara de cel specificat in “<>”, va aparea
eroare de compilare:

Argument ’1′: cannot convert from ‘int’ to ‘List.Cont’

Cu clasa List se pot face operatii asematoare cu cele din ArrayList (Add, Count, Remove,
BinarySearch etc).

Proprietatile Count si Capacity

Aceste proprietati furnizeaza informatii despre obiectul de tip List. Proprietatea Count, ca si in
cazul ArrayList, identifica numarul de elemente existente in lista. Proprietatea Capacity arata
cate elemente mai pot fi adaugate in lista fara ca aceasta sa fie redimensionata.

List<int> lista = new List<int>(4);


lista.Add(10);
//va afisa 1
Console.WriteLine(lista.Count);
//va afisa 4
Console.WriteLine(lista.Capacity);
Evident, rezultatul returnat de proprietatea Capacity va fi intotdeauna mai mare decat cel
returnat de proprietatea Count. In cazul in care se doreste eliminarea acestei diferente, se
foloseste metoda TrimExcess().

lista.TrimExcess();
//sunt egale
Console.WriteLine(lista.Capacity);
Console.WriteLine(lista.Count);

Metoda Clear

Metoda Clear va elimina toate elementele din List Conturi. Apelul acestei metode este similar
cu atribuirea liste catre null sau cu instantierea unui nou obiect (= new List…) in locul celui ale
carui componente le dorim sterse.

//sterge elem din lista


conturi.Clear();

Copierea unui vector intr-o lista

Presupunem ca avem un vector si vrem sa construim o lista care sa contina elementele acestuia.
Vom transmite vectorul ca parametru iar lista va copia elementele din el.

Exemplu:

//creem un vector de int


int[] vector = new int[] { 3, 4, 5, 6 };
//copiem elementele vectorului in lista
List<int> lista = new List<int>(vector);

Console.WriteLine(lista.Count);

O lista cu membri clasei se gaseste pe msdn.

Notiunea de generic a fost adaugata in C# pentru evitarea conversiilor, type-safety, reducerea


operatiilor de boxing. Codul generic aduce un plus de performanta si calitate programelor scrise
in C#.

In articolul urmator, vom prezenta o alta clasa, care lucreaza tot cu grupuri de obiecte,
Dictionary, avand rolul principal de stabili o relatie intre o cheie si o valoare.

Dictionary

O alta colectie generica utila in .Net este Dictionary. Ea functioneaza pe principiul perechilor
cheie/valoare.

Dictionarele stabilesc o relatie (“map“) intre o cheie si o valoare. De exemplu, avem numarul
de identificare al unui angajat, caruia ii putem atribui un obiect care reprezinta angajatul.
Clasa generica Dictionary face parte din spatiul de nume System.Collection.Generics. Pentru
folosirea ei, vom folosi:

using System.Collections.Generic;

Folosind in continuare clasa Cont, folosita in articolele despre ArrayList si List<T>, construim
un dictionar care va stoca perechi cheie/valoare de tipul string/Cont:

//instanta a clasei Cont


Cont contStudent = new Cont();

//dictionar de tip string/Cont


Dictionary<string,Cont> dictionar = new Dictionary<string, Cont>();

//adaugam un element in dictionar


dictionar.Add("Student",contStudent);

Va aparea eroare la compilare, daca vom incerca sa adaugam o valoare de alt tip decat Cont.

//eroare
MyClass cls = new MyClass();
dictionar.Add("String",cls);

“cannot convert from ‘Dictionary.MyClass’ to ‘Dictionary.Cont’“.

La accesarea unui element, nu apar probleme de conversie:

dictionar["Student"].Depune(50);

Metodele ContainsKey, ContainsValue

Adaugam mai multe elemente in dictionar:

Cont contElev = new Cont();


Cont contProfesor = new Cont();
dictionar.Add("Elev", contElev);
dictionar.Add("Profesor", contProfesor);

Incercam sa accesem cu ajutorul unei chei un element. Daca nici un element nu are o valoare
pentru cheia respectiva, va aparea exceptia KeyNotFoundException : “The given key was not
present in the dictionary”.

//exceptie
dictionar["a"].Depune(50);

In cazul folosirii clasei Dictionary, se recomanda mai intai testarea cheilor pentru a vedea daca
exista si de a evita primirea exceptiei, folosind metoda ContainsKey.

if (dictionar.ContainsKey("aa"))
{
Console.WriteLine("exista");
}
else
{
Console.WriteLine("nu exista");
}

Clasa Dictionary contine o metoda, ContainsValue, care cauta in toata colectia dupa valoare
data ca parametru:

//cautare dupa valoare


if (dictionar.ContainsValue(contElev))
{
Console.WriteLine("Exista...");
}

Metoda TryGetValue

TryGetValue va returna valoarea asociata unei chei. Folosirea acestei metode este eficienta
atunci cand se doreste obtinerea valorilor dintr-un dictionar unde programul acceseaza frecvent
chei care nu sunt in dictionar.

Metoda are doi parametri: cheie – a carei valori incercam sa o obtinem si valoare – contine
valoarea asociata cheii respective, in cazul in care este gasita. Daca nu este gasita, va returna
valoare implicita pentru tipul valoare.

Exemplu:

Ca sa testam daca metoda va returna corect valoarea cheii cautate, apelam metoda Depune,
pentru a modifica valoarea soldului.

contElev.Depune(40);

//creem o instanta de tip Cont


//pentru a putea fi returnata valoare
Cont cont = new Cont();

if (dictionar.TryGetValue("Elev", out cont))


{
Console.WriteLine("sold {0}", cont.Sold());
}
else
{
Console.WriteLine("Nu exista...");
}

Construim un dictionar un pic mai simplu, pentru a arata cum putem folosi KeyValuePair intr-
un ciclu foreach.

Dictionary<string, int> catalog = new Dictionary<string, int>();


catalog.Add("Produs1", 200);
catalog.Add("Produs2", 230);
catalog.Add("Produs3", 150);

foreach (KeyValuePair<string, int> pereche in catalog)


{
Console.WriteLine("{0},{1}", pereche.Key, pereche.Value);
}

Un lucru util in folosirea clasei Dictionary, este folosirea constructorului, atunci cand se
doreste construirea unei copii a unui dictionar existent. In urmatorul exemplu vom realiza o
copie a dictionarului catalog.

//construim o copie
//a unui dictionar existent
Dictionary<string, int> copie = new Dictionary<string, int>(catalog);
//afisam elementele din dictionar
foreach (KeyValuePair<string, int> pereche in copie)
{
Console.WriteLine("{0},{1}", pereche.Key, pereche.Value);
}

Pentru stergerea tuturor perechilor cheie/valoare din dictionar, se foloseste metoda Clear.

dictionar.Clear();

Daca se doreste eliminarea doar a unui element, folosim metoda Remove, care va avea ca
parametru o cheie.

//eliminarea unui element


dictionar.Remove("Elev");

Pentru a afla numarul de elemente, se utilizeaza proprietatea Count.

Console.WriteLine("Nr. elem. :{0}", dictionar.Count);

O lista cu membrii clasei Dictionary,se gaseste pe msdn.

Exista colectia non-generica, HashTable, care functioneaza la fel ca Dictionary, cu exceptia


faptului ca opereaza pe tipuri object.

In urma articolelor despre List si Dictionary, probabil va aparea intrebarea : Care trebuie
utilizata si cand? Recomandarea e ca Dictionary sa fie folosit atunci cand e nevoie de cautari
(“lookups”). In cazul unei cautari in clasa List, aceasta poate cauza probleme in cazul unui numar
foarte mare de elemente.

O colectie inrudita cu Dictionary este SortedDictionary, unde perechile sunt sortate dupa cheie.

Alte clase care se bazeaza pe dictionare, in .Net Framework, sunt:

HashTable (colectie de perechi cheie/valoare, organizata pe baza unui cod “hash” al cheii)
SortedList (perechi cheie/valoare care sunt sortate in functie de chei si care sunt accesate de
cheie si index),

StringDictionary ( un “hashtable” cu perechi cheie/valoare doar de tip string),

ListDictionary (dictionar optimizat pentru colectii mici de obiecte, cu mai putin de 10 elemente),
HybridDictionary (dictionar care se bazeaza pe ListDictionary cand numarul elementelor este
mic, iar cand numarul elementelor va creste, va folosi HashTable),

NameValueCollection (dictionar de perechi nume/valoare, elementele pot fi accesate prin nume


sau index).

Tipul Dictionary este util in situatiile cand e nevoie de stocarea obiectelor pe baza unui
identificator unic.

Colectiile generice dau foarte mult flexibilitate si sunt recomandate inainte celor non-generice.

In acest articol, vom lucra un exemplu in care vom folosi o colectie, un dictionar cu perechi de
chei de tip int si valori de tip string, dictionar care va fi construit pe baza a ceea ce introduce
utilizatorul de la tastatura. Dupa crearea lui, il vom salva intr-un fisier text pe hard. Apoi vom citi
colectia din fisierul text.

Vom alege un proiect de tip Console Application si vom explica notiuni care nu au fost
introduse pana acum.

Construim clasa DictionarExemplu, care va avea ca metode NumarElemente,


CreeazaDictionar, SalveazaDictionar, CitesteDictionar.

Mai intai, in interiorul clasei, declaram un obiect de tip Dictionary<int,string>:

//declarare obiect de tip Dictionary


Dictionary<int, string> catalog = null;

Acest obiect catalog va fi folosit in metodele CreeazaDictionar si SalveazaDictionar.

//functia de preluare a caracterelor de la tastatura


public int NumarElemente()
{
//cat timp vor fi introduse caractere de la tastatura
while (true)
{
//retinem caracterul
char ch = Console.ReadKey().KeyChar;

//vom retine numarul in aceasta varabila


int result;
//daca e numar, il returneaza functiei principale
if (int.TryParse(ch.ToString(), out result))
{
Console.WriteLine("");
//retunreaza numarul
return result;
}
}
}

Aceasta metoda din clasa DictionaryExemplu, va prelua caracterele introduse de utilizator, iar
daca va gasi numar, il va returna functiei Main. Pe acest numar il vom folosi pentru a sti cate
elemente va avea dictionarul pe care vrem sa il construim.

//functia pentru construirea dictionarului


public void CreeazaDictionar(int numarPersoane)
{
catalog = new Dictionary<int, string>();

//numarul de elemente va fi egal cu numarul primit de la tastura


for (int i = 0; i < numarPersoane; i++)
{
Console.WriteLine("Nume pentru persoana {0}", i);
string nume = Console.ReadLine();

//adaugam in dictionar iteratia la care a ajuns i


//si numele primit de la tastatura la iteratia respectiva
catalog.Add(i, nume);
}
Console.WriteLine("Dictionarul a fost creat.");
}

Metoda CreeazaDictionar va primi ca parametru numarul de elemente returnat de functia


NumarElemente si care va reprezenta numarul de elemente al dictionarului. Intr-o iteratie, vom
adauga cate un element dictionarului catalog cu ajutorul functiei Add (vezi articolul despre
Dictionary<K,V>), fara a depasi numarul maxim de elemente, numarPersoane. Vom afisa un
mesaj de confirmare dupa construirea dictionarului.

//functia salveaza dictionarul intr-un fisier


public void SalveazaDictionar()
{
//creeaza fisierul la calea pe
StreamWriter fisierIesire = File.CreateText("C:\\dictionar.txt");

//parcurgem dictionarul
foreach (KeyValuePair<int, string> el in catalog)
{
//scriere textului in fisier
fisierIesire.Write("(" +el.Key+",");
fisierIesire.WriteLine(el.Value+")");
}

//inchiderea fisierului
fisierIesire.Close();

Console.WriteLine("Dictionarul a fost salvat in fisier text.");


}
Metoda SalveazaDictionar va salva dictionarul catalog creat de metoda CreeazaDictionar si cu
ajutorul clasei StreamWriter din spatiul de nume System.IO, despre care vom discuta intr-un
viitor articol, dictionarul catalog va fi salvat sub forma unui fisier text, la calea data ca parametru
metodei CreateText. Daca vom naviga la acea cale, vom gasi un fisier text, cu numele
dictionar.txt, care va contine colectia catalog.

//citeste textul dintr-un fisier


public void CitesteDictionar()
{
//deschidem fisierul pentru a citi din el
StreamReader fisierIntrare = File.OpenText("C:\\dictionar.txt");

//definim o variabile string care va parcurge fisierul pana la final


string x;
//cat timp linia citita nu este goala
while ((x = fisierIntrare.ReadLine()) != null)
{
//scrie in consola
Console.WriteLine(x);
}
//inchidem fisierul
fisierIntrare.Close();

Console.WriteLine("Dictionarul a fost citit din fisierul text.");


}

Metoda CitesteDictionar, cu ajutorul clasei StreamReader, din acelasi spatiu de nume,


System.IO, va deschide fisierul text a carei cale a fost transmisa ca parametru metodei OpenText,
apoi va parcurge fisierul dictionar.txt si va scrie in consola fiecare sir de caractere gasit.

In metoda Main din clasa Program a proiectului, declaram si instantiem clasa


DictionarExemplu:

DictionarExemplu dictionarExemplu = new DictionarExemplu();

Preluam numarul de elemente primit de la utilizator:

int numarElem = dictionarExemplu.NumarElemente();

Vom transmite numarul functiei responsabila cu crearea dictionarului:

dictionarExemplu.CreeazaDictionar(numarElem);

Daca se doreste salvarea dictionarului creat in format txt pe hard:

dictionarExemplu.SalveazaDictionar();

Daca se doreste citirea dictionarului din fisierul text in care a fost salvat:

dictionarExemplu.CitesteDictionar();
Acesta a fost un exemplu scurt, practic, despre construirea unei colectii – dictionar (puteam
alege, foarte simplu, din cele prezentate in articolele precedente, List sau ArrayList) si salvarea
ei sub forma unui fisier text, pe hard, intr-o locatie specificata de noi. De asemenea, am construit
si o functie care sa citeasca dintr-un fisier text, continutul colectiei noastre.

In articolul viitor, vom prezenta interfata IEnumerator.

Pentru a descarca in format rar codul integral folosit in articolul curent – click aici!

System.IO pe scurt

Mai intai, sa ne reamintim ce inseamna un spatiu de nume.

De multe ori, aplicatiile au nevoie sa stocheze date pe hard-disk (salvarea intre mai multe
sesiuni ale datelor aplicatiei, data logging, troubleshooting, comunicarea cu alte aplicatii,
compresarea, decompresarea datelor, etc). Pentru aceasta, aplicatiile vor folosi clasele din
System.IO:

using System.IO;

System.IO contine clase care pot fi folosite la exploatarea, administrarea fisierelor si


directoarelor din sistemul de operare.

Clasele DirectoryInfo si Directory

Aceasta clasa contine metode statice pentru crearea, mutarea directoarelor, afisarea
subdirectoarelor. Clasa nu poate fi mostenita.

Alegem directorul Windows pentru a-i afisa subdirectoarele.

DirectoryInfo di = new DirectoryInfo(@"C:\Windows");


//cream un vector de obiecte DirectoryInfo
//obtinute prin apelul metodei GetDirectories
DirectoryInfo[] subDirs = di.GetDirectories();

Console.WriteLine("Directoare");
//afisam in consola fiecare director din vector
foreach (DirectoryInfo subDir in subDirs)
{
Console.WriteLine(subDir.Name);
}

Aceasta clasa ajuta la furnizarea multor informatii cu privire la directoare: nume, cale, marime,
extensii, data crearii, data ultimei accesari.

Metodele uzuale sunt Create (creeaza un director), Delete (sterge atat directorul cat si
continutul sau), MoveTo (muta directorul si continutul sau la o alta cale), CreateSubirectory,
GetFiles (returneaza lista de fisiere continute in directorul respectiv).
Descriere detaliata si membrii clasei DirectoryInfo, pe siteul Microsoft.

O alta clasa folosita pentru lucrul cu directoarele din sistemul de fisiere, este Directory.
Diferenta consta in faptul ca aceasta contine doar metode statice si se poate efectua doar o
singura operatie asupra unui director. Nu mai este necesar construirea unui obiect care sa
reprezinte directorul. Se intelege usor ca pentru situatiile cand e nevoie de efectuarea mai multor
operatii pe un director, se recomanda folosirea clasei DirectoryInfo.

O sa cream un director(folder) nou.

Exemplu:

//cream un director nou


//constructorul clasei va primi ca parametru calea
//unde va fi creat noul director
DirectoryInfo directorNou = new DirectoryInfo(@"C:\DirectorTest");

//testam daca directorul exista


if (directorNou.Exists)
Console.WriteLine("Directorul exista deja.");

//daca nu exista, il cream


else
{
directorNou.Create();
Console.WriteLine("Directorul a fost creat.");
}

Clasele FileInfo si File

In directorul recent creat, vom crea fisiere pe care le vom muta, copia, sterge.

Atunci cand vrem sa creem fisiere cu ajutorul programarii in C#, putem folosi clasele File sau
FileInfo. Diferenta intre ele, consta in faptul ca File are metode statice care efectueaza o singura
operatie asupra unui fisier, iar metodele din FileInfo pot fi apelate pentru mai multe operatii
asupra unui fisier. Diferenta este aceeasi ca si in cazul Directory si DirectoryInfo.

Crearea unui fisier text:

prin apelarea metodei statice File.Create()

File.Create(@"C:\DirectorTest\exemplu.txt");

prin apelarea metoda unui obiect de tipul FileInfo:

FileInfo fisier = new FileInfo(@"C:\DirectorTest\exempluDoi.txt");


// creeam fisierul
fisier.Create();
Apelam metoda WriteAllText pentru a crearea unui fisier text, scrierea unui string in el si apoi
inchiderea acestuia.

File.WriteAllText(@"C:\DirectorTest\exmpluFisierScris.txt", "text pentru


test");

Copierea unui fisier:

File.WriteAllText(@"C:\DirectorTest\test1.txt", "text/copiere");
File.Copy(@"C:\DirectorTest\test1.txt",@"C:\DirectorTest\test2.txt");

Mutarea unui fisier:

File.WriteAllText(@"C:\DirectorTest\test1.txt", "text/mutare");
File.Move(@"C:\DirectorTest\test1.txt",@"C:\DirectorTest\test2.txt");

Mai multe detalii despre FileInfo si File.

Clasa FileSystemInfo

Clasa FileSystemInfo reprezinta baza pentru DirectoryInfo si FileInfo. Nu se poate creea o


instanta a acesteia, pentru ca este o clasa abstracta. O lista cu metodele si proprietatile ei, aici.

Clase BinaryWriter si BinaryReader

Aceste clase scriu si citesc orice tip de date de baza intr-un format binar.

Inainte de folosirea claselor, vom crea un obiect de tip FileStream. Aceasta clasa creeaza sau
deschide un fisier.

//filestream pentru fisierul test


FileStream fisierScriere = new FileStream(@"C:\directortest\testbinar",
FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
//cu ajutorul fileStream, obiectul binarywriter
//va scrie fluxul in fisier
BinaryWriter bw = new BinaryWriter(fisierScriere);

Vom declara variabilele si le vom scrie binar.

//variabile
string sir = "text";
char car = 'a';
int numar = 10;
//scriem valorile in format binar
bw.Write(sir);
bw.Write(car);
bw.Write(numar);

Apelam metoda Close pentru eliberarea obiectelor de tip BinaryWriter si FileStream.


//inchidem si eliberam resursele
bw.Close();

Pentru citirea fisierului binar, creem din nou un obiect FileStream si apoi, un obiect
BinaryReader, clasa complementara a BinaryWriter.

FileStream fisierCitire = new FileStream(@"C:\directortest\testbinar",


FileMode.Open, FileAccess.Read, FileShare.None);
BinaryReader br = new BinaryReader(fisierCitire);

//facem ca citirea sa aiba loc de la inceputul fisierului


br.BaseStream.Seek(0, SeekOrigin.Begin);

Functia Read poate citi tipurile de date pe care functia Write le scrie. Diferenta consta in faptul
ca BinaryWriter are o singura metoda Write pentru scriere, in timp ce BinaryReader are o
metoda Read pentru fiecare tip de date (normal, pentru ca fiecare metoda va returna un tip de
data diferit). Dupa terminarea citirii, eliberam memoria prin apelul functiei Close.

//retine in variabile
string sirCitit = br.ReadString();
char carCitit = br.ReadChar();
int numarCitit = br.ReadInt16();
//afiseaza in consola
Console.WriteLine(sirCitit);
Console.WriteLine(carCitit);
Console.WriteLine(numarCitit);
//inchide si elibereaza resursele
br.Close();

Este recomandat ca la citirea fisierelor binare, sa se foloseasca mecanismul de “prindere” a


exceptiilor (try-catch).

Descriere detaliata a claselor FileStream, BinaryWriter, BinaryReader.

Clasele TextReader si TextWriter sunt clase de baza. Clasele StreamReader si StringReader


deriveaza din TextReader. In mod similar, StreamWriter si StringWriter deriveaza din clasa
abstracta TextWriter.

StreamWritersi StreamReader sunt folosite in scrierea/citirea unui flux(stream) intr-o anumita


codificare.

StringWriter si StringReader sunt folosite pentru scrierea/citirea sirurilor de caractere in


memorie.

Clasa FileSystemWatcher

Aceasta clasa contine metode, proprietati, care semnaleaza evenimente atunci cand au loc
modificari asupra fisierelor sau directoarelor dintr-un anumit director.
Exemplu:

Construim un obiect de tipul FileSystemWatcher, care va anunta daca un fisier dintr-un anumit
director a fost, de exemplu, redenumit.

FileSystemWatcher watcher = new FileSystemWatcher();


watcher.Path = @"C:\directortest";

Vom fi anuntati la redenumirea oricarui fisier sau director din directortest.

//incepe urmarirea schimbarilor


watcher.EnableRaisingEvents = true;
//ne anunta doar in cazul redenumirilor
//delegate pentru eveniment
watcher.Renamed += new RenamedEventHandler(watcher_Renamed);

Metoda indicata de delegate pentru evenimentul de redenumire va afisa fostul nume al


fisierului/directorului si numele curent.

private void watcher_Renamed(object sender, RenamedEventArgs e)


{
Console.WriteLine("Redenumit:" + e.OldName + " in " + e.Name);
}

Se observa ca acest eveniment transmite un parametru de tip RenamedEventHandler. Este un


caz singular, celelalte evenimente din aceasta clasa vor transmite eveniment de tip
FileSystemEventHandler.

watcher.Deleted += new FileSystemEventHandler(watcher_Deleted);

void watcher_Deleted(object sender, FileSystemEventArgs e)


{
Console.WriteLine("A fost sters "+e.Name+" din " + e.FullPath);
}

Obiectul watchermai poate folosi proprietatea Filter, pentru restrangerea ariei de fisiere
urmarite pentru modificari, proprieatea IncludeSubdirectories, pentru a urmari subdirectoare.

O lista detaliata cu toti membrii clasei se gaseste pe msdn.

Puteti citi mai multe despre spatiul de nume System.IO, pe msdn.

Dezvoltatorii au nevoie adesea sa acceseze fisierele si directoarele pentru a obtine informatii si


a efectua modificari. Spatiul de nume System.IO furnizeaza toate clasele necesare.

In articolul urmator, o sa descriu spatiul de nume System.Collection.

Enumerarea si compararea colectiilor


.Net Framework contine doua seturi de interfete standard pentru enumerarea si compararea
colectiilor. Un set nontype-safe, IEnumerable si IEnumerator si un set type-safe, IEnumerable
<T> si IEnumerator <T>. Toate intefetele din .Net Framework, prin conventie, isi incep numele
cu I.

Interfata IEnumerable contine o singura metoda, GetEnumerator. Obiectul returnat de aceasta


metoda, este un enumerator folosit pentru parcurgerea elementelor colectiei, el implementand
interfata IEnumerator.

In general, cand se implementeaza interfata IEnumerable, se implementeaza si interfata


asociata IEnumerator.

Interfata IEnumerator

Obiectul enumerator este folosit pentru parcurgerea elementelor din colectie (il putem privi ca
un indicator care arata catre elementele dintr-o lista).

Interfata Ienumerator are proprietatea:

object Current {get;}

si metodele :

bool MoveNext() – va directiona indicatorul catre urmatorul element din lista. Va returna true,
daca mai exista in colectie un alt element, false, daca nu.

void Reset() – va returna indicatorul inapoi la primul element din lista.

Prin construirea unui enumerator, cu ajutorul metodei GetEnumerator a unei colectii, si prin
apelarea repetata a metodei MoveNext si preluarea valorii din proprietatea Currentfolosind un
enumerator, putem naviga prin elementele unei colectii din element in element. Daca vrem sa
construim o colectie de clasa enumerabila, trebuie sa implementam interfata IEnumerable in
colectia de clase si sa furnizam implementarea interfetei IEnumerator care sa fie returnata de
metoda GetEnumerator a colectiei de clase.

Se observa ca proprietatea Current nu are un comportament type-safe, ea returnand un object.


Framework-ul .Net pune la dispozitie interfata generica IEnumerator<T>, a carei proprietate va
returna un obiect de tip T. La fel si in cazul interfetei IEnumerable<T>, a carei metoda
GetEnumerator va returna un Enumerator<T>. Exista totusi diferente, intre cele doua. Una ar fi
ca, interfata IEnumerator<T> nu contine metoda Reset, extinzand, insa, mai mult, interfata
IDisposable.

In exemplul urmator, vom folosi interfetele IEnumerable si IEnumerator pentru a folosi un


foreach pentru obiecte ale clasei Cont, la fel cum il folosim pentru tipurile de baza.
Construim clasa Cont care va contine 3 variabile membru, numarul unic al contului, numele
persoanei care detine contul si soldul contului si vom creea constructorul clasei (articolul despre
introducere in programarea pe obiecte). Mai adaugam si functia toString, pe care o vom
suprascrie pentru a afisa in consola pentru fiecare obiect al clasei, toti cei trei membri (articolul
despre polimorfism).

//variabile membru
private int numarCurent;
private string numeTitular;
private double sold;

//constructor
public Cont(int numarCurent, string numeTitular, double sold)
{
this.numarCurent = numarCurent;
this.numeTitular = numeTitular;
this.sold = sold;
}

//suprascriem functia
//pentru a afisa toate variabilele membru
public override string ToString()
{
return "Nr.Crt.:" + this.numarCurent.ToString() + "\nNume:" +
numeTitular + "\nSold:" + sold.ToString();
}

Vom construi clasa Conturi, care va contine un vector de obiecte Cont. Clasa va contine si o
metoda de adaugare in lista a obiectelor si va implementa cele doua interfete (articolul despre
interfete).

//clasa Conturi
//implementeaza interfetele
class Conturi : IEnumerable,IEnumerator
{
//construim un vector
ArrayList listaConturi = new ArrayList();

//variabila care va returna


//pozitia curenta a enumeratorului
private int pozitie = -1;

//metoda pentru adaugarea unui obiect de tip


// in lista
public void AddEmployee(Cont c)
{
//adauga in lista
listaConturi.Add(c);
}
//implementarea interfetei IEnumerable
//va returna un enumerator
public IEnumerator GetEnumerator()
{
return (IEnumerator)this;
}

//implementarea interfetei IEnumerator

public bool MoveNext()


{
if (pozitie < listaConturi.Count - 1)
{
//incrementam
++pozitie;
return true;
}
//nu mai sunt elemente in colectie
return false;
}

public void Reset()


{
//pozitia initiala
pozitie = -1;
}

public object Current


{
get
{
//returneaza elementul indicat de enumerator
return listaConturi[pozitie];
}
}
}

In functia Main, creem colectia:

Conturi ContList = new Conturi();

//cconstruim obiecte de tip Cont


Cont c1 = new Cont(1, "Cont#1", 1250.75);
Cont c2 = new Cont(2, "Cont#2", 3131);
Cont c3 = new Cont(3, "Cont#3", 400);

//construim o colectie
ContList.AddEmployee(c1);
ContList.AddEmployee(c2);
ContList.AddEmployee(c3);

Construim obiectul enumerator:

IEnumerator ContEnumerator = ContList.GetEnumerator();

Initial, obiectul enumerator este situat inaintea primului element al colectiei. Vom apela
metoda Reset(), pentru a fi siguri de acest lucru:

ContEnumerator.Reset();
Parcurgem colectia:

//se invoca mai intai metoda MoveNext inainte de accesarea


//Current, altfel va aparea exceptie la executie
while (ContEnumerator.MoveNext())
{
Console.WriteLine((Cont)ContEnumerator.Current);
}

Atentie! Folosirea corecta a interfetei IEnumerator presupune mai intai apelul metodei
MoveNext(), apoi accesarea proprietatii Current.

Obiectul enumerator nu are acces exclusiv asupra colectiei, parcurgerea cu ajutorul acestuia nu
este thread-safe, pentru ca in acelasi timp alte fire de executie pot aduce modificari colectiei. O
solutie ar fi blocarea colectiei in timpul parcurgerii.

In articolul urmator, vom discuta despre cateva clase din spatiul de nume System.IO.

Codul pentru acest articol este aici.

Colectii in C#

Colectiile reprezinta un mod de stoacare a obiectelor intr-o maniera structurata. Acestea sunt
foarte utilizate in programare. Framework-ul .Net pune la dispozitia dezvoltatorilor foarte multe
clase pentru lucrul cu colectiile. Toate aceste clase sunt continute in spatiul de nume
System.Collections.

.Net defineste o colectie ca fiind un obiect care implementeaza una sau mai multe interfete :
System.Collections.ICollection, System.Collections.IListSystem, Collections.IDictionary. Astfel,
vom avea o clasificare a colectiilor din framework:

Colectii ordonate
Aceste colectii implementeaza doar interfata ICollection si se deosebesc prin faptul ca ordinea
in care elementele sunt inserate determina si ordinea in care elementele sunt regasite in colectie.
Exemplu: Stack, Queue.

Colectii indexate
Acest tip de colectie se distinge prin faptul ca accesul la elemente se face prin index, (incepand
cu 0), similar unui vector. Exemplu: ArrayList.

Colectii pe baza de cheie


Colectiile pe baza de cheie implementeaza interfata IDictionary. Ele contin elemente care pot
fi accesate prin valoarea asociata cheii respectivului element (pereche cheie/valoare). Vom face o
analiza scurta a clasei SortedList.

Colectiile din System.Collections sunt colectii non-generice.


Exista cateva colectii importante, care pot fi folosite in dezvoltare. Folosirea acestor colectii in
mod non-generic este justificata doar in cazul mostenirilor sau atunci cand se doreste
compatibilitate cu versiunile mai vechi de 2.0 ale framework-ului.

O clasa importanta este ArrayList, pe care am descris-o in articolul despre ArrayList.

Stack

BitArray – este un vector care contine valoare de tip boolean, unde true este 1. false este 0.

//declaram un vector de tip bit


BitArray vectorBool = new BitArray(3);
//metoda set stabileste valoarea elementului
vectorBool.Set(0, true);
vectorBool.Set(1, false);
vectorBool.Set(2, true);

//parcurgem tabloul
foreach (bool b in vectorBool)
{
Console.WriteLine(b);
}

Metoda And realizeaza operatia logica “AND” (articolul despre operatori in .Net):

BitArray rezultat = new BitArray(3);


rezultat = vectorBool.And(rezultat);

foreach (var r in rezultat)


{
Console.WriteLine(r);
}

De asemenea exista si metodele Or, Xor, Not:

//SAU
rezultat = vectorBool.Or(rezultat);

//SAU EXCLUSIV
rezultat = vectorBool.Xor(rezultat);

//Negatie
rezultat = vectorBool.Not();

Toti membrii clasei BitArray sunt descrisi pe msdn.

SortedList

Aceasta clasa reprezinta o colectie de perechi cheie/valoare, care sunt sortate dupa cheie.
Elementele sunt accesate cu ajutorul cheii si al indecsilor. O colectie de tip SortedList este o
combinatie intre HashTable si un Array (articolul despre array). De ce? Pentru ca atunci cand
elementele acestui tip de colectie sunt accesate prin cheie folosind proprietatea Item, colectia se
comporta ca un HashTable. Cand elementele sunt accesate prin folosirea unui index cu ajutorul
metodelor GetByIndex sau SetByIndex, colectia se comporta ca un Array.

//construim lista si adaugam elemente


SortedList lista = new SortedList();
lista.Add("def", 5);
lista.Add("abc", 2);
lista.Add("ghi", 9);

//accesem valoarea unui element, va afisa 5


Console.WriteLine(lista["def"]);

Colectiile de acest tip vor sorta elementele dupa cheie. Daca vom accesa o valoare prin index,
acel index va fi indicat de pozitia cheii in colectie:

//va afisa 5
Console.WriteLine("Valoarea de la pozitia a doua: " +
lista.GetByIndex(1));

In lista, ne putem da seama usor, ca, sortate, elementele vor fi in ordinea: abc, def, ghi. Astfel,
valoarea returnata va fi 5.

Pentru mai multe detalii despre clasa SortedList, siteul Microsoft.

Stack

Clasa Stack (Stiva) reprezinta o structura de date de tip last-in-first-out (LIFO), adica ultimul
element introdus in colectie va fi primul care va iesi din colectie.

Un exemplu cu aceasta structura vom face in articolul urmator, folosind varianta generica a
clasei.

Queue

Clasa Queue (Coada) este o structura de date de tipul first-in-first-out (FIFO). Adica, primul
element intrat in colectie este primul element care va iesi din colectie.

Un exemplu cu aceasta structura vom face in articolul urmator, folosind varianta generica a
clasei.

Cand este necesar sa folosim o colectie?

Folosim o colectie cand este nevoie sa “punem la un loc” o serie de elemente care sunt
asemanatoare, apoi ne vom referi la ele ca la un grup de date, avand posbilitatea de a le parcurge
si de a le sorta.
Pot aparea probleme de performanta in folosirea colectiilor, pentru ca, de regula, atunci cand
apelam la colectii, inseamna ca avem de a face cu un numar mare de elemente. Implicit,
performanta aplicatiei va fi afectata. Un lucru foarte important este alegerea corecta, potrivita, a
tipului de colectie de care avem nevoie. Analizam daca se vor introduce toate elementele in
colectie la initializarea aplicatiei, sau pe parcurs. Mai tinem cont si de locul unde vor fi introduse
noile elementele in colectie (in mijloc, la sfarsit), sau daca ele vor fi stocate intr-o anumita ordine
sau nu.

Clasele din acest spatiu de nume sunt intuitive, produc o performanta buna, sunt usor de
folosit.

Toate detaliile despre spatiul de nume System.Collections sunt aici.

In urmatorul articol, vom prezenta spatiul de nume System.Collections.Generic.

System.Collection.Generic

Conceptul de generic este unul foarte puternic in limbajul C#. O introducere in aceasta
caracteristica a C# 2.0 am facut in acest articol, despre List.

Spatiul de nume System.Collection.Generic contine interfete care isi au corespondente non-


generice in spatiul de nume System.Collections. Acestea sunt: ICollection<T>, IComparer<T>,
IDictionary<K,V>, IEnumerable<T>, IEnumerator<T>, IEqualityComparer(T), IList<T>.

In ceea ce priveste clasele din cele doua spatii de nume, o corespondenta ar fi:

Generic NonGeneric
Collection<T> CollectionBase
Comparer<T> Comparer
Dictionary<K,V> Hashtable
List<T> ArrayList
SortedDictionary<K,V> SortedList
Stack<T> Stack
Queue<T> Queue
ReadOnlyCollection<T> ReadOnlyCollectionBase

Reamintim ca despre clasa Dictionary<K,V> am scris in acest articol.

Stack <T>
Colectia de tip Stack (Stiva) este o structura de date de tip LIFO ce stocheaza elemente care pot
fi apelate sau sterse printr-un singur pas. Personal, un bun exemplu de a-mi imagina o colectie de
tip stiva sunt vasele de la bucatarie care trebuie spalate. :)

Clasa Stack foloseste metoda Push pentru adaugarea unui element si metoda Pop pentru
scoaterea unui element din colectie.

Construim o clasa simpla Persoana si sa adaugam obiecte de acest tip intr-o stiva.

class Persoana
{
public string nume;
public string prenume;

public Persoana(string Nume, string Prenume)


{
this.nume = Nume;
this.prenume = Prenume;
}
}

Declaram o colectie de tip stiva, care va stoca elemente de tipul Persoana.

Stack<Persoana> stiva = new Stack<Persoana>();

Apelam functia Push pentru a introduce elemente in stiva :

stiva.Push(new Persoana("Ionescu","Ion"));
stiva.Push(new Persoana("Georgescu","George"));
stiva.Push(new Persoana("Popescu", "Paul"));

Daca vom afisa elementele stivei, vom observa ca primul element afisat este ultimul element
inserat in colectie. (Popescu, Georgescu, Ionescu).

foreach (var p in stiva)


{
Console.WriteLine(p.nume);
}

Dorim sa eliminam un element din stiva. Apelam metoda Pop.

stiva.Pop();

Putem apela proprietatea Count pentru a verifica faptul ca stiva acum va detine cu un element
mai putin decat inaintea apelarii metodei Pop. Afisam elementele stivei ca in modul de mai sus si
observam ca elementul eliminat prin apelul metodei Pop a fost cel introdus ultimul in colectie. In
exemplul nostru, Popescu.

Exista o metoda care va returna obiectul aflat “deasupra”, ultimul introdus in stiva, metoda
Peek.
Console.WriteLine(stiva.Peek().nume);

Pentru lista completa a membrilor clasei Stack, site Microsoft.

Queue<T>

Clasa Queue (C0ada) este o structura de date de tip FIFO. Adica, primul element intrat in
colectie este primul element care va iesi din colectie. Un exemplu din lumea reala este “coada”,
multimea, ce se formeaza la un ghiseu la banca.

Continuam cu acceasi clasa Persoana si vom inseara obiecte de acest tip intr-o colectie Queue.

Declaram colectia:

Queue<Persoana> coada = new Queue<Persoana>();

Clasa Queue detine metoda Enqueue pentru inserarea unui element in colectie si metoda
Dequeue pentru eliminarea primului element inserat in lista.

Adaugam cateva elemente:

coada.Enqueue(new Persoana("Ionescu", "Ion"));


coada.Enqueue(new Persoana("Georgescu", "George"));
coada.Enqueue(new Persoana("Popescu", "Paul"));

Afisam:

foreach (var p in coada)


{
Console.WriteLine(p.nume);
}

Elementele sunt afisate in ordinea in care au fost introduse, primul element inserat fiind si
primul afisat.

Dorim sa eliminam un element din colectie si vom apela metoda Dequeue.

coada.Dequeue();

Putem apela proprietatea Count pentru a verifica numarul de elemente al colectiei, care acum
va fi mai mic cu un element decat inaintea apelarii metodei Dequeue. Afisam elementele cozii ca
in modul de mai sus si observam ca elementul eliminat prin apelul functiei Dequeue a fost cel
introdus primul in colectie. In exemplul nostru, Ionescu.

Asemanator clasei Stack, clasa Queue are metoda Peek, care va returna in acest caz, primul
element din colectie (“de la inceputul cozii”).

Console.WriteLine(coada.Peek());
Pentru lista completa a membrilor clasei Queue, siteul Microsoft.

Ambele clase contine metoda Clear pentru eliminarea tuturor elementelor din colectie.

coada.Clear();
stiva.Clear();

C# este un limbaj type-safe. Acest lucru presupune sa se stie tipul obiectului inainte de a-i
atribui o valoare.

Amintim ca, atunci cand vrem sa folosim una din colectiile generice, trebuie sa declaram
astfel:

using System.Collections.Generic;

.Net are un suport foarte vast pentru colectii. Exista foarte multe clase, metode, proprietati,
pentru a interactiona cu structuri de date variate din framework.

Ca avantaje ale colectiilor strong type: folosirea indecsilor, enumerarea, redimensionarea


dinamica.

Pentru ca sunt foarte puternice, se recomanda folosirea cu atentie a colectiilor si alegerea


corecta a tipului de colectie necesar.

Mai multe informatii despre spatiul de nume System.Collections.Generic exista pe msdn.

Am lasat la urma, poate si din cauza ca e cea mai folosita, List<T>. Este alternativa la
ArrayList pe care o folosesc de cand am descoperit clasele generice. Aceasta clasa
implementeaza o serie de interfete:

[SerializableAttribute]
public class List<T> : IList<T>, ICollection<T>,
IEnumerable<T>, IList, ICollection, IEnumerable

care permit accesul la diverse facilitati despre care voi discuta mai detaliat intr-un articol viitor.

La ce e buna folosirea acestei clase? Din punctul meu de vedere ea este un ArrayList
imbunatatit, in primul rand prin type safe. List<T> este pentru mine o colectie care stie sa se
redimensioneze automat pe masura ce ii adaog noi elemente si care “stie” ce tip de elemente i-au
fost introduse (<T>). Nu o sa vin in cazul acestei clase cu exemple de cod, pentru ca am dat
anterior, sau in alte articole de pe acest blog, destule exemple, ci doar o sa enumar cateva din
situatiile practice in care poate fi folositoare.

Exemple:
1. Avem o lista de obiecte, sa zicem persoane, si trebuie sa pastram intr-o lista numai persoanele
cu varsta de peste 18 ani. Nu stim de la inceput cate persoane au peste 30 de ani. Alternativele
sunt: ori numaream rezultatele (intr-o variabila cont), tracand o data peste lista numai pentru a
numara, si creem un array de persoane de dimensiunea cont insa aceasta abordare presupune
parcurgerea de doua ori a listei, o data pentur numarare si inca o data pentru adaugarea
persoanelor cu varsta de peste 18 ani in array; alta varianta este folosirea unui ArrayList, care se
redimensioneaza dinamic pe masura ce sunt adaugate personae, insa care are dezavantajul ca la
fiecare accesare a unui obiect al listei, obiectul accesat trebuie convertit (cast) in Person; a treia
varianta pe care eu o vad este tocmai varianta care foloseste o lista generica, List<Person>, care
realizeaza o lista care se mareste dinamic si care permite accesul direct, fara cast, la obiectele
stocate in ea.

2. Citim un fisier text, si trebuie sa pastram intr-un tablou toate liniile de text care contin text,
altceva decat spatii goale. Nu stim cate linii vom gasi, asa ca cea mai potrivita abordare e crearea
unui obiect de tip List<string> unde sa pastram liniile de text gasite.

3. Avem un set de date dintr-un tabel dintr-o baza de date, sa zicem sub forma unui DataTabel, si
trebuie sa pastram numai anumite date (randuri), care indeplinesc anumite criterii, eventual sub
forma unor obiecte construite din datele de pe randul respectiv.

4. Citim un fisier XML unde avem definita limba folosita de aplicatia noastra. Numarul de
cuvinte definite este necunoscut, asa ca o lista dinamica de string este solutia cea mai potrivita.

5. Citim un fisier HTML si vrem sa extragem toate linkurile continute. Din fiecare link
construim un obiect de tip Uri pe care le pastram intr-o lista de tip List<Uri>.

In urmatorul articol, vom descrie System.Diagnostics, un spatiu de nume care contine o serie de
clase foarte interesante.

System.Diagnostics

.Net Framework contine spatiul de nume System.Diagnostics, care permite interactiunea cu event
log din Windows, monitorizeaza proceselor sistem din retea, monitorizeaza proceselor locale,
monitorizarea performantei sistemului.

Administratorii de sistem folosesc foarte mult Windows event log, un centralizator cu


informatii despre sistemul de operare, activitatile aplicatiilor si erori. Un exemplu: sistemul de
operare Windows creeaza event logs la pornire si inchidere.

Exista 3 tipuri de event log foarte des intalnite: system (stocheaza evenimente care nu sunt
legate de securitatea sistemului), securitate (stocheaza evenimente care legate de sesiunile
utilizatorilor, accesul la fisiere si registri), application (stocheaza evenimente de la toate
aplicatiile care nu au deja creat un event log specific).

Evenimentele au nevoie de o sursa, iar ca sa adaugam una, va trebui sa avem drepturi de


adminstrare asupra sistemului.

Din clasa EventLog apelam metoda statica CreateEventSource.


string sursa = "AplicatieTest";
EventLog elog = new EventLog();
//verifica daca exista sursa
if (!EventLog.SourceExists(sursa))
{
//daca nu, o creem
EventLog.CreateEventSource(sursa, sursa);
}

Dupa ce am reusit inregistrarea aplicatiei AplicatieTest. ca o sursa, putem adauga un eveniment


folosind o instanta a clasei:

//adaugam un eveniment la event log pentru AplicatieTest


EventLog elog = new EventLog();
elog.Source = sursa;
elog.WriteEntry("Eroare", EventLogEntryType.Error, 1001, 15);

Un mesaj detaliat al erorilor este recomandat doar in masura in care nu se afiseaza parole,
detalii despre utilizatori, conexiuni, etc.

Pentru citirea evenimentelor, accesam colectia EventLog.Entries:

foreach (EventLogEntry intrare in elog.Entries)


{
Console.WriteLine(intrare.Message);
}

Mai multe detalii despre clasa EventLog se gasesc pe msdn.

O alta parte importanta pe care o trateaza spatiul de nume System.Diagnostics este


performanta. Aceasta este masurata cu ajutorul unor contoare, astfel, administratorul sistemului
sau dezvoltatorul aplicatiei poate monitoriza orice aspect al performantei aplicatiei.

Se pot monitoriza componente ca: procesoare, memorie, retea.

Sistemul de operare Windows detine o multime de indicatori de performanta care permit


monitorizarea activitatilor sistemului in timp real. Numarul indicatorilor difera de la sistem la
sistem, in functie de hardware-ul si software-ul instalat.

Indicatorii sunt impartiti in categorii. Vom folosi PerformanceCounterCategory pentru a afisa


lista categoriilor de indicatori din sistem:

//GetCategories va returna lista indicatorilor


//salvam toti indicatorii intr-un vector
PerformanceCounterCategory[] listaIndicatori =
PerformanceCounterCategory.GetCategories();

//parcurgem vectorul cu indicatori


foreach (var ind in listaIndicatori)
{
Console.WriteLine(ind.CategoryName);
}

Pentru a monitoriza performanta cu ajutorul unui program in C#, folosim clasa


PerformanceCounter. In acest exemplu, vom afisa un indicator din categoria Processor.

//construim un obiect PerformanceCounter


//care va monitoriza procesorul
PerformanceCounter ip = new
PerformanceCounter("Processor", "% Processor Time", "_Total");
// primul apel al metodei reseteaza indicatorul
ip.NextValue();
Thread.Sleep(1000);
// afisam uzura procesorului in secunda trecuta
Console.WriteLine(ip.NextValue());

Un alt exemplu: afisam indicatorul pentru afisarea memoriei Ram disponibila:

PerformanceCounter indicatorRam = new PerformanceCounter("Memory",


"Available MBytes");

Console.WriteLine(indicatorRam.NextValue());

Mai multe informatii despre clasa PerformanceCounter, pe msdn.com.

Aplicatiile au nevoie sa “cunoasca” aspecte ale computerului. De exemplu, procesele care


ruleaza la un moment dat, unitati de stocare, reactia la schimbari in sistem. Pentru toate
acestea, .Net Framework foloseste clasa Process si Windows Management Instrumentation.

Clasa Process

Afisam toate procesele active la momentul respectiv, apeland functia statica GetProcesses
(procesele rulate de alti utilizatori nu sunt vizibile intotdeauna). Pentru fiecare proces, ii vom
afisa numele prin proprietatea ProcessName:

foreach (Process p in Process.GetProcesses())


{
Console.WriteLine(p.ProcessName);
}

Pentru ca proprietatile unui proces sa ramana actuale si valide, apelam metoda Refresh.

p.Refresh();

Alte cateva proprietati des folosite ale clasei Process sunt:

MachineName (“.” inseamna computerul local)

//afiseaza numele computerului pe care ruleaza procesul


Console.WriteLine(p.ProcessName+" "+p.MachineName);
BasePriority

//afiseaza numele si prioritatea procesului


Console.WriteLine(p.ProcessName + " " + p.BasePriority);

Clasa Process pune la dispozitie metoda statica Start, pentru a porni un nou process. Nu trebuie
decat sa ii specificam numele fisierului executabil:

Process notepad = new Process();


notepad.StartInfo.FileName = "notepad.exe";

notepad.Start();

Pentru o privire detaliata asupra clasei Process vizitati siteul Microsoft.

Sistemul de operare Windows expune foarte multe informatii despre computer si despre el
insusi prin intermediul WMI. Asemenea informatii sunt utile in instalarea/configurarea aplicatiei.

Ca un rezumat al articolului, in cadrul spatiului de nume System.Diagnostics, am ales sa


discutam despre:

Event logging – este o modalitate centralizata pentru a inregistra evenimente software si


hardware importante pentru aplicatie. Windows furnizeaza o interfata utilizator pentru
vizualizarea log-urilor, Event Viewer.

Performanta – se refera la viteza executiei unui program. Ea este influentata direct de calitatea
codului scris pentru aplicatie. Astfel, se pot face modificari majore sau minore asupra codului
pentru a mari viteza de executie. O recomandare este stabilirea unor puncte de performanta si
monitorizarea ei in cursul dezvoltarii aplicatiei.

Proces – in termeni generali, se poate defini ca o aplicatie care ruleaza. Un thread (fir de
executie) este unitatea de baza pe care sistemul de operare o aloca procesului.

Despre toti membrii spatiului de nume System.Diagnostics, puteti afla pe siteul msdn.com.

Pentru incheiere am pastrat ceva special: System.Diagnostics.Stopwatch – din puctul meu de


vedere cea mai folosita clasa a acestui namespace la nivel de practica: oricine are nevoie, la un
moemnt data, sa stie cat timp dureaza executia unui anumit bloc de aplicatie. Ca sa scurtez
povestea: Stopwatch e o clasa care permite masurarea timpului scurs intre doua puncte ale
aplicatiei. Modul de folosireeste simplu:

using System.Diagnostics;

//.......
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
//blocul de cod a carui timp de executie vrem sa il masuram
stopWatch.Stop();
Console.WriteLine("Executia a durat: " + stopWatch.ElapsedMilliseconds +
"milisecunde")
//.......

Utilitatea acestui tip de masuratoare depinde de la aplicatie la aplicatie. Eu, in practica, am


folosit ceva asemanator pentru a verifica timpul necesar unei aplicatii care imi genereaza
documente pdf online pentru un document; alt loc unde am folosit este pentru o aplicatie care
genereaza niste grafice pe baza unor date care vin dintr-un webservice si trebuie sa generez la
fiecare incarcare a unei pagini web un numar nedefinit de grafice – intre zero si cateva zeci. Mai
important decat masurarea duratei unei operatii este insa masurarea diferentei de timp intre
executarea codului o data si de 100 de ori. Sau cu alte cuvinte, sa generez un grafic imi ia 80
milisecunde. Cat timp imi trebuie pentru a genera 40 de grafice? Daca e 80 milisecunde x 40 ar
trebui sa mai lucrez la algoritm!

Un alt exemplu de folosire a acestei clase este penru optimizarea (sau el putin cunoasterea)
problemelor de conectare la o baza de date sau la un webservice. E important sa stii, cand scrii o
aplicatie, daca iti ia 1 secunda sa citesti ceva din baza de date sau daca iti ia o milisecunda. La fel
si pentru webservice.

Informatiile obtinute despre timpul de executie al anumitor portiuni – blocuri – de cod sunt, in
cazul meu cel putin, informatii care ajung in logurile aplicatiei. Daca scriu loguri, atunci scriu si
informatii despre timpul de executie.

In articolul urmator vom discuta despre spatiul de nume System.Text.

HASH

O functie hash este o procedura bine definita sau o functie matematica ce converteste o cantitate
de date de dimensiuni varibile intr-o secventa de date de dimensiuni mici, de obicei de
dimensiune fixa, care depinde de tipul algoritmului folosit si nu de cantitatea (lungimea) datelor
de intrare.

O functie hash are (ar trebui sa aiba) urmatoarea proprietate: este imposibil de gasit, prin calcule,
un alt bloc de date care sa aiba aceeasi valoare hash. Cu alte cuvinte, daca pentru un bloc se date
dat calculam valoarea hash, este imposibila gasirea prin intermediul unui calcul a unui alt bloc a
carui valoare hash sa fie aceeasi. Functiile hash sunt folosite in semnaturi digitale sau pentru
verificarea integritatii datelor.

Valoarea hash are o lungime fixa indiferent de lungimea datelor pentru care a fist calculata.
Valoarea hash a doua blocuri de date ar trebui sa fie identica daca cele doua blocuri de date sunt
identice. Modificari minore in blocul de date duce la aparitia de modificari nepredictibile si
importante in valoarea hash calculata.

O functie Hash nu este inversabila. Este imposibila reconstituirea satelor care au generau o
anumita valoare hash.
.Net, in namespace-ul System.Security.Cryptography contine o serie de clase care
implementeaza divesi algoritmi hash. HashAlgorithm este o clasa abstracta, folosita ca baza
pentru diversi algoritmi de hash. In exemplul pe care vreau sa il prezint aici o sa folosesc 4 clase,
derivate din HashAlgorithm care implementeaza 4 lgoritmi de criptare: SHA1, SHA256,
SHA384, SHA512. Aceste clase sunt: SHA1Managed, SHA256Managed, SHA384Managed si
SHA512Managed.

Pentru inceput am construit un enum, in care am introdus cei 4 algoritmi pe care il voi folosi, si
cu ajutorul caruia userul va putea alege functia de hash dorita:

public enum HashAlgoritm


{
SHA1,
SHA256,
SHA384,
SHA512
}

Aplicatia exemplu este o aplicatie winform, care contine 2 textbox-uri, un comboBox si un


buton. Primul textbox va fi locul unde userul poate introduce un text pe care vrea sa il cripteze.
Al doilea textbox va afisa stringul encriptat folosind algoritmul ales din comboBox. Operatia de
criptare va avea loc la apasarea butonului. Form-ul e atat de simplu, incat nu o sa pun screenshot-
uri sau codul care il defineste.

Functia care face criptarea este urmatoarea:

public static string ComputeHash(string textToHash, HashAlgoritm


hashAlgorithm)
{
// plain text to a byte array.
byte[] textToHashBytes = Encoding.UTF8.GetBytes(textToHash);

HashAlgorithm hash;

// Initialize the hashing algorithm class.


switch (hashAlgorithm)
{
case HashAlgoritm.SHA1:
hash = new SHA1Managed();
break;

case HashAlgoritm.SHA256:
hash = new SHA256Managed();
break;

case HashAlgoritm.SHA384:
hash = new SHA384Managed();
break;

case HashAlgoritm.SHA512:
hash = new SHA512Managed();
break;
default:
hash = new SHA1Managed();
break;
}

// Compute hash value for the text


byte[] hashBytes = hash.ComputeHash(textToHashBytes);

// Convert the result into a base64-encoded string.


string hashValue = Convert.ToBase64String(hashBytes);

// Return the result.


return hashValue;
}

Criptarea, asa cum se observa din cosul de mai sus, se aplica pe array de biti, motiv pentru care
textul este “convertit” in byte[]. Deasemeni, rezultatul criptatii este tot un array de biti, care,
pentru a fi reprezentat ca string se foloseste Convert.ToBase64String

System.Text

Dezvoltatorii au nevoie foarte des de a procesa text pentru ca interactiunea aplicatiei cu


utilizatorul se bazeaza pe introducerea textului. Acesta trebuie sa fie validat, reformatat.

O expresie regulata reprezinta un set de caractere care este comparat cu un string pentru a
determina daca string-ul respectiv indeplineste cerintele unui anumit format. O expresie regulata
mai poate fi utila in extragerea/inlocuirea unor portiuni de text. Exemplu: stringuri care au
numere, siruri de caractere doar cu litere mici, siruri cu format hexadecimal.

Exemplu: vom creea doua siruri de caractere (pe primul il vom considera expresie regulata) si
vom determina daca primul sir de caractere se va potrivi cu al doilea sir de caractere.

Construim cele doua siruri:

Console.WriteLine("Expresia regulata:");
string expresieRegulata = Console.ReadLine();
Console.WriteLine("String pentru comparare:");
string stringC = Console.ReadLine();

Apelam metoda IsMatch din clasa Regex pentru a verifica daca cele doua siruri de caractere se
potrivesc. Adaugam declaratia de utilizarea a spatiului de nume
System.Text.RegularExpressions(link).

if (Regex.IsMatch(stringC, expresieRegulata))
Console.WriteLine("Potrivire!");
else
Console.WriteLine("Nepotrivire!");
Functiei IsMatch ii mai putem adauga un parametru reprezentand o enumerare a optiunilor cu
privire la modul in care se face potrivirea – RegexOptions(link).

Consideram expresia regulata ^\d{5}$ . Aceasta inseamna ca stringul va trebui sa aiba fix 5
caractere ({5}) si acestea trebuie sa fie doar cifre (\d). Inceputul sirului de caractere este marcat
de ^ si $ reprezinta sfarsitul sirului.

Revenind la exemplul anterior, expresia regulata va fi:

string expresieRegulata = "^\\d{5}$";

iar stringul care va fi comparat cu aceasta va fi compus din 5 numere.

Expresiile regulate pot creea foarte multa confuzie, mai ales la citirea lor.:P Sunt foarte greu de
utilizat daca nu sunt cunoscute bine, insa reprezinta o metoda eficienta in validarea informatiilor
introduse de utilizator.

Pentru mai multe informatii despre clasele care lucreaza cu expresii regulate, vizitati msdn.

Fiecare sir de caractere dintr-un text este codificat folosind unul din standardele de codificare.
De cele mai multe ori, acest lucru este facut automat de .Net Framework. Sunt situatii, cand este
nevoie de a controla procesele de codificare si decodificare – interoperabilitatea cu sistemele
UNIX care folosesc alte tehnici de codificare, citirea si scrierea fisierelor in alte limbi.

Codul ASCII este fundamentul pentru tipurile existente de codificare.

.Net Framework utilizeaza Unicode UTF-16 pentru reprezentarea caracterelor. Formatul


UNICODE furnizeaza pentru fiecare caracter cate un numar unic, indiferent de platforma, de
program sau de limba.

O lista cu standardele de codificare UNICODE poate fi gasita pe siteul unicode.org.

Spatiul de nume System.Text contine clase pentru codificarea si decodificarea caracterelor.

Un exemplu de folosire a clasei Encoding este convertirea caracterelor in bytes, apoi codificarea
acestora in Korean.

// codificarea Korean
Encoding codificare = Encoding.GetEncoding("Korean");
// Convert ASCII bytes to Korean encoding
//convertim bytes ASCII in Korean
byte[] codat;
codat = codificare.GetBytes("Test!");
// afiseaza codurile bytes-urilor
for (int i = 0; i < codat.Length; i++)
Console.WriteLine("Byte {0}: {1}", i, codat[i]);
Cand e nevoie de unirea, concatenarea sirurilor de caractere (stringuri) folosim clasa
StringBuilder. Spre deosebire de tipul string, tipul stringBuilder poate fi schimbat fara a fi
copiat. Aceasta clasa creeaza siruri de caractere dinamice (“mutable”) – sirurile de caractere
“clasice” sunt immutable.

Exemplu:

//creeam o instanta a clasei


StringBuilder stringBuilder = new StringBuilder();
//apelam metoda de concatenare
stringBuilder.Append("abc");

Metoda Append adauga continutul argumentelor sale, fiecare argument avand apelata metoda
toString.

O metoda asemanatoare este AppendLine, cu diferenta ca aceasta va adauga o linie noua la


sfarsit. Deci, urmatorul sir de caractere care va fi adaugat stringbuilder-ului va fi pe o linie noua.

stringBuilder.AppendLine();

Clasa StringBuilder are mai multi constructori. Astfel, putem construi o instanta a acesteia
careia sa ii stabilim un string initial sau/si capacitatea stringbuilder-ului.

StringBuilder sb = new StringBuilder("abc", 34);

sb va avea un string initial “abc” si nu va putea retine mai mult de 34 de caractere.

Putem folosi proprietatile Capacity si MaxCapacity pentru setarea/afisarea numarului de


caractere.

//setam numarul maxim de caractere


stringBuilder.Capacity = 20;
//numarul maxim de caractere care poate fi
//continut de instanta curenta
Console.WriteLine(stringBuilder.Capacity);
//capacitatea maxima
Console.WriteLine(stringBuilder.MaxCapacity);

Capacitatea maxima este egala cu valoarea maxima a unui tip valoare int32 (int32.MaxValue).
In cazul in care numarul de caractere depaseste aceasta valoare, vom primi exceptie de tipul
ArgumentOutOfRangeException.

Pentru a aduce modificari continutului stringbuilder-ului, se pot utiliza functiile Replace,


Insert, Remove.

//inlocuim caracterul a cu caracterul z


stringBuilder.Replace('a', 'z');
//dupa a patra pozite, va insera stringul BBB
stringBuilder.Insert(4, "BBB");
//eliminam de la pozitia 2 un element
stringBuilder.Remove(2, 1);

O varianta pentru clasa StringBuilder sunt vectorii de caractere, mult mai simpli. Pe acestia ii
vom folosi atunci cand stim dimensiunea, marimea absoluta a stringului si avem operatii care nu
includ iteratii.

Folosita cu atentie, clasa StringBuilderpoate imbunatati performanta programului.

Pentru toti membrii clasei StringBuilder, aflati mai multe detalii pe siteul Microsoft.

Clasele care lucreaza cu expresiile regulate sunt organizate in spatiul de nume


System.Text.RegularExpressions. Printre cele mai importante sunt: Regex, Match,
MatchCollection, Capture.

Clasa StringBuilder functioneaza ca un string, caruia ii putem atribui, inlocui, sterge


caractere/siruri de caractere.

Spatiul de nume System.Text contine clase care permit lucrul cu ASCII,Unicode, UTF-7, UTF-
8, standarde de codificare.

Detalii mai multe despre spatiul de nume System.Text pot fi gasite pe msdn.

Sortare pe Array

De multe ori in practica stocam diverse date in array-uri. Pe aceste date, pastrate in array-uri
avem nevoie sa executam diverse operatii. Printre operatiile cel mai des folosite – cel putin de
mine – sunt operatii de sortate. Fie ca avem liste de persoane, liste de produse, liste de stringuri,
la un moment dat va trebui sa le asezam (afisam) intr-o anumita ordine.

In urma cu ceva timp am scris despre asta – .Net IComparer – se defineste un CustomComparer
care implementeaza interfata IComparer, bla, bla… Nu vreau sa reiau ceea ce am scris in
articolul precedent aici. Acum vreau sa dau o alta metoda de a face respectiva sortare, care
foloseste tot un iComparer, insa scrierea e mai scurta.

Presupunand ca avem un array de obiecte de tip Person[] oameni = new Person[]{ …. }; putem
sa il sortam scriind un comparer inline la modul urmator:

Array.Sort(oameni, delegate(Person p1, Person p2)


{
return p1.Age.CompareTo(p2.Age);
});

care va sorta lista (array-ul) respectiv dupa varsta persoanelor. E mai simplu, mi se pare mie, ca
si scriere decat in articolul anterior?
Codul complet este urmatorul:

Clasa Person este definita astfel:


public class Person
{
private string firstName;
private string lastName;
private uint age;

public Person(string fName, string lName, uint age)


{
firstName = fName;
LastName = lName;
this.age = age;
}

public string FirstName


{
get { return firstName; }
set { firstName = value; }
}

public string LastName


{
get { return lastName; }
set { lastName = value; }
}

public uint Age


{
get { return age; }
set { age = value; }
}

public override string ToString()


{
return string.Format("{0} {1} - {2}", FirstName, LastName, Age);
}
}

Iar in metoda Main creem un array de persoane, pe care il afisam nesortat, il sortam si il afisam
inca odata pentru a vedea efetul sortari:

class Program
{
static void Main(string[] args)
{
Person[] oameni = new Person[]{
new Person("Ion", "Popescu", 22),
new Person("Geo", "Ionescu", 15),
new Person("Pop", "Vasilescu", 32),
new Person("Ady", "Georgescu", 28),
new Person("Ron", "Niculescu", 18),
new Person("Pan", "Petrescu", 22)
};
Array.ForEach<Person>(oameni, p => Console.WriteLine(p));
//for (int i = 0; i < oameni.Length; i++)
// Console.WriteLine(oameni[i]);

Console.WriteLine("\n\nDupa sortare:\n");
Array.Sort(oameni, delegate(Person p1, Person p2)
{
return p1.Age.CompareTo(p2.Age);
});

Array.ForEach<Person>(oameni, p => Console.WriteLine(p));


//for (int i = 0; i < oameni.Length; i++)
// Console.WriteLine(oameni[i]);

Console.ReadKey();
}
}

Acest cod afiseaza:

Ion Popescu - 22
Geo Ionescu - 15
Pop Vasilescu - 32
Ady Georgescu - 28
Ron Niculescu - 18
Pan Petrescu - 22

Dupa sortare:
Geo Ionescu - 15
Ron Niculescu - 18
Pan Petrescu - 22
Ion Popescu - 22
Ady Georgescu - 28
Pop Vasilescu - 32

As vrea sa subliniez modul mai simplu, ca scriere, de afisare a elementelor array-ului:


In loc de:

for (int i = 0; i < oameni.Length; i++)


Console.WriteLine(oameni[i]);

am scris mai simplu (sau mai putin):

Array.ForEach<Person>(oameni, p => Console.WriteLine(p));

Array.ForEach<(Of <(T>)>) primeste doi parametri:

public static void ForEach<T>(


T[] array,
Action<T> action
)
: arrayul pe care lucram si un delegate Action<(Of <(T>)>) Delegate unde specificam actiunea
pe care o executam asupra fiecarui element din Array.

Date&Time

Data si timpul in .Net

In orice limbaj de programare si in orice program, la un moment dat este necesara folosirea datei
sau timpului si diverselor operatii cu acestea. In .Net exista o structura specializata care se ocupa
de data si timp, DateTime. Lucrul cu structura DateTime este simplu, intuitiv.

Exista 12 constructori in structura DateTime, si cateva metode (proprietati) statice care permit
crearea sau obtinerea unei instante a acestei structuri. Toate posibilitatile de obtinere a unui
obiect de tip DateTime prin folosirea contructorului sunt prezentate pe msdn aici. In afara de
aceasta cale, mai exista si alte posibilitati de a obtine un obiect de tip DateTime:

1. DateTime d = DateTime.Now;
2. DateTime d = DateTime.Today;
3. DateTime d = DateTime.UtcNow;
4. DateTime d = DateTime.Parse(dateAsString);

…si altele.

Interfetele implementate de DateTime sunt: IComparable, IComparable, IFormattable si


IConvertible.

Astazi voi scrie cateva exemple de cod care folosesc anumite operatii cu data – pe principiul ca
un exemplu e mai bun decat 2 pagini de teorie!Asadar, programul pe care l-am scris
demonstreaza folosirea anumitor operatii simple cu date. Voi posta in continuare codul c# si apoi
voi face cateva remarci pe marginea codului, desi codul este destul de bine comentat. Codul este
impartit in mai multe metode, pentru a usura intelegerea lui (sper ca aceasta fragmentare sa nu
ingreuneze citirea!).

using System;

namespace zeltera.DateTimeDemo
{
static class DateTimeDemo
{
static Random rnd = new Random();

static void Main(string[] args)


{
PrintDate();
PrintYesterday();
PrintTomorrow();

Console.Write("Azi este: ");


PrintDayName(DateTime.Today);
Console.Write("Prima zi din an a fost ");
PrintDayName(new DateTime(DateTime.Now.Year, 1, 1));

Console.Write("Ultima zi din acest an va fi ");


PrintDayName(new DateTime(DateTime.Now.Year, 12, 31));

for (int i = 0; i < 25; i++)


{
Console.WriteLine(i + ". Random date: " +
GetRandomDate().ToShortDateString());
}

Console.WriteLine("\n---------\n");

// Diferenta dintre doua date


DateTime d1 = GetRandomDate();
DateTime d2 = GetRandomDate();
TimeSpan ts = d1 > d2 ? d1 - d2 : d2 - d1;
Console.WriteLine("Diferenta dintre {0} si {1} este de {2} zile",
d1.ToShortDateString(), d2.ToShortDateString(), ts.Days);

Console.ReadKey();
}

/// <summary>
/// Afiseaza data de astazi
/// </summary>
static void PrintDate()
{
Console.WriteLine("Astazi: " + DateTime.Now.ToString());
}

/// <summary>
/// Afiseaza data de ieri
/// </summary>
static void PrintYesterday()
{
Console.WriteLine("Data de ieri: " + DateTime.Now.AddDays(-
1).ToShortDateString());
}

/// <summary>
/// Afiseaza data de maine
/// </summary>
static void PrintTomorrow()
{
Console.WriteLine("Data de ieri: " +
DateTime.Now.AddDays(1).ToShortDateString());
}

/// <summary>
/// Afiseaza numele zilei pentru o data specifica
/// </summary>
/// <param name="d"></param>
static void PrintDayName(DateTime d)
{
Console.WriteLine(d.DayOfWeek.ToString());
}

static DateTime GetRandomDate()


{
int d, m, y;
m = rnd.Next(1, 13);
y = rnd.Next(1900, DateTime.Now.Year + 1);
while (true)
{
try
{
d = rnd.Next(1, 32);
return new DateTime(y, m, d);
}
catch (Exception ex)
{
//exceptie apare numai in cazul in care ziua este o
valoare nepermisa pentru luna
//respectiva. Daca luna admite numai 30 de zile, si
numarul random generat pentru
//data este 31, o exceptie este aruncata.
//Exista si alte posibilitati (generarea datei in functie
de luna pentru care se
//genereaza ziua, insa am ales sa nu folosesc aceasta
varianta.
continue;
}
}
}
}
}

Un posibil rezultat al executiei acestui program este:

Astazi: 4/6/2010 10:56:27 PM


Data de ieri: 4/5/2010
Data de ieri: 4/7/2010
Azi este: Tuesday
Prima zi din an a fost Friday
Ultima zi din acest an va fi Friday
0. Random date: 12/2/1911
1. Random date: 11/15/1935
2. Random date: 7/14/1953
3. Random date: 8/30/1965
4. Random date: 4/19/1919
5. Random date: 1/1/1927
6. Random date: 7/4/2004
7. Random date: 7/20/1909
8. Random date: 9/6/1907
9. Random date: 12/13/1943
10. Random date: 10/19/1972
11. Random date: 12/14/1997
12. Random date: 5/24/1936
13. Random date: 2/28/1938
14. Random date: 12/5/1959
15. Random date: 2/16/1970
16. Random date: 1/18/1984
17. Random date: 2/14/1900
18. Random date: 5/14/1902
19. Random date: 2/10/1928
20. Random date: 8/2/1909
21. Random date: 10/11/2003
22. Random date: 4/6/1933
23. Random date: 8/8/2001
24. Random date: 5/7/1966

---------

Diferenta dintre 8/15/1960 si 8/21/1948 este de 4377 zile

Asadar, am scris o serie de metode care executa tascuri simple:

1. PrintDate – afiseaza data de astazi. Instanta DateTime este obtinuta folosing proprietatea
statica Now
2. PrintYesterday – afiseaza data de ieri, pentru a demontra folosirea metodei AddDays
3. PrintTomorrow – afiseaza data de maine
4. PrintDayName – afiseaza numele zilei (in engleza) al datei transmise ca argument
5. GetRandomDate – genereaza o data aleatoare
6. diferenta dintre doua date (exprimata in zile) – calculeaza diferenta dintre doua date si
afiseaza rezultatul ca numar de zile. Aici se poate vedea si folosirea compararii a doua
date: TimeSpan ts = d1 > d2 ? d1 – d2 : d2 – d1;

Cam atat, deocamdata despre lucrul cu data (cu timpul se lucreaza in mod asemanator). Intr-unul
din articolele urmatoare voi vorbi despre foramtarea pentru afisare a unei instante a DateTime.

Extinderea unei clase

Microsoft a introdus, in versiunea 3.5 a .Net framework posibilitatea de a extinde o clasa fara a
creea o noua clasa, derivata din clasa pe care vrem sa o extindem. Pana in versiunea 2.0, pentur a
introduce (adauga) noi metode unei clase singura posibilitate era crearea unei noi clase, care sa
mosteneasca clasa pe care vrem sa o extindem. Daca clasa pe care o vroiam completata era
declasata sealed, derivarea ei nu este posibila. Extinderea rezolva problema claselor declarate
sealed – orice clasa accepta extinderi.

Mai concret, ce e aia? Sa luam un exemplu simplu: clasa String – are o serie de metode care se
aplica pe sirurile de caractere. Printre metodele pe care le pot folosi nu se gaseste insa, de
exemplu, o metoda care sa imi inverseze (reverse) un string. Am mai multe posibilitati: scriu o
metoda in locul unde am nevoie care sa faca asta, scriu codul de inversare inline (e mic… 2 linii,
doar), imi creez o clasa de tip CommonHelperFunctions unde pun toate metodele ajutatoare etc.
Sau… extind clasa String adaugandu-i o metoda noua: Reverse();
Pentru a exemplifica mai bine, am ales sa extind clasa String adaugandu-i urmatoarele metode:
Reverse, IsDate, IsInteger, IsDouble, CountNonEmptyChars, CountWords, AlternateCase.
Numele metodelor pe care vreau sa le introduc spun (in engleza, ce-i drept) cam ce face
respectiva metoda. Pentru a implementa ceea ce am propus mai sus se procedeaza in felul
urmator: se creeaza o clasa publica, statica, in care se definesc metodele repsective ca metode
statice si care accepta ca prim parametru ceva de genul urmator: this string str.

In continuare voi lista codul care implementeaza cele 7 metode propuse:

public static class StringExtensions


{
private static string separators = ",.?!;:/<>(){}[]\"'= \n\r\t";

/// <summary>
/// Reverse the string
/// </summary>
/// <param name="str">string to reverse</param>
/// <returns>reversed string</returns>
public static String Reverse(this String str)
{
if (str == null)
return null;
StringBuilder rev = new StringBuilder();
for (int i = str.Length - 1; i >= 0; i--)
rev.Append(str[i]);
return rev.ToString();
}

///<summary>
///Check is a string represent a valid date
///</summary>
///<param name="str">string to check</param>
/// <returns>true if the string represent a valid date, else
false</returns>
public static bool IsDate(this String str)
{
try
{
DateTime.Parse(str);
return true;
}
catch
{
return false;
}
}

///<summary>
///Check if the string is a valid number (integer)
///</summary>
///<param name="str">string to check</param>
/// <returns>true if the string represent a integer, else
false</returns>
public static bool IsInteger(this String str)
{
try
{
int.Parse(str);
return true;
}
catch
{
return false;
}
}

/// <summary>
/// Check if the string is a valid number (double)
/// </summary>
/// <param name="str">string to check</param>
/// <returns>true if the string represent a double, else
false</returns>
public static bool IsDouble(this String str)
{
try
{
double.Parse(str);
return true;
}
catch
{
return false;
}
}

/// <summary>
/// Counts all the not empty chars on the given string
/// </summary>
/// <param name="str">string to count</param>
/// <returns>the number of non empty chars found</returns>
public static int CountNonEmptyChars(this string str)
{
int counter = 0;
for (int i = 0; i < str.Length; i++)
{
if (Char.IsWhiteSpace(str[i]))
continue;
counter++;
}
return counter;
}

/// <summary>
/// Count the words on a given string
/// </summary>
/// <param name="str">a phrase (string)</param>
/// <returns>number of words</returns>
public static int CountWords(this string str)
{
return str.Split(separators.ToCharArray(),
StringSplitOptions.RemoveEmptyEntries).Length;
}
///
<summary>
///
Transforms a string by alternating the letters case
///
</summary>
///
<param name="str">string to transform</param>
/// <param name="startsWithUpper">build the transformed string
starting with a uppercase char</param>
/// <returns> the transformed string</returns>
public static string AlternateCase(this string str, bool
startsWithUpper)
{
if (str == null)
return null;
StringBuilder alt = new StringBuilder();
bool upc = startsWithUpper;
for (int i = 0; i < str.Length; i++)
{
if (upc)
alt.Append(Char.ToUpper(str[i]));
else
alt.Append(Char.ToLower(str[i]));
upc = !upc;
}
return alt.ToString();
}
}

Acum, de fiecare data cand vrem ca aceste metode sa fie disponibile pentur a fi folosite cu clasa
String, trnuie doar sa folosim o instructiune using care sa importe numele de spatiu unde am
definit clasa StringExtensions.

Odata inclus numele de spatiu in proiectul curent, pentur orice obiect de tip string vom avea
disponibile toate cele 7 metode definite. In plus, aceste metode vor fi vizibile si in intelisense-ul
visual studio (cam asa):
O sa inchei cu un exemplu care exemplifica folosirea a tot ce am scris mai sus:

class Program
{
static void Main(string[] args)
{
string testString = "The quick brown fox jumps over the lazy dog";
Console.WriteLine("Reverse:");
Console.WriteLine(testString.Reverse());

Console.WriteLine("Caractere nevide: {0}",


testString.CountNonEmptyChars());

Console.WriteLine("Numar de cuvinte: {0}",


testString.CountWords());

Console.WriteLine("Alternate case:");
Console.WriteLine(testString.AlternateCase(true));

Console.ReadKey();
}
}

care are ca rezultat:

Reverse:
god yzal eht revo spmuj xof nworb kciuq ehT
Caractere nevide: 35
Numar de cuvinte: 9
Alternate case:
ThE QuIcK BrOwN FoX JuMpS OvEr tHe lAzY DoG

FAQ:

1. Q: Merge pe Visual studio 2005 sau 2003?


A: Nu. (din cate stiu eu).

2. Q: Cum pot totusi folosi extensiile daca nu am Visual Studio mai nou de
2005?
A: Instaleaza unul. Microsoft ofera versiuni express ale ultimului Visual
Studio (gratis).

3. Q: E codul prezentat optim?


A: Nu stiu. Codul folosit in exemplu e doar pentru a demostra extinderea
unei clase si nu a fost optimizat/verificat.

4. Q: Am o idee de extindere/imbunatatire a exemplului prezentat. Ce fac?


A: Scrii un comentariu. Daca exemplul e relevant, il includ in articol.

Clasa Process
Sunt situatii in care din aplicatia pe care o scriem avem nevoie sa pornim o alta aplicatie. Daca
lucram in .Net asta se face cu ajutorul clasei Process aflata in namespace-ul System.Diagnostics.
Astazi o sa fac o scurta prezentare a acestei clase, urmand ca in viitor sa revin cu mai multe
detalii si mai multe exemple.

Ce putem face, simplu, cu ajutorul acestei clase. Sa pornim, de exemplu, un notepad! La ce ne


trebuie? Deocamdata nu ne trebuie, e doar un exemplu:

using System.Diagnostics;

//.... cod

static void Main(string[] args)


{
Process p = new Process();
p.StartInfo = new ProcessStartInfo("notepad.exe");
p.Start();
}

//.... cod

Codul de mai sus, asa cum ai ghicit, deschide o sesiune de notepad. notepad.exe se poate inlocui
cu orice alt executabil (cale absoluta, relativa, numele executabilului daca el este definit prin nu
stiu ce variabile de system).

Insa nu e singura posibilitate. Nume aplicatiei poate fi inlocuit cu… un url. Codul devine:

//…. cod

static void Main(string[] args)


{
Process p = new Process();
p.StartInfo = new ProcessStartInfo(“http://google.com/”);
p.Start();
}

//…. cod

care, evident, va deschide browserul default al sistemului si va incarca pagina specificata (google
in cazul asta).

Odata pornit, un proces poate fi controlat in diverse feluri. Un mod de a controla un process este
sa il opresti. Asta se face cu ajutorul metodei Kill() asociata obiectului:

using System.Diagnostics;

//.... cod
static void Main(string[] args)
{
Process p = new Process();
p.StartInfo = new ProcessStartInfo("notepad.exe");
p.Start();

Thread.Sleep(2000); //asteapta 5 secunde


p.Kill();
}

//.... cod

Asa cum se observa, obiectul de tip Process are o proprietate p.StartInfo de tip ProcessStartInfo
unde se definesc practic parametrii procesului care va fi pornit.

Cred ca destul de utila este pornirea unui proces care reprezinta o aplicatie de tip consola.
Aceasta difera un pic de procesele de tip fereastra, prin faptul ca din consola se primesc date
(output-ul consolei) sau, eventual, se transmit date. Pentru a executa si primi output-ul unei
aplicatii de tip consola se procedeaza asa:

using System.Diagnostics;

//.... cod

static void Main(string[] args)


{
Process p = new Process();
ProcessStartInfo psi = new ProcessStartInfo(externalConsoleApp);
//calea spre aplicatia console
psi.CreateNoWindow = false; //nu vreau sa fie creata fereastra
aplicatiei
psi.RedirectStandardOutput = true; //vreau sa redirectez
StandardOutput pt a-l putea citi
psi.UseShellExecute = false; //trebuie setata false pt a putea
redirecta StandardOutput

p.StartInfo = psi;
p.Start();
while (!p.StandardOutput.EndOfStream)
{
Console.Write(Char.ConvertFromUtf32(p.StandardOutput.Read()));
//Console.WriteLine(p.StandardOutput.ReadLine()); //citeste
linie cu linie
//Console.WriteLine(p.StandardOutput.ReadToEnd()); //citeste tot
out-put-ul ca bloc - nu trebuie executat in block while.
}
Console.WriteLine();
Console.WriteLine("Exit code: {0}", p.ExitCode);

Console.ReadKey();
}
//.... cod
Argumente in linia de comanda

Marea majoritate a programelor pe care le folosim accepta la executie o serie de parametri. Asta
arata cam asa: numeProgram.exe [lista de parametri], unde [lista de parametri] reprezinta o
lista optionala de parametri transmisi programului numeProgram.exe.
Cei care au folosit sisteme de operare MS_DOS sau unix/linux stiu cel mai bine cat de util este
ca un program sa poata fi executat cu o lista de parametri si sa nu trebuiasca sa interactionam cu
el pe parcursul executiei.

Sa presupunem ca am un program care muta fisierele din directorul Images in directorul Pictures.
Cand programul intalneste un fisier care este ReadOnly sau Hidden ar trebui sa ceara
confirmarea (asa cum Windows Explorer face) pentru a muta fisierul. Sunt cazuri cand vreau sa
nu fiu intrebat. Vreau sa pot sa ii spun de la inceput: nu ma intreba, daca exista ceva ReadOnly,
muta fara sa intrebi, sau treci mai departe. Cum as vrea sa pot scrie asta? Cam asa: muta.exe -ro
yes -h no. Tradus, asta ar suna cam asa: daca fisierul care trebuyie mutat e ReadOnly (-ro), muta-
l. Daca fisierul e hidden (-h), nu il muta.

Un exemplu mai bun decat cel prezentat anterior este batranul notepad.exe. Cand vrem sa vedem
un fisier text, pornim Windows Explorer, gasim fisierul care ne intereseaza si cu un simplu dublu
click fisierul respectiv este deschis in notepad. Cum functioneaza asta? Simplu: windowsul
executa, in fundal, o comanda de genul: notepad.exe fullPath_fisier.txt, unde fullPath_fisier.txt
este parametru trimis aplicatiei notepad.

O alta operatie pe care o putem face prin intermediul programului notepad e sa tiparim un fisier
text. Pentru a face asta, ne folosim de un alt argument: notepad.exe /P fullPath_fisier.txt.

Scopul articolului de astazi e sa construiesc o aplicatie simpla, consola, care sa accepte o lista de
parametri. Voi construi o aplicatie consola pentru a simplifica exemplele oferite, insa orice
executabil (consola, fereastra) poate primi argumente.

Asa cum probabil stiti, executia unui program incepe in metoda Main. Aceasta metoda arata cam
asa:

static void Main(string[] args)


{
//... aplicatia porneste
}

Dupa cum se poate vedea, metoda Main are un argument: string[] args, care reprezinta exact
ceea ce utilizatorul va trimite ca parametru/i programului nostru. Array-ul va fi creat prin ruperea
string-ului trimis ca parametru acolo unde apar spatiile albe (space).
Pentru a verifica daca am primit sau nu parametri, este suficient sa facem urmatoarea verificare:

if(args.Length > 0)
{
//...avem ceva parametri
}

Hai sa creem un program simplu, un fel de hello world, care sa primeasca ceva parametri si sa si
faca ceva cu ei.

static void Main(string[] args)


{
string fName = "";
string lname ="";

if(args.Length > 0)
{
//...avem ceva parametri
for(int i=0; i<args.Length; i++)
{
if(args[i] == "-fn" && i + 1 < args.Length) //verificam daca
avem cava dupa -fn
{
fName = args[i+1];
}
if(args[i] == "-ln" && i + 1 < args.Length) //verificam daca
avem cava dupa -ln
{
lName = args[i+1];
}
}
}
else
{
Console.WriteLine("Lipsesc parametrii");
return;
}
Console.WriteLine("Salut {0} {1}", fName, lName);
}

In exemplul prezentat nu fac verificari in ceea ce priveste corectitudinea parametrilor. In viata


reala, in practica, verificarile trebuiesc facute. Pot exista situatii in care sunt folosite niste valori
prestabilite iar prin parametri primiti de la utilizatori doar modificam aceste valori. de exemplu,
in cazul exemplului dat la inceput, cu programul care muta fisiere dintr-un folder in altul.
Utilizatorul poate executa urmatoarea linie: muta.exe -ro yes -h no. Ceea ce ar trebui sa faca
parametri trimisi programului este sa modifici niste valori prestabilite. Probabil, intern,
programul muta.exe ar treebui sa aiba ceva de genul:

bool askForReadOnly = true;


bool askForHidden = true;

Daca userul nu specifica altceva in linia de comanda, atunci aceste valori ar trebui folosite.
Foarte multe aplicatii accepta parametri transmisi in linia de comanda, insa sunt si foarte mult
programatori, sau companii, care nu insclud optiunea de a transmite acest tip de parametri. Poate
data viitoare cand scrii o aplicatie te gandesti: ce ar fi daca in loc sa pot deschide un fisier word
cu dubluclick din windows explorer ar trebui sa deschid Word, sa apas Open, sa caut locul unde
e fisierul pe care vreau sa il deschis etc.

Am scris acest articol mai mult pentru a aminti programatorilor de aceasta posibilitate si mai
putin pentru a da exemple bune de folosire.

DEBUG

Una dintre cele mai importante facilitati pe care o ofera Visual Studio este cea de Debug. Ce
inseamna asta? A face Debug inseamna a analiza ce se intampla, la executie, in interiorul
aplicatiei asupra careia facem Debug. Aceasta operatie are ca scop gasirea si repararea erorilor
(bugs) dintr-o aplicatie. Aceasta operatie se face cu ajutorul uor instrumente speciale, numite
debuggers. Cu ajutorul acestor unelte se poate vedea la runtime ce se intampla in timpul
executiei aplicatiei asupra careia se face operatia de debug. In cazul Visual Studio uneltele sunt
incluse in pachetul Visual Studio, userul netrebuind sa instaleze separat nimic.

Exista, din cate stiu eu, doua metode de a porni o operatie de debug: prin apasarea butonului
Start Debuging din toolbar-ul Standard (sau comanda din meniul Debug) sau, metoda a doua,
prin atasarea de o aplicatie care ruleaza, folosind comanda Attach to Process, din acelasi meniu.
Vezi in imaginile urmatoare cele doua optiuni.

O captura de ecran pentru butonul debug:


si o captura de ecran pentru meniul Debug:

O sa explic cum se folosesc fiecare dintre aceste optiuni. O sa scriu un program scurt, caruia o sa
ii fac Debug. Acest program, simplu, care contine, deocamdata, numai metoda Main, este
urmatorul:

class Program
{
static void Main(string[] args)
{
Console.Write("Name: ");
string str = Console.ReadLine();
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i + ". Hello " + str);
}
}
}

Tot ce face acest program e sa citeasca de la tastatura un string si sa afiseze de 5 ori mesajul
Hello plus stringul citit de la tastatura, iar in fata fiecarui rand afisat apare si numarul itinetatiei.
La executie vedem ceva de genul:

Ady
0. Hello Ady
1. Hello Ady
2. Hello Ady
3. Hello Ady
4. Hello Ady

Pentru a Opri executia programului la un moment dat, avem nevoie de un BreakPoint. Un


BreakPoint este un punct in cod unde debuggerul opreste temporar executia programului si
permite examinarea starii lui in momentul respectiv. Pentru a stabili un astfel de punct (loc) ceea
ce trebuie facut e sa clickam in editor, in Visual Studio, in marginea stanga a ferestrei in care
editam codul, in dreptul liniei unde vrem sa stabilim acel BreakPoint. In programul exemplu eu
am stabilit 2 astfel de locuri:

Se observa ca linia unde executia programului va fi intrerupta este colorata diferit.


Apasam butonul Debug (sau tasta F5) pentru a porni operatia de Debug:

In imaginea anterioara se observa cum linia unde a setat primul breakPoint este evidentiata
diferit, cu galben. Asta inseamna ca programul este oprit in puctul respectiv. Pentru a continua, e
nevoie sa ii spunem Debugger-ului sa mearga mai departe – apasam tasta F5 pentru a continua
executia programului, tasta F10 pentru a continua executia prin executarea urmatoareil linii de
program (numai o linie) sau F11 pentru a continua executia prin salt in interiorul functiei care se
executa in linia in care am oprit – numai in cazul in care suntem intr-o linie in care se executa
cod definit intr-o alta metoda – nu e cazul actual.
Eu apas acum F5. Programul isi continua executia si eu trebuie sa introduc un nume. Scriu in
fereastra consola un nume: “Ady” si apas enter. Programul isi continua executia pana la
urmatorul breakPoint unde debugger-ul preia controlul. In fereastra codului, in momentul in care
sunt in Debug Mode am acces la memoria aplicatiei, in sensul ca pot vedea starea obiectelor care
imi compun aplicatia. Daca pozitionez cursorul mouse-ului deasupra numelui unui obiect, Visual
Studio imi va afisa valoarea respectivei valori asa cum este ea in momentul respectiv in
memorie. Pozitionand cursorul mouse-ului deasupra variabilel str, voi vedea valoarea ei,
respectiv numele introdus de mine in fereastra Consola:

Daca versiunea de Visual Studio este Profesional, valoarea variabilelor se poate medifica in
fereastra debug, la runtime. Insa despre asta intr-un alt articol. Continui executia programului, de
data asta linie cu linie, apasand tasta F10. Observ ca la fiecare apasare a tastei respective executia
avanseaza, iar linia care se executa in momentul curent este evidentiata. Acum urmaresc valorile
variabilei i in interioru ciclului for:

Continui asa, pana cand programul isi termina executia.

A doua metoda de a intra in modul debug este prin atasarea la un proces care ruleaza. Pornim
aplicatia (apasam Ctrl+ F5, adica start without debugging, sau executam din folderul
\HelloWorld\HelloWorld\bin\Debug\HelloWorld.exe aplicatia. In momentul in care aplicatia a
ajuns in puctul in care ne cere sa introducem numele, revenim la visual studio si din meniul
Debug apelam Attach to process:

In acest moment avem o fereastra care contine numele proceselor care sunt executate in
calculator:

Selectam procesul care are numele identic cu numele aplicatiei noastre si apasam butonul Attach.
Din acest moment procesul de debug continua ca si in primul caz, descris mai sus. Programul
opreste la ficare BreakPoint, poate fi executat linie cu linie, se pot modifica valorile variabilelor
(Pro) etc.

In momentul in care Visual studio este in mod Debug, click cu butonul din dreapta deschide un
meniu care are cateva comenzi importante, dintre care vreau sa evidentiez doua: Run To Cursor
si Set next Statement:

Ce sunt cele doua comenzi? Prima, Run To Cursor este utila in cazul in care vrem sa continuam
executia programului pana intr-un anumit punct, punct in care sa fie iarasi intrerupta executia.
Putem sa punem acolo un breakPoint nou, sau putem sa pozitionam cursorul in editor in locul
respectiv si sa apelam comanda Run To Cursor. A doua comanda este Set next Statement. Cu
ajutorul acesti comenzi se continua executia programului de la un punct dorit de programator –
inainte sau dupa punctul curent. La ce foloseste asta? Pentru a evita o eroare (care trebuie
corectata ulterior), pentru a relua executia unui bloc etc.

Acesta este un prim articol despre operatia de debug. Vor mai fi si altle.

Multumesc si astept comentarii, pareri si sugestii.