c sharp curs

31
Catedra Automatică și Tehnologii Informaționale Disciplina MIDPS l.u. Boleac Ruslan Introducere in .Net si C# Structura unei aplicatii C#. O aplicatie C# (.Net in general) este formata din una sau mai multe clase, grupate in spatii de nume (namespaces)*. Este obligatoriu ca una din aceste clase (si numai una) sa contina un "punct de intrare" (entry point), si anume metoda Main**. O parte din clasele folosite de aplicatie sunt deja definite. Folosirea acestora se face fie "importand" spatiile de nume care contin definitia acestora, fie apeland clasa folosind numele complet. Prima metoda este folosita atunci cand clasele dintr-un namespace sunt folosite de mai multe ori. 1 2 3 4 5 6 7 8 9 10 //"Importarea" spatiilor de nume ce vor fi folosite. using System; //Definirea unui nou namespace namespace FirstApplication { //Definirea unei clase public class FirstClass { //Definitiile si declaratiile variabilelor si metodelor

Transcript of c sharp curs

Page 1: c sharp curs

Catedra Automatică și Tehnologii

Informaționale

Disciplina MIDPS

l.u. Boleac Ruslan

Introducere in .Net si C#

Structura unei aplicatii C#.

O aplicatie C# (.Net in general) este formata din una sau mai multe clase, grupate in spatii de

nume (namespaces)*. Este obligatoriu ca una din aceste clase (si numai una) sa contina un

"punct de intrare" (entry point), si anume metoda Main**.

O parte din clasele folosite de aplicatie sunt deja definite. Folosirea acestora se face fie

"importand" spatiile de nume care contin definitia acestora, fie apeland clasa folosind numele

complet. Prima metoda este folosita atunci cand clasele dintr-un namespace sunt folosite de mai

multe ori.

1

2

3

4

5

6

7

8

9

10

//"Importarea" spatiilor de nume ce vor fi folosite.

using System;

//Definirea unui nou namespace

namespace FirstApplication

{

//Definirea unei clase

public class FirstClass

{

//Definitiile si declaratiile variabilelor si metodelor

Page 2: c sharp curs

11

12

13

14

...

//Metoda Main

}

}

Fig. 1: Structura unei aplicatii foarte simple

*Nu exista restrictii la alegea numelor pentru clase si fisiere (cum exista in Java), fisierele putand

avea orice nume si contine definitia a mai mult de o singura clasa. Singura restrictie este sa nu

existe ambiguitati (sa nu existe doua clase cu acelasi nume in acelasi spatiu de nume, etc.)

**De fapt punctul de intrare intr-o aplicatie poate fi modificat (sa fie diferit de metoda Main);

acest lucru poate fi facut manipuland direct codul MSIL.

Metoda Main

Metoda Main este punctul de intrare in aplicatie - este functia care se executa atunci cand

aplicatia este pornita. La fel ca si in C++, metoda Main poate avea mai multe signaturi, fiecare

dintre acestea fiind corecta.

La fel ca orice alta metoda C# (.Net) Main nu poate fi globala. Ea trebuie definita ca fiind

membru intr-o clasa.

1

2

3

4

5

6

7

8

9

10

11

public class FirstClass

{

...

static void Main() {...}

static int Main() {...}

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

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

...

}

Fig. 2: Singnaturile metodei Main

Se observa ca metoda Main trebuie sa fie statica (Puteti explica de ce?). De asemenea poate

intoarce o valoare intreaga. Acea valoare reprezinta rezultatul executiei programului (la fel ca in

C++). Prin conventie, daca un program intoarce valoarea 0 inseamna ca s-a executat cu succes. O

valoare diferita de 0 inseamna eroare, si codurile de eroare sunt definite de programator. (La fel

ca si in C++ aceasta este doar o conventie si nu un standard.)

Intr-o aplicatie nu poate exista mai mult de un singur punct de intrare (mai multe definiri ale

metodei Main, fie si in clase diferite). Definirea in mai multe locuri va genera erori la compilare.

Daca Main este definita cu parametru (string[] args), atunci aplicatia poate accepta parametri.

Acestia vor fi trimisi functiei in parametrul args.

Page 3: c sharp curs

Spatii de nume

Toate clasele, interfetele, etc. existente in .Net Framework formeaza o ierarhie. La baza acestei

ierarhii sta spatiul de nume System. Aici sunt unele clase des folosite (ex: clasa Console pentru

lucrul cu tastatura si afisarea la ecran.).

Alte namespace-uri importante sunt:

System.IO - pentru lucrul cu dispozitivele input/output (ex: accesul la fisiere)

System.Collections - pentru lucrul cu colectii de obiecte de acelasi tip (liste, stive,

etc.)

System.Xml - pentru manipularea datelor in format xml

System.Windows.Forms - pentru lucrul cu forme windows (controale windows)

Prima aplicatie

Fie urmatoarea aplicatie C#:

1

2

3

4

5

6

7

8

9

10

11

12

using System;

namespace Laborator1

{

public class HelloWorld

{

static void Main ()

{

Console.Write ("Hello World!");

}

}

}

Fig. 3: Aplicatia "Hello World"

In programul anterior am definit o clasa HelloWorld (pe care am pus-o in spatiul de nume

Laborator1) care contine o singura metoda: Main. Functia Main apeleaza, la randul sau, functia

Write din clasa Console. Clasa Console este definita in spatiul de nume System si contine

functii pentru lucrul cu iesirea/intrarea standard (consola). O parte dintre aceste medode sunt

statice (Puteti da un exemplu? ) astfel nefiind necesara declararea unui obiect de tip Console.

In cazul in care ar fi lipsit linia 1 din program ("importarea" unui namespace) ar fi trebuit sa

apelam functia Write folosind numele ei complet (si anume: System.Console.Write ("Hello

World!"))

MSIL - Microsoft Intermediate Language

Unul din marile avantaje ale platformei .Net este interoperabilitatea interlimbaj. Acest lucru a

fost incercat si anterior dar cu un succes limitat. Un exemplu in acest sens il reprezinta COM-ul.

Page 4: c sharp curs

In COM este posibil sa creezi o componenta intr-un limbaj (de exemplu Visual Basic) si sa o

folosesti in alt limbaj (de exemplu C++). Totusi COM-ul nu permite mostenirea unei clase

definita intr-un limbaj in alt limbaj. .Net framework permite acest lucru.

Pentru a intelege cum este posibila interoperabilitatea interlimbaj trebuie urmarit ce se intampla

atunci cand se compileaza o aplicatie. La compilare nu este creat un fisier binar care poate apoi fi

executat, ci un fisier (sau mai multe) care contine (printre altele) cod MSIL. Acest cod MSIL

seamana destul de mult cu assembler-ul (dar nu este assembler). Fisierul obtinut dupa

compilare are tot extensia exe, la fel ca orice binar normal.

Cand aplicatia este rulata, codul MSIL este incarcat si executat de CLR (Common Language

Runtime). Pentru a intelege mai bine se poate spune ca CLR-ul este un fel de Masina Virtuala

Java (cu observatia ca CLR ofera mult mai multe servicii codului executat decat JVM. Desi se

poate face o paralela intre cele doua medii de executie acestea nu trebuiesc confundate sau

considerate echivalente.)

Fisierul obtinut in urma compilarii unui program .Net este un assembly. Un assembly contine, pe

langa cod MSIL si metadata si resurse. In metadata sunt incluse toate informatiile despre tipurile

din assembly (signaturile tuturor metodelor, claselor, etc.) Pentru ca in orice moment signatura

unei clase este cunoscuta (indiferent daca avem codul sursa sau un assembly care contine clasa)

aceasta poate fi extinsa prin mostenire din orice limbaj. Datorita metadatei nu mai este nevoie de

bibliotecile de tipuri, folosite foarte mult in COM, pentru ca metada actioneaza ca o biblioteca de

tipuri. Cu alte cuvinte crearea componentelor in .Net este mult mai simpla.

Pentru a vedea cum arata codul MSIL putem face o decompilare a aplicatiei anterioare folosind

utilitarul ildasm.exe (care se instaleaza o data cu .Net Framework)

1

2

3

4

5

6

7

8

9

1

0

1

1

1

2

1

3

1

4

1

5

1

6

.class public beforefieldinit HelloWorld extends object

{

.method public hidebysig specialname rtspecialname instance void

.ctor() cil managed

{

// Code Size: 7 byte(s)

.maxstack 1

L_0000: ldarg.0

L_0001: call instance void object::.ctor()

L_0006: ret

}

.method private hidebysig static void Main() cil managed

{

.entrypoint

// Code Size: 11 byte(s)

.maxstack 1

L_0000: ldstr "Hello World!"

L_0005: call void [mscorlib]System.Console::Write(string)

L_000a: ret

}

}

Page 5: c sharp curs

1

7

1

8

1

9

2

0

2

1

Fig. 4: Codul MSIL pentru aplicatia HelloWorld.exe

La o privire sumara asupra codului de mai sus se remarca doua lucruri:

1. Clasa HelloWorld este derivata din clasa Object (la linia 1 avem extends object imediat

dupa declararea clasei.). In .Net toate tipurile mostenesc clasa Object. Aceasta derivare

nu trebuie facuta in mod explicit de programator, compilatorul adaugand automat codul

necesar.

2. Clasa HelloWorld contine doua metode, desi in codul C# am definit numai una.

Compilatorul a adaugat si un constructor (constructorul implicit), identificat cu numele

.ctor (linia 3). Trebuie amintit ca toti constructorii de instanta sunt identificati in codul

MSIL cu numele .ctor, In cazul de fata constructorul nu face nimic altceva decat sa

apeleze constructorul clasei de baza (linia 8).

Manipularea parametrilor aplicatiei

Aplicatiile .Net pot primi parametri de la tastatura. Asa cum se arata in figura 2, functia Main are

2 signaturi cu parametru (liniile 7 si 8). Acel parametru este un vector de string-uri si va contine

parametrii aplicatiei care pot fi accesati folosind indici: args[0], args[1], etc.

string[] este tip si, in consecinta, poate avea metode si date membru. Lungimea vectorului args

poate fi obtinuta folosind astfel de date membru. args.Length va contine lungimea vectorului

args.

Tipuri de date fundamentale in .Net

In .Net tipurile se impart in doua categorii, si anume tipuri valoare si tipuri referinta. Cele mai

importante doua tipuri referinta sunt object si string. Acestea sunt cele mai importante pentru ca

sunt folosite intens in orice aplicatie. Asta nu inseamna ca celelalte nu sunt importante.

Atunci cand un obiect este de tipul referinta, pe stiva nu este pusa valoarea acelui obiect, ci o

referinta la el. Obiectul va avea memorie alocata in heap si pe stiva este pusa o referinta catre

zona de memorie ocupata in heap. In consecinta referinta este un pointer type-safe - adica avem

siguranta ca la adresa de memorie de pe stiva avem un obiect de tipul corect (sau valoarea null).

Urmatoarele tipuri sunt tipuri referinta: clasele, array-urile, interfetele si tipurile delegat.

Page 6: c sharp curs

Pe de alta parte o variabila de tip valoare va contine pe stiva direct valoarea, si nu o referinta.

Exista mai multe tipuri valoare predefinite in .Net printre care se numara structurile (struct),

enumeratile (enum) si tipurile primitive* (int, byte, short, long, double, etc.)

Principala diferenta intre class si struct este ca primul e tip referinta si cel de-al doilea e tip

valoare. Din acest motiv nu erste recomandat sa se creeze structuri care au o dimensiune mare.

*Tipurile primitive sunt toate niste structuri, numele mai sus mentionate fiind niste alias-uri.

Astfel int este un alias pentru System.Int32, short pentru System.Int16, byte pentru

System.Int8, long pentru System.Int64, etc. Folosirea numelui complet al tipului sau al alias-

ului nu produce decat o diferenta sintactica, codul MSIL aratand la fel.

Tip CTS C# Alias Descriere

System.Object object Tipul de baza pentru toate tipurile CTS.

System.String string String

System.Sbyte Sbyte Valoare intreaga pe 8 biti (cu semn).

System.Byte byte Valoare intreaga pe 8 biti (fara semn).

System.Int16 short Valoare intreaga pe 16 biti (cu semn).

System.UInt16 ushort Valoare intreaga pe 16 biti (fara semn).

System.Int32 int Valoare intreaga pe 32 biti (cu semn).

System.UInt32 uint Valoare intreaga pe 32 biti (fara semn).

System.Int64 long Valoare intreaga pe 64 biti (cu semn).

System.UInt64 ulong Valoare intreaga pe 64 biti (fara semn).

System.Char char Caracter unicode (pe 16 biti).

System.Single float Numar in virgula mobila pe 32 biti, conform

IEEE.

System.Double double Numar in virgula mobila pe 64 biti, conform

IEEE.

System.Boolean bool Valoare booleana (true/false)

System.Decimal decimal

Valoare pe 128 de biti care retine 28 de

zecimale semnificative, folosit in calcule unde

este necesara o precizie mare (ex: calcule

finaciare)

Page 7: c sharp curs

C#. Tipuri de date. Instructiuni de control.

Colectii

Colectii - clasele Array si ArrayList

Clasa Array, definita in spatiul de nume System, este clasa de baza pentru vectori in .Net. Desi

este o clasa abstracta, furnizeaza o metoda CreateInstance pentru crearea de vectori. De

asemenea furnizeaza metode pentru manipulare, cautare si sortare.

Proprietate Descriere

IsFixedSize Intoarce o valoare booleana care indica daca dimensiunea

vectorului poate fi modificata.

IsReadOnly Intoarce o valuare booleana care indica daca vectorul e read-

only sau nu.

IsSyncronized Indica daca vectorul e thread-safe.

Length Numarul total de elemente din vector (numarand toate

dimensiunile).

Rank Intoarce numarul de dimensiuni ale vectorului.

SyncRoot Intoarce un obiect folosit pentru a sincroniza accesul la

elementele vectorului.

Fig. 1: Proprietatile clasei Array

Moduri de creare a unui array:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

Declararea unui vector de intregi.

int[] intregi;

//Initializarea vecatorului

intregi = new int[3] {1, 2, 3};

//Creeaza un vector cu doua dimensiuni.

Array nume = Array.CreateInstance( typeof(string), 2, 4 );

//Alta metode a crea un vector cu doua dimensiuni, si initializare

//Un vector declarat astfel se numeste vector rectangular

string[,] alteNume = { { "Ion", "Vasile"} , {"Popescu", "Ionescu"} };

string[,] adrese = new string[2, 3];

//Jagged Arrays - elementele unui astfel de array sunt array-uri

int[][] altiIntregi = new int[2][];

altiIntregi[0] = new int[3];

altiIntregi[1] = new int[10];

Fig. 2: Moduri de decrare si initializare a vectorilor

Page 8: c sharp curs

.Net Framework pune la dispozitia programatorilor mai multe clase folosite pentru manipularea

colectiilor de obiecte de acelasi tip*. Acestea sunt definite in spatiul de nume

System.Collection.

ArrayList este clasa cel mai des folosita atunci cand se lucreaza cu liste de obiecte. ArrayList

lucreaza intern cu un Array de object. Pe masura ce obiecte noi sunt adaugate in lista, array-ul

este redimensionat. Redimensionarea este facuta doar atunci cand este necesar. In acest mod este

furnizat un mecanism de acces rapid la elemente.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

//Creem doua obiecte de tipul ArrayList

ArrayList nume = new ArrayList();

ArrayList prenume = new ArrayList();

//Adaugam valori in aceste liste

nume.Add ("Ionescu");

nume.Add ("Popescu");

prenume.Add ("Ion");

prenume.Add ("Vasile");

prenume.Add ("Cristi");

//Afisam la consola continutul listelor

for (int i = 0; i < prenume.Count; i++)

Console.WriteLine (prenume[i]);

//O metoda mai eleganta de parcurgere a listelor

foreach (string str in nume)

Console.WriteLine (str);

Fig. 3: ArrayList in actiune.

La linia 13 din figura 3 am folosit proprietatea Count pentru a prelua numarul de elemente din

lista. Aceasta nu trebuie confundata cu proprietatea Size care intoarce dimensiunea array-ului

folosit intern de ArrayList. Size reprezinta numarul de elemente care pot fi retinute fara a se

mai face o redimensionare a array-ului (Obs: aceasta redimensionare este transparenta pentru

programator, facandu-se automat cand se adauga un nou element si nu mai este loc).

La linia 17 am folosit o noua metoda de acces a elementelor unei liste, si anume instructiunea

foreach. Instructiunea foreach poate fi folosita doar asupra tipurilor care implementeaza interfata

IEnumerable. Aceasta interfata este implementata de toate colectiile din .Net precum si de clasa

Array.

Desi in exemplul anterior am lucrat doar cu stringuri, intr-un ArrayList pot fi adaugate

elemente de orice tip (pentru ca toate sunt de tipul object).

*Pentru a face colectiile cat mai generale, acestea vor lucra intern cu tipul object. Cum toate

tipurile sunt derivate din tipul object colectiile pot lucra cu orice tip definit de programator.

Acest lucru are atat avantaje cat si dezavantaje.

Accesori

Page 9: c sharp curs

Accesorii sunt modalitati de acces la variabilele membru ale unui obiect intr-un mod sigur.

Accesorii mai sunt numiti si proprietati ale unei clase (a se vedea figura 1 cu proprietatile clasei

Array).

Sintaxa pentru definirea unei proprietati este urmatoarea:

1

2

3

4

5

6

7

8

9

1

0

1

1

1

2

1

3

1

4

1

5

1

6

1

7

1

8

1

9

2

0

2

1

2

2

2

3

2

4

2

5

2

6

2

7

2

8

2

9

public class Student

{

string m_nume;

string m_prenume;

int m_nota;

//getter si setter

public string Nume

{

get { return m_nume; }

set { m_nume = value; }

}

//setter

public string Prenume

{

set { m_prenume = value; }

}

//getter

public string NumeComplet

{

get { return m_nume + " " + m_prenume; }

}

public static void Main ()

{

Student student = new Student();

//setam numele si penumele studentului

student.Nume = "Popescu";

student.Prenume = "Vasile";

//Afisam numele studentului pe ecran

Console.WriteLine (student.NumeComplet); //Pe ecran se va

afisa: Popescu Vasile

}

}

Page 10: c sharp curs

3

0

3

1

3

2

3

3

3

4

3

5

3

6

Fig. 4: Accesori

La definire accesorii au sintaxa asemanatoare cu a unei metode, si la apel se foloseste sintaxa

unei variabile membru.

Accesorii simplifica foarte mult sintaxa pentru obtinerea unor valori ale datelor membru sau

setarea lor, si nu numai. Daca pentru a pune o anumita variabila membru pe o valoare folosim

proprietati, putem face niste verificari inainte. De exemplu la setarea numelui unui student putem

testa daca numele nu contine caractere interzise (cum ar fi @). Asemenea teste erau posibile si in

alte limbaje, dar trebuie construite functii speciale atat pentru setarea valorii (ex: SetName.), iar

pentru obtinerea valorii trebuia implementata alta functie (ex: GetName). Introducerea accesorilor

a simplificat mult sintaxa.

Proprietatile pot fi numai read-only (este definit numai getter-ul - cum este NumeComplet), write-

only (este definit numai setter-ul - cum este Prenume) sau atat read cat si write (cand este definit

atat getter-ul cat si setter-ul - cum este Nume). In exemplul de mai sus, daca incercam sa obtinem

doar valoarea pentru prenume (ex: string prenumeStudent = student.Prenume;) vom

obtine o eroare la compilare.

Pentru a seta valoarea, accesorii au un parametru spcial: value (liniile 11 si 17). Acesta contne

valoarea care urmeaza sa fie setata (e un fel de parametru al unei functii, doar ca nu e declarat

explicit). Acesta are ca tip tipul proprietatii (string in cazul de mai sus).

Indexatori

Indexatorii sunt o alta modalitate de a usura scrierea unui program. Indexatorii permit accesul la

un obiect folosind indici, la fel ca in cazul vectorilor.

Programului de mai sus i-am putea adauga urmatorul indexator:

1

2

3

public class Student

{

...

Page 11: c sharp curs

4

5

6

7

8

9

1

0

1

1

1

2

1

3

1

4

1

5

1

6

1

7

1

8

1

9

2

0

2

1

2

2

2

3

2

4

2

5

2

6

2

7

2

8

2

9

3

0

3

1

3

2

3

3

3

4

//Definirea indexatorului

public string this[string camp]

{

get

{

if (camp == "nume")

return m_nume;

else if (camp == "prenume")

return m_prenume;

else

return null;

}

set

{

if (camp == "nume")

m_nume = value;

else

//fa ceva

}

}

...

public static void Main ()

{

...

Console.WriteLine (student["nume"]);//Pe ecran se va afisa:

Popescu

student["nume"] = "Ionescu";//Am schimbat numele

studentului :)

}

}

Page 12: c sharp curs

Fig. 5: Indexatori

Implementarea indexatorului este similara cu cea a unei proprietati, putand avea numai setter,

getter sau pe amandaua.

Indexatorul din figura 5 primeste un parametru de tip string (intre parantezele patrate - la linia 6).

Nu este obligatoriu ca acel parametru sa fie de tipul string, el putand avea orice alt tip, ramanand

in sarcina programatorului utilizarea lui corecta. (In figura 3 la linia 14 am folosit un indexator -

implementat de ArrayList - care primea ca parametru in paranteza patrata o valoare de tipul int)

Instructiuni de control

if

for

foreach

while

switch

goto

return

Operatori C#

Categorie Operatori Asociativitate

Primari (x), x.y, f(x), a[x], x++, x--, new,

typeof, sizeof, checked, unchecked

Unari +, -, !, ~, ++x, --x, (T)x

Operatori de multiplicitate *, /, % stanga

Operatori de adunare +, - stanga

Shiftare <<, >> stanga

Relationali <, >, <=, >=, is stanga

Egalitate == stanga

SI logic & stanga

XOR logic (sau exclusiv) ^ stanga

SAU logic | stanga

SI conditional && stanga

SAU conditional || stanga

Operator conditional ?: dreapta

Asignare =, *=, /=, %=, +=, -=, <<=, >>=, &=,

^=, ?= dreapta

Fig. 6: Operatorii C#

Page 13: c sharp curs

In tabelul de mai sus sunt prezentati operatorii C#, precedenta fiind de sus in jos (operatorii cu

precedenta cea mai mare sunt cei primari, si operatorii cu precedenta cea mai mica sunt

operatorii de asignare).

String-uri

Clasa System.String (sau string) reprezinta un sir de caractere imutabil - adica valoarea unui

string nu poate fi modificata dupa creare. Metodele care par sa modifice un string creaza de fapt

un alt sir de caratere care contine modificarile si nu-l modifica pe primul. In afara de clasa strin

.Net Framework ofera si alete clase pentru lucrul cu siruri de caractere: StringBuilder,

StringFormat, StringCollection etc. care ofera metode pentru comparare, inserare, indexare,

cautare, joining, splitting, inlocuire, copiere, formatare etc. a sirurilor de caractere.

Figura urmatoare prezinta formatatorii pentru stringuri folosind exemple:

1

2

3

4

5

6

7

8

9

1

0

1

1

1

2

1

3

1

4

1

5

1

6

1

7

1

8

1

9

2

0

2

1

2

2

string.Format (123);//123

string.Format ("{0}", 123);//123

string.Format ("{0:D3}", 123);//123

string.Format ("{0,5} {1,5}", 123, 456);//Aliniere la dreapta: " 123

456"

string.Format ("{0,-5} {1,-5}", 123, 456);//Aliniere la stanga: "123 456

"

string.Format ("{0,-10:D6} {1,-10:D6}", 123, 456);//"000123 0000456

"

string.Format ("{0:C}", 123456);//$123,456.00

string.Format ("{0:C5}", 123456);//$123,456.00000

string.Format ("{0:D}", 123456);//123456

string.Format ("{0:D5}", 123456);//123456

string.Format ("{0:E}", 123456);//1.234560E+005

string.Format ("{0:E5}", 123456);//1.23456E+005

string.Format ("{0:F}", 123456);//123456.00

string.Format ("{0:F5}", 123456);//123456.00000

string.Format ("{0:G}", 123456);//123456

string.Format ("{0:G5}", 123456);//1.23456E5

string.Format ("{0:N}", 123456);//123,456.00

string.Format ("{0:N5}", 123456);//123,456.00000

string.Format ("{0:P}", 123456);//12,345,600.00 %

string.Format ("{0:P5}", 123456);//12,345,600.00000 %

string.Format ("{0:X}", 123456);//1E240

string.Format ("{0:X5}", 123456);//1E240

Fig. 7: Formatarea stringurilor.

Page 14: c sharp curs

1. Implementati interfata IList din spatiul de nume System.Collections. Implementarea

se va face in fiecare din urmatoarele doua clase:

SimpleList - lista de stringuri, in sensul obisnuit, in care adaugarea unui element

se face la sfarsitul listei.

SortedList - lista va contine stringuri sortate alfabetic, in care adaugarea unui

element se face la pozitia corespunzatoare ordinii alfabetice.

Se cere implementarea tuturor metodelor interfetei IList, pentru ambele clase, cu

functionalitatea specifica fiecareia.

Proiectati o functie Main cu urmatorul schelet:

se declara o variabila de tip IList

utilizatorul opteaza pentru unul din cele doua tipuri de liste

in functie de optiune, se face instantierea variabilei de tipul IList declarate mai

sus

se adauga 10 stringuri in lista

se apeleaza celelalte functii implementate.

se afiseaza lista folosind iteratori.

Observatie: interfata IList mosteneste interfata IEnumerable (care permite iterarea

asupra elementelor din lista)!

Windows Forms

Exceptii

Unul din scopurile principale ale CLR este acela de a elimina erorile furnizand mecanisme

pentru managementul automat al memoriei si resurselor precum si prin prinderea erorilor la

compilare, dat fiind faptul ca limbajele .Net sunt puternic tipizate (un tip nu poate minti

"spunand" ca este altceva decat ceea ce este). Pornind cu aceste concepte erori de genul buffer

underrun sau buffer overrun, folosirea unui obiect dupa ce memoria ocupata de el a fost

dealocata nu mai sunt posibile. Totusi unele erori (care tin de logica programului) nu pot fi

tratate decat la rulare (la runtime): erori de genul impartire la zero, anumite fisiere solicitate de

aplicatie nu exista, etc. Pentru tratarea acestor erori sunt folosite exceptii.

Pentru tratarea corecta a exceptiilor trebuie facuta diferenta intre o exceptie si un eveniment

asteptat. De exemplu daca este citit continutul unui fisier si este gasit sfarsitul fisierului (adica nu

se mai poate citi mai departe din acel fisier) am intalnit un eveniment asteptat si nu o exceptie

Page 15: c sharp curs

(faptul ca nu mai putem citi nu este o exceptie.). In cazul in care in timpul citirii din fisier

sistemul de operare raporteaza ca nu mai poate citi pentru ca exista o eroare pe disc am intalnit o

exceptie (nu ne asteptam sa nu mai putem citi din cauza unei erori pe disc).

Notiunii de exceptie trebuie sa-i asociem si un context. Si anume daca incercam sa ne conectam

la alt computer din retea si nu reusim (adica primim o exceptie) contextul acestei exceptii este

motivul pentru care nu am reusit conexiunea: username/password nu erau corecte, computerul

respectiv nu exista in retea, computerul exista in retea dar nu accepta conexiuni la programul

nostru, etc. Avand aceste informatii suplimentare (adica contextul exceptiei) putem sa gasim o

solutie pentru problema (ex: daca username/password nu sunt corecte putem sa cerem

utilizatorului sa le mai introduca o data).

Folosirea exceptiilor in C# are la baza 4 cuvinte cheie: throw, try, catch si finally. Atunci cand

executia unei metode nu mai poate continua din cauza unei situatii exceptionale aceasta va

"arunca" o exceptie. In exemplul urmator este prezentata o functie care verifica daca executia

programului poate continua. In cazul in care gaseste o eroare grava o exceptie de tipul

BigErrorException este aruncata (linia 4).

1

2

3

4

5

6

7

public void CheckForErrors ()

{

if (bigError)

throw new BigErrorException();

else

ContinueExecution();

}

Fig. 1: O functie care arunca o exceptie.

Prinderea unei astfel de exceptii este facuta intr-o instructiune de tipul try...catch... finally.

1

2

3

4

5

6

7

8

9

1

0

1

1

1

2

1

3

1

4

1

5

public void ExecuteProgram ()

{

try

{

//do something

CheckForErrors();

Console.WriteLine ("Everything looks fine. No errors ... yet!");

//do something else

}

catch (BigErrorException ex)

{

Console.WriteLine ("A big error occured. Panic [y/n]?");

//Do something to solve the error, if possible.

//All the necessary information about the error should be in the

'ex' object.

}

catch (Exception ex)

{

Console.WriteLine ("Unknown error!");

//do something here

return;

Page 16: c sharp curs

1

6

1

7

1

8

1

9

2

0

2

1

2

2

2

3

2

4

2

5

2

6

}

finally

{

Console.WriteLine ("Done! With or without errors.");

}

}

Fig. 2: Prinderea unei exceptii.

Existenta blocului finally este optionala. In cazul in care acesta exista se va executa indiferent

daca o exceptie a aparut sa nu. In cazul in care apare o exceptie CLR-ul verifica tipul acelei

exceptii pentru a selecta care bucata de cod se va ocupa de tratarea ei. Daca exceptia este de tipul

BigErrorException codul de la liniile 11-15 se va ocupa de tratarea ei. Daca exceptia aparuta e

de un alt tip va fi tratata de codul de la liniile 17-21.

In .Net toate exceptiile sunt derivate din clasa Exception. Din acest motiv instructiunea catch de

la linia 16 va prinde toate exceptiile neprinse pana in acel moment. (Ordinea in care declarati

blocurile te tip catch este importanta!).

La linia 20 se remarca o instructiune return. Executia acestei instructiuni nu inseamna iesirea

imediata din functie. Iesirea din functie va avea loc doar dupa executia blocului finally. Tot ca o

observatie relativ la iesirea dintr-o functie, instructiunea return este ilegala intr-un bloc de tip

finally.

Ce se intampla in cazul in care nu exista nici o instructiune pentru prinderea unei exceptii intr-un

cod care poate genera exceptii?

Cum poate fi rescrisa linia 16 din figura 2 pentru a prinde toate exceptiile fara a ne mai folosi de

tipul Exception?

Exceptii customizate

In .Net programatorul are posibilitatea sa-si creeze propriile exceptii, specifice aplicatiei pe care

o face. Pentru a crea propria exceptie tot ce trebuie sa faca un programator este sa creeze o clasa

Page 17: c sharp curs

derivata din Exception sau ApplicationException. Este recomandat ca un programator sa-si

deriveze propriile exceptii din ApplicationException, facand-use astfel distinctie intre

exceptiile customizate si cele definite in BCL (Base Class Library).

Delegati si evenimente

O alta mare noutate existenta in CTS (Common Type System) o reprezinta tipurile numite

generic delegates. Din punctul de vedere al cuiva care foloseste delegati, acestia sunt tipuri

referinta care incapsuleaza o metoda cu o anumita signatura. (Atentie: un delegat nu este o

functie, ci un tip referinta!!!). In .Net delegatii sunt folositi pentru a furniza functionalitatea de

callback precum si tratarea in mod asincron a evenimentelor.

Folosita in mod intensiv in programarea windows, functionalitatea de callback permite trimiterea

unui pointer la o functie altei functii (ca parametru). De exemplu functia EnumWindows (aflata in

API-ul Win32), enumera toate ferestrele deschise si pentru fiecare fereastra apeleaza o functie pe

care a primit-o ca paramatru.

Figura urmatoare prezinta modul de declarare si folosire a delegatilor.

1

2

3

4

5

6

7

8

9

1

0

1

1

1

2

1

3

1

4

1

5

1

6

1

7

1

8

1

9

2

0

2

public delegate void WorkDoneDelegate (string name);

public class Supervisor

{

private string m_name;

public Supervisor (string name)

{

m_name = name;

}

public void EmployeeWorkDone (string name)

{

Console.WriteLine ("{0}: {1} has finished his work.", m_name,

name);

}

}

public class Employee

{

public WorkDoneDelegate workDone;

private string m_name;

public Employee (string name)

{

m_name = name;

}

public void Work()

{

//do work.

if (workDone != null)

workDone(m_name);

}

}

public class TestApplication

{

Page 18: c sharp curs

1

2

2

2

3

2

4

2

5

2

6

2

7

2

8

2

9

3

0

3

1

3

2

3

3

3

4

3

5

3

6

3

7

3

8

3

9

static void Main ()

{

Supervisor boss = new Supervisor ("Boss");

Employee worker = new Employee ("John");

worker.workDone = new WorkDoneDelegate (boss.EmployeeWorkDone);

worker.Work();

}

}

Fig. 3: Declararea si folosirea delegatilor.

La linia 1 din figura anterioara am declarat tipul delegat. Desi pare ca am dclarat o functie

WorkDoneDelegate este o clasa derivata din clasa System.MulticastDelegate. Compilatorul

C# adauga codul necesar pentru aceasta, sintaxa de mai sus fiind doar syntactic sugar. De fapt,

acest tip va contine o metoda care are signatura de la linia 1.

Tipul Employee contine ca data membru o instanta de tipul WorkDoneDelegate. Inainte de

terminarea executiei funtiei Work este apelat acest delegat (in cazul in nu care exista nimeni

interesat ca acest worker si-a terminat treaba - adica testul de la linia 26 intoarce false - nu se

face nici un apel - nu exista listeneri).

In exemplul anterior totul pare OK. Cand "John" isi va termina treaba seful va fi instiintat. Dar ce

se inampla in cazul in care si presedintele vrea sa fie instiintat de acet lucru? Adaugand

instructiunea worker.workDone = new WorkDoneDelegate (president.EmployeeWorkDone)

Page 19: c sharp curs

nu se va obtine efectul dorit pentru ca este suprascris listenerul sefului, si seful nu va mai fi

instiintat.

Solutia este ca tipul Employee sa declasenze un eveniment atunci cand isi termina treaba.

1

2

3

4

5

6

7

8

9

1

0

1

1

1

2

1

3

1

4

1

5

1

6

1

7

1

8

public class Employee

{

...

public event WorkDoneDelegate workDone;

...

}

public class TestApplication

{

static void Main ()

{

Supervisor boss = new Supervisor ("Boss");

Supervisor president = new Supervisor ("President");

Employee worker = new Employee ("John");

worker.workDone += new WorkDoneDelegate (boss.EmployeeWorkDone);

worker.workDone += new WorkDoneDelegate

(president.EmployeeWorkDone);

worker.Work();

}

}

Fig. 4: Evenimente.

Se observa ca acum dupa ce workDone a devenit eveniment adaugarea listenerilor nu se mai face

folosind operatorul = ci operatorul +=. Asfel oricine doreste sa asculte la acel eveniment isi poate

adauga propriul listener, fara sa interfereze cu ceilalti listeneri. Acum workDone va mentine o

lista cu toti listenerii. In momentul in care este declansat acest eveniment este apelat fiecare

listener din lista. Ordinea de apelare este aceeasi cu cea a adaugarii lor in lista.

Eliminarea unui listener din lista se face folosind operatorul -=.

ADO.Net

ADO.Net este o multime de biblioteci orientate obiect care permit interactiunea cu sistemele de

stocare a informatiilor. De obicei aceste sisteme sunt reprezentate de bazele de date, dar pot fi si

fisiere text, fisiere XML, fisiere Excel, etc. In continuare ne vom ocupa de interactiunea cu

bazele de date.

Page 20: c sharp curs

Un server de baze de date pe care se pot rula exemplele urmatoare este MSDE (Microsoft

Desktop Engine 2000) si poate fi descarcat gratuit de pe site-ul Microsoft.

Data Provider

ADO.Net permite interactiunea cu diverse tipuri de baze de date. Totusi nu exista un singur set

de clase care sa pemita acest lucru. Deoarece fiecare tip de baza de date foloseste alte protocoale

trebuie sa gasim o metoda sa folosim protocolul corect in fiecare caz. Unele sisteme mai vechi

folosesc protocolul ODBC, altele mai noi, folosesc protocolul OleDb, etc.

ADO.Net furnizeaza metode asemanatoare de comunicare cu fiecare sistem de stocare a

informatiilor, dar fiecare este implementata separat intr-o biblioteca. Aceste biblioteci sunt

numite Data Providers si poarta numele protocolului sau sistemului cu care permite

interactiunea. Tabelul urmator prezinta o lista cu o parte dintre acesti provideri.

Provider Prefix API Descriere

ODBC Data Provider Odbc Data Sources care folosesc protocolul ODBC (in

mod normal baze date mai vechi).

OleDb Data Provider OleDb Data Sources care expun interfata OleDb (Access,

Excel, etc.)

Oracle Data Provider Oracle Pentru bazele de date Oracle.

SQL Data Provider Sql Pentru Microsoft Sql Server si MSDE

Borland Data Provider Bdp Ofera acces la mai multe tipuri de baze de date

cum ar fi Interbase, SQL Server, IBM DB2 si Oracle.

Fig. 1: Data Providers

Obiectele ADO.Net

SqlConnection

Primul lucru pe care un programator trebuie sa-l faca atunci cand vrea sa comunice cu o baza de

date este sa deschide o conexiune la aceasta. Conexiunea "spune" celorlalte obiecte cu ce baza de

date lucreaza. Conexiunea se ocupa de logica low-level asociata protocolului. Acest lucru

usureaza foarte mult munca unui programator, acesta neavand decat sa instantieze obiectul

conexiune, sa deschida conexiunea, sa faca operatiile de care are nevoie asupra bazei de date si

apoi sa inchida conexiunea. Datorita modului in care celelalte clase ADO.Net sunt implementate

uneori este nevoie de chiar mai putin decat atat.

Page 21: c sharp curs

Desi folosirea conexiunilor este mult simplificata in ADO.Net, programatorul trebuie sa le

inteleaga foarte bine pentru a lua deciziile corecte. O conexiune este o resursa foarte importanta.

Daca aplicatia va fi folosita pe o singura masina, asupra unei singure baze de date, importanta

acestei resurse este mai putin clara. Dar daca este vorba de o aplicatie enterprise, folosita

simultan de multi utilizatori asupra aceleiasi baze de date importanta conexiunii este mult mai

clara. Fiecare conexiune reprezinta overhead pentru server si nu exista nici un server care sa

suporte overhead infinit.

Un obiect SqlConnection este la fel ca orice alt obiect C#. de cele mai multe ori declararea si

instantierea se face in acelasi timp:

1

2

3

4

5

6

7

8

9

1

0

SqlConnection sqlConn = new SqlConnection(

"Data Source=(local);Initial Catalog=Northwind;Integrated

Security=SSPI");

Conectarea folosind un cont anume

SqlConnection sqlConn1 = new SqlConnection(

"Data Source=DatabaseServer;Initial Catalog=Northwind;User

ID=YourUserID;Password=YourPassword");

//sau pentru OleDb

OleDbConnection oleDbConn = new OleDbConnection(

"Provider=Microsoft.Jet.OLEDB.4.0;Data

Source=MyDatabase.mdb");

Fig. 2: Instantierea unui obiect de tipul SqlConnect

Obiectul SqlConnection de mai sus este instantiat folosind un constructor care primeste ca

parametru un string. Acest argument este stringul de conectare.

Parametru al stringului de conectare Descriere

Data Source

Identifica masina server. Poate sa fie masina locala,

numele unui computer din domeniu sau o adresa

IP.

Initial Catalog Numele bazei de date.

Integrated Security Setat la valoarea SSPI pentru a face conexiunea

folosind contul windows al utilizatorului.

User ID Numele de utilizator configurat pe serverul SQL.

Password Parola atasata utilizatorului de la User ID.

Fig. 3: Stringurile de conectare contin perechi de tipul cheie/valoare despre modul in care se

Page 22: c sharp curs

va face conectarea la baza de date.

Scopul instantierii unui obiect de tip SqlConnection este ca alte obiecte ADO.Net sa poata lucra

cu baza de date. Alte obiecte, cum ar fi SqlDataAdapter si SqlCommand, au constructori care

primesc obiectul conexiune ca parametru. Atunci cand se lucreaza cu o baza de date trebuie

urmati pasii:

1. Instantierea unui obiect SqlConnection; 2. Deschiderea conexiunii; 3. Trimiterea conexiunii ca parametru altor obiecte ADO.Net; 4. Realizarea operatiunilor asupra bazei de date; 5. Inchiderea conexiunii.

Exemplul urmator prezinta modul de folosire a unei conexiuni, pas cu pas:

1

2

3

4

5

6

7

8

9

1

0

1

1

1

2

1

3

1

4

1

5

1

6

1

7

1

8

1

9

2

0

2

using System;

using System.Data;

using System.Data.SqlClient;

// Prezinta modul de lucru cu un obiect SqlConnect

class SqlConnectionDemo

{

static void Main()

{

// 1. Instantiaza conexiunea

SqlConnection conn = new SqlConnection(

"Data Source=(local);Initial

Catalog=Northwind;Integrated Security=SSPI");

SqlDataReader rdr = null;

try

{

// 2. Deschide conexiunea

conn.Open();

// 3. Trimite conexiunea altui obiect ADO.Net

SqlCommand cmd = new SqlCommand("SELECT * FROM

Customers", conn);

//

// 4. Realizarea operatiunilor asupra bazei de date

//

// Obtine rezultatul interogarii

rdr = cmd.ExecuteReader();

// Afiseaza valoarea CustomerID a fiecarei

inregistrari

while (rdr.Read())

{

Page 23: c sharp curs

1

2

2

2

3

2

4

2

5

2

6

2

7

2

8

2

9

3

0

3

1

3

2

3

3

3

4

3

5

3

6

3

7

3

8

3

9

4

0

4

1

4

2

4

3

4

4

4

5

Console.WriteLine (rdr[0]);

}

}

finally

{

// inchide reader-ul

if (rdr != null)

{

rdr.Close();

}

// 5. Inchide conexiunea

if (conn != null)

{

conn.Close();

}

}

}

}

Page 24: c sharp curs

4

6

4

7

4

8

4

9

5

0

5

1

5

2

Fig. 4: Folosirea unui obiect obiect de tipul SqlConnect

Asa cum se vede la linia 19 deschiderea conexiunii se face apeland metoda Open() a instantei

SqlConnection. Orice operatii asupra unei conexiuni care nu a fost inca deschisa genereaza o

exceptie.

Inainte de a folosi conexiunea trebuie sa instiintam celelalte obiecte ADO.Net despre care

conexiune este vorba. Facem acest lucru la linia 22 din figura 4, trimitand ca parametru

constructorului SqlCommand obiectul conn. Orice operatie pe care o va face instanta cmd va

folosi aceasta conexiune.

Obiectul care foloseste conexiunea este cmd, de tipul SqlCommand. Acesta face o interogare in

baza de date adupra tabelului Customers. Rezultatul este intors intr-un obiect de tipul

SqlDataReader, iar in bucla while este afisat continutul primei coloane din fiecare rand din

tabelul obtiunut (noi stim ca aceasta coloana este coloana CustomerID). Important de retinut este

ca obiectele SqlCommand si SqlDataReader folosesc un obiect de tipul SqlConnection, si astfel

stiu cu care baza de date sa lucreze.

Atunci cand am terminat de folosit obiectul conn inchidem conexiune (linia 48). Lasarea

conexiunii deschise are implicatii grave in performanta aplicatiei.

SqlCommand

Obiectele de tipul SqlCommand permit specificare tipului de actiune asupra bazei de date. De

exemplu se poate face o interogare, inserare, modificare sau stergere.

Instantierea unui obiect de tipul SqlCommand se face ca la linia 22 din figura 4. Aceste este

modul cel mai des intalnit de instantiere. Ia ca parametru un string, care este comanda ce va fi

executata, si o referinta la un obiect de tipul SqlConnect.

Page 25: c sharp curs

Atunci cand faci o interogare in baza de date, obtii un tabel rezultat care trebuie sa poata fi

vizualizat. Pentru a obtine acest lucru folosind obiecte SqlCommand este folosita metoda

ExecuteReader care intoarce un obiect de tipul SqlDataReader. Exemplul de mai jos arata

modul de obtinere a unei instante SqlDataReader.

1

2

3

4

5

// 1. Instantiaza o noua comanda cu o fraza select si o conexiune

SqlCommand cmd = new SqlCommand ("SELECT CategoryName FROM Categories",

conn);

// 2. Executa interogarea

SqlDataReader rdr = cmd.ExecuteReader();

Fig. 5: Realizarea unei interogari in baza de date.

Pentru a insera valori intr-o baza de date trebuie apelata functia ExecuteNonQuery pe un obiect

SqlCommand. Exemplul urmator arata modul de inserare intr-o baza de date.

1

2

3

4

5

6

7

8

9

// pregateste stringul comanda

string insertString = @"INSERT INTO Categories (CategoryName, Description)

VALUES ('Miscellaneous', 'Whatever doesn''t fit

elsewhere')";

// 1. Instantiaza o noua comanda

SqlCommand cmd = new SqlCommand (insertString, conn);

// 2. Apeleaza ExecuteNonQuery pentru a executa comanda

cmd.ExecuteNonQuery();

Fig. 6: Inserarea intr-o baza de date.

Modificarea si stergerea datelor dintr-o baza de date se face la fel ca si inserarea, dar ca punem

primul parametru al constructorului SqlCommand pe valoarea corespunzatoare.

Uneori avem nevoie dintr-o baza de date doar de o singura valoare, care poate fi suma, media,

etc. inregistrarilor dintr-un tabel. Apeland ExecuteReader si apoi calculand acea valoare in

program nu este cea mai eficienta metoda de a ajunge la rezultat. Cea mai buna metoda este sa

Page 26: c sharp curs

lasam baa de date sa faca ceea ce este necesar si sa intoarca o singura valoare. Acest lucru il face

metoda ExecuteScalar:

1

2

3

4

5

// 1. Instantiaza o noua comanda

SqlCommand cmd = new SqlCommand ("SELECT count(*) FROM Categories", conn);

// 2. Apeleaza ExecuteScalar pentru a executa comanda

int count = (int) cmd.ExecuteScalar();

Fig. 7: Obtinerea unei singure valori.

SqlDataReader

Tipul SqlDataReader este folosit pentru a citi date in cea mai eficienta metoda posibila. NU

poate fi folosit pentru scriere. O data citita o informatie nu mai poate fi citita inca o data.

SqlDataReader citeste secvential date.

Datprita faptului ca citeste doar inainte (forward-only) permite acestui tip de date sa fie foarte

rapid in citire. Overhead-ul asociat este foarte mic (overhead generat cu inspectarea rezultatului

si a scrierii in baza de date). Daca intr-o aplicatie este nevoie doar de informatii care vor fi citite

o singura data, sau rezultatul unei interogari este prea mare ca sa fie retinut in memorie (caching)

SqlDataReader este solutia cea mai buna.

Obtinerea unei instante de tipul SqlDataReader este putin diferita de instantierea normala -

trebuie apelata metoda ExecuteDataReader. Daca pentru instantiere este folosit operatorul new

veti obtine un obiect cu care nu puteti face nimic pentru ca nu are o conexiune si o comanda

atasate.

SqlDataReader obtine datele intr-un stream secvential. Pentru a citi aceste informatii trebuie

apelata metoda Read; aceasta citeste un singur rand din tabelul rezultat. Metoda clasica de a citi

informatia dintr-un SqlDataReader este de a itera intr-o bucla while asa cum se vede in figura 4

la liniile 32-35.

Metoda Read intoarce true cat timp mai este ceva de citit din stream.

SqlDataReader implementeaza si indexatori (am obtinut prima coloana in exemplul din figura 4

folosind indexatori numerici). In exemplul din figura 4 nu este foarte clar pentru cineva care

citeste codul ca acolo este vorba de coloana CustomerID (decat daca s-a uitat si in baza de date).

Din aceasta cauza este preferata utilizarea indexatorilor de tipul string. In acest caz codul devine:

1

2

3

4

5

// Obtine rezultatul interogarii

rdr = cmd.ExecuteReader();

// Afiseaza valoarea CustomerID a fiecarei inregistrari

while (rdr.Read())

{

Page 27: c sharp curs

6

7

8

Console.WriteLine (rdr["CustomerID"]);

}

Fig. 8: Folosirea indexatorilor asupra unui SqlDataReader.

Valoeare indexului trebuie sa fie numele coloanei din tabelul rezultat.

Indiferent ca se foloseste un index numeric sau unul de tipul string indexatorii intorc totdeauna

un obiect de tipul object fiind necesara conversia.

Dupa ce un reader nu mai este folosit acesta trebuie inchis apeland metoda Close (linia 42 din

figura 4)

SqlDataAdapter

Pana acum am vazut cum putem efectua operatii asupra unei baze de date folosind obiecte de

tipul SqlCommand si SqlDataReader. Problema cu aceasta abordare este ca pe parcursul intregii

tranzactii conexiunea trebuie sa fie deschisa.

Voi prezenta in continuare o metoda care nu necesita o conexiune permanenta la o baza de date -

si anume folosind obiecte de tipul DataSet si SqlDataAdapter.

Un DataSet este o reprezentare in memorie a unui data store (un sistem de stocare si obtinere a

datelor). Un DataSet contine o multime de tabele asupra carora se pot executa diverse operatii.

Un DataSet doar retine informatii si nu interactioneaza cu un data source. SqlDataAdapter este

cel care se ocupa administrarea conexiunilor cu data source si ofera comportamentul de lucru in

mod deconectat. SqlDataAdapter deschide o conexiune doar atunci cand este nevoie si o

inchide imediat ce si-a terminmat treaba. De exemplu SqlDataAdapter realizeaza urmatoarele

operatiuni atunci cand trebuie sa populeze un DataSet:

1. deschide conexiunea; 2. populeaza DataSet-ul; 3. inchide conexiunea;

si urmatoarele operatiuni atunci cand trebuie sa faca update in baza de date:

1. deschide conexiunea; 2. scrie modificarile din DataSet in baza de date; 3. inchide conexiunea;

Intre operatiunea de populare a DataSet-ului si cea de update conexiunile la data source sunt inchise.

Intre aceste operatii in DataSet se poate scrie sau citi. Acestea sunt mecanismele de a lucra in mod

deconectat. Pentru ca aplicatia tine deschisa conexiunea la baza de date doar atunci cand este necesar,

devine mai scalabila.

Page 28: c sharp curs

Crearea unui obiect de tipul DataSet se face folosind operatorul new

1 DataSet dsCustomers = new DataSet ();

Fig. 9: Instantierea unui DataSet.

Constructorul unui DataSet nu necesita parametri. Exista totusi o supraincarcare a acestuia care

primeste ca parametru un string, si este folosit atunci cand trebuie sa se faca o serializare a

datelor intr-un fisier XML. In acest moment avem un DataSet gol si avem nevoie de un

SqlDataAdapter pentru a-l popula.

Un obiect SqlDataAdapter contine mai multe obiecte SqlCommand si un obiect SqlConnection

pentru a citi si scrie date.

1 SqlDataAdapter daCustomers = new SqlDataAdapter ("SELECT CustomerID,

CompanyName FROM Customers", conn);

Fig. 10: Instantierea unui SqlDataAdapter.

Codul de mai sus creaza un obiect de tipul SqlDataAdapter, daCustomers. Comanda SQL

specifica cu ce date va fi populat un DataSet, iar conexiunea conn trebuie sa fi fost creata

anterior, dar nu si deschisa. Responsabilitatea deschiderii conexiunii revine adapterului in la

apelul metodelor Fill si Update.

SqlDataAdapter contine mai multe obiecte comanda: cate unul pentru inserare, update, delete si

select. Prin intermediul constructorului putem instantia doar comanda de interogare. Instantierea

celorlalte se face fie prin intermediul proprietatilor pe care le expune SqlDataAdapter, fie

folosind obiecte de tipul SqlCommandBuilder.

1 SqlCommandBuilder cmdBldr = new SqlCommandBuilder (daCustomers);

Fig. 11: Instantierea unui SqlCommandBuilder.

La initializarea unu SqlCommandBuilder am apelat un constructor care primeste ca parametru un

adapter, pentru care vor fi construite comenzile. SqlCommandBuilder are limitari: nu poate

construi decat comenzi simple si care se aplica unui singur tabel. Atunci cand trebui ca sa facem

comenzi care vor folosi mai multe tabele este recomandata construirea separata a comnezilor si

apoi atasarea lor adapterului folosind proprietati.

O data ce avem cele doua instante, DataSet si SqlDataAdapter, putem sa populam DataSet-ul.

1 daCustomers.Fill (dsCustomers, "Customers");

Fig. 12: Popularea unui DataSet.

Page 29: c sharp curs

Metoda Fill din exemplul anterior primeste doi parametri: un DataSet pe care-l va popula si un

string care va fi numele tabelului (nu numele tabelului din baza de date, ci al tabelului rezultat in

DataSet) care va fi creat. Scopul acestui nume este identificarea ulterioara a tabelului. In cazul

in care nu este specificat nici un nume de tabel, acestea vor fi adaugate in DataSet sub numele

Table1, Table2, ...

Un DataSet poate fi folosit ca data source pentru un DataGrid atat in ASP.Net cat si pentru cel

din Windows Forms. Mai jos este prezentat un exemplu de legare a unui DataSet la un

DataGrid:

1

2

3

DataGrid dgCustomers = new DataGrid();

dgCustomers.DataSource = dsCustomers;

dgCustomers.DataMembers = "Customers";

Fig. 13: Legarea unui DataSet la un DataGrid.

La linia 2 setez un DataSet ca DataSource pentru un DataGrid. Acest lucru ii spune grid-ului ce

informatii sa afiseze. Un grid stie sa afiseze mai multe tabele dintr-un DataSet, afisand un semn

"+" permitandu-i utilizatorului sa aleaga care tabel sa fie afisat din cele disponibile. Pentru a

suprima afisarea acelui semn "+" din GUI am setat si proprietatea DataMembers pe numele

tabelului care va fi afisat. Numele tabelului este acelasi care l-am folosit ca parametru in apelul

metodei Fill

Dupa ce au fost facute modificari intr-un DataSet acestea trebuie scrise si in baza de date.

Actualizarea se face prin apelul metodei Update

1 daCustomers.Update (dsCustomers, "Customers");

Fig. 14: Actualizarea bazei de date.

SqlParameter

Atuci cand lucrati cu bazele de date veti avea nevoie, de cele mai multe ori sa filtrati rezultatul

dupa diverse criterii. De obicei acest lucru se face in functii de niste criterii pe care utilizatorul le

specifica (ex: vreti sa vedeti doar clientii anume oras).

Dupa cum am vazut, o interogare asociata unui obiect SqlCommand este un simplu string. Cea

mai simpla metoda de filtrare a rezultatelor este sa construiti acel string in mod dinamic, dar

aceasta metoda nu este recomandata.

1

2

3

// sa nu faceti asa!!

SqlCommand cmd = new SqlCommand(

"SELECT * FROM Customers WHERE city = '" + inputCity + "'";

Page 30: c sharp curs

Fig. 15: Un exemplu despre construirea nerecomandata a interogarii.

Motivul pentru care o astfel de construire a unei interogari este nerecomandata este ca nu se

poate avea incredere in input-ul utilizatorului. De obicei inputCity este introdus de utilizator

intr-un TextBox. Folosind acel TextBox un utilizator rau intentionat poate sa introduca cod care

poate duce la coruperea bazei de date, accesarea informatiilor confidentiale, etc.

In loc sa construiti dinamic stringul de interogare folositi interogari cu parametri. Orice valoare

pusa intr-un parametru nu va fi tratata drept cod SQL, ci ca valoare a unui camp, facand aplicatia

mai sigura. Pentru a folosi interogari cu parametri urmati pasii:

1. Construiti stringul pentru SqlCommand folosind parametri; 2. Creati un obiect SqlParameter asignand valorile corespunzatoare; 3. Adaugati obiectul SqlParameter la obiectul SqlCommand, folosind proprietatea Parameters.

Deci primul pas este construirea unui string de interogare parametrizat. Pentru a specifica locul

unde vor fi inserate valorile parametrilor folositi marcatorul @. Sintaxa este urmatoarea:

1

2

3

// 1. Declarati un obiect SqlCommand care are stringul de interogare

parametrizat

SqlCommand cmd = new SqlCommand(

"SELECT * FROM Customers WHERE city = @City", conn);

Fig. 16: Obiect comanda cu stringul de interogare parametrizat.

In contructorul din exemplul anterior stringul de interogare contine un parametru @City. Atunci

cand comanda va fi executata in string @City va fi inlocuit cu valoarea aflata in obiectul

SqlParameter atasat. In exemplul din figura 16 folosim o interogare cu un singur parametru, dar

pot exista oricati parametri, si pentru fiecare din acestia trebuie sa asociem un obiect

SqlParameter. In cazul in care pentru un parametru din stringul de interogare nu avem asociata

o instanta de tipul SqlParameter vom obtine o eroare la rulare. Acelasi lucru se intampla si daca

avem mai multe instante SqlParameter pentru un parametru.

Acum trebuie sa declaram o instanta de tipul SqlParameter:

1

2

3

4

// 1. Definiti parametrii utilizati in stringul de interogare

SqlParameter param = new SqlParameter();

param.ParameterName = "@City";

param.Value = inputCity;

Fig. 17: Declararea unei instante de tipul SqlParameter.

Si acum trebuie sa adaugam acet parametru obiectului comanda:

Page 31: c sharp curs

1

2 // 1. Adauga parametrii definiti obiectului comanda

cmd.Parameters.Add(param);

Fig. 18: Adaugararea parametrilor obiectului comanda.