datele de tip "open source".
permite conversia automata a datelor.
Salvati fila cu numele Lista2.cs si compilati.
int.Se utilizeaza metoda standard ConvertAll().
initial elemente de tip PointF,apoi elemente de tip Point.
de date si aplicatie).
Salvati fila cu numele Dictionar1.cs si compilati.
,aceste obiecte permit si o serie
de bucle de filtrare conditionate prin cheia de indexare.Aceasta cheie
de indexare,poate fi orice cod alfanumeric.
EXEMPLU:
in exercitiul de mai sus,se pot filtra toate elementele cu valoarea
"paint.exe",pentru a obtine extensia filelor produse,sau se pot filtra
toate elementele cu extensia >"mdb".Practic se lucreaza la fel ca si
in cazul tabelelor,unde elementele pot fi selectate fie in functie de
coloana din tabel,fie in functie de linia din tabel.
Daca necesitatile de programare o impun si nici una dintre clasele
standard nu satisface aceste necesitati,se pot declara si defini clase
noi de tip colectie,cu campuri de date,proprietati si metode specifice.
Nu este obligatoriu,dar este recomandabil ca si aceste clase nou definite
sa respecte sablonul general al claselor tip colectie,adica sa contina:
un enumerator,o metoda de clonare a datelor (CopyTo),capacitatea de a
numara elementele din container (Count),si una sua mai multe metode de
sincronizare a thread-urilor paralele.
Exista si clase de tip colectie specializate pentru un anumit tip
de date.
EXEMPLU: StringDictionary implementeaza o clasa de tip Hash Table in
care atat cheia de indexare cat si valoarea elementelor este strict de
tip string.
Alte clase de tip colectie se adapteaza la contextul de executie.
EXEMPLU: HybridDictionary class implementeaza o clasa ListDictionary
atat timp cat numarul de elemente arhivate este mic,dar se transforma
automat in Hash Table imediat ce numarul de elemente depaseste indexul
numeric( cel din domeniul de definitie initial ),practic schimba automat
metoda de indexare a elementelor.
Mai mult decat atat,o serie de obiecte vizuale,utilizeaza automat
obiecte de tip colectie,pentru a executa o serie oarecare de operatii.
EXEMPLU: Un obiect de tip ComboBox,va apela la un obiect de tip List
sau Dictionary pentru a-si incarca automat lista de optiuni,citind
datele dintr-o resursa oarecare (utilizeaza un link spre resursa).
Incercati sa editati o lista cu aplicatiile claselor de tip colectie.
-76-
EXCEPTII SI ERORI
O exceptie este o situatie neasteptata,ce intervine in timpul executiei
unui program.O astfel de exceptie poate fi genrata de un cod imperfect,de
incompatibilitati ale datelor preluate din diverse resurse,de sistemul de
operare,de o eroare umana a utilizatorului sau de coruperea accidentala a
datelor.
EXEMPLU: -aplicatia citeste date dintr-o fila de resurse si aceasta fila
este stearsa sau suprascrisa accidental de un alt utilizator.
Nu se pot scrie coduri absolut perfecte,dar o mare parte dintre aceste
accidente de executie pot fi preintampinate.Pentru acest scop,platforma
.Net include o clasa specializata,denumita EXCEPTION.In momentul in care
executia programului este intrerupta de o exceptie,sistemul de operare va
genera automat un obiect de tip EXCEPTION,in care va arhiva pe scurt,tipul
de situatie depistata.In mod normal,executia va fi intrerupta,iar eroarea
va putea fi identificata doar de catre un programator avizat.
De cele mai multe ori,exceptia care a generat intreruperea programului
nu este vitala pentru executia aplicatiei.Din acest motiv,a fost imaginat
un mecanism prin care programul sa poata identifica si corecta eroarea,
pentru a putea continua executia,chiar daca o parte dintre date vor fi
afectate de eroare.Prin acest mecanism,se permite proceselor automate sa
elibereze memoria,sau sa comunice cu alte aplicatii paralele,inainte de a
intrerupe programul pentru remedierea defectiunii.Acest mecanism este
foarte important,mai ales atunci cand mai multe aplicatii conlucreaza in
retea pentru un anumit scop.
Cele mai frecvente erori generate de sistem sunt:
System.ArithmeticException - este generata de o eroare aritmetica
System.ArrayTypeMismatchException - incompatibilitatea tipului de data
System.DivideByZeroException - apare la impartirea prin zero
System.IndexOutOfRangeException - apare la un index negativ sau inexistent
System.InvalidCastException - este generata la erorile de implementare
System.NullReferenceException - apare la apelul unor date nedefinite
System.OutOfMemoryException - apare la supraincarcarea memoriei
System.OverFlowException - apare cand se depaseste domeniul de definitie
System.StackOverFlowException - tamponul de executie este supraincarcat
System.TypeInitializationException - constructorul static emite exceptia
In toate aceste situatii de executie,sistemul de operare identifica
eroarea si construieste automat un obiect de tip Exception,in care descrie
situatia identificata si momentul in care a intervenit.Pentru ca executia
sa nu fie intrerupta,toate situatiile in care este posibil sa apara astfel
de exceptii,pot fi incluse intr-o bucla de tip TRY...CATCH,sau pot fi
conditionate prin atribute,sau prin directive de precompilare.Atributele
au fost prezentate intr-un capitol anterior,asa ca in acest capitol se
vor exemplifica doar buclele TRY...CATCH,desi se pot imagina si alte
solutii pentru tratarea exceptiilor.
Spre deosebire ce C++ si Java,buclele TRY...CATCH din limbajul C sharp,
permit si o bucla suplimentara denumita FINALLY,ce se executa in ambele
situatii: daca intervine sau daca nu intervine o exceptie.Bucla FINALLY
se utilizeaza de obicei pentru operatiile de finalizare a executiei si
eliberare a memoriei (dar se poate exploata si pentru alte scopuri).
-77-
Cel mai simplu exemplu este o eroare aritmetica generata de impartirea
prin zero.
EXEMPLU:
using System;
class Exceptie {
public static void Main() {
F1 f1 = new F1();
Console.Write(" 12/4 = ")'
int x = f1.Fractie(12,4);
Console.WriteLine(x);
Console.Write(" 12/0= ");
x= f1.Fractie(12,0);
Console.WriteLine(x);
Console.ReadLine();
}}
class F1 {
public int Fractie(int x,int y){
try { return (x / y); }
catch (System.DivideByZeroException dbz)
{ System.Console.WriteLine(dbz); return 0; }
}}
Salvati fila de mai sus cu numele Exceptie1.cs si compilati.Pentru a
observa diferenta,rescrieti exercitiul fara bucla TRY...CATCH.
In exemplul de mai sus,daca nu intervine exceptia se executa bucla TRY,
iar daca intervine exceptia se executa bucla CATCH.Acelasi mecanism se
putea obtine si cu un atribut conditional (vezi capitolul).
Exceptiile nu sunt intotdeauna identificate doar de sistemul de operare.
Daca este necesar,exceptiile si mesajele de eroare pot fi elibarate de
orice proces de executie cu o comanda THROW.Dupa eliberare,aceste exceptii
pot fi identificate si tratate la fel ca si cele eliberate de sistem.
EXEMPLU:
using System;
public class Exceptie2 {
static void Main() {
try {
Console.WriteLine("Se executa bucla TRY ! ");
throw new NullReferenceException();
}
catch (NullReferenceException e)
{ Console.WriteLine("EXCEPTIE= {0} ",e); }
finally
{ Console.WriteLine("Se executa bucla FINALLY !"); }
Console.ReadLine();
}}
Salvati fila cu numele Exceptie2.cs si compilati.
In exemplul de mai sus,se observa ca exceptia a fost emisa chiar din
interiorul buclei TRY...CATCH si a fost interceptata si tratata imediat.
In mod normal,o astfel de exceptie este emisa de un proces din alt modul,
sau chiar de un alt utilizator din retea,pentru a semnala o anumita
situatie,ce necesita o functie de tratare a exceptiei.
Exemplu: un anumit utilizator va fi exceptat de la acces la program.
-78-
Probabil ca exceptia cea mai frecevnta din sistem este generata de
absenta uneia dintre resursele apelate.De exemplu,un set oarecare de date
esentiale trebuie sa fie citite dintr-o fila de resurse,inainte de a fi
procesate.
EXEMPLU:
using System;
using System.IO;
public class ProcessFile{
public static void Main() {
FileStream fs = null;
try {
fs = new FileStream("Filadorita.txt",FileMode.Open);
StreamReader sr = new StreamReader(fs);
string line;
line = sr.ReadLine();
Console.WriteLine(line);
}
catch(FileNotFoundException e)
{
Console.WriteLine("[Fila cautata nu exista !] {0}",e);
}
finally
{
if (fs != null) fs.Close();
}
Console.ReadLine();
}}
Salvati fila cu numele Exceptie3.cs si compilati.
In exemplul de mai sus,se incerca deschiderea si citirea unei file
care nu exista in memorie.Bucla CATCH va intercepta eroarea de tip
FileNotFoundException si va executa blocul de tratare a exceptiei.Daca
adaugati o fila denumita Filadorita.txt,in care editati un text oarecare,
se va executa bucla TRY,apoi se va executa automat si bucla FINALLY,prin
care se inchide stream-ul si se elibereaza memoria.Daca fila ramane
deschisa,nu va mai putea fi apelata pana la inchiderea programului.Daca
fila respectiva este o resursa din retea,ea va ramane blocata pentru toti
ceilalti utilizatori,pana la terminarea programului.Din acest motiv,este
esential ca atunci cand se citesc date din file de resurse,sa se adauge
si o bulca automata pentru inchiderea filei.
Emiterea,interceptarea si tratarea erorilor se poate face si pentru
situatii exceptionale,in care doriti sa implementati un anumit artificiu
de programare,sau doriti sa subliniati un anumit aspect.Totusi nu este
recomandabil sa se abuzeze de acest mecanism.Fiecare exceptie si bucla de
interceptare a exceptiei,fragmenteaza executia,introduce cel putin inca
un thread suplimentar,adauga seturi intregi de operatii si intarzie
executia programului.Din acest motiv,este recomandabil ca situatiile ce
pot genera exceptii sa fie evitate atunci cand este posibil si sa fie
tratate doar atunci cand nu pot fi evitate prin alt procedeu.Exista si
programatori analisti,care construiesc un sistem complex de depanare
automata a programelor,dar cu pretul unui consum nejustificat de memorie
suplimentara.Decizia finala apartine fiecarui programator.
-79-
THREADS (Fire de executie)
Evolutia tehnicii de calcul a dus la dezvoltarea unor procesoare din
ce in ce mai performante,capabile sa lucreze simultan cu volume din ce
in ce mai mari de informatie.Totusi,memoria de executie a procesorului
este limitata,mult mai mica decat volumul total al programelor prelucrate.
Din acest motiv,sistemul de operare imparte datele ce intra in procesor
in calupuri mai mici,ce urmeaza sa fie procesate succesiv.Fiecare astfel
de bloc de date,nu este impartit dezordonat,ci este inclus intr-un stream,
si este organizat cu ajutorul unui obiect specializat.Aceste obiecte
poarta numele de thread = fir de executie.Aceste fire de executie permit
o serie de operatii:
1. -mai multe aplicatii pot fi executate simultan
2. -se poate stabili o lista de prioritati,astfel incat aplicatiile sa
fie executate preferential atunci cand memoria este limitata
3. -se pot sincroniza doua sau mai multe aplicatii,pentru a organiza
competitia fata de aceleasi file de resurse
4. -se pot executa in background operatii de intretinere,supraveghere si
control,managementul memoriei,depanare automata...etc.
Sistemul de operare Windows,exemplifica cel mai clar acest gen de
operatii.Astfel,utilizatorul poate sa scrie un text,sa asculte muzica si
simultan sa copieze niste date din reteaua Internet.Fiecare aplicatie
este controlata de sistemul Windows cu ajutorul unui thread separat,
incarcat intr-un stream cu executie indepenedenta.Ca rezultat,oricare
dintre aplicatii va putea fi intrerupta sau lansata,in orice moment.
Procesarea paralela poate fi importanta si in cazul aplicatiilor C#.
Presupunand ca aplicatia este formata din mai multe module executabile
mici,acestea vor putea fi lansate in executie cu un AppDomain.
EXEMPLU:
using System;
class Thread1 {
static void Main() {
AppDomain dom1 = AppDomain.CreateDomain("d1");
dom1.ExecuteAssembley(@"c:\v3\Clasa1.exe");
dom1.ExecuteAssembley(@"c:\v3\Clasa3.exe");
AppDomain.Unload(dom1);
}};
Salvati fila cu numele Thread1.cs si compilati.
In exemplul,de mai sus,exista un singur thread de executie (cel creat
automat de sistemul de operare),iar procesarea se face serial.Ca rezultat,
cele doua ferestre nu pot fi deschise simultan.Dupa ce se inchide prima
fereastra se va deschide cea de a doua.In cazul in care cele doua
ferestre trebuie sa fie deschise simultan,solutia este ca fiecare dintre
ele sa fie lansata in executie cu un thread separat.
Platforma .Net,ofera clasa specializata Thread din System.Threading,
cu ajutorul careia se pot construi obiecte de tip thread.Fiecare obiect
va contine metode si proprietati ce permit organizarea fluxului de
executie.
In exemplul de mai sus,se poate crea cate un thread pentru fiecare
dintre cele doua ferestre.In acest mod,fiecare dintre cele doua module
va putea fi executat si controlat separat.
-80-
EXEMPLU:
using System;
using System.Threading;
class Thread1 {
public class Worker {
public void Fir1(){
AppDomain dom1 = AppDomain.CreateDomain("d1");
dom1.ExecuteAssembley(@"c:\v3\Clasa1.exe");
AppDomain.Unload(dom1);
}
public void Fir2(){
AppDomain dom2 = AppDomain.CreateDomain("d2");
dom2.ExecuteAssembley(@"c:\v3\Clasa3.exe");
AppDomain.Unload(dom2);
}}
static void Main(){
Worker fir1 = new Worker();
Thread t1 = new Thread(fir1.Fir1);
t1.Start();
Worker fir2 = new Worker();
Thread t2 = new Thread(fir2.Fir2);
t2.Start();
}}
Salvati fila cu numele Thread2.cs si compilati.
In exemplul de mai sus,Clasa1.exe si Clasa3.exe sunt doua module
executabile create anterior (vezi capitolul clase).Se observa ca pentru
a crea un thread,constructorul are ca parametru o functie,adica functia
ce urmeaza sa fie executata in acel stream.Pentru ca modulele sa fie
complet independente,am inclus in functia fiecaruia dintre ele toate
operatiile necesare,inclusiv cele de eliberare a memoriei.Ca rezultat,
cele doua ferestre lansate in executie,se comporta ca si cand ar fi
doua programe complet independente.Lansarea in executie a unui thread
se face prin metoda Start(),iar intarzierea executiei se poate face prin
metoda Sleep().
In exemplul de mai sus,ambele thread-uri sunt declarate identic,asa
ca sunt procesate in ordinea in care apar in program.In mediul real de
executie Windows,procesorul lucreaza simultan cu cateva mii de astfel
de thread-uri (vezi fereastra Windows Task Manager).Atunci cand memoria
de operare este la limita,sistemul de operare va elimina o parte dintre
aceste thread-uri de la executie si le va transfera intr-un tampon de
memorie auxiliara,unde stau "pe linie de asteptare",pana cand pot fi
reluate spre executie.Acest proces se executa in functie de ordinea in
care au fost trimise spre procesor si in functie de dimensiunea fiecarui
thread.Pentru a controla acest proces,se pot seta prioritati de executie
pentru thread-urile esentiale,cu ajutorul proprietatii Thread.Priority.
Exista cinci grade de prioritate: Highest,AboveNormal,Normal,BelowNormal,
si Lowest.Pentru a simplifica executia,thread-urile care contin operatii
esentiale,cum ar fi destructorii si eliberarea memoriei,vor fi setate cu
prioritatea cea mai mare (Highest),iar cele care contin doar operatii
optionale sau neimportante,vor fi setate cu prioritatea cea mai mica.
-81-
O alta proprietate extrem de utila este Thread.ThreadState.Daca se
citeste aceasta proprietate in diferite momente ale executiei,se poate
afla status-ul de moment al firului de executie.Aceste volori pot fi:
Aborted,AbortRequested,Background,Running,Stopped,StopRequested,Suspended,
SuspendRequested,Unstarted sau WaitSleepJoin.Cunoasterea status-ului
pentru fiecare thread este esentiala in etapa de depanare.
EXEMPLU:
using System;
using System.Threading;
public class ThreadExemplu {
public static void Proces1() {
for(int i = 0; i < 3; i++){
Console.WriteLine("Proces1- operatia: {0} ",i);
}}
public static void Proces2() {
for(int i = 0; i < 3; i++){
Console.WriteLine("Process2- operatia: {0} ",i);
}}
public static void Proces3() {
for(int i = 0; i < 3; i++){
Console.WriteLine("Process3- operatia: {0} ",i);
}}
public static void Main(){
Console.WriteLine("Incepe thread-ul Main: ");
Thread t1 = new Thread(new ThreadStart(Proces1));
t1.Priority = ThreadPriority.Lowest;
t1.Start();
Console.WriteLine("Firul1 este= {0}",t1.ThreadState);
Thread t2 = new Thread(new ThreadStart(Proces2));
t2.Priority = ThreadPriority.Highest;
t2.Start();
Console.WriteLine("Firul1 este= {0}",t1.ThreadState);
Console.WriteLine("Firul2 este= {0}",t2.ThreadState);
Thread t3 = new Thread(new ThreadStart(Proces3));
t3.Priority = ThreadPriority.Normal;
t3.Start();
Console.WriteLine("Firul1 este= {0} ",t1.ThreadState);
Console.WriteLine("Firul3 este= {0} ",t3.ThreadState);
t1.Join();t2.Join();t3.Join();
Console.WriteLine("Firul1 este= {0} ",t1.ThreadState);
Console.ReadLine();
}}
Salvati exemplul cu numele Thread3.cs si compilati.
In exemplul de mai sus,sunt trei fire de executie cu prioritati
diferite.Se observa cum firul t1,cel cu prioritatea cea mai mica este
stopat pana cand se executa cele doua fire cu prioritate mai mare.Pentru
crearea firelor de executie au utilizat clasa delegat ThreadStart prin
care se conecteaza thread-ul la metoda ce urmeqza sa fie executata (vezi
System.Threading).Metoda Join(),blocheaza fiecare fir,pana la terminarea
executei.
-82-
O situatie particulara apare atunci cand mai multe fire de executie,
exploateaza acelasi obiect (sau fila de resurse).Daca un fir de executie
intrerupe operatiile executate de alt fir de executie,pentru a castiga
accesul la resurse,atunci primul fir de executie va fi invalid,sau chiar
mai mult decat atat,va contine date invalide.Pentru a evita acest gen de
situatii,este necesara sincronizarea firelor de executie,astfel incat la
un anumit moment dat,un singur thread sa poata accesa obiectul sursa.
Cea mai simpla sincronizare se face cu ajutorul unui obiect de tip
Monitor,care prin metoda Enter() blocheaza resursa si prin metoda Exit()
elibereaza resursa.
EXEMPLU:
using System;
using System.Threading;
using System.Collections;
public class T1 {
public static Queue stiva1 = new Queue();
public static void Proces1() {
for (int i = 0; i < 10; i++) {
Monitor.Enter(stiva1);
stiva1.Enqueue("text");
Console.WriteLine("Proces1 - operatia: {0}", i);
Monitor.Exit(stiva1);
Thread.Sleep(250);
}}
public static void Proces2() {
for (int i = 0; i < 10; i++) {
Monitor.Enter(stiva1);
stiva1.Enqueue("text");
Console.WriteLine("Proces2- operatia: {0}", i);
Monitor.Exit(stiva1);
Thread.Sleep(150);