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 { public public public } 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] string string int Persoana nume; prenume; varsta;

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 (int)tipulEnum.ZileSaptamana.Miercuri); este a {0} zi din saptamana ",

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 dowhile 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 dowhile 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 dowhile este ca instructiunile din dowhile 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 tipulEnum.ZileSaptamana luni = (tipulEnum.ZileSaptamana)1;
Tipurile referinta in .Net

scrie:

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 listaNume.Length);

de

elemente

din

vector

este

{0}",

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 + * / % ++ Exemplu :
int numar = 5; int numar2 = 13; //Adunare Console.WriteLine(numar + numar2); //Scadere

Semnificatie Adunare ( si plus unar ) Scadere ( si minus unar ) Inmultire Impartire Impartire Modulo ( rest ) Incrementare Decrementare

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

< > <= >= Exemplu

mai mic mai mare mai mic sau egal mai mare sau egal

//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 access public internal protected private protected internal Explicatii access nelimitat acces permis doar in clasa sau spatiul de nume in care e cuprinsa acces in clasa curenta sau in cele derivate implicit.Doar pentru clasele interioare folosit pentru clasele interioare semnificand 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 public internal protected explicatie membrul accesibil de oriunde accesbil doar intr-un bloc functional al unei aplicatii .Net accesibil oricarui membru al clasei care-l contine si al claselor derivate implicit. acces permis doar pentru clasa care contine membrul accesibil oricarui membru al al clasei care il contine si al

private

protected internal

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 tip tip } 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(); }

nume nume-metoda

{ (param); nume-metoda(param);

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(); 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 StringReverseDelegate(test.StringReverse); //apelul unei metode prin intermediul delegarii str = strReverse("Test"); Console.WriteLine(str); = new //construim delegat

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. amnat: 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 > { return } else { sold = return } sold) false;

sold - suma; 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")) {

} else { }

Console.WriteLine("exista");

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 intrun 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 CreeazaDictionar, SalveazaDictionar, CitesteDictionar. ca metode NumarElemente,

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 lista.GetByIndex(1)); de la pozitia a doua: " +

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 nongenerice 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 Collection<T> Comparer<T> Dictionary<K,V> List<T> SortedDictionary<K,V> Stack<T> Queue<T> ReadOnlyCollection<T> NonGeneric CollectionBase Comparer Hashtable ArrayList SortedList Stack Queue 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[] PerformanceCounterCategory.GetCategories(); //parcurgem vectorul cu indicatori foreach (var ind in listaIndicatori) { Console.WriteLine(ind.CategoryName);

listaIndicatori

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 "Available MBytes"); indicatorRam = new PerformanceCounter("Memory",

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 screenshoturi 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, UTF8, 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[] { Person[] oameni = new new Person("Ion", new Person("Geo", new Person("Pop", new Person("Ady", new Person("Ron", new Person("Pan", };

args) Person[]{ "Popescu", 22), "Ionescu", 15), "Vasilescu", 32), "Georgescu", 28), "Niculescu", 18), "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 Geo Pop Ady Ron Pan Popescu - 22 Ionescu - 15 Vasilescu - 32 Georgescu - 28 Niculescu - 18 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. 2. 3. 4. DateTime DateTime DateTime DateTime d d d d = = = = DateTime.Now; DateTime.Today; DateTime.UtcNow; 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 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();

".

Random

date:

"

/// <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 DateTime.Now.AddDays(1).ToShortDateString()); } /// <summary> /// Afiseaza numele zilei pentru o data specifica /// </summary> /// <param name="d"></param> static void PrintDayName(DateTime d) {

de

ieri:

"

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. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24.

Random Random Random Random Random Random Random Random Random Random Random Random

date: date: date: date: date: date: date: date: date: date: date: date:

2/28/1938 12/5/1959 2/16/1970 1/18/1984 2/14/1900 5/14/1902 2/10/1928 8/2/1909 10/11/2003 4/6/1933 8/8/2001 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 2. 3. 4. 5. 6.

statica Now PrintYesterday afiseaza data de ieri, pentru a demontra folosirea metodei AddDays PrintTomorrow afiseaza data de maine PrintDayName afiseaza numele zilei (in engleza) al datei transmise ca argument GetRandomDate genereaza o data aleatoare 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 false</returns> public static bool IsInteger(this String str) { /// /// /// /// /// /// /// ///

integer,

else

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 false</returns> public static bool IsDouble(this String str) { try { double.Parse(str); return true; } catch { return false; } }

double,

else

/// <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 testString.CountNonEmptyChars()); Console.WriteLine("Numar testString.CountWords()); Console.WriteLine("Alternate case:"); Console.WriteLine(testString.AlternateCase(true)); Console.ReadKey(); } } de nevide: cuvinte: {0}", {0}",

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 { Process p.StartInfo 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

void p = = new

Main(string[]

args)

new Process(); ProcessStartInfo(http://google.com/);

static void Main(string[] args) { Process p = new Process(); p.StartInfo = new ProcessStartInfo("notepad.exe"); p.Start(); Thread.Sleep(2000); p.Kill(); } //.... cod //asteapta 5 secunde

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), mutal. 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) avem cava dupa -fn { fName = args[i+1]; } if(args[i] == "-ln" && i + 1 < args.Length) avem cava dupa -ln { lName = args[i+1]; } } } else { Console.WriteLine("Lipsesc parametrii"); return; } Console.WriteLine("Salut {0} {1}", fName, lName); }

//verificam daca

//verificam daca

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

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

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.