Sunteți pe pagina 1din 333

Limbajul C#

Ph.D. Lucian Sasu

Cuprins
1 Platforma Microsoft .NET 1.1 Prezentare general . . . . . . . . . . . . a 1.2 Arhitectura platformei Microsoft .NET . 1.3 Componente ale lui .NET Framework . . 1.3.1 Common Intermediate Language 1.3.2 Common Language Specication 1.3.3 Common Language Runtime . . . 1.3.4 Common Type System . . . . . . 1.3.5 Metadate . . . . . . . . . . . . . 1.3.6 Assemblies . . . . . . . . . . . . . 1.3.7 Assembly cache . . . . . . . . . . 1.3.8 Garbage collection . . . . . . . . 1.4 Trsturi ale platformei .NET . . . . . . aa 2 Tipuri predenite, tablouri, string-uri 2.1 Vedere general asupra limbajului C# a 2.2 Tipuri de date . . . . . . . . . . . . . . 2.2.1 Tipuri predenite . . . . . . . . 2.2.2 Tipuri valoare . . . . . . . . . . 2.2.3 Tipul enumerare . . . . . . . . 2.3 Tablouri . . . . . . . . . . . . . . . . . 2.3.1 Tablouri unidimensionale . . . . 2.3.2 Tablouri multidimensionale . . 2.4 Siruri de caractere . . . . . . . . . . . 2.4.1 Expresii regulate . . . . . . . . 3 Clase, instruciuni, spaii de t t 3.1 Clase vedere general . . a 3.2 Transmiterea de parametri 3.3 Conversii . . . . . . . . . . 3.3.1 Conversii implicite nume . . . . . . . . . . . . . . . . 3 . . . . . . . . . . . . . . . . . . . . . . . . . . 11 11 13 14 14 14 15 16 17 18 18 19 19 23 23 25 25 27 32 38 38 40 43 45 47 47 51 60 60

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . .

4 3.3.2 Conversiile implicite ale expresiilor constante 3.3.3 Conversii explicite . . . . . . . . . . . . . . 3.3.4 Boxing i unboxing . . . . . . . . . . . . . . s 3.4 Declaraii de variabile i constante . . . . . . . . . . t s 3.5 Instruciuni C# . . . . . . . . . . . . . . . . . . . . t 3.5.1 Declaraii de etichete . . . . . . . . . . . . . t 3.5.2 Instruciuni de selecie . . . . . . . . . . . . t t 3.5.3 Instruciuni de ciclare . . . . . . . . . . . . t 3.5.4 Instruciuni de salt . . . . . . . . . . . . . . t 3.5.5 Instruciunile try, throw, catch, nally . . . t 3.5.6 Instruciunile checked i unchecked . . . . . t s 3.5.7 Instruciunea lock . . . . . . . . . . . . . . . t 3.5.8 Instruciunea using . . . . . . . . . . . . . . t 3.6 Spaii de nume . . . . . . . . . . . . . . . . . . . . t 3.6.1 Declaraii de spaii de nume . . . . . . . . . t t 3.6.2 Directiva using . . . . . . . . . . . . . . . . 3.7 Declararea unei clase . . . . . . . . . . . . . . . . . 3.8 Membrii unei clase . . . . . . . . . . . . . . . . . . 3.9 Constructori de instana . . . . . . . . . . . . . . . t 3.10 Cmpuri . . . . . . . . . . . . . . . . . . . . . . . . a 3.10.1 Cmpuri instane . . . . . . . . . . . . . . . a t 3.10.2 Cmpuri statice . . . . . . . . . . . . . . . . a 3.10.3 Cmpuri readonly . . . . . . . . . . . . . . . a 3.10.4 Cmpuri volatile . . . . . . . . . . . . . . . a 3.10.5 Iniializarea cmpurilor . . . . . . . . . . . . t a 3.11 Constante . . . . . . . . . . . . . . . . . . . . . . . 3.12 Metode . . . . . . . . . . . . . . . . . . . . . . . . . 3.12.1 Metode statice i nestatice . . . . . . . . . . s 3.12.2 Metode externe . . . . . . . . . . . . . . . . 4 Clase (continuare) 4.1 Proprieti . . . . . . . . . . . . at 4.2 Indexatori . . . . . . . . . . . . 4.3 Operatori . . . . . . . . . . . . 4.3.1 Operatori unari . . . . . 4.3.2 Operatori binari . . . . . 4.3.3 Operatori de conversie . 4.3.4 Exemplu: clasa Fraction 4.4 Constructor static . . . . . . . . 4.5 Clase imbricate . . . . . . . . . 4.6 Destructori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

CUPRINS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 63 66 67 68 68 69 70 72 72 73 73 73 74 75 76 80 81 82 82 83 83 84 84 85 85 86 87 87 89 89 95 101 101 103 103 105 107 108 111

CUPRINS 4.7 4.8 Clase statice . . . . . . . . . . . . . . . . . . . . Specializarea i generalizarea . . . . . . . . . . . s 4.8.1 Specicarea motenirii . . . . . . . . . . s 4.8.2 Apelul constructorilor din clasa de baz a 4.8.3 Operatorii is i as . . . . . . . . . . . . s Clase sealed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5 113 115 115 116 117 118

4.9

5 Clase, structuri, interfee, delegai t t 119 5.1 Polimorsmul . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 5.1.1 Polimorsmul parametric . . . . . . . . . . . . . . . . . 119 5.1.2 Polimorsmul adhoc . . . . . . . . . . . . . . . . . . . 119 5.1.3 Polimorsmul de motenire . . . . . . . . . . . . . . . . 120 s 5.1.4 Virtual i override . . . . . . . . . . . . . . . . . . . . 121 s 5.1.5 Modicatorul new pentru metode . . . . . . . . . . . . 122 5.1.6 Metode sealed . . . . . . . . . . . . . . . . . . . . . . . 125 5.1.7 Exemplu folosind virtual, new, override, sealed . . . . . 126 5.2 Clase i metode abstracte . . . . . . . . . . . . . . . . . . . . 128 s 5.3 Tipuri pariale . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 t 5.4 Structuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 5.4.1 Structuri sau clase? . . . . . . . . . . . . . . . . . . . . 135 5.5 Interfee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 t 5.5.1 Clase abstracte sau interfee? . . . . . . . . . . . . . . 141 t 5.6 Tipul delegat . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 5.6.1 Utilizarea delegailor pentru a specica metode la runtime143 t 5.6.2 Delegai statici . . . . . . . . . . . . . . . . . . . . . . 146 t 5.6.3 Multicasting . . . . . . . . . . . . . . . . . . . . . . . . 147 6 Metode anonime. Evenimente. Excepii. t 6.1 Metode anonime . . . . . . . . . . . . . . . . 6.2 Evenimente . . . . . . . . . . . . . . . . . . . 6.2.1 Publicarea i subscrierea . . . . . . . . s 6.2.2 Evenimente i delegai . . . . . . . . . s t 6.2.3 Comentarii . . . . . . . . . . . . . . . 6.3 Tratarea excepiilor . . . . . . . . . . . . . . . t 6.3.1 Tipul Exception . . . . . . . . . . . . . 6.3.2 Aruncarea i prinderea excepiilor . . . s t 6.3.3 Re ncercarea codului . . . . . . . . . . 6.3.4 Compararea tehnicilor de manipulare a 6.3.5 Sugestie pentru lucrul cu excepiile . . t 151 . 151 . 153 . 154 . 154 . 160 . 161 . 161 . 162 . 173 . 175 . 176

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . erorilor . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

CUPRINS 177 . 177 . 180 . 181 . 182 . 183 . 183 . 186 . 191 . 192 . 193 . 194 . 195 . 196 . 196 . 196 . 197 . 197 . 198 . 200

7 Colecii. Clase generice. t 7.1 Colecii . . . . . . . . . . . . . . . . . . . . . . . . . . . t 7.1.1 Iteratori pentru colecii . . . . . . . . . . . . . . . t 7.1.2 Colecii de tip list . . . . . . . . . . . . . . . . . t a 7.1.3 Colecii de tip dicionar . . . . . . . . . . . . . . t t 7.2 Crearea unei colecii . . . . . . . . . . . . . . . . . . . . t 7.2.1 Colecie iterabil (stil vechi) . . . . . . . . . . . . t a 7.2.2 Colecie iterabil (stil nou) . . . . . . . . . . . . . t a 7.3 Clase generice . . . . . . . . . . . . . . . . . . . . . . . . 7.3.1 Metode generice . . . . . . . . . . . . . . . . . . . 7.3.2 Tipuri generice . . . . . . . . . . . . . . . . . . . 7.3.3 Constrngeri asupra parametrilor de genericitate . a 7.3.4 Interfee i delegai generici . . . . . . . . . . . . t s t 7.4 Colecii generice . . . . . . . . . . . . . . . . . . . . . . . t 7.4.1 Probleme cu coleciile de obiecte . . . . . . . . . t 7.4.2 Colecii generice . . . . . . . . . . . . . . . . . . . t 7.5 Elemente specice C# 3.0 . . . . . . . . . . . . . . . . . 7.5.1 Proprieti implementate automat . . . . . . . . . at 7.5.2 Iniializatori de obiecte . . . . . . . . . . . . . . . t 7.5.3 Iniializatori de colecii . . . . . . . . . . . . . . . t t

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

8 ADO.NET 203 8.1 Ce reprezint ADO.NET? . . . . . . . . . . . . . . . . . . . . 203 a 8.2 Furnizori de date ADO.NET . . . . . . . . . . . . . . . . . 204 n 8.3 Componentele unui furnizor de date . . . . . . . . . . . . . . . 204 8.3.1 Clasele Connection . . . . . . . . . . . . . . . . . . . . 205 8.3.2 Clasele Command . . . . . . . . . . . . . . . . . . . . . 206 8.3.3 Clasele DataReader . . . . . . . . . . . . . . . . . . . . 206 8.3.4 Clasele DataAdapter . . . . . . . . . . . . . . . . . . . 206 8.3.5 Clasa DataSet . . . . . . . . . . . . . . . . . . . . . . . 206 8.4 Obiecte Connection . . . . . . . . . . . . . . . . . . . . . . . . 206 8.4.1 Proprieti . . . . . . . . . . . . . . . . . . . . . . . . . 207 at 8.4.2 Metode . . . . . . . . . . . . . . . . . . . . . . . . . . 209 8.4.3 Evenimente . . . . . . . . . . . . . . . . . . . . . . . . 209 8.4.4 Stocarea stringului de conexiune ier de congurare 209 n s 8.4.5 Gruparea conexiunilor . . . . . . . . . . . . . . . . . . 211 8.4.6 Mod de lucru cu conexiunile . . . . . . . . . . . . . . . 212 8.5 Obiecte Command . . . . . . . . . . . . . . . . . . . . . . . . 212 8.5.1 Proprieti . . . . . . . . . . . . . . . . . . . . . . . . . 213 at 8.5.2 Metode . . . . . . . . . . . . . . . . . . . . . . . . . . 214 8.5.3 Utilizarea unei comenzi cu o procedur stocat . . . . . 216 a a

CUPRINS 8.5.4 Folosirea comenzilor parametrizate . Obiecte DataReader . . . . . . . . . . . . . 8.6.1 Proprieti . . . . . . . . . . . . . . . at 8.6.2 Metode . . . . . . . . . . . . . . . . 8.6.3 Crearea i utilizarea unui DataReader s 8.6.4 Utilizarea de seturi de date multiple . 8.6.5 Seturi de date cu tip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . baza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7 217 218 219 219 220 221 221 223 223 224 225 225 226 226 229 229 230 231 233 235 237 239 239 241 241 242 243 245 245 246 246 247 249 250 252 254 255 256 256

8.6

9 ADO.NET (2) 9.1 Obiecte DataAdapter . . . . . . . . . 9.1.1 Metode . . . . . . . . . . . . 9.1.2 Proprieti . . . . . . . . . . . at 9.2 Clasa DataSet . . . . . . . . . . . . . 9.2.1 Coninut . . . . . . . . . . . . t 9.2.2 Clasa DataTable . . . . . . . 9.2.3 Relaii t ntre tabele . . . . . . 9.2.4 Popularea unui DataSet . . . 9.2.5 Clasa DataTableReader . . . . 9.2.6 Propagarea modicrilor ctre a a 9.3 Tranzacii ADO.NET . . . . . . . t n 9.4 Lucrul generic cu furnizori de date . 9.5 Tipuri nulabile . . . . . . . . . . . . 10 LINQ (I) 10.1 Generaliti . . . . . . . . . . . . at 10.2 Motivaie . . . . . . . . . . . . . t 10.2.1 Codul clasic ADO.NET . 10.2.2 Nepotrivirea de paradigme 10.3 LINQ to Objects: exemplicare . 10.4 Mecanisme utilizate de LINQ . . 10.4.1 Inferena tipului . . . . . . t 10.4.2 Tipuri anonime . . . . . . 10.4.3 Metode pariale . . . . . . t 10.4.4 Metode de extensie . . . . 10.4.5 Expresii lambda . . . . . . 10.5 Operatori LINQ . . . . . . . . . . 10.6 LINQ to Objects . . . . . . . . . 10.6.1 Filtarea cu Where . . . . . 10.6.2 Operatorul de proiecie . . t 10.6.3 Operatorul let . . . . . . 10.6.4 Operatorul SelectMany . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . de date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8 10.6.5 Jonciuni . . . . . . t 10.6.6 Grupare . . . . . . . 10.6.7 Ordonare . . . . . . 10.6.8 Agregare . . . . . . . 10.6.9 Partiionare . . . . . t 10.6.10 Concatenarea . . . . 10.6.11 Referirea de elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . din secvene . t . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

CUPRINS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258 258 259 260 262 262 263 265 265 268 268 269 269 270

11 LINQ (II): Linq to SQL 11.1 Obinerea unui context de date . . . . . . . . . . . . . . . . t 11.2 Adugarea, modicarea i tergerea de a s s nregistrri tabel a n a 11.2.1 Adugarea . . . . . . . . . . . . . . . . . . . . . . . . a 11.2.2 Modicarea unei nregistrri . . . . . . . . . . . . . . a 11.2.3 Stergerea unei nregistrri . . . . . . . . . . . . . . . a 11.3 Opiuni de arcare a datelor . . . . . . . . . . . . . . . . . t nc 12 Atribute. Fire de execuie t 12.1 Atribute . . . . . . . . . . . . . . . . . . . . . . 12.1.1 Generaliti . . . . . . . . . . . . . . . . at 12.1.2 Atribute predenite . . . . . . . . . . . . 12.1.3 Exemplicarea altor atribute predenite 12.1.4 Atribute denite de utilizator . . . . . . 12.2 Fire de execuie . . . . . . . . . . . . . . . . . . t 12.3 Managementul threadurilor . . . . . . . . . . . 12.3.1 Pornirea threadurilor . . . . . . . . . . 12.3.2 Metoda Join() . . . . . . . . . . . . . . 12.3.3 Suspendarea relor de execuie . . . . . t 12.3.4 Omorrea threadurilor . . . . . . . . . a 12.3.5 Sugerarea prioritilor relor de execuie at t 12.3.6 Fire fundal i re prim-plan . . . . n s n 12.4 Sincronizarea . . . . . . . . . . . . . . . . . . . 12.4.1 Clasa Interlocked . . . . . . . . . . . . . 12.4.2 Instruciunea lock . . . . . . . . . . . . . t 12.4.3 Clasa Monitor . . . . . . . . . . . . . . . 13 Nouti C# 4.0 at n 13.1 Parallel Linq . . . . . . . . . . . . . . . . 13.2 Parametri cu nume i parametri opionali s t 13.3 Tipuri de date dinamice . . . . . . . . . 13.4 COM Interop . . . . . . . . . . . . . . . 13.5 Covariana i contravariana . . . . . . . t s t . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

273 . 273 . 273 . 275 . 277 . 280 . 283 . 283 . 283 . 286 . 286 . 287 . 290 . 291 . 292 . 295 . 296 . 297 301 . 301 . 303 . 305 . 306 . 308

CUPRINS

14 Fluxuri 311 14.1 Sistemul de iere . . . . . . . . . . . . . . . . . . . . . . . . . 311 s 14.1.1 Lucrul cu directoarele: clasele Directory i DirectoryInfo 312 s 14.1.2 Lucrul cu ierele: clasele FileInfo i File . . . . . . . . 314 s s 14.2 Citirea i scrierea datelor . . . . . . . . . . . . . . . . . . . . . 319 s 14.2.1 Clasa Stream . . . . . . . . . . . . . . . . . . . . . . . 319 14.2.2 Clasa FileStream . . . . . . . . . . . . . . . . . . . . . 321 14.2.3 Clasa MemoryStream . . . . . . . . . . . . . . . . . . . 321 14.2.4 Clasa BueredStream . . . . . . . . . . . . . . . . . . . 322 14.2.5 Clasele BinaryReader i BinaryWriter . . . . . . . . . 323 s 14.2.6 Clasele TextReader, TextWriter i descendentele lor . . 324 s 14.3 Operare sincron i asincron . . . . . . . . . . . . . . . . . . 326 as a 14.4 Streamuri Web . . . . . . . . . . . . . . . . . . . . . . . . . . 329 14.5 Serializarea . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 14.5.1 Crearea unui obiect serializabil . . . . . . . . . . . . . 329 14.5.2 Serializarea . . . . . . . . . . . . . . . . . . . . . . . . 330 14.5.3 Deserializarea unui obiect . . . . . . . . . . . . . . . . 330 14.5.4 Date tranziente . . . . . . . . . . . . . . . . . . . . . . 331 14.5.5 Operaii la deserializare . . . . . . . . . . . . . . . . . 331 t Bibliograe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333

10

CUPRINS

Curs 1 Platforma Microsoft .NET


1.1 Prezentare general a

Platforma .NET 3.5 este un cadru de dezvoltare a softului, sub care se vor realiza, distribui si rula aplicaiile de tip forme Windows, aplicaii WEB t t si servicii WEB. Ea const trei pri principale: Common Language Runa n at time, clasele specice platformei i ASP.NET. O infrastructur ajuttoare, s a a Microsoft .NET Compact Framework este un set de interfee de programare t care permite dezvoltatorilor realizarea de aplicaii pentru dispozitive mobile t precum telefoane inteligente i PDA-uri1 . s .NET Framework constituie un nivel de abstractizare ntre aplicaie i t s nucleulul sistemului de operare (sau alte programe), pentru a asigura portabilitatea codului; de asemenea integreaz tehnologii care au fost lansate de a catre Microsoft incepnd cu mijlocul anilor 90 (COM, DCOM, ActiveX, etc) a sau tehnologii actuale (servicii Web, XML). Platforma const cteva grupe de produse: a n a 1. Unelte de dezvoltare - un set de limbaje (C#, Visual Basic .NET, J#, C++/CLI, JScript.NET, Objective-C, Python, Smalltalk, Eiel, Perl, Fortran, Cobol, Lisp, Haskell, Pascal, RPG, etc), un set de medii de dezvoltare (Visual Studio .NET, Visio), infrastructura .NET Framework, o bibliotec cuprinztoare de clase pentru crearea serviciilor Web a a 2 (Web Services) , aplicaiilor Web (Web Forms, ASP.NET MVC) i a t s aplicaiilor Windows (Windows Forms). t 2. Servere specializate - un set de servere Enterprise .NET: SQL Server 2008, Exchange 2007, BizTalk Server 2006, SharePoint, etc, care pun la
1 2

Deniia ocial Microsoft, martie 2005, www.microsoft.com/net/basics/glossary.asp t a Serviciilor Web - aplicaii care ofer servicii folosind Web-ul ca modalitate de acces. t a

11

12

CURS 1. PLATFORMA MICROSOFT .NET dispoziie funcionaliti diverse pentru stocarea bazelor de date, email, t t at 3 aplicaii B2B . t 3. Servicii Web - cel mai notabil exemplu este .NET Passport - un mod prin care utilizatorii se pot autentica pe site-urile Web vizitate, folosind un singur nume i o parol pentru toate. Alt exemplu este MapPoint s a care permite vizualizarea, editarea si integrarea hartilor. 4. Dispozitive - noi dispozitive nonPC, programabile prin .NET Compact Framework, o versiune redus a lui .NET Framework: Pocket PC Phone a Edition, Smartphone, Tablet PC, Smart Display, XBox, set-top boxes, etc.

Motivul pentru care Microsoft a trecut la dezvoltarea acestei platforme este maturizarea industriei software, accentunduse urmtoarele direcii: a a t 1. Aplicaiile distribuite - sunt din ce ce mai numeroase aplicaiile de t n t tip client / server sau cele pe mai multe nivele (ntier). Tehnologiile distribuite actuale cer de multe ori o mare anitate faa de productor t a i prezint o carena acut a interoperrii cu Web-ul. Viziunea actual s a t a a a se deprteaz de cea de tip client/server ctre una care calculatoare, a a a n dispozitive inteligente i servicii conlucreaz pentru atingerea scopurilor s a propuse. Toate acestea se fac deja folosind standarde Internet neproprietare (HTTP, XML, SOAP). 2. Dezvoltarea orientat pe componente - este de mult timp cerut sima a plicarea integrrii componentelor software dezvoltate de diferii proa t ductori. COM (Component Object Model) a realizat acest deziderat, a dar dezvoltarea i distribuirea aplicaiilor COM este prea complex. s t a Microsoft .NET pune la dispoziie un mod mai simplu de a dezvolta i t s a distribui componente. 3. Modicri ale paradigmei Web - de-a lungul timpului sau adus a mbuntiri tehnologiilor Web pentru a simplica dezvoltarea aplicaiilor. a at t ultimii ani, dezvoltarea aplicaiilor Web sa mutat de la prezentare In t (HTML i adiacente) ctre capacitate sporit de programare (XML i s a a s SOAP). 4. Ali factori de maturizare a industriei software - reprezint contientit a s zarea cererilor de interoperabilitate, scalabilitate, disponibilitate; unul din dezideratele .NET este de a oferi toate acestea.
Bussiness to Bussiness - Comer electronic t ntre parteneri de afaceri, diferit de a comerul electronic t ntre client i afacere (Bussiness to Consumer B2C). s
3

1.2. ARHITECTURA PLATFORMEI MICROSOFT .NET

13

1.2

Arhitectura platformei Microsoft .NET

Figura 1.1: Arhitectura .NET Figura 1.1 schematizeaz arhitectura platformei Microsoft .NET. Orice a program scris ntr-unul din limbajele .NET este compilat Common Inn termediate Language4 , concordana cu Common Language Specication n t (CLS). Aceste limbaje sunt sprijinite de o bogat colecie de biblioteci de a t clase, ce pun la dispoziie faciliti pentru dezvoltarea de Web Forms, Wint at dows Forms i Web Services. Comunicarea dintre aplicaii i servicii se face pe s t s baza unor clase de manipulare XML i a datelor, ceea ce sprijin dezvoltarea s a aplicaiilor cu arhitectur n-tier. Base Class Library exist pentru a asigura t a a funcionalitate de nivel sczut, precum operaii de I/O, re de execuie, lucrul t a t t cu iruri de caractere, comunicaie prin reea, etc. Aceste clase sunt reunite s t t sub numele de .NET Framework Class Library, ce permite dezvoltarea rapid a a aplicaiilor. La baza tuturor se a cea mai important component a lui t a a a .NET Framework - Common Language Runtime, care rspunde de execuia a t ecrui program. Evident, nivelul inferior este rezervat sistemului de operare. a Trebuie spus c platforma .NET nu este exclusiv dezvoltat pentru sistemul a a
Anterior numit i Microsoft Intermediate Language (MSIL) sau Intermediate Lans guage (IL).
4

14

CURS 1. PLATFORMA MICROSOFT .NET

de operare Microsoft Windows, ci i pentru arome de Unix (FreeBSD sau s 5 Linux - a se vedea proiectul Mono ).

1.3
1.3.1

Componente ale lui .NET Framework


Common Intermediate Language

Una din uneltele de care dispune ingineria software este abstractizarea. Deseori vrem s ferim utilizatorul de detalii, s punem la dispoziia altora a a t mecansime sau cunotine generale, care s permit atingerea scopului, fr s t a a aa a necesare cunoaterea tuturor detaliilor. Dac interfaa rmne neschims a t a a bat, se pot modica toate detaliile interne, fr a afecta aciunile celorlai a aa t t beneciari ai codului. cazul limbajelor de programare, s-a ajuns treptat la crearea unor nivele In de abstractizare a codului rezultat la compilare, precum p-code (cel produs de compilatorul Pascal-P) i bytecode (binecunoscut celor care au lucrat Java). s n Bytecode-ul Java, generat prin compilarea unui ier surs, este cod scris s a ntrun limbaj intermediar care suport POO. Bytecod-ul este acelai timp o a n s abstractizare care permite executarea codului Java, indiferent de platforma int, atta timp ct aceast platform are implementat o main virtual t a a a a a a s a a Java, capabil s traduc mai departe ierul class cod nativ. a a a s n Microsoft a realizat i el propria sa abstractizare de limbaj, aceasta nus mindu-se Common Intermediate Language. Dei exist mai multe limbaje s a de programare de nivel nalt (C#, Managed C++, Visual Basic .NET, etc), la compilare toate vor produce cod acelai limbaj intermediar: Common n s Intermediate Language. Asemntor cu bytecod-ul, CIL are trsturi OO, a a aa precum abstractizarea datelor, motenirea, polimorsmul, sau concepte care s s-au dovedit a extrem de necesare, precum excepiile sau evenimentele. t De remarcat c aceast abstractizare de limbaj permite rularea aplicaiilor a a t independent de platform (cu aceeai condiie ca la Java: s existe o main a s t a s a virtual pentru acea platform). a a

1.3.2

Common Language Specication

Unul din scopurile .NET este de a sprijini integrarea limbajelor astfel at programele, dei scrise diferite limbaje, pot interopera, folosind din nc s n plin motenirea, polimorsmul, s ncapsularea, excepiile, etc. Dar limbajele t nu sunt identice: de exemplu, unele sunt case sensitive, altele nu. Pentru a se asigura interoperabilitatea codului scris diferite limbaje, Microsoft n
5

www.go-mono.com

1.3. COMPONENTE ALE LUI .NET FRAMEWORK

15

a publicat Common Language Specication (CLS), un subset al lui CTS (Common Type System, vezi 1.3.4), coninnd specicaii de reguli necesare t a t pentru integrarea limbajelor. CLS denete un set de reguli pentru compilatoarele .NET, asigurnd s a faptul c ecare compilator va genera cod care interfereaz cu platforma a a (mai exact, cu CLRul vezi mai jos) ntr-un mod independent de limbajul surs. Obiectele i tipurile create diferite limbaje pot interaciona fr a s n t aa probleme suplimentare. Combinaia CTS/CLS realizeaz de fapt interopert a area limbajelor. Concret, se poate ca o clas scris C# s e motenit a a n a s a de o clas scris Visual Basic care arunc excepii ce sunt prinse de cod a a n a t scris C++ sau J#. n

1.3.3

Common Language Runtime

CLR este de departe cea mai important component a lui .NET Framea a work. Este responsabil cu managementul i execuia codului scris limbaje a s t n .NET, aat format CIL; este foarte similar cu Java Virtual Machine. CLR n instaniaz obiectele, face vericri de securitate, depune obiectele memt a a n orie, disponibilizeaz memoria prin garbage collection. a urma compilrii unei aplicaii poate rezulta un ier cu extensia exe, In a t s dar care nu este un executabil portabil Windows, ci un executabil portabil .NET (.NET PE). Acest cod nu este deci un executabil nativ, ci se va rula de ctre CLR, a ntocmai cum un ier class este rulat cadrul JVM. CLR s n folosete tehnologia compilrii JIT - o implementare de main virtual, s a s a a n care o metod sau o funcie, momentul care este apelat pentru prima a t n n a oar, este tradus cod main. Codul translatat este depus a a n s a ntr-un cache, evitnd-se astfel recompilarea ulterioar. Exist 3 tipuri de compilatoare a a a JIT: 1. Normal JIT - a se vedea descrierea de mai sus. 2. Pre-JIT - compileaz a ntregul cod cod nativ singur dat. mod n a a In normal este folosit la instalri. a 3. Econo-JIT - se foloete pe dispozitive cu resurse limitate. Compileaz s s a codul CIL bit cu bit, elibernd resursele folosite de codul nativ ce este a stocat cache. n esena, activitatea unui compilator JIT este destinat a In t a mbunti pera at formana execuiei, ca alternativ la compilarea repetat a aceleiai buci t t a a s at de cod cazul unor apelri multiple. Unul din avantajele mecanismului JIT n a apare clipa care codul, o dat ce a fost compilat, se execut pe diverse n n a a

16

CURS 1. PLATFORMA MICROSOFT .NET

procesoare; dac maina virtual este bine adaptat la noua platform, atunci a s a a a acest cod va benecia de toate optimizrile posibile, fr a mai nevoie a aa recompilarea lui (precum C++, de exemplu). n

1.3.4

Common Type System

Pentru a asigura interoperabilitatea limbajelor din .NET Framework, o clas scris C# trebuie s e echivalent cu una scris VB.NET, o a a n a a a n interfaa scris Managed C++ trebuie s e perfect utilizabil Managed t a n a a n Cobol. Toate limbajele care fac parte din pleiada .NET trebuie s aibe un set a comun de concepte pentru a putea integrate. Modul care acest deziderat n s-a transformat realitate se numete Common Type System (CTS); orice n s limbaj trebuie s recunoasc i s poat manipula nite tipuri comune. a as a a s O scurt descriere a unor faciliti comune (ce vor exhaustiv enumerate a at i tratate cadrul prezentrii limbajului C#): s n a 1. Tipuri valoare - general, CLR-ul (care se ocup de managementul i n a s execuia codului CIL, vezi mai jos) suport dou tipuri diferite: tipuri t a a valoare i tipuri referina. Tipurile valoare reprezint tipuri alocate s t a pe stiv i nu pot avea valoare de null. Tipurile valoare includ tipuas rile primitive, structuri i enumerri. Datorit faptului c de regul s a a a a au dimensiuni mici i sunt alocate pe stiv, se manipuleaza ecient, s a reducnd overhead-ul cerut de mecanismul de garbage collection. a 2. Tipuri referin - se folosesc dac variabilele de un anumit tip cer ta a resurse de memorie semnicative. Variabilele de tip referina conin t t adrese de memorie heap i pot null. Transferul parametrilor se face s rapid, dar referinele induc un cost suplimentar datorit mecanismului t a de garbage collection. 3. Boxing i unboxing - motivul pentru care exist tipuri primitive este s a a orice variabil .NET este acelai ca i Java: performana. Ins s s n t a n compatibil cu clasa Object, rdcina ierarhiei existente .NET. De a a a n exemplu, int este un alias pentru System.Int32, care se deriveaz din a System.ValueType. Tipurile valoare se stocheaz pe stiv, dar pot a a oricnd convertite a ntr-un tip referina memorat heap; acest mecant n ism se numete boxing. De exemplu: s int i = 1; //i - un tip valoare Object box = i; //box - un obiect referinta Cnd se face boxing, se obine un obiect care poate gestionat la fel a t ca oricare altul, fcnduse abstracie de originea lui. a a t

1.3. COMPONENTE ALE LUI .NET FRAMEWORK

17

Inversa boxing-ului este unboxing-ul, prin care se poate converti un obiect tipul valoare echivalent, ca mai jos: n int j = (int)box; unde operatorul de conversie este sucient pentru a converti de la un obiect la o variabil de tip valoare. a 4. Clase, proprieti, indexatori - platforma .NET suport pe deplin proat a gramarea orientat pe obiecte, concepte legate de obiecte ( a ncapsularea, motenirea, polimorsmul) sau trsturi legate de clase (metode, cms aa a puri, membri statici, vizibilitate, accesibilitate, tipuri imbricate, etc). De asemenea se includ trsturi precum proprieti, indexatori, eveniaa at mente. 5. Interfee - reprezint acelai concept precum clasele abstracte din C++ t a s (dar coninnd doar funcii virtuale pure), sau interfeele Java. O t a t t clas care se deriveaz dintr-o interfaa trebuie s implementeze toate a a t a metodele acelei interfee. Se permite implementarea simultan a mai t a multor interfee ( rest motenirea claselor este simpl). t n s a 6. Delegai - inspirai de pointerii la funcii din C, ce permit programarea t t t generic. Reprezint versiunea sigur a pointerilor ctre funcii din a a a a t C/C++ i sunt mecanismul prin care se trateaz evenimentele. s a Exist numeroase componente ale lui CLR care fac cea mai impora l tant parte a lui .NET Framework: metadata, assemblies, assembly cache, a reection, garbage collection.

1.3.5

Metadate

Metadatele nseamn date despre date. cazul .NET, ele reprezint dea In a talii destinate a citite i folosite de ctre platform. Sunt stocate s a a mpreun a cu codul pe care descrie. Pe baza metadatelor, CLR tie cum s instanieze l s a t obiectele, cum s le apeleze metodele, cum s acceseze proprietile. Printra a at un mecanism numit reectare, o aplicaie (nu neaprat CLR) poate s int a a terogheze aceast metadat i s ae ce expune un tip de date (clas, struca as a a tur, etc). a Mai pe larg, metadata conine o declaraie a ecrui tip i cte o declaraie t t a s a t pentru ecare metod, cmp, proprietate, eveniment al tipului respectiv. a a Pentru ecare metod implementat, metadata conine informaie care pera a t t mite arctorului clasei respective s localizeze corpul metodei. De asemena nc a a mai poate conine declaraii despre cultura aplicaiei respective, adic despre t t t a localizarea ei (limba folosit partea de interfaa utilizator). a n t

18

CURS 1. PLATFORMA MICROSOFT .NET

1.3.6

Assemblies

Un assembly reprezint un bloc funcional al unei aplicaii .NET. El a t t formeaz unitatea fundamental de distribuire, versionare, reutilizare i pera a s misiuni de securitate. La runtime, un tip de date exist interiorul unui a n assembly (i nu poate exista exteriorul acestuia). Un assembly conine s n t metadate care sunt folosite de ctre CLR. Scopul acestor assemblies este a s se asigure dezvoltarea softului mod plug-and-play. Dar metadatele nu a n sunt suciente pentru acest lucru; mai sunt necesare i manifestele. s Un manifest reprezint metadate despre assembly-ul care gzduiete tia a s purile de date. Conine numele assembly-ului, numrul de versiune, referiri t a la alte assemblies, o list a tipurilor assembly, permisiuni de securitate i a n s altele. Un assembly care este arit mp t ntre mai multe aplicaii are de asemenea t un shared name. Aceast informaie care este unic este opional, neaprnd a t a t a aa manifestul unui assembly daca acesta nu a fost gndit ca o aplicaie parn a t tajat. a Deoarece un assembly conine date care descriu, instalarea lui poate t l fcut copiind assemblyul directorul destinaie dorit. Cnd se dorete rua a n t a s larea unei aplicaii coninute assembly, manifestul va instrui mediul .NET t t n despre modulele care sunt coninute assembly. Sunt folosite de asemenea t n i referinele ctre orice assembly extern de care are nevoie aplicaia. s t a t Versionarea este un aspect deosebit de important pentru a se evita aa s numitul DLL Hell. Scenariile precedente erau de tipul: se instaleaza o aplicaie care aduce nite iere .dll necesare pentru functionare. Ulterior, o t s s alt aplicaie care se instaleaz suprascrie aceste iere (sau mcar unul din a t a s a ele) cu o versiune mai nou, dar cu care vechea aplicaie nu mai funcioneaz a t t a corespunztor. Reinstalarea vechii aplicaii nu rezolv problema, deoarece a t a a doua aplicaie nu va funciona. Dei ierele dll conin informaie relativ t t s s t t la versiune interiorul lor, ea nu este folosit de ctre sistemul de operare, n a a ceea ce duce la probleme. O soluie la aceast dilem ar instalarea ierelor t a a s dll directorul aplicaiei, dar acest mod ar disprea reutilizarea acestor n t n a biblioteci.

1.3.7

Assembly cache

Assembly cache este un director aat mod normal directorul %windir% n n \Assembly. Atunci cnd un assembly este instalat pe o main, el va a s a adugat assembly cache. Dac un assembly este descrcat de pe Internet, a n a a el va stocat assembly cache, n ntr-o zon tranzient. Aplicaiile instalate a a t vor avea assemblies ntr-un assembly cache global.

1.4. TRASATURI ALE PLATFORMEI .NET

19

acest assembly cache vor exista versiuni multiple ale aceluiai assemIn s bly. Dac programul de instalare este scris corect, va evita suprascrierea a assembly-urilor deja existente (i care funcioneaz perfect cu acplicaiile s t a t instalate), adugnd doar noul assembly. Este un mod de rezolvare a proba a lemei DLL Hell, unde suprascrierea unei biblioteci dinamice cu o variant a mai nou putea duce la nefuncionarea corespunztoare a aplicaiilor antea t a t rior instalate. CLR este cel care decide, pe baza informaiilor din manifest, t care este versiunea corect de assembly de care o aplicaie are nevoie. Acest a t mecanism pune capt unei epoci de trist amintire pentru programatori i a a s utilizatori.

1.3.8

Garbage collection

Managementul memoriei este una din sarcinile cele mai consumatoare de timp programare. Garbage collection este mecanismul care se declaneaz n s a atunci cnd alocatorul de memorie rspunde negativ la o cerere de alocare de a a memorie. Implementarea este de tip mark and sweep: se presupune iniial t c toat memoria alocat se poate disponibiliza, dupa care se determin a a a a care din obiecte sunt referite de variabilele aplicaiei; cele care nu mai sunt t referite sunt dealocate, celelalte zone de memorie sunt compactate. Obiectele a cror dimensiune de memorie este mai mare dect un anumit prag nu mai a a sunt mutate, pentru a nu crete semnicativ penalizarea de performana. s t general, CLR este cel care se ocup de apelarea mecanismului de In a garbage collection. Totui, la dorina, programatorul poate sugera rularea s t lui.

1.4

Trsturi ale platformei .NET a a

Prin prisma celor prezentate pna acum putem rezuma urmtoarele tra a a sturi: a Dezvoltarea multilimbaj: Deoarece exist mai multe limbaje pentru a aceast platform, este mai uor de implementat pri specice lima a s at n bajele cele mai adecvate. Numarul limbajelor curent implementate este mai mare dect 10. Aceast dezvoltare are vedere i debugging-ul a a n s aplicaiilor dezvoltate mai multe limbaje. t n Independena de procesor i de platform: CIL este independent t s a de procesor. O dat scris i compilat, orice aplicaie .NET (al crei a as a t a management este fcut de ctre CLR) poate rulat pe orice platform. a a a a

20

CURS 1. PLATFORMA MICROSOFT .NET Datorit CLR-ului, aplicaia este izolat de particularitile hardware a t a at sau ale sistemului de operare. Managementul automat al memoriei: Problemele de dealocare de memorie sunt mare parte rezolvate; overhead-ul indus de ctre n a mecanismul de garbage collection este suportabil, ind implementat n sisteme mult mai timpurii. Menionm totui c la ora actual exist t a s a a a a posibilitatea de a avea memory leak intr-o aplicaie gestionat de nc t a ctre platforma .NET6 . a Suportul pentru versionare: Ca o lecie aat din perioada de t nvt a DLL Hell, versionarea este acum un aspect de care se ine cont. Dac t a o aplicaie a fost dezvoltat i testat folosind anumite componente, t a s a instalarea unei componente de versiune mai nou nu va atenta la buna a funcionare a aplicaiei discuie: cele dou versiuni vor coexista t t n t a panic, alegerea lor ind fcut pe baza manifestelor. s a a Sprijinirea standardelor deschise: Nu toate dispozitivele ruleaz a sisteme de operare Microsoft sau folosesc procesoare Intel. Din aceast a cauz orice este strns legat de acestea este evitat. Se folosete XML i a a s s cel mai vizibil descendent al acestuia, SOAP. Deoarece SOAP este un protocol simplu, bazat pe XML, ce folosete ca protocol de transmisie s 7 HTTP , el poate trece uor de rewall-uri, spre deosebire de DCOM s sau CORBA. Distribuirea uoar: Actualmente instalarea unei aplicaii sub Wins a t dows nseamn copierea unor iere nite directoare anume, moa s n s dicarea unor valori regitri, instalare de componente COM, etc. n s Dezinstalarea complet a unei aplicaii este in majoritatea cazurilor a t o utopie. Aplicaiile .NET, datorit metadatelor i reectrii trec de t a s a aceste probleme. Se dorete ca instalarea unei aplicaii s nu s t a nsemne mai mult dect copierea ierelor necesare a s ntr-un director, iar dezinstalarea aplicaiei s se fac prin tergerea acelui director. t a a s Arhitectur distribuit: Noua losoe este de a asigura accesul la a a servicii Web distribuite; acestea conlucreaz la obinerea informaiei a t t dorite. Platforma .NET asigur suport i unelte pentru realizarea acesa s tui tip de aplicaii. t Interoperabilitate cu codul unmanaged: Codul unmanaged se refer la cod care nu se a totalitate sub controlul CLR. El este rulat a a n
6 7

How to Detect and Avoid Memory and Resource Leaks in .NET Applications Folosit la transmiterea paginilor Web pe Internet

1.4. TRASATURI ALE PLATFORMEI .NET

21

de CLR, dar nu beneciaz de CTS sau garbage collection. Este vorba a de apelurile funciilor din DLL-uri, folosirea componentelor COM, sau t folosirea de ctre o component COM a unei componente .NET. Codul a a existent se poate folosi continuare. n Securitate: Aplicaiile bazate pe componente distribuite cer automat t securitate. Modalitatea actual de securizare, bazat pe drepturile cona a tului utilizatorului, sau cel din Java, care codul suspectat este rulat n ntr-un sandbox, fr acces la resursele critice este aa nlocuit .NET n de un control mai n, pe baza metadatelor din assembly (zona din care provine - ex. Internet, intranet, maina local, etc) precum i a s a s politicilor de securitate ce se pot seta.

22

CURS 1. PLATFORMA MICROSOFT .NET

Curs 2 Vedere general asupra limbajului a C#. Tipuri predenite. Tablouri. Siruri de caractere
2.1 Vedere general asupra limbajului C# a

C#1 este un limbaj de programare imperativ, obiectorientat. Este foarte asemntor cu Java i C++, motiv pentru care curba de aare este foarte a a s nvt lin. a Un prim exemplu de program, care conine o serie de elemente ce se vor t alni continuare, este: nt n using System; class HelloWorld { public static void Main() { Console.WriteLine(Hello world!); } } Prima linie using System; este o directiv care specic faptul c se vor a a a folosi clasele care sunt incluse spaiul de nume2 System; un spaiu de n t t nume este o colecie de tipuri sau o grupare de alte spaii de nume care pot t t folosite ntr-un program (detalii vor date mai trziu). cazul de faa, a In t clasa care este folosit din acest spaiu de nume este Console. Mai departe, a t
1 2

# se pronun sharp ta Eng: namespace

23

24

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

orice program este coninut t ntro clas, cazul nostru HelloWorld. Punctul a n de intrare aplicaie este metoda Main, care nu preia acest exemplu nici n t n un argument din linia de comand i nu returneaz explicit un indicator de as a stare a terminrii. a Pentru operaiile de intrareieire cu consola se folosete clasa Console; t s s pentru ea se apeleaz metoda static WriteLine, care aeaz pe ecran mesajul, a a s a dup care face trecerea la linie nou. a a O variant uor modicat a programului de mai sus, care se salut a s a n a persoanele ale cror nume este transmis prin linia de comand: a a using System; class HelloWorld { public static void Main( String[] args) { for( int i=0; i<args.Length; i++) { Console.WriteLine( Hello {0}, args[i]); } } } exemplul precedent metoda principal preia o list de parametri transmii In a a s din linia de comand (un ir de obiecte de tip String) i va aa pentru a s s s ecare nume Hello urmat de numele de indice i (numerotarea parametrilor ncepe de la 0). Construcia {0} va t nlocuit cu primul argument care a urmeaz dup Hello {0}. La executarea programului de mai sus forma: a a n HelloWorld Ana Dan, se va aa pe ecran: s Hello Ana Hello Dan Metoda Main poate s returneze o valoare a ntreag, care s e folosit a a a de ctre sistemul de operare pentru a semnala dac procesul sa a a ncheiat cu succes sau nu3 . Menionm faptul c limbajul este casesensitive4 . t a a Ca metode de notare Microsoft recomand folosirea urmtoarelor dou a a a convenii: t conventie Pascal, care prima liter a ecrui cuvnt se scrie ca liter n a a a a mare; exemplu: LoadData, SaveLogFile
3 4

De exemplu iere de comenzi prin testarea variabilei de mediu ERRORLEVEL n s Face distincie t ntre litere mari i mici s

2.2. TIPURI DE DATE

25

convenia tip "cmil" este la fel ca precedenta, dar primul caracter al t a a primului cuvnt nu este scris ca liter mare; exemplu: userIdentier, a a rstName. general, convenia tip Pascal este folosit pentru tot ce este vizibil (pubIn t a lic), precum nume de clase, metode, proprieti, etc. Parametrii metodelor at i numele cmpurilor se scriu cu convenia cmil. Se recomand evitarea s a t a a a folosirii notaiei ungare (numele unei entiti trebuie s se refere la semantica t at a ei, nu la tipul de reprezentare).

2.2

Tipuri de date

C# prezint dou grupuri de tipuri de date: tipuri valoare i tipuri a a s referin. Tipurile valoare includ tipurile simple (ex. char, int, oat)5 , tita purile enumerare i structur i au ca principale caracteristici faptul c ele s as a conin direct datele referite i sunt alocate pe stiv sau inline t s a ntro structur. Tipurile referina includ tipurile clas, interfaa, delegat i tablou, a t a t s toate avnd proprietatea c variabilele de acest tip stocheaz referine ctre a a a t a obiectele coninute. Demn de remarcat este c toate tipurile de date sunt t a derivate (direct sau nu) din tipul System.Object, punnd astfel la dispoziie a t un mod unitar de tratare a lor.

2.2.1

Tipuri predenite

C# conine un set de tipuri predenite, pentru care nu este necesar t a referirea vreunui spaiu de nume via directiva using sau calicare complet: t a string, object, tipurile ntregi cu semn i fr semn, tipuri numerice virgul s aa n a mobil, tipurile bool i decimal. a s Tipul string este folosit pentru manipularea irurilor de caractere cods icate Unicode; coninutul obiectelor de tip string nu se poate modica6 . t Clasa object este rdcina ierarhiei de clase din .NET, la care orice tip (ina a clusiv un tip valoare) poate convertit. Tipul bool este folosit pentru a reprezenta valorile logice true i false. s Tipul char este folosit pentru a reprezenta caractere Unicode, reprezentate pe 16 bii. Tipul decimal este folosit pentru calcule care erorile determinate t n de reprezentarea virgul mobil sunt inacceptabile, el punnd la dispoziie n a a a t 28 de cifre zecimale semnicative.
5 6

De fapt acestea sunt structuri, prezentate alt curs n Spunem despre un string c este invariabil - engl. immutable a

26

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

Tabelul de mai jos conine lista tipurilor predenite, artnd totodat t aa a cum se scriu valorile corespunztoare: a

2.2. TIPURI DE DATE Tabelul 2.1: Tipuri predenite.

27

Tip object string sbyte short int long byte ushort uint ulong

Descriere rdacina oricrui tip a a o secvena de caractere Unicode t tip ntreg cu semn, pe 8 bii t tip ntreg cu semn, pe 16 bii t tip ntreg cu semn, pe 32 bii t tip ntreg cu semn, pe 64 bii t tip ntreg tip ntreg tip ntreg tip ntreg fr aa fr aa fr aa fr aa semn, semn, semn, semn, pe pe pe pe 8 bii t 16 bii t 32 bii t 64 de bii t

oat double bool char decimal

tip cu virgul mobil, simpl precizie a a a tip virgul mobil, dubl precizie n a a a tip boolean tip caracter din setul Unicode tip zecimal cu 28 de cifre semnicative

Exemplu object a = null; string s = hello; sbyte val = 12; short val = 12; int val = 12; long val1 = 12; long val2=34L; byte val = 12; ushort val = 12; uint val = 12; ulong val1=12; ulong val2=34U; ulong val3=56L; ulong val4=76UL; oat val=1.23F; double val1=1.23; double val2=4.56D; bool val1=false; bool val2=true; char val=h; decimal val=1.23M;

Fiecare din tipurile predenite este un alias pentru un tip pus la dispoziie t de sistem. De exemplu, string este alias pentru clasa System.String, int este alias pentru System.Int32.

2.2.2

Tipuri valoare

C# pune programatorului la dispoziie tipuri valoare, care sunt e struct turi, e enumerri. Exist un set predenit de structuri numite tipuri simple, a a identicate prin cuvinte rezervate. Un tip simplu este e de tip numeric7 , e boolean. Tipurile numerice sunt tipuri ntregi, virgul mobil sau decimal. n a a Tipurile intregi sunt sbyte, byte, short, ushort, int, uint, long, ulong, char; cele virgul mobil sunt oat i double. Tipurile enumerare se pot deni n a a s de ctre utilizator. a
7

Engl: integral type

28

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

Toate tipurile valoare deriveaz din clasa System.ValueType, care la rndul a a ei este derivat din clasa object (alias pentru System.Object). Nu este posia bil ca dintrun tip valoare s se deriveze. Atribuirea pentru un astfel de tip a nseamn copierea valorii dintro parte alta. a n Structuri Un tip structur este un tip valoare care poate s conin declaraii de a a t a t constante, cmpuri, metode, proprieti, indexatori, operatori, constructori a at sau tipuri imbricate. Vor descrise ntrun capitol urmtor. a Tipuri simple C# pune are predenit un set de tipuri structuri numite tipuri simple. Tipurile simple sunt identicate prin cuvinte rezervate, dar acestea reprezint a doar aliasuri pentru tipurile struct corespunztoare din spaiul de nume a t System; corespondena este dat tabelul de mai jos: t a n Tabelul 2.2: Tipuri simple i corespondenele lor cu tis t purile din spaiul de nume System. t Tabelul 2.2 Cuvnt rezervat Tipul alias a sbyte System.SByte byte System.Byte short System.Int16 ushort System.UInt16 int System.Int32 uint System.UInt32 long System.Int64 ulong System.UInt64 char System.Char oat System.Single double System.Double bool System.Boolean decimal System.Decimal

Deoarece un tip simplu este un alias pentru un tip struct, orice tip simplu are membri. De exemplu, tipul int, ind un tip alias pentru System.Int32, urmtoarele declaraii sunt legale: a t

2.2. TIPURI DE DATE int i = int.MaxValue; //constanta System.Int32.MaxValue string s = i.ToString(); //metoda System.Int32.ToString() string t = 3.ToString(); //idem double d = Double.Parse("3.14"); Tipuri ntregi

29

C# suport nou tipuri a a ntregi: sbyte, byte, short, ushort, int, uint, long, ulong i char. Acestea au urmtoarele dimensiuni i domeniu de valori: s a s sbyte reprezint tip cu semn pe 8 bii, cu valori de la -128 la 127; a t byte reprezint tip fr semn pe 8 bii, a aa t ntre 0 i 255; s short reprezint tip cu semn pe 16 bii, a t ntre -32768 i 32767; s ushort reprezint tip fr semn pe 16 bii, a aa t ntre 0 i 65535; s int reprezint tip cu semn pe 32 de bii, a t ntre 231 i 231 1; s uint reprezint tip fr semn pe 32 de bii, a aa t ntre 0 i 232 1; s long reprezint tip cu semn pe 64 de bii, a t ntre 263 i 263 1; s ulong reprezint tip fr semn pe 64 de bii, a aa t ntre 0 i 264 1; s char reprezint tip fr semn pe 16 bii, cu valori a aa t ntre 0 i 65535. s Mulimea valorilor posibile pentru char corespunde setului de caractere t Unicode. Reprezentarea unei variable de tip ntreg se poate face sub form de ir a s de cifre zecimale sau hexazecimale, urmate eventual de un prex. Numerele exprimate hexazecimal sunt prexate cu 0x sau 0X. Regulile dup care n a se asigneaz un tip pentru o valoare sunt: a 1. dac irul de cifre nu are un sux, atunci el este considerat ca ind a s primul tip care poate s conin valoarea dat: int, uint, long, ulong; a t a a 2. dac irul de cifre are suxul u sau U, el este considerat ca ind din as primul tip care poate s conin valoarea dat: uint, ulong; a t a a 3. dac irul de cifre are suxul l sau L, el este considerat ca ind din a s primul tip care poate s conin valoarea dat: long, ulong; a t a a 4. dac irul de cifre are suxul ul, uL, Ul, UL, lu, lU, Lu, LU, el este a s considerat ca ind din tipul ulong.

30

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

Dac o valoare este afara domeniului lui ulong, apare o eroare la coma n pilare. Literalii de tip caracter au forma: caracter unde caracter poate exprimat printrun caracter, printro secvena escape simpl, secvena escape t a t prima form poate folosit hexazecimal sau secvena escape Unicode. In a t a orice caracter exceptnd apostrof, backslash i new line. Secvena escape a s t simpl poate : \, \", \\, \0, \a, \b, \f, \n, \r, \t, \v, cu semnicaiile a t cunoscute din C++. O secvena escape hexazecimal t a ncepe cu \x urmat de 14 cifre hexa. Dei ca reprezentare, char este identic cu ushort, nu toate s operaiile ce de pot efectua cu ushort sunt valabile i pentru char. t s cazul care o operaie aritmetic produce un rezultat care nu poate In n t a reprezentat tipul destinaie, comportamentul depinde de utilizarea operan t torilor sau a declaraiilor checked i unchecked (care se pot utiliza surs sau t s n a din linia de compilare): context checked, o eroare de depire duce la arunn as carea unei excepii de tip System.OverowException. context unchecked, t In eroarea de depire este ignorat, iar biii semnicativi care nu mai as a t ncap n reprezentare sunt eliminai. t Exemplu: byte i=255; unchecked { i++; }//i va avea valoarea 0, nu se semnaleaza eroare checked { i=255; i++;//se va arunca exceptie System.OverflowException } Pentru expresiile aritmetice care conin operatorii ++, - -, +, - (unar i bit s nar), *, / i care nu sunt coninute interiorul unui bloc de tip checked, s t n comportamentul este specicat prin intermediul opiunii /checked[+|] dat t din linia de comand pentru compilator. Dac nu se specic nimic, atunci a a a se va considera implicit unchecked. Tipuri virgul mobil n a a Sunt prezente 2 tipuri numerice virgul mobil: oat i double. Tipun a a s rile sunt reprezentate folosind precizie de 32, respectivi 64 de bii, folosind t formatul IEC 60559, care permit reprezentarea valorilor de 0 pozitiv i 0 s negativ (se comport la fel, dar anumite operaii duc la obinerea acestor a t t

2.2. TIPURI DE DATE

31

dou valori), + i (obinute prin arirea unui numr strict pozia s t mp t a tiv, respectiv strict negativ la 0), a valorii NotaNumber (NaN) (obinut t a prin operaii virgul mobil invalide, de exemplu 0/0 sau 1), precum t n a a i un set nit de numere. Tipul oat poate reprezenta valori cuprinse s ntre 45 38 1.5 10 i 3.4 10 (i din domeniul negativ corespunztor), cu o pres s a cizie de 7 cifre. Double poate reprezenta valori cuprinse ntre 5.0 10324 i s 308 1.7 10 cu o precizie de 15-16 cifre. Operaiile cu oating point nu duc niciodat la apariia de excepii, dar t a t t ele pot duce, caz de operaii invalide, la valori 0, innit sau NaN. n t Literalii care specic un numr reprezentat virgul mobil au forma: a a n a a literalreal:: cifre-zecimale . cifre-zecimale exponentoptional sux-de-tip-realoptional . cifre-zecimale exponentoptional sux-de-tip-realoptional cifre-zecimale exponent sux-de-tip-realoptional cifre-zecimale sux-de-tip-real, unde exponent:: e semnoptional cifre-zecimale E semnoptional cifre-zecimale, semn este + sau -, sux-de-tip-real este F, f, D, d. Dac nici un sux de tip a real nu este specicat, atunci literalul dat este de tip double. Suxul f sau F specic tip oat, d sau D specic double. Dac literalul specicat nu a a a poate reprezentat tipul precizat, apare eroare de compilare. n

Tipul decimal Este un tip de date reprezentat pe 128 de bii, gndit a folosit calcule t a n nanciare sau care necesit precizie mai mare. Poate reprezenta valori aate a intervalul 1.0 1028 i 7.9 1028 , cu 28 de cifre semnicative. Acest tip n s nu poate reprezenta zero cu semn, innit sau NaN. Dac urma operaiilor, a n t un numr este prea mic pentru a putea reprezentat ca decimal, atunci el a este fcut 0, iar dac este prea mare, rezult o excepie. Diferena principal a a a t t a faa de tipurile virgul mobil este c are o precizie mai mare, dar un t n a a a domeniu de reprezentare mai mic. Din cauza aceasta, nu se fac conversii implicite ntre nici un tip virgul mobil i decimal i nu este posibil n a a s s a mixarea variabilelor de acest tip ntr-o expresie, fr conversii explicite. aa Literalii de acest tip se exprim folosind ca sux-de-tip-real caracterele m a sau M. Dac valoarea specicat nu poate reprezentat prin tipul decimal, a a a apare o eroare la compilare.

32

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

Tipul bool Este folosit pentru reprezentarea valorilor de adevr true i false. Literalii a s care se pot folosi sunt true i false. Nu exist conversii standard s a ntre bool i nici un alt tip. s

2.2.3

Tipul enumerare

Tipul enumerare este un tip valoare, construit pentru a permite declararea constantelor nrudite, ntro manier clar i sigur din punct de vedere al a as a tipului. Un exemplu este: using System; public class Draw { public enum LineStyle { Solid Dotted, DotDash } public void DrawLine(int x1, int y1, int x2, int y2, LineStyle lineStyle) { if (lineStyle == LineStyle.Solid) { //cod desenare linie continua } else if (lineStyle == LineStyle.Dotted) { //cod desenare linie punctata } else if (lineStyle == LineStyle.DotDash) { //cod desenare segment linie-punct } else { throw new ArgumentException(Invalid line style);

2.2. TIPURI DE DATE } } } class Test { public static void Main() { Draw draw = new Draw(); draw.DrawLine(0, 0, 10, 10, Draw.LineStyle.Solid); draw.DrawLine(0, 0, 10, 10, (Draw.LineStyle)100); } }

33

Al doilea apel este legal, deoarece valorile care se pot specica pentru un enum nu sunt limitate la valorile declarate enum. Ca atare, programatorul n trebuie s fac validri suplimentare pentru a determina consistena valorilor. a a a t cazul de faa, la apelul de metod se arunc o excepie (excepiile vor In t a a t t tratate pe larg ntr-un curs viitor). Ca i mod de scriere a enumerrilor, se sugereaz folosirea conveniei s a a t Pascal att pentru numele tipului ct i pentru numele valorilor coninute. a a s t Enumerrile nu pot declarate abstracte i nu pot derivate. Orice a s enum este derivat automat din System.Enum, care este la rndul lui derivat a din System.ValueType; astfel, metodele motenite de la tipurile printe sunt s a utilizabile de ctre orice variabil de tip enum. a a Fiecare tip enumerare care este folosit are un tip de reprezentare8 , pentru a se cunoate ct spaiu de memorie trebuie s e alocat unei variabile de s a t a acest tip. Dac nu se specic nici un tip de reprezentare (ca mai sus), atunci a a se presupune implicit tipul int. Specicarea unui tip de reprezentare (care poate orice tip integral, exceptnd tipul char) se face prin enunarea tipului a t dup numele enumerrii: a a enum MyEnum : byte { small, large } Specicarea este folosit atunci cnd dimensiunea memorie este impora a n tant, sau cnd se dorete crearea unui tip de indicator (un tip ag) al crui a a s a
8

Engl: underlying type

34

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

numr de stri difer de numrul de bii alocai tipului int (modelare de a a a a t t ag-uri): enum ActionAttributes : ulong { Read = 1, Write = 2, Delete = 4, Query = 8, Sync = 16 //etc } ... ActionAttributes aa=ActionAttributes.Read|ActionAttributes.Write | ActionAttributes.Query; ... mod implicit, valoarea primului membru al unei structuri este 0, i In s ecare variabl care urmeaz are valoarea mai mare cu o unitate dect precea a a denta. La dorina, valoarea ecrui cmp poate specicat explicit: t a a enum Values { a = 1, b = 2, c = a + b } Urmtoarele observaii se impun relativ la lucrul cu tipul enumerare: a t 1. valorile specicate ca iniializatori trebuie s e reprezentabile prin t a tipul de reprezentare a enumerrii, altfel apare o eroare la compilare: a enum Out : byte { A = -1 }//eroare semnalata la compilare 2. mai muli membri pot avea aceeai valoare (manevr dictat de semant s a a tica tipului construit): enum ExamState {

2.2. TIPURI DE DATE passed = 10, failed = 1, rejected = failed }

35

3. dac pentru un membru nu este dat o valoare, acesta va lua valoarea a a membrului precedent + 1 (cu excepia primului membru vezi mai t sus) 4. nu se permit referine circulare: t enum CircularEnum { A = B, B }//A depinde explicit de B, B depinde implicit de A //eroare semnalata la compilare 5. este recomandat ca orice tip enum s conin un membru cu valoarea a t a 0, pentru c anumite contexte valoarea implicit pentru o variabil a n a a enum este 0, ceea ce poate duce la inconsistene i bug-uri greu de t s depanat enum Months { InvalidMonth,//are valoarea implicita 0, fiind primul element January, February, //etc } Tipurile enumerare pot convertite ctre tipul lor de baz i a a s napoi, folosind o conversie explicit (cast): a enum Values { a = 1, b = 5, c= 3 }

36

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

class Test { public static void Main() { Values v = (Values)3; int ival = (int)v; } } Valoarea 0 poate convertit ctre un enum fr cast: a a aa ... MyEnum me; ... if (me == 0) { //cod } Urmtorul cod arat cteva din articiile care pot aplicate tipului a a a enumerare: obinerea tipului unui element de tip enumerare precum i a t s tipului clasei de baz, a tipului de reprezentare, a valorilor coninute (ca a t nume simbolice i ca valori), conversie de la un string la un tip enumers are pe baza numelui, etc. Exemplul este preluat din <FrameworkSDK> \Samples\Technologies\ValueAndEnumTypes. using System; namespace DemoEnum { class DemoEnum { enum Color { Red = 111, Green = 222, Blue = 333 } private static void DemoEnums() { Console.WriteLine("\n\nDemo start: Demo of enumerated types."); Color c = Color.Red;

2.2. TIPURI DE DATE

37

// What type is this enum & what is it derived from Console.WriteLine(" The " + c.GetType() + " type is derived from " + c.GetType().BaseType); // What is the underlying type used for the Enums value Console.WriteLine(" Underlying type: " + Enum.GetUnderlyingType( typeof(Color))); // Display the set of legal enum values Color[] o = (Color[]) Enum.GetValues(c.GetType()); Console.WriteLine("\n Number of valid enum values: " + o.Length); for (int x = 0; x < o.Length; x++) { Color cc = ((Color)(o[x])); Console.WriteLine(" {0}: Name={1,7}\t\tNumber={2}", x, cc.ToString("G"), cc.ToString("D")); } // Check if a value is legal for this enum Console.WriteLine("\n 111 is a valid enum value: " + Enum.IsDefined( c.GetType(), 111)); // True Console.WriteLine(" 112 is a valid enum value: " + Enum.IsDefined( c.GetType(), 112)); // False // Check if two enums are equal Console.WriteLine("\n Is c equal to Red: " + (Color.Red == c));//True Console.WriteLine(" Is c equal to Blue: " + (Color.Blue == c));//False // Display the enums value as a string using different format specifiers Console.WriteLine("\n cs value as a string: " + c.ToString("G"));//Red Console.WriteLine(" cs value as a number: " + c.ToString("D"));//111 // Convert a string to an enums value c = (Color) (Enum.Parse(typeof(Color), "Blue")); try { c = (Color) (Enum.Parse(typeof(Color), "NotAColor"));//Not valid, //raises exception } catch (ArgumentException)

38

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

{ Console.WriteLine(" }

NotAColor is not a valid value for this enum.");

// Display the enums value as a string Console.WriteLine("\n cs value as a string: " + c.ToString("G"));//Blue Console.WriteLine(" cs value as a number: " + c.ToString("D"));//333 Console.WriteLine("Demo stop: Demo of enumerated types."); } static void Main() { DemoEnums(); } } }

2.3

Tablouri

De multe ori se dorete a se lucra cu o colecie de elemente de un anumit s t tip. O soluie pentru aceast problem o reprezint tablourile. Sintaxa de t a a a declarare este asemntoare cu cea din Java sau C++, dar ecare tablou a a este un obiect, derivat din clasa abstract System.Array. Accesul la elea mente se face prin intermediul indicilor care ncep de la 0 i se termin la s a numrul de elemente-1 (pentru un tablou unidimensional; cadrul unui a n tablou multidimensional valoarea indicelui maxim este numrul de elemente a de pe dimensiunea respectiv minus 1); orice depire a indicilor duce la a as apariia unei excepii: System.IndexOutOfRangeException. O variabil de t t a tip tablou poate avea valoare de null sau poate s indice ctre o instana a a t valid. a

2.3.1

Tablouri unidimensionale

Declararea unui tablou unidimensional se face prin plasarea de paranteze drepte ntre numele tipului tabloului i numele su, ca mai jos9 : s a int[] sir;
Spre deosebire de Java, nu se poate modica locul parantezelor, adic nu se poate a scrie: int sir[].
9

2.3. TABLOURI

39

Declararea de mai sus nu duce la alocare de spaiu pentru memorarea irului; t s instanierea se poate face ca mai jos: t sir = new int[10]; Exemplu: using System; class Unidimensional { public static int Main() { int[] sir; int n; Console.Write(Dimensiunea vectorului: ); n = Int32.Parse( Console.ReadLine() ); sir = new int[n]; for( int i=0; i<sir.Length; i++) { sir[i] = i * i; } for( int i=0; i<sir.Length; i++) { Console.WriteLine(sir[{0}]={1}, i, sir[i]); } return 0; } } acest exemplu se folosete proprietatea10 Length, care returneaz numrul a a In s tuturor elementelor vectorului (lucru mai vizibil la tablourile multidimensionale rectangulare). De menionat c acest context n i sir nu se pot declara t a n s la un loc, adic declaraii de genul int[] sir, n; sau int n, []sir; sunt a t incorecte (prima este corect din punct de vedere sintactic, dar ar rezulta c a a n este i el un tablou; al doilea caz, declaraia nu este corect sintactic). s n t a Se pot face iniializri ale valorilor coninute t a t ntrun tablou: int[] a = new int[] {1,2,3}; sau forma mai scurt: n a int[] a = {1,2,3};
10

Pentru noiunea de proprietate, vezi la partea despre clase. t

40

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

2.3.2

Tablouri multidimensionale

C# cunoate dou tipuri de tablouri multidimensionale: rectangulare i s a s neregulate11 . Numele lor vine de la forma pe care o pot avea. Tablouri rectangulare Tablourile rectangulare au proprietatea c numrul de elemente pentru o a a anumit dimensiune este pstrat constant; altfel spus, acest tip de tablouri a a au chiar form dreptunghiular. a a int[,] tab; unde tab este un tablou rectangular bidimensional. Instanierea se face: t tab = new int[2,3]; rezultnd un tablou cu 2 linii i 3 coloane; ecare linie are exact 3 eleemnte a s si acest lucru nu se poate schimba pentru tabloul declarat. Referirea la elementul aat pe linia i i coloana j se face cu tab[i, j]. s La declararea tabloului se poate face i iniializare: s t int[,] tab = new int[,] {{1,2},{3,4}}; sau, mai pe scurt: int[,] tab = {{1, 2}, {3, 4}}; Exemplu: using System; class Test { public static void Main() { int[,] tabInm = new int[10,10]; for( int i=0; i<tabInm.GetLength(0); i++ ) { for( int j=0; j<tabInm.GetLength(1); j++) { tabInm[i,j] = i * j; } }
11

Engl: jagged arrays.

2.3. TABLOURI

41

for( int i=0; i<tabInm.GetLength(0); i++) { for( int j=0; j<tabInm.GetLength(1); j++) { Console.WriteLine({0}*{1}={2}, i, j, tabInm[i,j]); } } Console.WriteLine(tabInm.Length={0}, tabInm.Length); } } Dup ce se aeaz tabla a s a nmulirii pn la 10, se va aa: tabInm.Length=100, t a a s deoarece proprietatea Length d numrul total de elemente aat tablou (pe a a n toate dimensiunile). Am folosit a metoda GetLength(d) care returneaz ns a numrul de elemente aate pe dimensiunea numrul d (numrarea dimensia a a unilor ncepe cu 0). Determinarea numrului de dimensiuni pentru un tablou rectangular la a runtime se face folosind proprietatea Rank a clasei de baz System.Array. a Exemplu: using System; class Dimensiuni { public static void Main() { int[] t1 = new int[2]; int[,] t2 = new int[3,4]; int[,,] t3 = new int[5,6,7]; Console.WriteLine(t1.Rank={0}\nt2.Rank={1}\nt3.Rank={2}, t1.Rank, t2.Rank, t3.Rank); } } Pe ecran va aprea: a t1.Rank=1 t2.Rank=2 t3.Rank=3

42

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

Tablouri neregulate a Un tablou neregulat12 reprezint un tablou de tabouri. Declararea unui tablou neregulat cu dou dimensiuni se face ca mai jos: a int[][] tab; Referirea la elementul de indici i i j se face prin tab[i][j]. s Exemplu: using System; class JaggedArray { public static void Main() { int[][] a = new int[2][]; a[0] = new int[2]; a[1] = new int[3]; for( int i=0; i<a[0].Length; i++) { a[0][i] = i; } for( int i=0; i<a[1].Length; i++) { a[1][i] = i * i; } for(int i=0; i<a.Length; i++) { for( int j=0; j<a[i].Length; j++ ) { Console.Write({0} , a[i][j]); } Console.WriteLine(); } Console.WriteLine(a.Rank={0}, a.Rank); } } va scrie pe ecran: 0 1 0 1 4 a.Rank=1
12

Engl: jagged array

2.4. SIRURI DE CARACTERE

43

Ultima linie aat se explic prin faptul c un tablou neregulat este un s a a a vector care conine referine, deci este unidimensional. t t Iniializarea valorilor unui tablou neregulat se poate face la declarare: t int[][] myJaggedArray = new int [][] { new int[] {1,3,5,7,9}, new int[] {0,2,4,6}, new int[] {11,22} }; Forma de mai sus se poate prescurta la: int[][] myJaggedArray = { new int[] {1,3,5,7,9}, new int[] {0,2,4,6}, new int[] {11,22} };

2.4

Siruri de caractere

Tipul de date folosit pentru reprezentarea irurilor de caractere este clasa s System.String (pentru care se poate folosi aliasul "string"; reamintim c a este un tip predenit). Obiectele de acest tip sunt imutabile (caracterele coninute nu se pot schimba, dar pe baza unui ir se poate obine un alt ir). t s t s Sirurile pot conine secvene escape i pot de dou tipuri: regulate i de t t s a s tip "verbatim"13 . Sirurile regulate sunt demarcate prin ghilimele i necesit s a secvene escape pentru reprezentarea caracterelor escape. t Exemplu: String a = "string literal"; String versuri = "vers1\nvers2"; String caleCompleta = "\\\\minimax\\protect\\csharp"; Pentru situaia care se utilizeaz masiv secvene escape, se pot folosi t n a t irurile verbatim. Un literal de acest tip are simbolul "@" s naintea ghilimelelor de nceput. Pentru cazul care ghilimelele sunt alnite interiorul irului, n nt n s ele se vor dubla. Un ir de caractere poate reprezentat pe mai multe rnduri s a fr a folosi caracterul \n. Sirurile verbatim sunt folosite pentru a face referiri aa la iere sau chei regitri, sau pentru expresii regulate. s n s Exemple:
13

Engl: verbatim literals

44

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI

String caleCompleta=@"\\minimax\protect\csharp"; //ghilimelele se dubleaza intr-un verbatim string String s=@"notiunea ""aleator"" se refera..."; //string multilinie reprezentat ca verbatim String dialog=@"-Alo? Cu ce va ajutam? -As avea nevoie de o informatie."; Operatorii "==" i "!=" pentru dou iruri de caractere se comport s a s a felul urmtor: dou iruri de caractere se consider egale dac sunt e n a a s a a amndou null, e au aceeai lungime i caracterele de pe aceleai poziii a a s s s t coincid; "!=" d negarea relaiei de egalitate. Clasa String pune la dispoziie a t t metode pentru: comparare (Compare, CompareOrdinal, CompareTo), cutare a (EndsWith, StartsWith, IndexOf, LastIndexOf), modicare (a se telege n obinerea altor obiecte pe baza celui curent - Concat, CopyTo, Insert, Join, t PadLeft, PadRight, Remove, Replace, Split, Substring, ToLower, ToUpper, Trim, TrimEnd, TrimStart)14 . Accesarea unui caracter aat pe o poziie i t a unui ir s se face prin folosirea parantezelor drepte, cu aceleai restricii s s t asupra indicelui ca i pentru tablouri: s[i]. s clasa object se a metoda ToString() care este suprascris ecare In a a n clas ale crei instane pot tiprite. Pentru obinerea unei reprezentri a a t a t a diferite se folosete metoda String.Format(). s Un exemplu de folosire a funciei Split() pentru desprirea unui ir t at s n funcie de separatori este: t using System; class Class1 { static void Main(string[] args) { String s = "Oh, I hadnt thought of that!"; char[] x = { , , }; String[] tokens = s.Split( x ); for(int i=0; i<tokens.Length; i++) { Console.WriteLine("Token: {0}", tokens[i]); } } } va aa pe ecran: s
14

A se vedea exemplele din MSDN.

2.4. SIRURI DE CARACTERE Token: Token: Token: Token: Token: Token: Token: Oh I hadnt thought of that!

45

De remarcat c pentru caracterul apostrof nu este obligatorie secvena esa t cape cazul irurilor de caractere. Al doilea lucru care trebuie explicat n s este c al doilea token este cuvntul vid, care apare a a ntre cei doi separatori alturai: virgula i spaiul. Metoda Split() nu face gruparea mai multor a t s t separatori, lucru care ar de dorit prezena a doi separatori alturai. n t a t Pentru aceasta putem apela la dou metode. Prima presupune folosirea unei a variante supra arcate a metodei Split, care se precizeaz ca al doilea nc n a parametru optiunea de ignorare a rezultatelor goale: String[] tokens = s.Split( new char[]{ , ,}, StringSplitOptions.RemoveEmptyEntries ); A doua modalitate se bazeaz pe folosirea expresiilor regulate. a Pentru a lucra cu iruri de caractere care permit modicarea lor (cons catenri repetate, substituiri de subiruri) se folosete clasa StringBuilder, a s s din spaiul de nume System.Text. t

2.4.1

Expresii regulate

cazul care funciile din clasa String nu sunt sucient de puternice, In n t namespaceul System.Text.RegularExpresions pune la dispoziie o clas de t a lucru cu expresii regulate numit Regex. Expresiile regulate reprezint o a a metod extrem de facil de a opera cutri/ a a a a nlocuiri pe text. Forma expresiilor regulate este cea din limbajul Perl. Aceast clas folosete o tehnic interesant pentru mrirea performana a s a a a elor: dac programatorul vrea, se scrie o secvena "din mers" pentru a t a t implementa potrivirea expresiei regulate, dup care codul este rulat15 . a Exemplul anterior poate rescris corect din puct de vedere al funcionalitii t at prin folosirea unei expresii regulate, pentru a prinde i cazul separatorilor s multipli adiaceni: t class ExpresieRegulata {
15

Codul este scris direct IL. n

46

CURS 2. TIPURI PREDEFINITE, TABLOURI, STRING-URI static void Main(string[] args) { String s = "Oh, I hadnt thought of that!"; //separator: virgula, spatiu sau punct si virgula //unul sau mai multe, orice combinatie Regex regex = new Regex("[, ;]+"); String[] strs = regex.Split(s); for( int i=0; i<strs.Length; i++) { Console.WriteLine("Word: {0}", strs[i]); }

} care va produce: Word: Word: Word: Word: Word: Word: Oh I hadnt thought of that!

Curs 3 Clase generaliti. Instruciuni. at t Spaii de nume t


3.1 Clase vedere general a

Clasele reprezint tipuri referina. O clas poate s moteneasc o singur a t a a s a a clas i poate implementa mai multe interfee. as t Clasele pot conine constante, cmpuri, metode, proprieti, evenimente, t a at indexatori, operatori, constructori de instana, destructori, constructori de t clas, tipuri imbricate. Fiecare membru poate conine un nivel de protecie, a t t care controleaz gradul de acces la el. O descriere este dat tabelul 3.1: a a n Tabelul 3.1: Modicatori de acces ai membrilor unei clase Accesor public protected Semnicaie t Acces nelimitat Acces limitat la clasa conintoare t a sau la tipuri derivate din ea internal Acces limitat la acest assembly protected internal Acces limitat la acest assembly sau la tipuri derivate din clas a private Acces limitat la clas; a modicatorul implicit de acces

using System; 47

48

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

class MyClass { public MyClass() { Console.WriteLine("Constructor instanta"); } public MyClass( int value ) { myField = value; Console.WriteLine("Constructor instanta"); } public const int MyConst = 12; private int myField = 42; public void MyMethod() { Console.WriteLine("this.MyMethod"); } public int MyProperty { get { return myField; } set { myField = value; } } public int this[int index] { get { return 0; } set { Console.WriteLine("this[{0}]={1}", index, value); } } public event EventHandler MyEvent; public static MyClass operator+(MyClass a, MyClass b)

3.1. CLASE VEDERE GENERALA { return new MyClass(a.myField + b.myField); } }

49

class Test { static void Main() { MyClass a = new MyClass(); MyClass b = new MyClass(1); Console.WriteLine("MyConst={0}", MyClass.MyConst); //a.myField++;//gradul de acces nu permite lucrul direct cu campul a.MyMethod(); a.MyProperty++; Console.WriteLine("a.MyProperty={0}", a.MyProperty); a[3] = a[1] = a[2]; Console.WriteLine("a[3]={0}", a[3]); a.MyEvent += new EventHandler(MyHandler); MyClass c = a + b; } static void MyHandler(object Sender, EventArgs e) { Console.WriteLine("Test.MyHandler"); } internal class MyNestedType {} } Constanta este un membru al unei clase care reprezint o valoare nemoda icabil, care poate evaluat la compilare. Constantele pot depinde a a de alte constante, atta timp ct nu se creeaz dependene circulare. a a a t Ele sunt considerate automat membri statici (dar este interzis s se a foloseasc specicatorul static faa lor). Ele pot accesate exclua n t siv prin intermediul numelui de clas (MyClass.MyConst), i nu prin a s intermediul vreunei instane (a.MyConst). t Cmpul este un membru asociat ecrui obiect; cmpul stocheaza o valoare a a a care contribuie la starea obiectului.

50

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

Metoda este un membru care implementeaz un calcul sau o aciune care a t poate efectuat asupra unui obiect sau asupra unei clase. Metodele a statice (care au antet cuvntul cheie static) sunt accesate prin intern a mediul numelui de clas, pe cnd cele nestatice (metode instana) sunt a a t apelate prin intermediul unui obiect. Proprietatea este un membru care d acces la o caracteristic a unui obiect a a sau unei clase. Exemplele folosite pn acum includeau lungimea unui a a vector, numrul de caractere ale unui ir de caractere, etc. Sintaxa a s pentru accesara cmpurilor i a proprietilor este aceeai. Reprezint a s at s a o alt modalitate de implementare a accesorilor pentru obiecte. a Evenimentul este un membru care permite unei clase sau unui obiect s a pun la dispoziia altora noticri asupra evenimentelor. Tipul acestei a t a declaraii trebuie s e un tip delegat. O instana a unui tip delegat t a t ncapsuleaz una sau mai multe entiti apelabile. Exemplu: a at public delegate void EventHandler(object sender, System.EventArgs e); public class Button { public event EventHandler Click; public void Reset() { Click = null; } } using System; public class Form1 { Button Button1 = new Button1(); public Form1() { Button1.Click += new EventHandler(Button1_Click); } void Button1_Click(object sender, EventArgs e ) { Console.WriteLine("Button1 was clicked!");

3.2. TRANSMITEREA DE PARAMETRI } public void Disconnect() { Button1.Click -= new EventHandler(Button1_Click); } }

51

Mai sus clasa Form1 adaug Button1_Click ca tratare de eveniment1 a pentru evenimentul Click al lui Button1. metoda Disconnect(), acest In event handler este aturat. nl Operatorul este un membru care denete semnicaia (supra arcarea) s t nc unui operator care se aplic instanelor unei clase. Se pot supra arca a t nc operatorii binari, unari i de conversie. s Indexatorul este un membru care permite unui obiect s e indexat a n acelai mod ca un tablou (pentru programatorii C++: supra arcarea s nc operatorului []). Constructorii instan sunt membri care implementeaz aciuni cerute ta a t pentru iniializarea ecrui obiect. t a Destructorul este un membru special care implementeaz aciunile cerute a t pentru a distruge o instana a unei clase. Destructorul nu are parametri, t nu poate avea modicatori de acces, nu poate apelat explicit i este s apelat automat de ctre garbage collector. a Constructorul static este un membru care implementeaz aciuni necesare a t pentru a iniializa o clas, mai exact membrii statici ai clasei. Nu poate t a avea parametri, nu poate avea modicatori de acces, nu este apelat explicit, ci automat de ctre sistem. a Motenirea este de tip simplu, iar rdcina ierarhiei este clasa object (alias s a a System.Object).

3.2

Transmiterea de parametri

Parametrii metodelor permit transmiterea de valori la apel. general, In transmiterea se face prin valoare. Acest lucru nseamn c la apelul unei a a metode stiva gestionat de compilator se copiaz valoarea parametrului n a a
1

Engl: event handler

52

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

actual transmis, iar la revenire din metod aceast valoare va tears. a a s a Exemplicm listingul 3.1 acest lucru pentru tipul valoare; gura 3.1 a n n este o explicaie a comportamentului. t Listing 3.1: Transmiterea de parametri de tip valoare prin valoare using System ; c l a s s DemoTipValoare { s t a t i c void f ( int b ) { Console . WriteLine ( " l a i n t r a r e i n f : {0} " , b ) ; ++b ; Console . WriteLine ( " l a i e s i r e d i n f : {0} " , b ) ; } s t a t i c void Main ( ) { int a = 1 0 0 ; Console . WriteLine ( " i n a i n t e de i n t r a r e i n f : {0} " , a ) ; f ( a ); Console . WriteLine ( "dupa e x e c u t a r e a l u i f : {0} " , a ) ; } } Executarea programului din listingul 3.1 va avea ca rezultat: inainte de intrare in f: 100 la intrare in f: 100 la iesire din f: 101 dupa executarea lui f: 100 Pentru variable de tip referina, pe stiv se depune tot o copie a valorii t a a pentru un asemenea tip de variabil acest lucru obiectului. Ins a nseamn c a a pe stiv se va depune ca valoare adresa de memorie la care este stocat obiectul a respectiv. Ca atare, metoda apelat poate s modice starea obiectului care a a se transmite. Codul de exemplicare este dat listingul 3.2, iar exemplul n este prezentat grac gura 3.2. n Listing 3.2: Transmiterea de parametri de tip referina prin valoare. Modit carea strii obiectului este vizibil i dup ce se iese din funcie a as a t c l a s s Employee { public S t r i n g Name ; // a c c e s p u b l i c pe camp p e n t r u // s i m p l i f i c a r e a e x e m p l u l u i

3.2. TRANSMITEREA DE PARAMETRI

53

(a) Inainte de apelul metodei f : variabila local a este alocat pe a a stiv i are valoarea 3. as

(b) La intrarea n f : pe stiv se aloc a a spaiu pentru valt oarea trimis prin a parametru; valoarea provine din copierea valorii parametrului actual a.

(c) Dup modicarea a valorii lui b, nainte de ieirea din f. Se observ s a c modicarea se face a strict asupra valorii lui b, neafectnd pe a. a

(d) Dup ieirea din f. a s Spaiul alocat pe stiv t a pentru parametrul b este disponibilizat, valoarea modicat a n metoda f se pierde.

Figura 3.1: Transmiterea prin valoare a variabilelor de tip valoare, conform codului din listingul 3.1

54

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME public decimal S a l a r y ; // idem

} c l a s s Test { s t a t i c void Main ( ) { Employee e = new Employee ( ) ; e . Name = " I o n e s c u " ; e . S a l a r y = 300M; System . Console . WriteLine ( " i n a i n t e de a p e l : name={0} , s a l a r y ={1}" , e . Name , e . S a l a r y ) ; method ( e ) ; System . Console . WriteLine ( "dupa a p e l : name={0} , s a l a r y ={1}" , e . Name , e . S a l a r y ) ; } s t a t i c void f ( Employee emp ) { emp . S a l a r y += 100M; emp . Name = " IonI o n e s c u " ; } } Rezultatul executrii codului din listingul 3.2 este: a inainte de apel: name=Ionescu, salary=300 dupa apel: name=Ion-Ionescu, salary=400 Totui, chiar i cazul tipului referina s s n t ncercarea de a recrea interin orul unei metode un obiect transmis ca parametru nu are nici un efect dup a s terminarea ei, dup cum este artat listingul 3.3 i gura 3.3. a a n Listing 3.3: Transmitere de parametru de tip referina prin valoare. Modit carea adresei obiectului nu este vizibil dup ieirea din funcie a a s t using System ; c l a s s MyClass { public int x ; //camp p u b l i c p e n t r u s i m p l i t a t e a } c l a s s Test

1 2 3 4 5 6 7

3.2. TRANSMITEREA DE PARAMETRI

55

(a) Inainte de apelul metodei f : variabila local e este alocat pe stiv i are valoarea a a a s 4000, reprezentnd adresa de memorie din a heap unde se gsete obiectul creat. a s

(b) La intrarea f, n nainte de atribuire: pe stiv se aloc spaiu pentru valoarea trimis prin a a t a parametru; valoarea provine din copierea valorii parametrului actual e. Cele dou variabile vor a indica spre acelai obiect din heap. s

(c) Dup atribuire, a nainte de ieirea din f. Se s observ modicarea aprut heap. a a a n

(d) Dup ieirea din f. Spaiul alocat pe stiv pena s t a tru parametrul emp este disponibilizat, dar valorile din heap rmn aa cum au fost ele modicate a a s n f.

Figura 3.2: Transmiterea prin valoare a variabilelor de tip referina, conform t codului din listingul 3.2

56
8 { 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 }

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

s t a t i c void f ( MyClass b ) { Console . WriteLine ( " i n t r a r e i n b = new MyClass ( ) ; b . x = 100; Console . WriteLine ( " i e s i r e d i n } s t a t i c void Main ( ) { MyClass a = new MyClass ( ) ; a . x = 100; Console . WriteLine ( " i n a i n t e de f (a ); Console . WriteLine ( "dupa a p e l : }

f : {0} " , b . x ) ;

f : {0} " , b . x ) ;

a p e l : {0} " , a . x ) ; {0} " , a . x ) ;

Ieirea codului din listingul 3.3 este: s inainte de intrare in iesire din dupa apel: apel: 100 f: 100 f: -100 100

Exist situaii care acest comportament nu este cel dorit: am vrea ca a t n efectul asupra unui parametru s se menin i dup ce metoda apelat sa a t as a a terminat. Un parametru referin este folosit tocmai pentru a rezolva problema ta transmiterii prin valoare, folosind referina (un alias) pentru entitatea dat t a de ctre metoda apelant. Pentru a transmite un parametru prin referina, a a t se prexeaz cu cuvntul cheie ref la apel i la declarare de metod, conform a a s a codului din listingul 3.4. Listing 3.4: Trimitere de parametri prin referina t using System ; c l a s s Test { s t a t i c void swap ( r e f int a , r e f int b ) { int t = a ; a = b;

3.2. TRANSMITEREA DE PARAMETRI

57

(a) Inainte de apelul metodei f : variabila local a este alocat pe stiv i are valoarea 4000, a a as reprezentnd adresa de memorie din heap unde a se gsete obiectul creat. a s

(b) La intrarea f, n nainte de instruciunea de t a a la linia 11 din listingul 3.3: pe stiv se aloc spaiu pentru valoarea trimis prin parametru; t a valoarea provine din copierea valorii parametrului actual a. Cele dou variabile vor indica spre a acelai obiect din heap. s

(c) Dup linia 12 a aceluiai listing. Se oba s serv modicrile aprute heap, iar b indic a a a n a spre noul obiect. Obiectul a rmne cu starea a a nealterat. a

(d) Dup ieirea din f. Spaiul alocat pe stiv a s t a pentru parametrul b este disponibilizat.

Figura 3.3: Transmiterea prin valoare a variabilelor de tip referina nu permit t modicarea referinei. Figura este asociat listingul 3.3 t a

58

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME b = t; } s t a t i c void Main ( ) { int x=1, y=2; Console . WriteLine ( " i n a i n t e de a p e l : x={0} , y={1}" , x , y ) ; swap ( r e f x , r e f y ) ; Console . WriteLine ( "dupa a p e l : x={0} , y={1}" , x , y ) ; // s e va a f i s a : // i n a i n t e de a p e l : x =1, y=2 // dupa a p e l : x =2, y=1 }

} Explicaia acestui comportament este c la apelul metodei swap, se trimit t a nu cpii ale valorilor x i y, ci adresele de memorie unde se a stocate x i o s a s y pe stiv (altfel zis, referine ctre x i y). Ca atare, efectul atribuirilor din a t a s cadrul metodei swap sunt vizibile i dup ce sa revenit din apelul ei. s a Una din trsturile specice parametrilor referina este c valorile pentru aa t a care se face apelul trebuie s e iniializate. Neasignarea de valori pentru x a t i y, precum listingul 3.5 duce o eroare de compilare: Use of unassigned s n local variable x . Listing 3.5: Eroare: variabilele trimise prein ref trebuie s e iniializate a t c l a s s TestRef { s t a t i c void f ( r e f int x ) { x = 100; } s t a t i c void Main ( ) { int x ; f ( ref x ) ; } } Exist cazuri care dorim s obinem acelai efect ca la parametrii a n a t s referina, dar fr a trebui s iniializm argumentele date de ctre metoda t aa a t a a apelant, de exemplu cnd valoarea acestui parametru se calculeaz ina a a n

3.2. TRANSMITEREA DE PARAMETRI

59

teriorul metodei apelate. Pentru aceasta exist parametrii de ieire2 , simia s lari cu parametrii referina, cu deosebirea c nu trebuie asignat o valoare t a a parametrului de apel. Un exemplu este dat listingul 3.6. n Listing 3.6: Utilizarea de parametri de ieire s using System ; c l a s s Test { s t a t i c void Main ( ) { int l = 1 0 ; double a r e a ; ComputeSquareArea ( l , out a r e a ) ; Console . WriteLine ( " Area i s : {0} " , a r e a ) ; } s t a t i c void computeSquareArea ( double l , out double a r e a ) { area = l l ; } } Pentru toate tipurile de parametri de mai sus exist o corespondena de a t 1 la 1 ntre parametrii actuali i cei formali. Un parametru vector3 permite s o relaie de tipul unul-la-muli: mai muli parametri actuali pot referite t t t prin intermediul unui singur parametru formal. Un astfel de parametru se declar folosind modicatorul params. Pentru o implementare de metod, a a putem avea cel mult un parametru de tip vector i acesta trebuie s e s a ultimul lista de parametri. Acest parametru formal este tratat ca un n tablou unidimensional. Un exemplu este dat listingul 3.7; se remarc n a modalitile de apel diferit. at Listing 3.7: Exemplu de utilizare de parametru de tip vector using System ; c l a s s Test { s t a t i c void f (params int [ ] a r g s ) { Console . WriteLine ( " numarul de p a r a m e t r i : {0} " , a r g s . Length ) ;
2 3

In original: output parameters. In original: parameter array.

60

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME for ( int i = 0 ; i < a r g s . Length ; i ++) { Console . WriteLine ( " a r g s [ { 0 } ] = { 1 } " , i , a r g s [ i ] ) ; } } s t a t i c void Main ( ) { f (); f (1); f (1 , 2); f (new int [ ] { 1 , 2 , 3 } ) ; }

} Acest tip de transmitere se folosete i de ctre metoda WriteLine (sau s s a Write) a clasei Console, i.e. exist aceast clas o metod de forma4 : a n a a a public s t a t i c void WriteLine ( string format , params Object [ ] a r g s ) {...}

3.3

Conversii

O conversie permite ca o expresie de un anumit tip s e tratat ca ind a a de alt tip. Conversiile pot implicite sau explicite, aceasta specicnd de a fapt dac un operator de conversie este sau nu necesar. a

3.3.1

Conversii implicite

Sunt clasicate ca i conversii implicite urmtoarele: s a conversiile identitate conversiile numerice implicite conversiile implicite de tip enumerare conversiile implicite de referine t boxing
4

A se vedea MSDN.

3.3. CONVERSII conversiile implicite ale expresiilor constante conversii implicite denite de utilizator

61

Conversiile implicite pot aprea a ntro varietate de situaii, de exemplu t apeluri de funcii sau atribuiri. Conversiile implicite predenite nu determin t a niciodat apariia de excepii. a t t Conversiile indentitate O conversie identitate convertete de la un tip oarecare ctre acelai tip. s a s Conversiile numerice implicite Conversiile numerice implicite sunt: de la sbyte la short, int, long, oat, double, decimal; de la byte la short, ushort, int, uint, long, ulong, oat, double, decimal; de la short la int, long, double, decimal; de la ushort la int, uint, long, ulong, oat, double, decimal; de la int la long, oat, double, decimal; de la uint la long, ulong, oat, double, decimal; de la long la oat, double, decimal; de la ulong la oat, double, decimal; de la char la ushort, int, uint, long, ulong, oat, double, decimal; de la oat la double. Conversiile de la int, uint, long, ulong la oat, precum i cele de la long s sau ulong la double pot duce la o pierdere a preciziei, dar niciodat la o a reducere a ordinului de mrime. Alte conversii numerice implicite niciodat a a nu duc la pierdere de informaie. t Conversiile de tip enumerare implicite O astfel de conversie permite ca literalul 0 s e convertit la orice tip a enumerare (chiar dac acesta nu conine valoarea 0) - a se vedea 2.2.3, pag. a t 32.

62

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

Conversii implicite de referine t Conversiile implicite de referine implicite sunt: t de la orice tip referina la object; t de la orice tip clas B la orice tip clas A, dac B este derivat din A; a a a de la orice tip clas A la orice interfaa B, dac A implementeaz B; a t a a de al orice interfaa A la orice interfaa B, dac A este derivat din B; t t a a de la orice tip tablou A cu tipul AE la un tip tablou B avnd tipul BE , a cu urmtoarele condiii: a t 1. A i B au acelai numr de dimensiuni; s s a 2. att AE ct i BE sunt tipuri referina; a a s t 3. exist o conversie implicit de tip referina de la AE la BE a a t de la un tablou la System.Array; de la tip delegat la System.Delegate; de la orice tip tablou sau tip delegat la System.ICloneable; de la tipul null la orice variabil de tip referina; a t Conversie de tip boxing Permite unui tip valoare s e implicit convertit ctre un alt tip de date, a a aat deasupra ierarhie. Astfel, se poate face conversie de la o variabil de n a tip enumerare sau de tip structur ctre tipul object sau System.ValueType a a sau ctre o interfaa pe care structura o implementeaz. O descriere mai a t a amnunit este dat seciunea 3.3.4. a t a a n t

3.3.2

Conversiile implicite ale expresiilor constante

Permit urmtoarele tipuri de conversii: a o expresie constant de tip int poate convertit ctre tipurile sbyte, a a a byte, short, ushort, uint, ulong, cu condiia ca valoarea expresiei cont stante s se ae domeniul tipului destinaie; a n t o expresie constant de tip long poate convertit la tipul ulong, dac a a a valoarea ce se convertete nu este negativ. s a

3.3. CONVERSII Conversii implicite denite de utilizator

63

Constau ntro conversie implicit standard opional, urmat de execuia a t a a t unui operator de conversie implicit utilizator urmat de alt conversie ima a a plicit standard opional. Regulile exacte sunt descrise [6]. a t a n

3.3.3

Conversii explicite

Urmtoarele conversii sunt clasicate ca explicite: a toate conversiile implicite conversiile numerice explicite conversiile explicite de enumerri a conversiile explicite de referine t unboxing conversii explicite denite de utilizator Din cauz c orice conversie implicit este de asemenea i una explicit, a a a s a aplicarea operatorului de conversie este redundant: a int x = 0; long y = (long)x;//(long) este redundant Conversii numerice explicite Sunt conversii de la orice tip numeric la un alt tip numeric pentru care nu exist conversie numeric implicit: a a a de la sbyte la byte, ushort, uint, ulong, char; de la byte la sbyte, char; de la short la sbyte, byte, ushort, uint, ulong, char; de la ushort la sbyte, byte, short, char; de la int la sbyte, byte, short, ushort, int, char; de la uint la sbyte, byte, short, ushort, int, uint, long, ulong, char; de la long la sbyte, byte, short, ushort, int, uint, ulong, char;

64

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME de la ulong la sbyte, byte, short, ushort, int, uint, long, char; de la char la sbyte, byte, short; de la oat la sbyte, byte, short, ushort, int, uint, long, ulong, decimal; de la double la sbyte, byte, short, ushort, int, uint, long, ulong, char, oat, decimal; de la decimal la sbyte, byte, short, ushort, int, uint, long, ulong, char, oat, double;

Pentru c astfel de conversii pot aprea pierderi de informaie, exist a n a t a dou contexte care se fac aceste conversii: checked i unchecked. a n s context checked, conversia se face cu succes dac valoarea care se conIn a vertete este reprezentabil de ctre tipul ctre care se face conversia. s a a a In cazul care conversia nu se poate face cu succes, se va arunca excepia Sysn t context unchecked, conversia se face tem.OverowException. In ntotdeauna, dar se poate ajunge la pierdere de informaie sau la valori ce nu sunt bine t nedenite (vezi [6], pag. 115116). Conversii explicite de enumerri a Conversiile explicite de enumerri sunt: a de la sbyte, byte, short, ushort, int, uint, long, ulong, char, oat, double, decimal la orice tip enumerare; de la orice tip enumerare la sbyte, byte, short, ushort, int, uint, long, ulong, char, oat, double, decimal; de la orice tip enumerare la orice tip enumerare. Conversiile de tip enumerare se fac prin tratarea ecrui tip enumerare a ca ind tipul ntreg de reprezentare, dup care se efectueaz o conversie a a implict sau explicit a a ntre tipuri (ex: dac se dorete conversia de la un tip a s enumerare E care are tipul de reprezentare int la un tip byte, se va face o conversie explicit de la int la byte; invers, se va face o conversie implict de a a la byte la int). Conversii explicite de referine t Conversiile explicite de referine sunt: t de la object la orice tip referina; t

3.3. CONVERSII

65

de la orice tip clas A la orice tip clas B, cu condiia ca A s e clas a a t a a de baz pentru B; a de la orice tip clas A la orice tip interfaa B, dac A nu este nederiva t a abil i A nu implementeaz pe B; as a de la orice tip interfaa A la orice tip clas B, dac B nu este nederivt a a abil sau cu condiia ca B s implementeze A; a t a de la orice tip interfaa A la orice tip interfaa B, dac A nu este derivat t t a din B; de la un tip tablou A cu elemente de tip AE la un tip tablou B cu elemente BE , cu condiiile: t 1. A i B au acelai numr de dimensiuni; s s a 2. AE i BE sunt tipuri referina; s t 3. exist o conversie de referina explicit de la AE al BE a t a de la System.Array i interfeele pe care le implementeaz la orice tip s t a tablou; de la System.Delegate i interfeele pe care le implementeaz la orice s t a tip delegat. Acest tip de conversii cer vericare la runtime. Dac o astfel de conversie a eueaz, se va arunca o excepie de tipul System.InvalidCastException. s a t Unboxing Unboxing-ul permite o conversie explicit de la object sau System.ValueType a la orice tip valoare, sau de la orice tip interfaa la orice tip valoare care imt plementeaz tipul interfaa. Mai multe detalii se vor da seciunea 3.3.4. a t n t Conversii explicite denite de utilizator Constau ntro conversie standard explicit opional, urmat de execuia a t a a t unei conversii explicite, urmat de o alt conversie standard explicit opional. a a a t a

66

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

3.3.4

Boxing i unboxing s

Boxingul i unboxingul reprezint modalitatea prin care C# permite s a utilizarea simpl a sistemului unicat de tipuri. Spre deosebire de Java, unde a exist tipuri primitive (care nu pot conine metode) i tipuri referina, C# a t s t n toate tipurile sunt derivate din clasa object (alias System.Object). De exemplu, tipul int (alias System.Int32) este derivat din clasa System.ValueType care la rndul ei este derivat din clasa object (alias System.Object). Ca a a atare, un ntreg este compatibil cu object. Boxing Conversia de tip boxing permite oricrui tip valoare s e implicit convera a tit ctre tipul object sau ctre un tip interfaa implementat de tipul valoare. a a t Boxingul unei valori const alocarea unei variabile de tip obiect i copierea a n s valorii iniiale acea instana. t n t Procesul de boxing al unei valori sau variabile de tip valoare se poate telege ca o simulare de creare de clas pentru acel tip: n a sealed class T_Box { T value; public T_Box(T t) { value = t; } } Astfel, declaraiile: t int i = 123; object box = i; corespund conceptual la: int i = 123; object box = new int_Box(i); Pentru secvena: t int i = 10;//linia 1 object o = i;//linia 2 int j = (int)o;//linia 3

3.4. DECLARATII DE VARIABILE SI CONSTANTE

67

procesul se desfoar ca gura 3.4: la linia 1, se declar i se iniializeaz as a n as t a o variabil de tip valoare, care va conine valoarea 10.La urmtoarea linie a t a se va crea o referina o ctre un obiect alocat heap, care va conine att t a n t a valoarea 10, ct i o informaie despre tipul de dat coninut ( cazul nostru, a s t a t n System.Int32). Unboxingul se face printro convenie explicit, ca linia t a n a treia.
i 10

o 10 j 10

System.Int32

Figura 3.4: Boxing i unboxing s Determinarea tipului pentru care sa fcut a mpachetarea se face prin intermediul operatorului is: int i = 123; object o = i; if (o is int) { Console.Write("Este un int inauntru!"); } Boxingul duce la o clonare a valorii care va coninut. t a secvena: t int i = 10; object o = i; i++; Console.WriteLine("in o: {0}", o); va aa valoarea s nglobat obiect, 10. a n Altfel spus,

3.4

Declaraii de variabile i constante t s

Variabilele i constantele trebuie declarate C#. Opional, pentru varis n t abile se poate specica valoarea iniial, iar pentru constante acest lucru este t a obligatoriu. O variabil trebuie s aib valoarea asignat denit a a a a a nainte ca

68

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

valoarea ei s e utilizat, cazul care este declarat interiorul unei a a n n a n metode. Este o eroare ca ntrun sub-bloc s se declare o variabil cu acelai a a s nume ca blocul conintor: n t a void F() { int x = 3, y;//ok const double d = 1.1;//ok { string x = "Mesaj: ";//eroare, x mai este declarat //in blocul continator int z = x + y;//eroare, y nu are o valoare definita asignata } } Constantele au valori iniiale care trebuie s se poat evalua la compilare. t a a

3.5
3.5.1

Instruciuni C# t
Declaraii de etichete t

O etichet poate prexa o instruciune. Ea este vizibil a t a n ntregul bloc i toate sub-blocurile coninute. O etichet poate referit de ctre s t a a a o instruciune goto: t class DemoLabel { int F(int x) { if (x >= 0) goto myLabel; x = -x; myLabel: return x; } static void Main() { DemoLabel dl = new DemoLabel(); dl.f(); } }

3.5. INSTRUCTIUNI C#

69

3.5.2

Instruciuni de selecie t t

Instruciunea if t Instruciunea if execut o instruciune funcie de valoarea de adevr a t a t n t a unei expresii logice. Are formele: if (expresie logica) instructiune; if (expresie logica) instructiune; else instructiune; Instruciunea switch t Permite executarea unei instruciuni funcie de valoarea unei expresii, t n t care se poate regsi sau nu a ntro list de valori candidat: a switch (expresie) { case eticheta: instructiune; case eticheta: instructiune; ... default: instructiune; } O etichet reprezint o expresie constant. O instruciune poate s i lipseasc a a a t as a i acest caz se va executa instruciunea de la caseul urmtor, sau de la des n t a fault. Seciunea default poate s lipseasc. Dac o instruciune este nevid, t a a a t a atunci va trebui s e terminat cu o instruciune break sau goto case exprea a t sieConstanta sau goto default. Expresia dup care se face selecia poate de tip sbyte, byte, short, ushort, a t int, uint, long, ulong, char, string, enumerare. Dac valoarea expresiei se a regsete printre valorile specicate la clauzele case, atunci instruciunea a s t corespunztoare va executat; dac nu, atunci instruciunea de la clauza a a a t default va executat (dac ea exist). Spre deosebire de C i C++, e interzis a a a s s se foloseasc fenomenul de "cdere" de la o etichet la alta; continuarea a a a a se face folosind explicit goto. switch (i) { case 0: Console.WriteLine("0"); break; case 1: Console.Write("Valoarea ");

70

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME goto case 2; case 2: case 3: Console.WriteLine(i); break; case 4: goto default; default: Console.WriteLine("Numar in afara domeniului admis"); break;//neaparat, altfel eroare de compilare

} Remarcm exemplul de mai sus c chiar i cazul lui default e necesar a n a s n s se foloseasc instruciune de salt ( cazul nostru break ); o motivaie ar a a t n t c aceast clauz default nu e necesar s e trecut ultima switch, ci chiar a a a a a n i pe prima poziie desigur caz mai rar alnit. s t nt Exist un caz care break, goto case valoare sau goto default pot s a n a lipseasc: cnd este evident c o asemenea instruciune break/goto nu ar a a a t putea atins (i.e. sunt prezente instruciunile return, throw sau o ciclare a t despre care se poate arma la compilare c este innit). a a

3.5.3

Instruciuni de ciclare t

Exist 4 instruciuni de ciclare: while, do, for, foreach. a t Instruciunea while t Permite executarea unei instruciuni atta timp ct valoarea unei expresii t a a logice este adevrat (ciclu cu test anterior). a a Sintaxa: while (expresie logica) instructiune; interiorul unei astfel de instruciuni se poate folosi o instruciune de salt In t t de tip break sau continue. while { r = a = b = } (r != 0) a%b; b; r;

3.5. INSTRUCTIUNI C# Instruciunea do t

71

Execut o instruciune o dat sau de mai multe ori, ct timp o condiie a t a a t logic este adevrat (ciclu cu test posterior). a a a Exemplu: do { S += i++; }while(i<=n) Poate conine instruciuni break sau continue. t t Instruciunea for t Execut o secvena de iniializare, dup care va executa o instruciune a t t a t atta timp ct o condiie este adevrat (ciclu cu test anterior); poate s a a t a a a conin un pas de reiniializare (trecerea la pasul urmtor). Se permite t a t a folosirea instruciunilor break i continue. t s Exemplu: for (int i=0; i<n; i++) { Console.WriteLine("i={0}", i); } Instruciunea foreach t Enumer elementele dintro coleie, executnd o instruciune pentru ecare a t a t element. Colecia poate s e orice instana a unei clase care implementeaz t a t a interfaa System.Collections.IEnumerable. t Exemplu: int[] t = {1, 2, 3}; foreach( int x in t) { Console.WriteLine(x); } Elementul care se extrage este de tip readonly (deci nu poate transmis ca parametru ref sau out i nu se poate aplica un operator care s schimbe s a i valoarea).

72

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

3.5.4

Instruciuni de salt t

Permit schimbarea ordinii de execuie a instruciunilor. Ele sunt: break, t t continue, goto, return, throw. Instruciunea break t Produce ieirea forat dintrun ciclu de tip while, do, for, foreach. s t a Instruciunea continue t Pornete o nou iteraie interiorul celui mai apropiat ciclu conintor s a t n t a de tip while, do, for, foreach. Instruciunea goto t Goto permite saltul al o anumit instruciune. Are 3 forme: a t goto eticheta; goto case expresieconstanta; goto default; Cerina este ca eticheta la care se face saltul s e denit cadrul funciei t a a n t curente i saltul s nu se fac interiorul unor blocuri de instruciuni, s a a n t deoarece nu se poate reface ntotdeauna contextul acelui bloc. Se recomand evitarea utilizrii intense a acestui cuvnt cheie, caz cona a a n trar se poate ajunge la fenomenul de "spagetti code". Pentru o argumentare consistent a acestei indicaii, a se vedea articolul clasic al lui Edsger W. Dijka t stra, "Go To Statement Considered Harmful": http://www.acm.org/classics/oct95/ Instruciunea return t Determin cedarea controlului funiei apelate de ctre funcia care a a t a t apelant. Dac funcia apelat are tip de retur, atunci instruciunea rea a t a t turn trebuie s e urmat de o expresie care suport o conversie implicit a a a a ctre tipul de retur. a

3.5.5

Instruciunile try, throw, catch, nally t

Permit tratarea excepiilor. Vor studiate detaliu la capitolul de t n excepii. t

3.5. INSTRUCTIUNI C#

73

3.5.6

Instruciunile checked i unchecked t s

Controleaz contextul de vericare de depire a domeniului pentru arita as metica pe ntregi i conversii. Au forma: s checked { //instructiuni } unchecked { //instructiuni } Vericare se va face la runtime.

3.5.7

Instruciunea lock t

Obine excluderea mutual asupra unui obiect pentru executarea unui t a bloc de instruciuni. Are forma: t lock (x) instructiune X trebuie s e de tip referina (dac este de tip valoare, nu se face boxing). a t a

3.5.8

Instruciunea using t

Determin obinerea a unei sau mai multor resurse, execut o instruciune a t a t i apoi disponibilizeaz resursa: s a using ( achizitie de resurse ) instructiune O resurs este o clas sau o structur care implementeaz interfaa System.Ia a a a t Disposable, care include o sigur metod fr parametri Dispose(). Achiziia a a aa t de resurse se poate face sub form de variabile locale sau a unor expresii; toate a acestea trebuie s e implicit convertibile la IDisposable. Variabilele locale a alocate ca resurse sunt readonly. Resursele sunt automat dealocate (prin apelul de Dispose) la sfritul instruciunii (care poate bloc de instruciuni). as t t Motivul pentru care exist aceast instruciune este unul simplu: uneori a a t se dorete ca pentru anumite obiecte care dein resurse importante s se s t a apeleze automat metod Dispose() de dezalocare a lor, ct mai repede cu a a putina. t Exemplu:

74

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

using System; using System.IO; class Test { static void Main() { using( TextWriter w = File.CreateText("log.txt") ) { w.WriteLine("This is line 1"); w.EriteLine("This is line 2"); } } }

3.6

Spaii de nume t

cazul crerii de tipuri este posibil s se foloseasc un acelai nume In a a a s pentru tipurile noi create de ctre dezvoltatorii de soft. Pentru a putea a folosi astfel de clase care au numele comun, dar responsabiliti diferite, treat buie prevzut o modalitate de a le adresa mod unic. Soluia la aceast a a n t a problem este crearea spaiilor de nume5 care rezolv, printro adresare coma t a plet astfel de ambiguiti. Astfel, putem folosi de exemplu clasa Buer din a at spaiul System (calicare complet: System.Buer), alturi de clasa Buer t a a din spaiul de nume Curs3: Curs3.Buer. t Crearea unui spaiu de nume se face prin folosirea cuvntului namespace: t a using System; namespace Curs3 { public class Buffer { public Buffer() { Console.WriteLine("Bufferul meu!"); } } } Se pot de asemenea crea spaii de nume imbricate. Altfel spus, un spaiu t t de nume este o colecie de tipuri sau de alte spaii de nume. t t
5

engl: namespaces

3.6. SPATII DE NUME

75

3.6.1

Declaraii de spaii de nume t t

O declaraie de spaiu de nume const cuvntul cheie namespace, urmat t t a n a de identicatorul spaiului de nume i de blocul spaiului de nume, delimitat t s t de acolade. Spaiile de nume sunt implicit publice i acest tip de acces nu se t s poate modica. interiorul unui spaiu de nume se pot utiliza alte spaii In t t de nume, pentru a se evita calicarea complet a claselor. a Identicatorul unui spaiu de nume poate simplu sau o secvena de t t identicatori separai prin ".". Cea de a doua form permite denirea de t a spaii de nume imbricate, fr a se imbrica efectiv: t aa namespace N1.N2 { class A{} class B{} } este echivalent cu: a namespace N1 { namespace N2 { class A{} class B{} } } Dou declaraii de spaii de nume cu aceeai denumire contribuie la a t t s declararea unui acelai spaiu de nume: s t namespace N1.N2 { class A{} } namespace N1.N2 { class B{} } este echivalent cu cele dou declaraii anterioare. a a t

76

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

3.6.2

Directiva using

Directiva using faciliteaz primul rnd utilizarea spaiilor de nume i a a n a t s tipurilor denite acestea; ele nu creeaz membri noi cadrul unitii de n a n at program care sunt folosite, ci au rol de a uura referirea tipurilor. Nu se n s pot utiliza interiorul claselor, structurilor, enumerririlor. n a Exemplu: e mai uor de s nteles un cod de forma: using System; class A { static void Main() { Console.WriteLine("Mesaj"); } } dect: a class A { static void Main() { System.Console.WriteLine("Mesaj"); } } Directiva using poate de fapt folosit att pentru importuri simbolice, a a ct i pentru crearea de aliasuri. a s Directiva using pentru import simbolic O directiv using permite importarea simbolic a tuturor tipurilor coninute a a t direct ntrun spaiu de nume, i.e. folosirea lor fr a necesar o calicare t aa a complet. Acest import nu se refer i la spaiile de nume coninute: a as t t namespace N1.N2 { class A{} } namespace N3.N4 { class B{};

3.6. SPATII DE NUME } namespace N5 { using N1.N2; using N3; class C { A a = null;//ok N4.B = null;//Eroare, N4 nu a fost importat } } Importarea de spaii de nume nu trebuie s duc la ambiguiti: t a a at namespace N1 { class A{} } namespace N2 { class A{} } namespace N3 { using N1; using N2; class B { A a = null;//ambiguitate: N1.A sau N2.A? } }

77

situaia de mai sus, conictul (care poate foarte uor cazul care se In t s n n folosesc tipuri produse de dezvoltatori diferii) poate rezolvat de o calicare t complet: a namespace N3 { using N1; using N2; class B {

78

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME N1.A a1 = null; N2.A a2 = null;//ok, nu mai este ambiguitate }

} Tipurile declarate interiorul unui spaiu de nume pot avea modicatori de n t acces public sau internal (modicatorul implicit). Un tip internal nu poate folosit prin import afara assembly-ului, pe cnd unul public, da. n a Directiva using ca alias Introduce un identicator care servete drept alias pentru un spaiu de s t nume sau pentru un tip. Exemplu: namespace N1.N2 { class A{} } namespace N3 { using A = N1.N2.A; class B { A a = null; } } Acelai efect se obine cre un alias la spaiul de nume N1.N2: s t nd t namespace N3 { using N = N1.N2; class B { N.A a = null; } } Identicatorul dat unui alias trebuie s e unic, adic interiorul unui a a n namespace nu trebuie s existe tipuri i aliasuri cu acelai nume: a s s

3.6. SPATII DE NUME

79

namespace N3 { class A{} } namespace N3 { using A = N1.N2.A;//eroare, deoarece simbolul A mai este definit } O directiv alias afecteaz doar blocul care este denit: a a n a namespace { using R } namespace { class B { R.A a } } N3 = N1.N2; N3

= null;//eroare, R nu este definit aici

adic directiva de alias nu este tranzitiv. Situaia de mai sus se poate rezolva a a t prin declarearea aliasului afara spaiului de nume: n t using R = namespace { class B { R.A a } } namespace { class C { R.A b } } N1.N2; N3

= null;

N3

= null;

Numele create prin directive de alias sunt ascunse de ctre alte declaraii a t care folosesc acelai identicator interiorul unui bloc: s n

80

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

using R = N1.N2; namespace N3 { class R{} class B { R.A a;//eroare, clasa R nu are membrul A } } Directivele de alias nu se inueneaz reciproc: t a namespace N1.N2{} namespace N3 { using R1 = N1; using R2 = N1.N2; using R3 = R1.N2;//eroare, R1 necunoscut }

3.7

Declararea unei clase

Declararea unei clase se face felul urmtor: n a atributeopt modicatori-de-clasaopt class identicator clasa-de-bazaopt corpclasa ;opt Modicatorii de clas sunt: a public - clasele publice sunt accesibile de oriunde; poate folosit att pentru a clase imbricate, ct i pentru clase care sunt coninute spaii de nume; a s t n t internal - se poate folosi att pentru clase imbricate, ct i pentru clase care a a s sunt coninute spaii de nume (este modicatorul implicit pentru t n t clase care sunt coninute spaii de nume). Semnic acces permis t n t a doar clasa sau spaiul de nume care o cuprinde; n t protected - se poate specica doar pentru clase imbricate; tipurile astfel calicate sunt accesibile clasa curent sau cele derivate (chiar n a n dac clasa derivat face parte din alt spaiu de nume); a a t private - doar pentru clase imbricate; semnic acces limitat la clasa cona intoare; este modicatorul implicit; t a

3.8. MEMBRII UNEI CLASE

81

protected internal - folosibil doar pentru clase imbricate; tipul denit este accesibil spaiul de nume curent, clasa conintoare sau tipurile n t n t a n derivate din clasa conintoare; t a new - permis pentru clasele imbricate; clasa astfel calicat ascunde un a membru cu acelai nume care este motenit; s s sealed - o clas sealed nu poate motenit; poate clas imbricat sau a s a a a nu; abstract - clasa care este incomplet denit i care nu poate instaniat; as t a folosibil pentru clase imbricat sau coninute spaii de nume; a t n t partial - clasa este denit mai multe iere a n s

3.8

Membrii unei clase

Corpul unei clase se specic felul urmtor: a n a { declaratii-de-membri };opt Membrii unei clase sunt ariti urmtoarele categorii: mp t n a constante cmpuri a metode proprieti at evenimente indexatori operatori constructori (de instana) t destructor constructor static tipuri Acestor membri le pot ataai modicatorii de acces: s t public - membrul este accesibil de oriunde;

82

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

protected - membrul este accesabil de ctre orice membru al clasei conintoare a t a i de ctre clasele derivate; s a internal - membrul este accesabil doar assembly-ul curent; n protected internal - reuniunea precedentelor dou; a private - accesabil doar clasa conintoare; este specicatorul implicit. n t a

3.9

Constructori de instana t

Un constructor de instana este un membru care implementeaz aciuni t a t care sunt cerute pentru a iniializa o instana a unei clase. Declararea unui t t astfel de constructor se face felul urmtor: n a atributeopt modicatori-de-constructor declarator-de-constructor corp-constructor Un modicator de constructor poate : public, protected, internal, private, extern. Un declarator de constructor are forma: nume-clasa (lista-parametrilor-formaliopt ) initializator-de-constructoropt unde initializatorul-de-constructor are forma: : base( lista-argumenteopt ) sau : this( lista-argumenteopt ). Corp-constructor poate : un bloc de declaraii i instruciuni delimitat de t s t acolade sau caracterul punct i virgul. s a Un constructor are acelai nume ca i clasa din care face parte i nu s s s returneaz un tip. Constructorii de instana nu se motenesc. Dac o clas a t s a a nu conine nici o declaraie de constructor de instana, atunci compilatorul va t t t crea automat unul implicit. O clas care este motenit dintr-o alt clas ce a s a a a nu are constructori fr parametri va trebui s utilizeze un apel de constructor aa a de clas de baz pentru care s furnizeze parametrii potrivii; acest apel se a a a t face prin intermediul iniializatorului de constructor. Un constructor poate t apela la un alt constructor al clasei din care face parte pentru a efectua iniializi. Cnd exist cmpuri instana care au o expresie de iniializare t r a a a t t afara constructorilor clasei respective, atunci aceste iniializri se vor face n t a nainte de apelul de constructor al clasei de baz. a

3.10

Cmpuri a

Un cmp reprezint un membru asociat cu un obiect sau cu o clas. a a a Modicatorii de cmp care se pot specica opional a t naintea unui cmp sunt a cei de mai sus, la care se adaug modicatorii new, readonly, volatile, static, a

3.10. CAMPURI

83

ce vor prezentai mai jos. Pentru orice cmp este necesar precizarea unui t a a tip de date, ce trebuie s aibe gradul de accesibilitate cel puin cu al cmpului a t a ce se declar. Opional, cmpurile pot iniializate cu valori compatibile. a t a t Un astfel de cmp se poate folosi e prin specicarea numelui su, e printr-o a a calicare bazat pe numele clasei sau al unui obiect. a Exemplu: class A { int a;//acces implicit de tip privat static void Main() { A objA = new A(); objA.a = 1;//se poate accesa in interiorul clasei } }

3.10.1

Cmpuri instane a t

Dac o declaraie de cmp nu include modicatorul static, atunci acel a t a cmp se va regsi orice obiect de tipul clasei curente care va instaniat. a a n t Modicri ale acestor cmpuri se vor face independent pentru ecare obiect. a a Deoarece un astfel de cmp are o valoare specic ecrui obiect, accesarea a a a lui se va face prin calicarea cu numele obiectului: objA.a = 1; (dac modicatorii de acces permit aa ceva). interiorul unui instane de a s In t clase se poate folosi cuvntul this, reprezentnd referina la obiectul curent. a a t

3.10.2

Cmpuri statice a

Cnd o declaraie de cmp include un specicator static, cmpul respectiv a t a a nu apaine ecrei instane particular, ci clasei ai. Accesarea unui t a t n nss cmp static din exteriorul clasei nu se face prin intermediul unui obiect, ci a prin numele clasei: class B { public static int V = 3; static void Main() {

84

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME B.V++;//corect V++;//corect B b = new B(); b.V++//incorect }

} Dac se face calicarea unui astfel de cmp folosind un nume de obiect se a a semnaleaz o eroare de compilare. a

3.10.3

Cmpuri readonly a

Declararea unui cmp de tip readonly (static sau nu) se face prin specia carea cuvntului readonly declaraia sa: a n t class A { public readonly string salut = Salut; public readonly string nume; public class A(string nume) { this.nume = nume; } } Atribuirea asupra unui cmp de tip readonly se poate face doar la declararea a sa (ca exemplu de mai sus) sau prin intermediul unui constructor. Valoarea n unor astfel de cmpuri nu e obligatorie a cunoscute la compilare. a

3.10.4

Cmpuri volatile a

Modicatorul "volatile" se poate specica doar pentru tipurile: byte, sbyte, short, ushort, int, uint, char, oat, bool; un tip enumerare avnd tipul de reprezentare byte, sbyte, short, ushort, a int, uint; un tip referina t Pentru cmpuri nevolatile, tehnicile de optimizare care reordoneaz instruciunile a a t pot duce la rezultate neateptate sau nepredictibile programe multithreads n ing care acceseaz cmpurile fr sincronizare (efectuabil cu instruciunea a a aa a t

3.11. CONSTANTE

85

lock). Aceste optimizri pot fcute de ctre compilator, de ctre sistemul a a a a 6 de rulare sau de ctre hardware. Urmtoarele tipuri de optimizri sunt a a a afectate prezena unui modicator volatile: n t citirea unui cmp volatile este garantat c se va ampla a a a nt nainte de orice referire la cmp care apare dup citire; a a orice scriere a unui cmp volatile este garantat c se va petrece dup a a a a orice instruciune anterioar care se refer la cmpul respectiv. t a a a

3.10.5

Iniializarea cmpurilor t a

Pentru ecare cmp declarat se va asigna o valoare implicit astfel: a a numeric: 0 bool: false char: \0 enum: 0 referina: null t

3.11

Constante

O constant este un cmp a crui valoare poate calculat la compia a a a lare. O constant poate prexat de urmtorii modicatori: new, public, a a a protected, internal, protected internal,private. Cuvantul new poate s se coma bine cu unul din ceilali 4 modicatori de acces. Pentru un cmp constant e t a obligatoriu s se asigneze o valoare calculabil la compilare: a a class A { public const int n=2; } Tipul unei constante poate sbyte, byte, short, ushort, int, uint, long, ulong, char, oat, double decimal, bool, string, enum, referina. Valoarea care se t asignez unei constante trebuie s admit o conversie implicit ctre tipul a a a a a constantei. Tipul unei constante trebuie s e cel puin la fel de accesibil ca a t i constanta ai. s nss
6

Engl: runtime system

86

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

Orice cmp constant este automat un cmp static. Un cmp constant a a a difer de un cmp static readonly: const-ul are o valoare cunoscut la coma a a pilare, pe cnd valoarea unui readonly poate iniializat la runtime a t a n interiorul constructorului (cel mai trziu, de altfel). a

3.12

Metode

O metod este un membru care implementeaz o aciune care poate a a t efectuat de ctre un obiect sau o clas. Antetul unei metode se declar a a a a n felul urmtor: a atributeopt modicator-de-metodaopt tip-de-retur nume (lista-parametrilorformaliopt ) corp-metoda unde modicator-de-metoda poate : orice modicator de acces new static virtual sealed override abstract extern Tipul de retur poate orice tip de dat care este cel puin la fel de accesibil a t ca i metoda ai sau void (absena informaiei returnate); nume poate un s nss t t identicator de metod din clasa curent sau un identicator calicat cu nua a mele unei interfee pe care o implementeaz (NumeInterfata.numeMetoda); t a parametrii pot de tip ref, out, params, sau fr nici un calicator; corpaa metoda este un bloc cuprins ntre acolade sau doar caracterul ; (dac este a vorba de o metod ce nu se implementeaz tipul curent). a a n Despre calicatorii virtual, override, sealed, new, abstract se va discuta mai pe larg ntro seciune viitoare. t

3.12. METODE

87

3.12.1

Metode statice i nestatice s

O metod se declar a static dac numele ei este prexat cu modicaa a a a torul de metod static. O astfel de metod nu opereaz asupra unei instane a a a t anume, ci doar asupra clasei. Este o eroare ca o metod static s fac a a a a referire la un membru nestatic al unei clase. Apelul unei astfel de metode se face prin NumeClasa.NumeMetoda sau direct NumeMetoda dac este apelat a a din context static al aceleiai clase (de exemplu de ctre o metod static, s a a a dar se poate i dintro clas imbricat a se vedea seciunea dedicat 4.5). s a a t a O metod nestatic nu are cuvntul static specicat; ea este apelabil a a a a pentru un obiect anume.

3.12.2

Metode externe

Metodele externe se declar folosind modicatorul extern; acest tip de a metode sunt implementate extern, de obicei alt limbaj dect C#. Deoarece n a o astfel de metod nu conine o implementare, corpul acestei metode este ;. a t Exemplu: se utilizeaz metoda MessageBox importat din biblioteca dll a a User32.dll: using System; using System.Runtime.InteropServices; class Class1 { [DllImport("User32.dll")] public static extern int MessageBox(int h, string m, string c, int type); static void Main(string[] args) { int retVal = MessageBox(0, "Hello", "Caption", 0); } }

88

CURS 3. CLASE, INSTRUCTIUNI, SPATII DE NUME

Curs 4 Clase (continuare)


4.1 Proprieti at

O proprietate este un membru care permite acces la partea de stare a unei clase. Exemple de proprieti sunt: lungimea unui ir, numele unui at s client, textul continut ntrun control de tip TextBox. Proprietile sunt at extensii naturale ale cmpurilor, cu deosebirea c ele nu presupun alocarea a a de memorie. Ele sunt de fapt nite metode (accesori) care permit citirea sau s setarea unor atribute ale unui obiect sau clase; reprezint modalitatea de a scriere a unor metode de tip get/set pentru clase sau obiecte. Declararea unei proprieti se face astfel: at modicator-de-proprietateopt tip numeproprietate denitie-getopt denitie-setopt unde modicator-de-proprietate este: atributeopt modicator-de-accesopt get corp-get atributeopt modicator-de-accesopt set corp-set Modicatorii de acces sunt: protected, internal, private, protected internal, public. Tipul unei proprieti specic tipul de dat ce poate accesat, i.e. ce at a a valori vor putea atribuite proprietii respective (dac accesorul de tip set a at a fost denit), respectiv care este tipul valorii returnate de aceast proprietate a (corespunztor accesorului de tip get). a Exemplu: using System; class Circle { private double radius; public double Radius { 89

90 get { return radius; } set { radius = value; }

CURS 4. CLASE (CONTINUARE)

} public double Area { get { return Math.PI * radius * radius; } set { radius = Math.Sqrt(value/Math.PI); } } } class Test { static void Main() { Circle c = new Circle(); c.Radius = 10; Console.WriteLine(Area: {0}, c.Area); c.Area = 15; Console.WriteLine(Radius: {0}. c.Radius) } } Un accesor de tip get corespunde unei metode fr parametri, care returneaz aa a o valoare de tipul proprietii. Cnd o proprietate este folosit at a a ntro expresie, accesorul get este o apelat pentru a returna valoarea cerut. a Un accesor de tip set corespunde unei metode cu un singur parametru de tipul proprietii i tip de retur void. Acest parametru implicit al lui set at s este numit ntotdeauna value. Cnd o proprietate este folosit ca destinatar a a ntro atribuire, sau cnd se folosesc operatorii ++ i , accesorului set i a s

4.1. PROPRIETATI

91

se transmite un parametru care reprezint noua valoare. a funcie de prezena sau absena accesorilor, o proprietate este clasiIn t t t cat dup cum urmeaz: a a a proprietate readwrite, dac are ambele tipuri de accesori; a proprietate readonly, dac are doar accesor de tip get; este o eroare de a compilare s se fac referire program la o proprietate sensul a a n n n care sar cere operarea cu un accesor de tip set; proprietate writeonly, dac este prezent doar accesorul de tip set; este a o eroare de compilare utilizarea unei proprieti at ntrun context care n ar necesar prezena accesorului get. a t Exist cazuri care se dorete ca un accesor sa aib un anumit grad a n s a de acces (public, de exemplu), iar celalat alt tip de acces (e.g. protected). Incepnd cu .NET Framework 2.0, acest lucru este posibil: a public class Employee { private string name; public Employee(string name) { this.name = name; } public string Name { get { return name; } protected set { name = value; } } } Intrun asemenea caz, trebuie respectat urmtoarea regul: a a a ntreaga proprietate trebuie s e declarat cu grad de acces mai larg dect accesorul a a a pentru care se resticioneaz gradul de acces. t a Demn de menionat este c proprietile pot folosite nu doar pentru t a at a asigura o sintax simplu de folosit pentru metodele tradiionale de tip a t get/set, ci i pentru scrierea controalelor .NET utilizator. s gura 4.1 este dat reprezentarea unui control utilizator: a In Codul corespunztor este dat mai jos: a using System; using System.Collections;

92

CURS 4. CLASE (CONTINUARE)

Figura 4.1: Control denit de utilizator

using using using using

System.ComponentModel; System.Drawing; System.Data; System.Windows.Forms;

namespace UserControlSample { public class UserControl1 : System.Windows.Forms.UserControl { private System.Windows.Forms.Label label1; private System.Windows.Forms.TextBox streetTextBox; private System.Windows.Forms.Label label2; private System.Windows.Forms.TextBox numberTextBox; private System.Windows.Forms.Label label3; private System.Windows.Forms.TextBox phoneTextBox; private System.ComponentModel.Container components = null; public UserControl1() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); } protected override void Dispose( bool disposing ) { if( disposing ) { if( components != null ) components.Dispose(); } base.Dispose( disposing ); }

4.1. PROPRIETATI #region Component Designer generated code

93

private void InitializeComponent() { this.label1 = new System.Windows.Forms.Label(); this.streetTextBox = new System.Windows.Forms.TextBox(); this.label2 = new System.Windows.Forms.Label(); this.numberTextBox = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); this.phoneTextBox = new System.Windows.Forms.TextBox(); this.SuspendLayout(); this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(8, 16); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(34, 13); this.label1.TabIndex = 0; this.label1.Text = "Street"; this.streetTextBox.Location = new System.Drawing.Point(56, 14); this.streetTextBox.Name = "streetTextBox"; this.streetTextBox.TabIndex = 1; this.streetTextBox.Text = ""; this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(8, 48); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(44, 13); this.label2.TabIndex = 2; this.label2.Text = "Number"; this.numberTextBox.Location = new System.Drawing.Point(56, 44); this.numberTextBox.Name = "numberTextBox"; this.numberTextBox.TabIndex = 3; this.numberTextBox.Text = ""; this.label3.AutoSize = true; this.label3.Location = new System.Drawing.Point(8, 79); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(37, 13); this.label3.TabIndex = 4; this.label3.Text = "Phone"; this.phoneTextBox.Location = new System.Drawing.Point(56, 75); this.phoneTextBox.Name = "phoneTextBox"; this.phoneTextBox.TabIndex = 5; this.phoneTextBox.Text = "";

94

CURS 4. CLASE (CONTINUARE) this.Controls.AddRange(new System.Windows.Forms.Control[] { this.phoneTextBox, this.label3, this.numberTextBox, this.label2, this.streetTextBox, this.label1}); this.Name = "UserControl1"; this.Size = new System.Drawing.Size(168, 112); this.ResumeLayout(false); } #endregion [Category ("Data"), Description ("Contents of Street Control")] public string Street { get{ return streetTextBox.Text; } set{ streetTextBox.Text = value; } } [Category ("Data"), Description ("Contents of Number Control")] public string Number { get{ return numberTextBox.Text; } set{ numberTextBox.Text = value; } } [Category ("Data"), Description ("Contents of Phone Control")] public string Phone { get{ return phoneTextBox.Text; } set{ phoneTextBox.Text = value; } } }

} Interesante sunt aici proprietile publice Street, Number i Phone care vor at s vizibile fereastra Properties atunci cnd acest control va adugat la o n a a form. Atributele cuprinse a ntre paranteze drepte sunt opionale, dar vor face t ca aceste proprieti s e grupate seciunea de date a ferestrei Properties, at a n t i nu cea Misc. s n

4.2. INDEXATORI

95

4.2

Indexatori

Uneori are sens tratarea unui obiect ca ind un vector de elemente. Un indexator este o generalizare a supra arcrii operatorului [] din C++. nc a Declararea unui indexator se face felul urmtor: n a atributeopt modicatori-de-indexatoropt declarator-de-indexator {declaratiide-accesori} Modicatorii de indexator pot : new, public, protected, internal, private, protected internal, virtual, sealed, override, abstract, extern. Declaratorul de indexator are forma: tip-de-retur this[lista-parametrilor-formali]. Lista parametrilor formali trebuie s conin cel puin un parametru i nu a t a t s poate s aibe vreun parametru de tip ref sau out. Declaraiile de accesor vor a t conine accesor get sau accesor set, asemntor cu cei de la proprieti. t a a at Exemple: 1. Exemplul 1: un indexator simplu: using System; class MyVector { private double[] v; public MyVector( int length ) { v = new double[ length ]; } public int Length { get { return length; } } public double this[int index] { get { return v[ index]; } set {

96 v[index] = value; } } }

CURS 4. CLASE (CONTINUARE)

class Test { static void Main() { MyVector v = new MyVector( 10 ); v[0] = 0; v[1] = 1; for( int i=2; i<v.Length; i++) { v[i] = v[i-1] + v[i-2]; } for( int i=0; i<v.Length; i++) { Console.WriteLine(v[ + i.ToString() + ]= + v[i]); } } } 2. Exemplul 2: supra arcarea indexatorilor: nc using System; using System.Collections; class DataValue { public DataValue(string name, object data) { this.name = name; this.data = data; } public string Name { get { return(name); } set

4.2. INDEXATORI { name = value; } } public object Data { get { return(data); } set { data = value; } } string name; object data; } class DataRow { ArrayList row; public DataRow() { row = new ArrayList(); } public void Load() { row.Add(new DataValue("Id", 5551212)); row.Add(new DataValue("Name", "Fred")); row.Add(new DataValue("Salary", 2355.23m)); } public object this[int column] { get { return(row[column - 1]); }

97

98

CURS 4. CLASE (CONTINUARE) set { row[column - 1] = value; } } private int findColumn(string name) { for (int index = 0; index < row.Count; index++) { DataValue dataValue = (DataValue) row[index]; if (dataValue.Name == name) return(index); } return(-1); } public object this[string name] { get { return this[findColumn(name)]; } set { this[findColumn(name)] = value; } } } class Test { public static void Main() { DataRow row = new DataRow(); row.Load(); DataValue val = (DataValue) row[0]; Console.WriteLine("Column 0: {0}", val.Data); val.Data = 12; // set the ID DataValue val = (DataValue) row["Id"]; Console.WriteLine("Id: {0}", val.Data);

4.2. INDEXATORI

99

Console.WriteLine("Salary: {0}", ((DataValue) row["Salary"]).Data); ((DataValue)row["Name"]).Data = "Barney"; // set the name Console.WriteLine("Name: {0}", ((DataValue) row["Name"]).Data); } } 3. Exemplul 3: Indexator cu mai muli parametri: t using System; namespace MyMatrix { class Matrix { double[,] matrix; public Matrix( int rows, int cols ) { matrix = new double[ rows, cols]; } public double this[int i, int j] { get { return matrix[i,j]; } set { matrix[i,j] = value; } } public int RowsNo { get { return matrix.GetLength(0); } }

100

CURS 4. CLASE (CONTINUARE) public int ColsNo { get { return matrix.GetLength(1); } } static void Main(string[] args) { MyMatrix m = new MyMatrix(2, 3); Console.WriteLine("Lines: {0}", m.RowsNo); Console.WriteLine("Columns: {0}", m.ColsNo); for(int i=0; i<m.RowsNo; i++) { for( int j=0; j<m.ColsNo; j++) { m[i,j] = i + j; } } for(int i=0; i<c.RowsNo; i++) { for( int j=0; j<c.ColsNo; j++) { Console.Write(c[i,j] + " "); } Console.WriteLine(); } } } }

Remarcm ca accesarea elementelor se face prin perechi de tipul get/set, a precum la proprieti. Ca i cazul proprietilor, este posibil ca un accesor at s n at s aibe un alt grad de acces dect cellalt, folosind acelasi mecanism: se a a a declar indexatorul ca avnd un anumit grad de accesibilitate, iar pentru un a a accesor se va declara un grad de acces mai restrictiv.

4.3. OPERATORI

101

4.3

Operatori

Un operator este un membru care denete semnicaia unei expresii ops t erator care poate aplicat unei instane a unei clase. Corespunde supra arcrii a t nc a operatorilor din C++. O declaraie de operator are forma: t atributeopt modicatori-de-operator declaratie-de-operator corp-operator Se pot declara operatori unari, binari i de conversie. s Urmtoarele reguli trebuie s e respectate pentru orice operator: a a 1. Orice operator trebuie s e declarat public i static. a s 2. Parametrii unui operator trebuie s e transmii prin valoare; a s 3. Acelai modicator nu poate aprea de mai multe ori antetul unui s a n operator

4.3.1

Operatori unari

Supra arcarea operatorilor unari are forma: nc tip operator operator-unar-supraincarcabil (tip identicator) corp Operatorii unari supra arcabili sunt: + - ! ++ true false. Urmtoarele nc a reguli trebuie s e respectate la supra arcarea unui operator unar (T a nc reprezint clasa care conine deniia operatorului): a t t 1. Un operator +, -, !, trebuie s preia un singur parametru de tip T i a s poate returna orice tip. 2. Un operator ++ sau trebuie s preia un singur parametru de tip T a i trebuie s returneze un rezultat de tip T. s a 3. Un operator unar true sau false trebuie s preia un singur parametru a de tip T i s returneze bool. s a Operatorii true i false trebuie s e ori ambii denii, ori nici unul (altfel s a t apare o eroare de compilare). Ei sunt necesari pentru cazuri de genul: if( a==true ) sau if( a==false )

102

CURS 4. CLASE (CONTINUARE)

Mai general, ei pot folosii ca expresii de control if, do, while i for, t n s precum i operatorul ternar "? :". Dei pare paradoxal, nu este obligatoriu s n s ca if (a==true) s e echivalent cu if (!(a==false)), de exemplu pentru a a tipuri SQL care pot avea valoare de nul, ceea ce nu nseam nici true, nici a false, ci lips de informaie. a t Exemplu: public struct DBBool { private int x; public static bool operator true(DBBool x) { return x.value > 0; } public static bool operator false(DBBool x) { return x.value <= 0; } ... } Exemplul de mai jos arat modul care se face supra arcarea operaa n nc torului ++, care poate folosit att ca operator de preincrementare ct i a a s ca operator de postincrementare: public class IntVector { public int Length { ... } // read-only property public int this[int index] { ... } // read-write indexer public IntVector(int vectorLength) { ... } public static IntVector operator++(IntVector iv) { IntVector temp = new IntVector(iv.Length); for (int i = 0; i < iv.Length; ++i) temp[i] = iv[i] + 1; return temp; } } class Test { static void Main()

4.3. OPERATORI { IntVector iv1 = new IntVector(4); // vector of 4x0 IntVector iv2; iv2 = iv1++; // iv2 contains 4x0, iv1 contains 4x1 iv2 = ++iv1; // iv2 contains 4x2, iv1 contains 4x2 } }

103

4.3.2

Operatori binari

Declararea unui operator binar se face astfel: tip operator operator-binar-supraincarcabil ( tip identicator, tip identicator) corp Operatorii binari supra arcabili sunt: + - * / % & | ^ << >> == != > < >= <=. nc Cel puin unul dintre cei doi parametri preluai trebuie s e de tipul conintor. t t a t a Operatorii de shiftare trebuie s aib primul parametru de tipul clasei care a a n se declar, iar al doilea parametru de tip int. Unii operatori trebuie s se a a declare pereche: n 1. operatorii == i != s 2. operatorii > i < s 3. operatorii >= i <= s Pentru operaratorul ==, este indicat i denirea metodei Equals(), deoarece as tipul respectiv va putea astfel folosit i de ctre limbaje care nu suport s a a supra arcarea operatorilor, dar pot apela metoda polimorc Equals() denit nc a a clasa object. n Nu se pot supa arca operatorii + =, =, / =, =; dar pentru ca nc acetia s funcioneze, este sucient s se supra s a t a ncarce operatorii corespunztori: a +, , /, .

4.3.3

Operatori de conversie

O declaraie de operator de conversie trebuie introduce o conversie denit t a de utilizator, care se va aduga (dar nu va suprascrie) la conversiile predea nite. Declararea unui operator de conversie se face astfel: implicit operator tip (tip parametru) corp explicit operator tip (tip parametru) corp Dup cum se poate deduce, conversiile pot implicite sau explicite. Un astfel a de operator va face conversia de la un tip surs, indicat de tipul parametrua lui din antet la un tip destinaie, indicat de tipul de retur. O clas poate s t a a

104

CURS 4. CLASE (CONTINUARE)

declare un operator de conversie de la un tip surs S la un tip destinaie T a t cu urmtoarele condiii: a t 1. S i T sunt tipuri diferite s 2. Unul din cele dou tipuri este clasa care se face denirea. a n 3. T i S nu sunt object sau tip interfaa. s t 4. T i S nu sunt baze una pentru cealalt. s a Un bun design asupra operatorilor de conversie are vedere urmtoarele: n a Conversiile implicite nu ar trebui s duc la pierdere de informaie sau a a t la apariia de excepii; t t Dac prima condiie nu este a t ndeplinit, atunci neaprat trebuie declarat a a a ca o conversie explicit. a Exemplu: using System; public class Digit { byte value; public Digit(byte value) { if (value < 0 || value > 9) throw new ArgumentException(); this.value = value; } public static implicit operator byte(Digit d) { return d.value; } public static explicit operator Digit(byte b) { return new Digit(b); } } Prima conversie este implicit pentru c nu va duce la pierderea de informaie. a a t Cea de doua poate s arunce o excepie (via constructor) i de aceea este a t s declarat ca i conversie explicit. a s a

4.3. OPERATORI

105

4.3.4

Exemplu: clasa Fraction

using System; public class Fraction { public Fraction(int numerator, int denominator) { Console.WriteLine("In constructor Fraction(int, int)"); this.numerator=numerator; this.denominator=denominator; } public Fraction(int wholeNumber) { Console.WriteLine("In Constructor Fraction(int)"); numerator = wholeNumber; denominator = 1; } public static implicit operator Fraction(int theInt) { System.Console.WriteLine("In conversie implicita la Fraction"); return new Fraction(theInt); } public static explicit operator int(Fraction theFraction) { System.Console.WriteLine("In conversie explicita la int"); return theFraction.numerator / theFraction.denominator; } public static bool operator==(Fraction lhs, Fraction rhs) { Console.WriteLine("In operator =="); if (lhs.denominator * rhs.numerator == rhs.denominator * lhs.numerator ) { return true; } return false; } public static bool operator !=(Fraction lhs, Fraction rhs) {

106

CURS 4. CLASE (CONTINUARE) Console.WriteLine("In operator !="); return !(lhs==rhs);

} public override bool Equals(object o) { Console.WriteLine("In metoda Equals"); if (! (o is Fraction) ) { return false; } return this == (Fraction) o; } public static Fraction operator+(Fraction lhs, Fraction rhs) { Console.WriteLine("In operator+"); // 1/2 + 3/4 == (1*4) + (3*2) / (2*4) == 10/8 int firstProduct = lhs.numerator * rhs.denominator; int secondProduct = rhs.numerator * lhs.denominator; return new Fraction( firstProduct + secondProduct, lhs.denominator * rhs.denominator ); //ar mai trebui facuta reducerea termenilor } public override string ToString( ) { String s = numerator.ToString( ) + "/" + denominator.ToString( ); return s; } private int numerator; private int denominator; } public class Tester { static void Main( ) { Fraction f1 = new Fraction(3,4); Console.WriteLine("f1: {0}", f1.ToString( )); Fraction f2 = new Fraction(2,4); Console.WriteLine("f2: {0}", f2.ToString( ));

4.4. CONSTRUCTOR STATIC Fraction f3 = f1 + f2; Console.WriteLine("f1 + f2 = f3: {0}", f3.ToString( )); Fraction f4 = f3 + 5; Console.WriteLine("f3 + 5 = f4: {0}", f4.ToString( )); Fraction f5 = new Fraction(2,4); if (f5 == f2) { Console.WriteLine("F5: {0} == F2: {1}", f5.ToString( ), f2.ToString( )); } } }

107

4.4

Constructor static

Un constructor static este un membru care implementeaz aciunile cerute a t pentru iniializara unei clase. Declararea unui constructor static se face ca t mai jos: atributeopt modicator-de-constructor-static identicator( ) corp Modicatorii de constructori statici se pot da sub forma: externopt static sau static externopt Constructorii statici nu se motenesc, nu se pot apela direct i nu se pot supras s arca. Un constructor static se va executa cel mult o dat nc a ntr-o aplicaie. t Se garanteaz faptul c acest constructor se va apela a a naintea primei creri a a unei instane a clasei respective sau t naintea primului acces la un membru static. Acest apel este nedeterminist, necunoscnduse exact cnd sau dac a a a se va apela. Un astfel de constuctor nu are specicator de acces i poate s s a acceseze doar membri statici. Exemplu: class Color { public Color(byte red, byte green, byte blue) { this.red = red; this.green = green; this.blue = blue; } byte red;

108 byte green; byte blue;

CURS 4. CLASE (CONTINUARE)

public static readonly Color Red; public static readonly Color Green; public static readonly Color Blue; // constructor static static Color() { Red = new Color(255, 0, 0); Green = new Color(0, 255, 0); Blue = new Color(0, 0, 255); } } class Test { static void Main() { Color background = Color.Red; } }

4.5

Clase imbricate

O clas conine membri, iar particular acetia pot i clase. a t n s s Exemplul 1: using System; class A { class B { public static void F() { Console.WriteLine(A.B.F); } } static void Main() { A.B.F(); }

4.5. CLASE IMBRICATE } Exemplul 2: public class List { // Private data structure private class Node { public object Data; public Node Next; public Node(object data, Node next) { this.Data = data; this.Next = next; } } private Node first = null; private Node last = null; //Interfata publica public void AddToFront(object o) {...} public void AddToBack(object o) {...} public object RemoveFromFront() {...} public object RemoveFromBack() {...} public int Count { get {...} } }

109

Accesarea unei clase imbricate se face prin NumeClasaExterioara.NumeClasaInterioara (aa cum este artat Exemplul 1), de unde se deduce c o s a n a clas imbricat se comport ca un membru static al tipului conintor. O a a a t a clas declarat interiorul unei alte clase poate avea unul din gradele de a a n accesibilitate public, protected internal, protected, internal, private (implicit este private). O clas declarat interiorul unei structuri poate declarat a a n a public, internal sau private (implicit private). Crearea unei instane a unei clase imbricate nu trebuie s e precedat t a a de crearea unei instane a clasei exterioare conintoare, aa cum se vede t t a s din Exemplul 1. O clas imbricat nu are vreo relaie special cu membrul a a t a predenit this al clasei conintoare. Altfel spus, nu se poate folosi this t a n interiorul unei clase imbricate pentru a accesa membri instana din tipul t conintor. Dac o clas imbricat are nevoie s acceseze membri instana ai t a a a a a t clasei conintoare, va trebui s primeasc prin constructor parametrul this t a a a care s se refer la o astfel de instana: a a t

110 using System; class C { int i = 123; public void F() { Nested n = new Nested(this); n.G(); }

CURS 4. CLASE (CONTINUARE)

public class Nested { C this_c; public Nested(C c) { this_c = c; } public void G() { Console.WriteLine(this_c.i); } } } class Test { static void Main() { C c = new C(); c.F(); } } Se observ de mai sus c o clas imbricat poate manipula toi membrii din a a a a t interiorul clasei conintoare, indiferent de gradul lor de accesibilitate. t a In cazul care clasa exterioar (conintoare) are membri statici, acetia pot n a t a s utilizai fr a se folosi numele clasei conintoare: t aa t a using System; class C { private static void F() {

4.6. DESTRUCTORI Console.WriteLine("C.F"); } public class Nested { public static void G() { F(); } } } class Test { static void Main() { C.Nested.G(); } }

111

Clasele imbricate se folosesc intens cadrul containerilor pentru care tren buie s se construiasc un enumerator. Clasa imbricat va acest caz a a a n strns legat de container i va duce la o implementare uor de urmrit i de a a s s a s ntreinut. t

4.6

Destructori

Managementul memoriei este fcut sub platforma .NET mod automat, a n de ctre garbage collector, parte component a CLRului. a a Acest mecanism de garbage collection scutete programatorul de grija s dealocrii memoriei. Dar exist situaii care se dorete s se fac mana a t n s a a agement manual al dealocrii resurselor (de exemplu al resurselor care in de a t sistemul de operare sau de servere: iere, conexiuni la reea sau la serverul s t de baze de date, ferestre, etc, sau al altor resurse al cror management nu se a face de ctre CLR). C# exist posibilitatea de a lucra cu destructori sau a In a cu metode de tipul Dispose(), Close(). Un destructor se declar felul urmtor: a n a atributeopt externopt ~identicator() corp-destructor unde identicator este numele clasei. Un destructor nu are modicator de acces, nu poate apelat manual, nu poate supra arcat, nu este motenit. nc s Un destructor este o scurttur sintactic pentru metoda Finalize(), care a a a este denit clasa System.Object. Programatorul nu poate s suprascrie a n a sau s apeleze aceast metod. a a a

112 Exemplu:

CURS 4. CLASE (CONTINUARE)

~MyClass() { // Perform some cleanup operations here. } Metoda de mai sus este automat translatat a n: protected override void Finalize() { try { // Perform some cleanup operations here. } finally { base.Finalize(); } } Problema cu destructorul este c el e chemat doar de ctre garbage colleca a tor, dar acest lucru se face nedeterminist (cu toate c apelarea de destructor a se face cele din urm, dac programatorul nu n a a mpiedic explicit acest a lucru). Exist cazuri care programatorul dorete s fac dealocarea manual, a n s a a astfel at s nu atepte ca garbage collectorul s apeleze destructorul. Pronc a s a gramatorul poate scrie o metoda care s fac acest lucru. Se sugereaz a a a denirea unei metode Dispose() care ar trebui s e explicit apelat atunci a a cnd resurse de sistem de operare trebuie s e eliberate. plus, clasa rea a In spectiv ar trebui s implementeze interfaa System.IDisposable, care conine a a t t aceast metod. a a acest caz, Dispose() ar trebui s inhibe executarea ulterioar a destrucIn a a torului (care am vazut ca e de fapt un nalizator, metoda Finalize) pentru instana curent. Aceast manevr permite evitarea eliberrii unei resurse t a a a a de dou ori. Dac clientul nu apeleaz explicit Dispose(), atunci garbage a a a collectorul va apela el destructorul la un moment dat. Intruct utilizatorul a poate s nu apeleze Dispose(), este necesar ca tipurile care implemeteaz a a aceast metod s deneasc de asemenea destructor. a a a a Exemplu: public class ResourceUser: IDisposable {

4.7. CLASE STATICE

113

public void Dispose() { hwnd.Release();//elibereaza o fereastra in Win32 GC.SuppressFinalization(this);//elimina apel de Finalize() } ~ResourceUser() { hwnd.Release(); } } Pentru anumite clase C# se pune la dispoziie o metod numit Close() t a a n locul uneia Dispose(): siere, socket-uri, ferestre de dialog, etc. Este indicat ca s se adauge o metod Close() care s fac doar apel de Dispose(): a a a a //in interiorul unei clase public void Close() { Dispose(); } Modalitatea cea mai indicat este folosirea unui bloc using, caz care se va a n elibera obiectul alocat (via metoda Dispose()) la sfritul blocului: as using( obiect ) { //cod }//aici se va apela automat metoda Dispose()

4.7

Clase statice

Incepnd cu versiunea 2.0 a platformei .NET s-a introdus posibilitatea de a a deni clase statice. Acest tip de clas se folosete atunci cnd se dorete a s a s accesarea membrilor fr a nevoie s se lucreze cu obiecte; se pot folosi aa a acolo unde buna funcionare nu este dependent de starea unor instane. t a t Pentru a crea o clas static se folosete cuvntul static declaraia a a s a n t de clas: a static class MyStaticClass { //membri statici }

114

CURS 4. CLASE (CONTINUARE)

Orice clas static are urmtoarele proprieti: a a a at 1. nu poate instaniat t a 2. nu poate motenit (este automat sealed, vezi seciunea 4.9) s a t 3. conine doar membri statici t Exemplu: public static class TemperatureConverter { public static double CelsiusToFahrenheit(string temperatureCelsius) { double celsius = Double.Parse(temperatureCelsius); double fahrenheit = (celsius * 9 / 5) + 32; return fahrenheit; } public static double FahrenheitToCelsius(string temperatureFahrenheit) { double fahrenheit = Double.Parse(temperatureFahrenheit); //Convert Fahrenheit to Celsius. double celsius = (fahrenheit - 32) * 5 / 9; return celsius; } } class TestTemperatureConverter { static void Main() { Console.WriteLine("Optiuni"); Console.WriteLine("1. Celsius->Fahrenheit."); Console.WriteLine("2. Fahrenheit->Celsius."); Console.Write(":"); string selection = Console.ReadLine(); double f, c = 0; switch (selection) { case "1": Console.Write("Temperatura Celsius: "); f = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine()); Console.WriteLine("Temperatura in Fahrenheit: {0:F2}", f); break;

4.8. SPECIALIZAREA SI GENERALIZAREA

115

case "2": Console.Write("Temperatura Fahrenheit: "); c = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine()); Console.WriteLine("Temperature in Celsius: {0:F2}", c); break; } } }

4.8

Specializarea i generalizarea s

Specializarea reprezint o tehnic de a obine noi clase pornind de la cele a a t existente. Deseori ntre clasele pe care le modelm putem observa relaii de a t genul este un/o: un om este un mamifer, un salariat este un angajat, etc. Toate acestea duc la crearea unei ierarhii de clase, care din clase de baz n a (mamifer sau angajat) descind alte clase, care pe lng comportament din a a clasa de baz mai au i caracteristici proprii. Obinerea unei clase derivate a s t plecnd de la alt clas se numete specializare iar operaia invers se numete a a a s t a s generalizare. O clas de baz denete un tip comun, compatibil cu oricare a a s din clasele derivate (direct sau indirect). C# o clas nu trebuie s moteneasc explicit din alt clas; acest In a a s a a a n caz se va considera c ea este implicit derivat din clasa predenit object a a a (tot una cu Object). C# nu permite motenire multipl, eliminnd asts a a fel complicaiile alnite C++. Ca alternativ, se permite totui implet nt n a s mentarea de mai multe interfee. t

4.8.1

Specicarea motenirii s

C# se pentru o clas D se denete clasa de baz B folosind urmtoarea In a s a a formul: a class D: B { //declaratii si instructiuni } Dac pentru o anumit clas nu se specic dou puncte urmate de numele a a a a a unei clase de baz atunci object va deveni baz pentru clasa cauz. a a n a Exemplu: //clasa de baza in C#

116 public class Employee { protected string name; protected string ssn; }

CURS 4. CLASE (CONTINUARE)

//clasa derivata in C# public class Salaried : Employee { protected double salary; public Salaried( string name, string ssn, double salary ) { this.name = name; this.ssn = ssn; this.salary = salary; } } Se observ c cmpurile name i ssn din clasa de baz sunt accesibile clasa a a a s a n derivat, datorit specicatorului de acces protected. a a

4.8.2

Apelul constructorilor din clasa de baz a

exemplul anterior nu sa denit nici un constructor clasa de baz In n a Employee; constructorul clasei derivate trebuie s fac iniializrile cmpurilor a a t a a conformitate cu parametrii transmii, chiar dac o parte din aceste cmpuri n s a a provin din clasa de baz. Mai logic ar ca clasa de baz s se gseasc un a n a a a a constructor care s iniializeze cmpurile proprii: name i ssn. Intruct cona t a s a structorii nu se motenesc, e nevoie ca clasa derivat s se fac un apel exs n a a a plicit al constructorului clasei de baz. Acest apel se face prin iniializator de a t constructor care are forma: dou puncte urmate de base(parametrii-efectivi). a public class Employee { protected string name; protected string ssn; public Employee( string name, string ssn) { this.name = name; this.ssn = ssn; System.Console.WriteLine(Employee constructor: {0}, {1}, name, ssn);

4.8. SPECIALIZAREA SI GENERALIZAREA } } public class Salaried : Employee { protected double salary; public Salaried(string name, string ssn, double salary): base(name, ssn) { this.salary = salary; System.Console.WriteLine(Salaried constructor: {0}, salary); } } class Test { Salaried s = new Salaried(Jesse, 1234567890, 100000.00); } La rulare se va obine: t Employee constructor: Jesse, 1234567890 Salaried constructor: 100000.00

117

de unde se deduce c apelul de constructor de clas de baz se face a a a naintea executrii oricror alte instruciuni coninute constructorul clasei derivate. a a t t n Dac o clas de baz nu are denit nici un constructor, atunci se va a a a crea unul implicit (fr parametri). Dac dup un constructor al unei clase aa a a derivate nu se specic un iniializator de constructor, atunci va apelat a t constructorul implicit (e creat automat de compilator, e scris de ctre a programator); dac nu exist nici un constructor implicit clasa de baz, a a n a atunci programatorul trebuie s specice un constructor din clasa de baz a a care va apelat, mpreun cu parametrii adecvai. a t

4.8.3

Operatorii is i as s

Operatorul is Operatorl is este folosit pentru a verica dac un anumit obiect este a de un anumit tip. Este folosit de obicei nainte operaiilor de downcasting t (conversie explicit de la un tip de baz la unul derivat). Operatorul se a a folosete astfel: s

118 instanta is NumeClasa

CURS 4. CLASE (CONTINUARE)

rezultatul acestei operaii ind true sau false. t Exemplu: Employee e = ...; if (e is Salaried) { Salaried s = (Salaried)e; } cazul care sar face conversia explicit iar obiectul nu este de tipul la In n a care se face conversia ar rezulta o excepie: System.InvalidCastException. t Operatorul as Acest operator este folosit pentru conversii explicite, returnnd un obiect a de tipul la care se face conversia sau null dac conversia nu se poate face a (nu se arunc excepii). Determinarea validitii conversiei se face testnd a t at a valoarea rezultat faa de null: dac rezultatul e null atunci conversia nu sa a t a putut face. Ca i precedentul operator se folosete special la downcasting. s s n Exemplu: Employee e = ...; Salaried s = e as Salaried; if (s != null) { //se lucreaza cu instanta valida de tip Salaried }

4.9

Clase sealed

Specicatorul sealed care se poate folosi naintea cuvntului cheie class a specic faptul c clasa curent nu se poate deriva. Este o eroare de compilare a a a ca o clas sealed s e declarat drept clas de baz. a a a a a

Curs 5 Clase - polimorsm, clase abstracte. Structuri, interfee, t delegai t


5.1 Polimorsmul

Polimorsmul este capacitatea unei entiti de a lua mai multe forme. at limbajul C# polimorsmul este de 3 feluri: parametric, adhoc i de In s motenire. s

5.1.1

Polimorsmul parametric

Este cea mai slab form de polimorsm, ind regsit majoritatea a a a a n limbajelor. Prin polimorsmul parametric se permite ca o implementare de funcie s poat prelucra orice numr de parametri. Acest lucru se poate t a a a obine prin folosirea C# a unui parametru de tip params (vezi 3.2). t n

5.1.2

Polimorsmul adhoc

Se mai numete i supra arcarea metodelor, mecanism prin care s s nc n cadrul unei clase se pot scrie mai multe metode, avnd acelai nume, dar a s tipuri i numere diferite de parametri de apel. Alegerea funciei care va s t apelat se va face la compilare, pe baza corespondenei a t ntre tipurile parametrilor de apel i tipurile parametrilor formali. s 119

120

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

5.1.3

Polimorsmul de motenire s

Este forma cea mai evoluat de polimorsm. Dac precedentele forme de a a polimorsm sunt aplicabile fr a se pune problema de motenire, acest aa s n caz este necesar s existe o ierarhie de clase. Mecanismul se bazeaz pe faptul a a c o clas de baz denete un tip care este compatibil din punct de vedere a a a s al atribuirii cu orice tip derivat, ca mai jos: class B{...} class D: B{...} class Test { static void Main() { B b = new D();//upcasting=conversie implicita catre baza B } } Intrun astfel de caz se pune problema: ce se ampl cu metodele avnd nt a a aceeai list de parametri formali i care se regsesc cele dou clase? s a s a n a S considerm exemplul urmtor: avem o clas Shape care conine o a a a a t metod public void Draw(); din Shape se deriveaz clasa Polygon care implea a menteaz aceeai metod mod specic. Problema care se pune este cum a s a n se rezolv un apel al metodei Draw context de upcasting: a n class Shape { public void Draw() { System.Console.WriteLine(Shape.Draw()); } } class Polygon: Shape { public void Draw() { System.Console.WriteLine(Polygon.Draw()); //desenarea s-ar face prin GDI+ } } class Test {

5.1. POLIMORFISMUL static void Main() { Polygon p = new Polygon(); Shape s = p;//upcasting p.Draw(); s.Draw(); } } La compilarea acestui cod se va obine un avertisment: t

121

warning CS0108: The keyword new is required on Polygon.Draw() because it hides inherited member Shape.Draw() dar despre specicatorul new vom vorbi mai jos (oricum, adugarea lui nu a va schimba cu nimic comportamentul de mai jos, doar va duce la dispariia t de avertisment). Codul de mai sus va aa: s Polygon.Draw() Shape.Draw() Dac prima linie aat este conform cu intuiia, cea de-a doua pare la a s a a t prima vedere ciudat, dar de fapt este perfect justicabil: apelul de metod a a a Draw() este rezolvat ecare caz la compilare pe baza tipului declarat al n obiectelor; ca atare apelul precedent este legat de corpul metodei Draw din clasa Shape, chiar dac s a fost instaniat de fapt pe baza unui obiect de tip a t Polygon. Este posibil ca s se doreasc schimbarea acestui comportament: apelul a a de metod Draw s e rezolvat funcie de tipul efectiv al obiectului care a a n t face acest apel, i nu de tipul formal declarat. cazul precedent, apelul s In s.Draw() trebuie s se rezolve de fapt ca ind ctre metoda Draw() din a a Polygon, pentru c acesta este tipul la rulare al obiectului s. Cu alte cuvinte, a apelul ar trebui s e rezolvat la rulare i nu la compilare, funcie de a s n t natura efectiv a obiectelor. Acest comportament polimorc este referit sub a denumirea polimorsm de motenire. s

5.1.4

Virtual i override s

Pentru a asigura faptul c legarea apelului de metode se face la rulare a i nu la compilare, e necesar ca clasa de baz s se specice c metoda s n a a a Draw() este virtual, iar clasa derivat pentru aceeai metod trebuie s a n a s a a se spun c este o suprascriere a celei din baz: a a a

122

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

class Shape{ public virtual void Draw(){...} } class Polygon{ public override void Draw(){...} } urma executrii metodei Main din clasa de mai sus, se va aa: In a s Polygon.Draw() Polygon.Draw() adic sa apelat metoda corespunztoare tipului efectiv de la rulare, ecare a a n caz. cazul care clasa Polygon este la rndul ei motenit i se dorete In n a s a s s ca polimorsmul s funcioneze continuare va trebui ca aceast a treia a t n n a clas s suprascrie (override) metoda Draw(). a a Un astfel de comportament polimorc este benec atunci cnd se folosete a s o colecie de obiecte de tipul unei clase de baz: t a Shape[] painting = new Shape[10]; painting[0] = new Shape(); painting[1] = new Polygon(); ... foreach( Shape s in painting) s.Draw();

5.1.5

Modicatorul new pentru metode

Modicatorul new se foloeste pentru a indica faptul c o metod dintr-o s a a clas derivat care are aceeai semntur cu una dintro clas de baz nu a a s a a a a este o suprascriere polimorc a ei, ci apare ca o nou metod. Este ca i a a a s cum metoda declarat new ar avea nume diferit. a S presupunem urmtorul scenariu: compania A creaz o clas A care a a a a are forma: public class A{ public void M(){ Console.WriteLine(A.M()); } }

5.1. POLIMORFISMUL

123

O alt companie B va crea o clas B care motenete clasa A. Compania a a s s B nu are nici o inuena asupra companiei A sau asupra modului care t n aceasta va face modicri asupra clasei A. Ea va deni interiorul clasei B a n o metod M() i una N(): a s class B: A{ public void M(){ Console.WriteLine(B.M()); N(); base.M(); } protected virtual void N(){ Console.WriteLine(B.N()); } } Atunci cnd compania B compileaz codul, compilatorul C# va produce a a urmtorul avertisment: a warning CS0108: The keyword new is required on B.M() because it hides inherited member A.M() Acest avertisment va notica programatorul c clasa B denete o metod a s a M(), care va ascunde metoda M() din clasa de baz A. Aceast nou metod a a a a ar putea schimba telesul (semantica) lui M(), aa cum a fost creat iniial n s t de compania A. Este de dorit astfel de cazuri compilatorul s avertizeze n a despre posibile nepotriviri semantice. Oricum, programatorii din B vor trebui s pun orice caz specicatorul new a a n naintea metodei B.M() pentru a elimina avertismentul. S presupunem c o aplicaie folosete clasa B() felul urmtor: a a t s n a class App{ static void Main(){ B b = new B(); b.M(); } } La rulare se va aa: s B.M() B.N() A.M()

124

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

S presupunem c A decide adugarea unei metode virtuale N() clasa sa, a a a n metod ce va apelat din M(): a a public class A { public void M() { Console.WriteLine(A.M()); N(); } protected virtual void N() { Console.WriteLine(A.N()); } } La o recompilare fcut de B, este dat urmtorul avertisment: a a a warning CS0114: B.N() hides inherited member A.N(). To make the current member override that implementation, add the override keyword. Otherwise, add the new keyword. acest mod compilatorul avertizeaz c ambele clase ofer o metod N() In a a a a a cror semantic poate s difere. Dac B decide c metodele N() nu sunt a a a a a semantic legate cele dou clase, atunci va specica new, informnd compin a a latorul de faptul c versiunea sa este una nou, care nu suprascrie polimorc a a metoda din clasa de baz. a Atunci cnd codul din clasa App este rulat, se va aa la ieire: a s s B.M() B.N() A.M() A.N() Ultima linie aat se explic tocmai prin faptul c metoda N() din B este s a a a declarat new i nu override (dac ar fost override, ultima linie ar fost a s a B.N(), din cauza polimorsmului). Se poate ca B s decid c metodele M() i N() din cele dou clase sunt a a a s a acest caz, ea poate terge deniia metodei B.M, iar legate semantic. In s t pentru a semnala faptul c metoda B.N() suprascrie metoda omonim din a a acest caz, metoda clasa printe, va a nlocui cuvntul new cu override. In a App.Main va produce:

5.1. POLIMORFISMUL A.M() B.N()

125

ultima linie ind explicat de faptul c B.N() suprascrie o metod virtual. a a a a

5.1.6

Metode sealed

O metod de tip override poate declarat ca ind de tip sealed, astfel a a mpiedicnduse suprascrierea ei a ntro clas derivat din cea curent: a a a using System; class A { public virtual void F() { Console.WriteLine(A.F()); } public virtual void G() { Console.WriteLine(A.G()); } } class B: A { sealed override public void F() { Console.WriteLine(B.F()); } override public void G() { Console.WriteLine(B.G()); } } class C: B { override public void G() { Console.WriteLine(C.G()); } } Modicatorul sealed pentru B.F va mpiedica tipul C s suprascrie metoda a F.

126

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

5.1.7

Exemplu folosind virtual, new, override, sealed

S presupunem urmtoare ierarhie de clase, reprezentat Fig. 5.1; o a a a n clas X moteneste o clas Y dac sensul sgeii este de la X la Y. Fiecare a s a a a t clas are o metod void foo() care determin aarea clasei care este a a a s n denit i pentru care se vor specica new, virtual, override, sealed. S a s a
A

+foo(): void

+foo(): void

+foo(): void

+foo(): void

Figura 5.1: Ierarhie de clase presupunem c clasa de test arat astfel: a a public class Test { static void Main() { A[] x = new A[4]; x[0] = new A(); x[1] = new B(); x[2] = new C(); x[3] = new D(); A a = new A(); B b = new B();

5.1. POLIMORFISMUL C c = new C(); D d = new D(); /* secventa 1 */ for(int i=0; i<4; i++) { x[i].foo(); } /* secventa 2 */ a.foo(); b.foo(); c.foo(); d.foo(); } }

127

funcie de specicatorii metodelor f() din ecare clas, se obin ieirile din In t a t s tabelul 5.1: Tabelul 5.1: Efecte ale diferiilor specicatori. t

Metoda Specicator Ieire secv. 1 s Ieire secv. 2 s Specicator Ieire secv. 1 s Ieire secv. 2 s Specicator Ieire secv. 1 s Ieire secv. 2 s Specicator Eroare de compilare deoarece C.f nu poate suprascrie metoda nevirtual B.f() a Specicator Ieire secv. 1 s Ieire secv. 2 s Avertisment la compilare deoarece

A.f() virtual A.f A.f virtual A.f A.f virtual A.f A.f virtual

B.f() override B.f B.f override B.f B.f new A.f B.f new

C.f() override C.f C.f new B.f C.f new A.f C.f override

D.f() override D.f D.f override D.f D.f new A.f D.f override

virtual virtual A.f A.f A.f B.f

override override A.f D.f C.f D.f

128

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI Tabelul 5.1 (continuare) B.f() C.f() D.f()

Metoda B.f nlocuiete A.f s Specicator Eroare de compilare deoarece deoarece B.f nu poate suprascris de C.f a

A.f()

virtual sealed override override override

5.2

Clase i metode abstracte s

Deseori pentru o anumit clas nu are sens crearea de instane, din cauza a a t unei generaliti prea mari a tipului respectiv. Spunem c aceast clas este at a a a abstract, iar pentru a a mpiedica efectiv crearea de instane de acest tip, se t va specica cuvntul abstract a naintea metodei. exemplele de anterioare, In clasele Employee i Shape ar putea gndite ca ind abstracte: ele conin s a t prea puin informaie pentru a putea crea instane utile. t a t t Analog, pentru o anumit metod din interiorul unei clase uneori nu se a a poate specica o implementare. De exemplu, pentru clasa Shape de mai sus, este imposibil s se dea o implementare la metoda Draw(), tocmai din cauza a generalitii acestei clase. Ar util dac pentru aceast metod programat a a a atorul ar obligat s dea implementri specice ale acestei metode pentru a a diversele clase derivate. Pentru a se asigura tratarea polimorc a acestui a tip abstract, orice metod abstract este automat i virtual. Orice metod a a s a a care este declarat abstract implic declararea clasei ca ind abstract. a a a a Exemplu: abstract class Shape { public abstract void Draw(); //remarcam lipsa implementarii si semnul punct si virgula } Orice clas care este derivat dintro clas abstract va trebui e s nu aib a a a a a a nici o metod abstract motenit fr implementare, e s se declare ca ind a a s a aa a abstract. Existena de metode neimplementate nu va permite instanierea a t t clasei respective.

5.3. TIPURI PARTIALE

129

5.3

Tipuri pariale t

Incepnd cu versiunea 2.0 a platfomei .NET este posibil ca deniia unei a t clase, interfee sau structuri s e fcut mai multe iere surs. Deniia t a a a n s a t clasei se obine din reuniunea prilor componente, lucru fcut automat de t at a ctre compilator. Aceast spargere fragmente este benec urmtoarele a a n a n a cazuri: atunci cnd se lucreaz cu proiecte mari, este posibil ca la o clas s a a a a trebuiasc s lucreze mai muli programatori simultan - ecare cona a t centrnduse pe aspecte diferite. a cnd se lucreaz cu cod generat automat, acesta poate scris separat a a stfel at programatorul s nu interfereze accidental cu el. Situaia nc a t este frecvent alnit cazul aplicaiilor de tip Windows Forms. nt a n t De exemplu, pentru o form nou creat (numit Form1) mediul Visual Studio a a a va scrie un ier numit Form1.Designer.cs care conine partea de iniializare s t t a controalelor i componentelor introduse de utilizator. Partea de tratare a s evenimentelor, constructori, etc este denit a ntr-un alt ier (Form1.cs). s Declararea unei pri a unei clase se face folosind cuvntul cheie partial at a naintea lui class. Exemplu: //fisierul Browser1.cs public partial class Browser { public void OpenPage(String uri) {...} } //fisierul Browser2.cs public partial class Browser { public void DiscardPage(String uri) {...} } Urmtoarele sunt valabile pentru tipuri pariale: a t cuvntul partial trebuie s apara exact a a nainte cuvintelor: class, interface, struct dac pentru o parte se specic un anumit grad de acces, aceasta nu a a trebuie s duc la conicte cu declaraiile din alte pri a a t at

130

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

dac o parte de clas este declarat ca abstract, atunci a a a a ntreaga clas a este considerat abstract a a dac o parte declar clasa ca ind sealed, atunci a a ntreaga clas este a considerat sealed a dac o parte declar c motenete o clas, atunci a a a s s a ntr-o alt parte nu a se mai poate specica o alt derivare a pri diferite pot s declare c se implementeaz interfee multiple at a a a t aceleai cmpuri i metode nu pot denite mai multe pri. s a s n at clasele imbricate pot s e declarate pri diferite, chiar dac clasa a n a t a conintoare e denit t a a ntr-un singur ier: s class Container { partial class Nested { void Test() { } } partial class Nested { void Test2() { } } } Urmtoarele elemente vor reunite pentru deniia clasei: comentarii a t XML, interfee, atribute, membri. t Exemplu: partial class Earth : Planet, IRotate { } partial class Earth : IRevolve { } este echivalent cu: class Earth : Planet, IRotate, IRevolve { }

5.4. STRUCTURI

131

5.4

Structuri

Structurile reprezint tipuri de date asemntoare claselor, cu principala a a a diferena c sunt tipuri valoare (o astfel de variabil va conine direct valt a a t oarea, i nu o adres de memorie). Sunt considerate versiuni uoare ale s a s claselor, sunt folosite predilect pentru tipuri pentru care aspectul comportamental este mai puin pronunat. t t Declaraia unei structuri se face astfel: t atributeopt modicatori-de-structopt struct identicator :interfeeopt corp ;opt t Modicatorii de structur sunt: new, public, protected, internal, private. O a structur este automat derivat din System.ValueType, care la rndul ei este a a a derivat din System.Object; de asemenea, este automat considerat sealed a a (nederivabil). Poate a s implementeze una sau mai multe interfee. a ns a t O structur poate s conin declaraii de constante, cmpuri, metode, a a t a t a proprieti, evenimente, indexatori, operatori, constructori, constructori statici, at tipuri imbricate. Nu poate conine destructor. t La atribuire, se face o copiere a valorilor coninute de ctre surs t a a n destinaie (indiferent de tipul cmpurilor: valoare sau referina). t a t Exemplu: using System; public struct Point { public Point(int xCoordinate, int yCoordinate) { xVal = xCoordinate; yVal = yCoordinate; } public int X { get { return xVal; } set { xVal = value; } } public int Y {

132

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI get { return yVal; } set { yVal = value; }

} public override string ToString( ) { return (String.Format({0}, {1}, xVal,yVal)); } public int xVal; public int yVal; } public class Tester { public static void MyFunc(Point loc) { loc.X = 50; loc.Y = 100; Console.WriteLine(In MyFunc loc: {0}, loc); } static void Main( ) { Point loc1 = new Point(200,300); Console.WriteLine(Loc1 location: {0}, loc1); MyFunc(loc1); Console.WriteLine(Loc1 location: {0}, loc1); } } Dup cum este dat exemplul de mai sus, crearea unei instane se face a n t folosind operatorul new ; dar acest caz, nu se va crea o instana memoria n t n heap, ci pe stiv. Transmiterea lui loc1 fcnduse prin valoare, adic metoda a a a a myFunc nu face dect s modice o copie de pe stiv a lui loc1. La revenire, a a a se va aa tot valoarea original, deoarece loc1 a rmas nemodicat: s a a Loc1 location: 200, 300

5.4. STRUCTURI In MyFunc loc: 50, 100 Loc1 location: 200, 300

133

Deseori pentru o structur se declar cmpurile ca ind publice, pentru a a a a nu mai necesare denirea accesorilor (simplicare implementrii). Ali a t programatori consider a c accesarea membrilor trebuie s se fac precum a ns a a a la clase, folosind proprieti. Oricare ar alegerea, limbajul o sprijin. at a Alte aspecte demne de reinut: t Cmpurile nu pot iniializate la declarare; altfel spus, dac exema t a n plul de mai sus se scria: public int xVal = 10; public int yVal = 20; s-ar semnalat o eroare la compilare. Nu se poate deni un constructor implicit. Cu toate acestea, compilatorul va crea un astfel de constructor, care va iniializa cmpurile la t a valorile lor implicite (0 pentru tipuri numerice sau pentru enumerri, a false pentru bool, null pentru tipuri referina). t Pentru tipul Point de mai sus, urmtoarea secvena de cod este corect: a t a Point a = new Point(0, 0); Point b = new Point(); i duce la crearea a dou puncte cu abcisele i ordonatele 0. Un cons a s structor implicit este apelat atunci cnd se creeaz un tablou de struca a turi: Point[] points = new Points[10]; for( int i=0; i<points.Length; i++ ) { Console.WriteLine(points[i]); } va aa de 10 ori puncte de coordonate (0, 0). De asemenea este apelat s la iniializarea membrilor de tip structur ai unei clase. t a De menionat pentru exemplul anterior c se creeaz un obiect de tip t a a tablou heap, dup care interiorul lui (i nu pe stiv!) se creeaz n a n s a a cele 10 puncte (alocare inline).

134

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

Nu se poate declara destructor. Acetia se declar numai pentru clase. s a Dac programatorul denete un constructor, atunci acesta trebuie s a s a dea valori iniiale pentru cmpurile coninute, altfel apare eroare la t a t compilare. Dac pentru instanierea unei structuri declarate interiorul unei a t n metode sau bloc de instruciuni general nu se apeleaz new, atunci t n a respectiva instana nu va avea asociat nici o valoare (constructorul imt a plicit nu este apelat automat!). Nu se poate folosi respectiva variabil a de tip structur dect dup ce i se iniializeaz toate cmpurile: a a a t a a {//bloc de instructiouni Point p; Console.WriteLine(p); } va duce la apariia erorii de compilare: t Use of unassigned local variable p Dar dup nite asignri de tipul: a s a p.xVal=p.yVal=0; aarea este posibil (practic, orice apel de metod pe instana este s a a t acum acceptat). Dac se a ncearc denirea unei structuri care conine un cmp de tipul a t a structurii, atunci va aprea o eroare de compilare: a struct MyStruct { MyStruct s; } va genera un mesaj din partea compilatorului: Struct member MyStruct.s of type MyStruct causes a cycle in the structure layout Dac o instana este folosit acolo unde un object este necesar, atunci a t a se va face automat o conversie implicit ctre System.Object (boxing). a a Ca atare, utilizarea unei structuri poate duce (dar nu obligatoriu, ci n funcie de context) la un overhead datorat conversiei. t

5.5. INTERFETE

135

5.4.1

Structuri sau clase?

Structurile pot mult mai eciente alocarea memoriei atunci cnd sunt n a reinute t ntrun tablou. De exemplu, crearea unui tablou de 100 de elemente de tip Point (de mai sus) va duce la crearea unui singur obiect (tabloul), iar cele 100 de instane de tip structur ar alocate inline vectorul creat t a n (i nu referine ale acestora). Dac Point ar declarat ca i clas, ar fost s t a s a necesar crearea a 101 instane de obiecte heap (un obiect pentru tablou, a t n alte 100 pentru puncte), ceea ce ar duce la mai mult lucru pentru garbage collector i ar putea duce la fragmentarea heap-ului. s Dar cazul care structurile sunt folosite colecii de tip Object n n n t (de exemplu un ArrayList), se va face automat un boxing, ceea ce duce la overhead (consum suplimentar de memorie i cicli procesor). De asemenea, s la transmiterea prin valoare a unei structuri, se va face copierea tuturor cmpurilor coninute pe stiv, ceea ce poate duce la un overhead semnicativ. a t a

5.5

Interfee t

O interfaa denete un contract. O clas sau o structur care implet s a a menteaz o interfaa ader la acest contract. Relaia dintre o interfaa i a t a t t s un tip care o implementeaz este deosebit de cea existent a a a ntre clase (este un/o): este o relaie de implementare. t O interfaa conine metode, proprieti, evenimente, indexatori. Ea a t t at ns nu va conine implementri pentru acestea, doar declaraii. Declararea unei t a t interfee se face astfel: t atributeopt modicatori-de-interfaaopt interface identicator baza-interfeeiopt t t corp-interfaa ;opt t Modicatorii de interfaa sunt: new, public, protected, internal, private. O t interfaa poate s moteneasc de la zero sau mai multe interfee. Corpul t a s a t interfeei conine declaraii de metode, fr implementri. Orice metod are t t t aa a a gradul de acces public. Nu se poate specica pentru o metod din interiorul a unei interfee: abstract, public, protected, internal, private, virtual, override, t ori static. Exemplu: interface IStorable { void Read( ); void Write(); } O clas care implementeaz o astfel de interfaa se declar ca mai jos: a a t a

136

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

class Document: IStorable { public void Read(){/*cod*/} public void Write(){/*cod*/} //alte declaratii } O clas care implementeaz o interfaa trebuie s deneasc toate metodele a a t a a care se regsesc interfaa respectiv, sau s declare metodele din interfaa a n t a a t ca ind abstracte. Urmtoarele reguli trebuie respectate la implementarea a de interfee: t 1. Tipul de retur al metodei din clas trebuie s coincid cu tipul de retur a a a al metodei din interfaa t 2. Tipul parametrilor formali din metod trebuie s e acelai cu tipul a a s parametrilor formali din interfaa t 3. Metoda din clas trebuie s e declarat public i nestatic. a a a as a Aceste implementri pot declarate folosind specicatorul virtual (deci suba clasele clasei curente pot folosi new i override). s Exemplu: using System; interface ISavable { void Read(); void Write(); } public class TextFile : ISavable { public virtual void Read() { Console.WriteLine("TextFile.Read()"); } public void Write() { Console.WriteLine("TextFile.Write()"); } } public class ZipFile : TextFile {

5.5. INTERFETE public override void Read() { Console.WriteLine("ZipFile.Read()"); } public new void Write() { Console.WriteLine("ZipFile.Write()"); } } public class Test { static void Main() { Console.WriteLine("\nTextFile reference to ZipFile"); TextFile textRef = new ZipFile(); textRef.Read(); textRef.Write(); Console.WriteLine("\nISavable reference to ZipFile"); ISavable savableRef = textRef as ISavable; if(savableRef != null) { savableRef.Read(); savableRef.Write(); } Console.WriteLine("\nZipFile reference to ZipFile"); ZipFile zipRef = textRef as ZipFile; if(zipRef!= null) { zipRef.Read(); zipRef.Write(); } } } La ieire se va aa: s s TextFile reference to ZipFile ZipFile.Read() TextFile.Write() ISavable reference to ZipFile ZipFile.Read()

137

138

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

TextFile.Write() ZipFile reference to ZipFile ZipFile.Read() ZipFile.Write() exemplul de mai sus se foloeste operatorul as pentru a obine o referina la In s t t general, se prefer ca apelul metodelor interfee, pe baza obiectelor create. In t a care sunt implementate din interfaa s se fac via o referina la interfaa t a a t t respectiv, obinut prin intermediul operatorului as (ca mai sus) sau dup a t a a o testare prealabil prin is urmat de conversie explicit, ca mai jos: a a a if (textRef is ISavable) { ISavable is = (ISavable)textRef; is.Read();//etc } general, dac se dorete doar rspunsul la In a s a ntrebarea "este obiectul curent un implementator al interfeei I ?", atunci se recomand folosirea operatorului t a is. Dac se tie c va trebui fcut i o conversie la tipul interfaa, atunci a s a a as t este mai ecient folosirea lui as. Armaia se bazeaz pe studiul codului IL a t a rezultat ecare caz. n S presupunem c exista o interfaa I avnd metoda M() care este ima a t a plementat de o clas C, care denete metoda M(). Este posibil ca aceast a a s a metod s nu aib o semnicaie afara clasei C, ca atare a e de dorit ca a a a t n metoda M() s nu e declarat public. Mecanismul care permite acest lua a a cru se numete implementare explicit. Aceast tehnic permite ascunderea s a a a metodelor motenite dintr-o interfaa, acestea devenind private (calicarea s t lor ca ind publice este semnalat ca o eroare). Implementarea explicit se a a obine prin calicarea numelui de metod cu numele interefei: t a t interface IMyInterface { void F(); } class MyClass : IMyInterface { void IMyInterface.F() { //... } }

5.5. INTERFETE

139

Metodele din interfee care sau implementat explicit nu pot declarate t abstract, virtual, override, new. Mai mult, asemenea metode nu pot accesate direct prin intermediul unui obiect (obiect.NumeMetoda), ci doar prin intermediul unei conversii ctre interfaa respectiv, deoarece prin implea t a mentare explicit a metodelor aceste devin private i singura modalitate de a s acces a lor este upcasting-ul ctre interfaa. a t Exemplu: using System; public interface IDataBound { void Bind(); } public class EditBox : IDataBound { // implementare de IDataBound void IDataBound.Bind() { Console.WriteLine("Binding to data store..."); } } class NameHidingApp { public static void Main() { Console.WriteLine(); EditBox edit = new EditBox(); Console.WriteLine("Apel EditBox.Bind()..."); //EROARE: Aceasta linie nu se compileaza deoarece metoda //Bind nu mai exista ca metoda publica in clasa EditBox edit.Bind(); Console.WriteLine(); IDataBound bound = edit; Console.WriteLine("Apel (IDataBound) EditBox.Bind()..."); // Functioneaza deoarece s-a facut conversie la IDataBound bound.Bind(); } }

140

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

Este posibil ca un tip s implementeze mai multe interfee. Atunci cnd a t a dou interfee au o metod cu aceeai semntur, programatorul are mai a t a s a a multe variante de lucru. Cel mai simplu, el poate s furnizeze o singur a a implementare pentru ambele metode, ca mai jos: interface IFriendly { void GreetOwner() ; } interface IAffectionate { void GreetOwner() ; } abstract class Pet { public virtual void Eat() { Console.WriteLine( "Pet.Eat" ) ; } } class Dog : Pet, IAffectionate, IFriendly { public override void Eat() { Console.WriteLine( "Dog.Eat" ) ; } public void GreetOwner() { Console.WriteLine( "Woof!" ) ; } } O alt modalitate este s se specice implicit care metod este implementat. a a a a class Dog : Pet, IAffectionate, IFriendly { public override void Eat() { Console.WriteLine( "Dog.Eat" ) ; } void IAffectionate.GreetOwner() {

5.5. INTERFETE Console.WriteLine( "Woof!" ) ; } void IFriendly.GreetOwner() { Console.WriteLine( "Jump up!" ) ; } } public class Pets { static void Main() { IFriendly mansBestFriend = new Dog() ; mansBestFriend.GreetOwner() ; (mansBestFriend as IAffectionate).GreetOwner() ; } } La ieire se va aa: s s Jump up! Woof! Dac a clasa Dog se adaug metoda a ns n a public void GreetOwner() { Console.WriteLine( "Woof!" ) ; }

141

atunci se poate face apel de tipul dog.GreetOwner() (variabila dog este instana de Dog); apelurile de metode din interfaa rmn de asemenea t t a a valide. Rezultatul este aarea mesajului Woof. s

5.5.1

Clase abstracte sau interfee? t

Att interfeele ct i clasele abstracte au comportamente similare i pot a t a s s folosite situaii similare. Dar totui ele nu se pot substitui reciproc. n t s Cteva principii generale de utilizare a lor sunt date mai jos. a Dac o relaie se poate exprima mai degrab ca este un/o dect altfel, a t a a atunci entitatea de baz ar trebui gndit ca o clas abstract. a a a a a Un alt aspect este bazat pe obiectele care ar folosi capabilitile din tipul at de baz. Dac aceste capabiliti ar folosite de ctre obiecte care nu sunt a a at a legate ntre ele, atunci ar indicat o interfaa. a t

142

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

Dezavantajul claselor abstracte este c nu poate dect baz unic pentru a a a a orice alt clas. a a

5.6

Tipul delegat

programare deseori apare urmtoarea situaie: trebuie s se execute o In a t a anumit aciune, dar nu se tie de dinainte care anume, sau chiar ce obiect va a t s trebui efectiv utilizat. De exemplu, un buton poate ti c trebuie s anune s a a t pe oricine este intresat despre faptul c fost apsat, dar nu va ti aprioric a a s cum va tratat acest eveniment. Mai degrab dect s se lege butonul de un a a a obiect particular, butonul va declara un delegat, pentru care clasa interesat a de evenimentul de apsare va da o implementare. a Fiecare aciune pe care utilizatorul o execut pe o interfaa grac det a t a claneaz un eveniment. Alte evenimente se pot declana independent de s a s aciunile utilizatorului: sosirea unui email, terminarea copierii unor iere, t s sfritul unei interogri pe o baz de date, etc. Un eveniment este o as a a ncapsulare a ideii c se ampl ceva la care programul trebuie s rspund. a nt a a a a Evenimentele i delegaii sunt strns legate deoarece rspunsul la acest evenis t a a ment se va face de ctre un event handler, care este legat de eveniment a printr-un delegat, Un delegat este un tip referina folosit pentru a t ncapsula o metod cu un a anumit antet (tipul parametrilor formali i tipul de retur). Orice metod care s a are acest antet poate legat la un anumit delegat. Intrun limbaj precum a C++, acest lucru se rezolv prin intermediul pointerilor la funii. Delegaii a t t rezolv aceeai problem, dar a s a ntro manier orientat obiect i cu garanii a a s t asupra siguranei codului rezultat, precum i cu o uoar generalizare (vezi t s s a delegaii multicast). t Un delegat este creat dup urmtoarea sintax: a a a atributeopt modicatori-de-delegatopt delegate tip-retur identicator( listaparam-formaliopt ); Modicatorul de delegat poate : new, public, protected, internal, private. Un delegat se poate specica att interiorul unei clase, ct i exteriorul a n a s n ei, ind de fapt o declaraie de clas derivat din System.Delegate. Dac este t a a a declarat interiorul unei clase, atunci este i static (asemntor cu statutul n s a a claselor imbricate). Exemplu: public delegate int WhichIsFirst(object obj1, object obj2);

5.6. TIPUL DELEGAT

143

5.6.1

Utilizarea delegailor pentru a specica metode la t runtime

S presupunem c se dorete crearea unei clase container simplu numit a a s Pair care va conine dou obiecte pentru care va putea face i sortare. Nu t a s se va ti aprioric care va tipul obiectelor coninute, deci se va folosi pentru s t ele tipul object. Dar sortarea celor dou obiecte se va face diferit, funcie a n t de tipul lor efectiv: de exemplu pentru nite persoane (clasa Student cele s n ce urmeaz) se va face dup nume, pe cnd pentru animale (clasa Dog) se a a a va face dup alt criteriu: greutatea. Containerul Pair va trebui s fac faa a a a t acestor clase diferite. Rezolvarea se va da prin delegai. t Clasa Pair va deni un delegat, WhichIsFirst. Metoda Sort de ordonare va prelua ca (unic) parametru o instana a metodei WhichIsFirst, care va t implementa relaia de ordine, funcie de tipul obiectelor coninute. Rezult n t t tatul unei comparaii t ntre dou obiecte va de tipul enumerare Comparison, a denit de utilizator: public enum Comparison { theFirstComesFirst = 0, //primul obiect din colectie este primul in ordinea sortarii theSecondComesFirst = 1 //al doilea obiect din colectie este primul in ordinea sortarii } Delegatul (tipul de metod care realizeaz compararea) se declar astfel: a a a //declarare de delegat public delegate Comparison WhichIsFirst( object obj1, object obj2); Clasa Pair se declar dup cum urmeaz: a a a public class Pair { //tabloul care contine cele doua obiecte private object[] thePair = new object[2]; //constructorul primeste cele doua obiecte continute public Pair( object firstObject, object secondObject) { thePair[0] = firstObject; thePair[1] = secondObject;

144 }

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

//metoda publica pentru ordonarea celor doua obiecte //dupa orice criteriu public void Sort( WhichIsFirst theDelegatedFunc ) { if (theDelegatedFunc(thePair[0],thePair[1]) == Comparison.theSecondComesFirst) { object temp = thePair[0]; thePair[0] = thePair[1]; thePair[1] = temp; } } //metoda ce permite tiparirea perechii curente //se foloseste de polimorfism - vezi mai jos public override string ToString( ) { return thePair[0].ToString()+", "+thePair[1].ToString(); } } Clasele Student i Dog sunt: s public class Dog { public Dog(int weight) { this.weight=weight; } //Ordinea este data de greutate public static Comparison WhichDogComesFirst( Object o1, Object o2) { Dog d1 = o1 as Dog; Dog d2 = o2 as Dog; return d1.weight > d2.weight ? Comparison.theSecondComesFirst : Comparison.theFirstComesFirst; } //pentru afisarea greutatii unui caine public override string ToString( )

5.6. TIPUL DELEGAT { return weight.ToString( ); } private int weight; } public class Student { public Student(string name) { this.name = name; } //studentii sunt ordonati alfabetic public static Comparison WhichStudentComesFirst( Object o1, Object o2) { Student s1 = o1 as Student; Student s2 = o2 as Student; return (String.Compare(s1.name, s2.name) < 0 ? Comparison.theFirstComesFirst : Comparison.theSecondComesFirst); } //pentru afisarea numelui unui student public override string ToString( ) { return name; } private string name; } Clasa de test este: public class Test { public static void Main( ) { //creaza cate doua obiecte //de tip Student si Dog //si containerii corespunzatori Student Stacey = new Student(Stacey); Student Jesse = new Student (Jess); Dog Milo = new Dog(10);

145

146

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI Dog Fred = new Dog(5); Pair studentPair = new Pair(Stacey, Jesse); Pair dogPair = new Pair(Milo, Fred); Console.WriteLine(studentPair\t: {0}, studentPair); Console.WriteLine(dogPair\t: {0}, dogPair); //Instantiaza delegatii WhichIsFirst theStudentDelegate = new WhichIsFirst(Student.WhichStudentComesFirst); WhichIsFirst theDogDelegate = new WhichIsFirst(Dog.WhichDogComesFirst); //sortare folosind delegatii studentPair.Sort(theStudentDelegate); Console.WriteLine(Dupa sortarea pe studentPair\t: {0}, studentPair.ToString( )); dogPair.Sort(theDogDelegate); Console.WriteLine(Dupa sortarea pe dogPair\t\t: {0}, dogPair.ToString( ));

} }

5.6.2

Delegai statici t

Unul din aspectele neelegante ale exemplului anterior este c e necesar ca a clasa Test s se instanieze delegaii care sunt necesari pentru a ordona n a t t obiectele din Pair. O modalitate mai bun este s se obin delegaii direct a a t a t din clasele Student i Dog. Acest lucru se obine prin crearea unui delegat s t static interiorul ecrei clase: n a public static readonly WhichIsFirst OrderStudents = new WhichIsFirst(Student.WhichStudentComesFirst); (analog pentru clasa Dog; de remarcat c static readonly nu se poate a nlocui cu const, deoarece iniilizatorul nu este considerat expresie cont stant). Declaraia de mai sus se folosete astfel: a t s ... studentpair.Sort(Student.OrderStudent); ... rezultatul ind identic. [2] este dat i o implementare de delegat ca proprietate static, aceasta In as a ducnd la crearea unui delegat doar cazul care este nevoie de el1 . a n n
1

Tehnic numit iniializaer trzie (lazy initialization) a a t a

5.6. TIPUL DELEGAT

147

5.6.3

Multicasting

Uneori este nevoie ca un delegat s poat apela mai mult de o singur a a a metod. De exemplu, atunci cnd un buton este apsat, se poate s vrei a a a a s efectuezi mai mult de o sigur aciune: s scrii a a t a ntrun textbox un ir s de caractere i s s a nregistrezi ntrun ier faptul c sa apsat acel buton s a a (logging). Acest lucru sar putea rezolva prin construirea unui vector de delegai care s conin toate metodele dorite, a s-ar ajunge la un cod greu t a t a ns de urmrit i inexibil; pentru un astfel de exemplu, a se vedea [2]. Mult mai a s simplu ar dac unui delegat i s-ar putea atribui mai multe metode. Acest a lucru se numete multicasting i este folosit intens la tratarea evenimentelor. s s Orice delegat care returnez void este un delegat multicast, care poate a tratat i ca un delegat single-cast. Doi delegai multicast pot combinai s t t folosind semnul +. Rezultatul unei astfel de adunri este un nou delegat a multicast care la apelare va invoca metodele coninute, ordinea care t n n sa fcut adunarea. De exemplu, dac Writer i Logger sunt delegai care a a s t returneaz void, atunci urmtoarea linie va produce combinarea lor a a ntrun singur delegat: myMulticastDelegate = Writer + Logger; Se pot aduga delegai multicast folosind operatorul +=, care va aduga a t a delegatul de la dreapta operatorului la delegatul multicast aat stnga sa: n a myMulticastDelegate += Transmitter; presupunnd c Transmitter este compatibil cu myMulticastDelegate (are a a aceeai semntur). Operatorul = funcioneaz invers faa de + =. s a a t a t Exemplu: using System; //declaratia de delegat multicast public delegate void StringDelegate(string s); public class MyImplementingClass { public static void WriteString(string s) { Console.WriteLine("Writing string {0}", s); } public static void LogString(string s) { Console.WriteLine("Logging string {0}", s);

148

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

} public static void TransmitString(string s) { Console.WriteLine("Transmitting string {0}", s); } } public class Test { public static void Main( ) { //defineste trei obiecte delegat StringDelegate Writer, Logger, Transmitter; //defineste alt delegat //care va actiona ca un delegat multicast StringDelegate myMulticastDelegate; //Instantiaza primii trei delegati //dand metodele ce se vor incapsula Writer = new StringDelegate( MyImplementingClass.WriteString); Logger = new StringDelegate( MyImplementingClass.LogString); Transmitter = new StringDelegate( MyImplementingClass.TransmitString); //Invoca metoda delegat Writer Writer("String passed to Writer\n"); //Invoca metoda delegat Logger Logger("String passed to Logger\n"); //Invoca metoda delegat Transmitter Transmitter("String passed to Transmitter\n"); //anunta utilizatorul ca va combina doi delegati Console.WriteLine( "myMulticastDelegate = Writer + Logger"); //combina doi delegati, rezultatul este //asignat lui myMulticastDelagate myMulticastDelegate = Writer + Logger; //apelaeaza myMulticastDelegate //de fapt vor fi chemate cele doua metode myMulticastDelegate( "First string passed to Collector");

5.6. TIPUL DELEGAT //Anunta utilizatorul ca se va adauga al treilea delegat Console.WriteLine( "\nmyMulticastDelegate += Transmitter"); //adauga al treilea delegat myMulticastDelegate += Transmitter; //invoca cele trei metode delagate myMulticastDelegate( "Second string passed to Collector"); //anunta utilizatorul ca se va scoate delegatul Logger Console.WriteLine( "\nmyMulticastDelegate -= Logger"); //scoate delegatul Logger myMulticastDelegate -= Logger; //invoca cele doua metode delegat ramase myMulticastDelegate( "Third string passed to Collector"); } } La ieire vom avea: s Writing string String passed to Writer Logging string String passed to Logger Transmitting string String passed to Transmitter myMulticastDelegate = Writer + Logger Writing string First string passed to Collector Logging string First string passed to Collector myMulticastDelegate += Transmitter Writing string Second string passed to Collector Logging string Second string passed to Collector Transmitting string Second string passed to Collector myMulticastDelegate -= Logger Writing string Third string passed to Collector Transmitting string Third string passed to Collector

149

150

CURS 5. CLASE, STRUCTURI, INTERFETE, DELEGATI

Curs 6 Metode anonime. Evenimente. Excepii. t


6.1 Metode anonime

Pentru a folosi un delegat a fost nevoie pn acum de a se crea de ecare a a dat o metod (i posibil i o nou clas care s conin aceast metod). a a s s a a a t a a a Exist a cazuri care corpul metodei este sucient de simplu pentru a nu a ns n necesita declararea explicit a metodei. C# 2.0 introduce aceast facilitate a a prin intermediul metodelor anonime. Varianta tradiional presupunea scrierea unui cod de forma: t a class SomeClass { delegate void SomeDelegate(); public void InvokeMethod() { SomeDelegate del = new SomeDelegate(SomeMethod); del(); } void SomeMethod() { MessageBox.Show("Hello"); } } Se poate deni o implementare folosind o metod anonim: a a class SomeClass { 151

152

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII. delegate void SomeDelegate(); public void InvokeMethod() { SomeDelegate del = delegate() { MessageBox.Show("Hello"); }; del(); }

} Metoda anonim este denit in-line i nu ca metod membr a unei clase. a a s a a Compilatorul este sucient de inteligent pentru a infera tipul delegatului, pe baza declaraiei de variabil delegat (del exemplul anterior). t a n O astfel de metod anonim se poate folosi oriunde este nevoie de o a a variabil de tip delegat, de exemplu ca parametru al unei metode: a class SomeClass { delegate void SomeDelegate(); public void SomeMethod() { InvokeDelegate(delegate(){MessageBox.Show("Hello");}); } void InvokeDelegate(SomeDelegate del) { del(); } } Exist i cazuri care se cere transmiterea de parametri metodei anonas n ime. Parametrii (tip + nume) se declar interiorul parantezelor cuvntului a n a delegate: class SomeClass { delegate void SomeDelegate(string str); public void InvokeMethod() { SomeDelegate del = delegate(string str) { MessageBox.Show(str);

6.2. EVENIMENTE }; del("Hello"); } }

153

Dac se omit cu totul parantezele de dup cuvntul delegate, atunci a a a se declar o metod anonim care este asignabil unui delegat cu orice a a a a semntur: a a class SomeClass { delegate void SomeDelegate(string str); public void InvokeMethod() { SomeDelegate del = delegate { MessageBox.Show("Salut"); }; del("Parametru ignorat"); } } Remarcm c a trebuie dai parametri delegatului ce se apeleaz; dac a a nc t a a delegatul declar parametri de tip out, atunci varianta de mai sus nu se a poate aplica.

6.2

Evenimente

todo: de facut paralela intre modalitatea primitiva/trista de implementare de la Dimecasts, Learning the Observer Pattern, vs. delegati Learning the Observer Pattern w/ Callbacks. Interfeele grace actuale cer ca un anumit program s rspund la evenit a a a mente. Un eveniment poate de exemplu apsarea unui buton, terminarea a transferului unui ier, selectarea unui meniu, etc; pe scurt, se ampl ceva s nt a la care trebuie s se dea un rspuns. Nu se poate prezice ordinea care se a a n petrec evenimentele, iar la apariia unuia se va cere reacionarea din partea t t sistemului soft. Alte clase pot interesate a rspunde la aceste evenimente. Modul n a care vor reaciona va extrem de particular, iar obiectul care semnaleaz n t a evenimentul (ex: un obiect de tip buton, la apsarea lui) nu trebuie s tie a as modul care se va rspunde. Butonul va comunica faptul c a fost apsat, n a a a iar clasele interesate acest eveniment vor reaciona consecina. n t n t

154

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII.

6.2.1

Publicarea i subscrierea s

C#, orice obiect poate s publice un set de evenimente la care alte clase In a pot s subscrie. Cnd obiectul care a publicat evenimentul i semnaleaza, a a l s a acest toate obiectele care au subscris la acest eveniment sunt noticate. In mod se denete o dependena de tip onetomany s t ntre obiecte astfel at nc dac un obiect si schimb starea, atunci toate celelate obiecte dependente a a sunt noticate i modicate automat. s De exemplu, un buton poate s notice un numr oarecare de observatori a a atunci cnd a fost apsat. Butonul va numit publicator 1 deoarece publia a c evenimentul Click iar celelalte clase sunt numite abonai 2 deoarece ele a t subscriu la evenimentul Click.

6.2.2

Evenimente i delegai s t

Tratarea evenimentelor C# se face folosind delegai. Clasa ce public n t a denete un delegat pe care clasele abonate trebuie s implementeze. Cnd s a l a evenimentul este declanat, metodele claselor abonate vor apelate prin s intermediul delegatului (pentru care se prevede posibilitatea de a multicast, astfel at s se permit mai muli abonai). nc a a t t Metodele care rspund la un eveniment se numesc event handlers. Prin a convenie, un event handler .NET Framework returneaz void i preia doi t n a s parametri: primul parametru este sursa evenimentului (obiectul publicator); al doilea parametru are tip EventArgs sau derivat din acesta. Declararea unui eveniment se face astfel: atributeopt modicatori-de-evenimentopt event tip numeeveniment Modicator-de-eveniment poate abstract, new, public, protected, internal, private, static, virtual, sealed, override, extern. Tip este un handler de eveniment (delegat multicast). Exemplu: public event SecondChangeHandler OnSecondChange; Vom da mai jos un exemplu care va construi urmtoarele: o clas Clock a a care folosete un eveniment (OnSecondChange) pentru a notica potenialii s t abonai atunci cnd timpul local se schimb cu o secund. Tipul acestui t a a a eveniment este un delegat SecondChangeHandler care se declar astfel: a public delegate void SecondChangeHandler( object clock, TimeInfoEventArgs timeInformation );
1 2

Engl: publisher Engl: subscribers

6.2. EVENIMENTE

155

conformitate cu metodologia de declarare a unui event handler, pomenit n a mai sus. Tipul TimeInfoEventArgs este denit de noi ca o clas derivat din a a EventArgs: public class TimeInfoEventArgs : EventArgs { public TimeInfoEventArgs( int hour, int minute, int second ) { this.hour = hour; this.minute = minute; this.second = second; } public readonly int hour; public readonly int minute; public readonly int second; } Aceast clas va conine informaie despre timpul curent. Informaia este a a t t t accesibil readonly. a Clasa Clock va conine o metod Run(): t a public void Run() { for(;;) { //dormi 10 milisecunde Thread.Sleep(10); //obtine timpul curent System.DateTime dt = System.DateTime.Now(); //daca timpul s-a schimbat cu o secunda //atunci notifica abonatii if( dt.Second != second) //second este camp al clasei Clock { //creeaza obiect TimeInfoEventArgs //ce va fi transmis abonatilor TimeInfoEventArgs timeInformation = new TimeInfoEventArgs(dt.Hour, dt.Minute, dt.Second); //daca cineva este abonat, atunci anunta-l if (OnSecondChange != null) { OnSeconChange(this, timeInformation);

156 }

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII.

} //modifica timpul curent in obiectul Clock this.second = dt.Second; this.minute = dt.Minute; this.hour = dt.Hour; } } Metoda Run creeaz un ciclu innit care interogheaz periodic ceasul sistem. a a Dac timpul sa schimbat cu o secund faa de timpul precedent, se vor a a t notica toate obiectele abonate dup care si va modica starea, prin cele a trei atribuiri nale. Tot ce rmne de fcut este s se scrie nite clase care s subscrie la evenia a a a s a mentul publicat de clasa Clock. Vor dou clase: una numit DisplayClock a a care va aa pe ecran timpul curent i o alta numit LogCurrentTime care s s a ar trebui s a nregistreze evenimentul ntrun ier, dar pentru simplitate va s aa doar la dispozitivul curent de ieire informaia transmis: s s t a public class DisplayClock { public void Subscribe(Clock theClock) { theClock.OnSecondChange += new Clock.SecondChangeHandler(TimeHasChanged); } void TimeHasChanged( object theClock, TimeInfoEventArgs ti) { Console.WriteLine("Current Time: {0}:{1}:{2}", ti.hour.ToString( ), ti.minute.ToString( ), ti.second.ToString( )); } } public class LogCurrentTime { public void Subscribe(Clock theClock) { theClock.OnSecondChange += new Clock.SecondChangeHandler(WriteLogEntry); }

6.2. EVENIMENTE //Aceasta metoda ar trebui sa scrie intr-un fisier //dar noi vom scrie la consola void WriteLogEntry( object theClock, TimeInfoEventArgs ti) { Console.WriteLine("Logging to file: {0}:{1}:{2}", ti.hour.ToString( ), ti.minute.ToString( ), ti.second.ToString( )); } }

157

De remarcat faptul c evenimentele sunt adugate folosind operatorul +=. a a Exemplul n ntregime este dat mai jos: using System; using System.Threading; //o clasa care va contine informatie despre eveniment //in acest caz va contine informatie disponibila in clasa Clock public class TimeInfoEventArgs : EventArgs { public TimeInfoEventArgs(int hour, int minute, int second) { this.hour = hour; this.minute = minute; this.second = second; } public readonly int hour; public readonly int minute; public readonly int second; } //clasa care publica un eveniment: OnSecondChange //clasele care se aboneaza vor subscrie la acest eveniment public class Clock { //delegatul pe care abonatii trebuie sa il implementeze public delegate void SecondChangeHandler( object clock, TimeInfoEventArgs timeInformation ); //evenimentul ce se publica public event SecondChangeHandler OnSecondChange; //ceasul este pornit si merge la infinit //va declansa un eveniment pentru fiecare secunda trecuta

158

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII.

public void Run( ) { for(;;) { //inactiv 10 ms Thread.Sleep(10); //citeste timpul curent al sistemului System.DateTime dt = System.DateTime.Now; //daca s-a schimbat fata de secunda anterior inregistrata //atunci notifica pe abonati if (dt.Second != second) { //creaza obiectul TimeInfoEventArgs //care va fi transmis fiecarui abonat TimeInfoEventArgs timeInformation = new TimeInfoEventArgs( dt.Hour,dt.Minute,dt.Second); //daca cineva a subscris la acest eveniment //atunci anunta-l if (OnSecondChange != null) { OnSecondChange(this,timeInformation); } } //modifica starea curenta this.second = dt.Second; this.minute = dt.Minute; this.hour = dt.Hour; } } private int hour; private int minute; private int second; } //un observator (abonat) //DisplayClock va subscrie la evenimentul lui Clock //DisplayClock va afisa timpul curent public class DisplayClock { //dandu-se un obiect clock, va subscrie //la evenimentul acestuia

6.2. EVENIMENTE public void Subscribe(Clock theClock) { theClock.OnSecondChange += new Clock.SecondChangeHandler(TimeHasChanged); } //handlerul de eveniment de pe partea //clasei DisplayClock void TimeHasChanged( object theClock, TimeInfoEventArgs ti) { Console.WriteLine("Current Time: {0}:{1}:{2}", ti.hour.ToString( ), ti.minute.ToString( ), ti.second.ToString( )); } } //un al doilea abonat care ar trebui sa scrie intr-un fisier public class LogCurrentTime { public void Subscribe(Clock theClock) { theClock.OnSecondChange += new Clock.SecondChangeHandler(WriteLogEntry); } //acest handler ar trebui sa scrie intr-un fisier //dar va scrie la standard output void WriteLogEntry( object theClock, TimeInfoEventArgs ti) { Console.WriteLine("Logging to file: {0}:{1}:{2}", ti.hour.ToString( ), ti.minute.ToString( ), ti.second.ToString( )); } } public class Test { static void Main( ) { //creaza un obiect de tip Clock Clock theClock = new Clock( );

159

160

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII. //creaza un obiect DisplayClock care //va subscrie la evenimentul obiectului //Clock anterior creat DisplayClock dc = new DisplayClock( ); dc.Subscribe(theClock); //analog se creeaza un obiect de tip LogCurrentTime //care va subscrie la acelasi eveniment //ca si obiectul DisplayClock LogCurrentTime lct = new LogCurrentTime( ); lct.Subscribe(theClock); //porneste ceasul theClock.Run( );

} } La ieire se va aa: s s Current Logging Current Logging Current Logging Current Logging Time: 14:53:56 to file: 14:53:56 Time: 14:53:57 to file: 14:53:57 Time: 14:53:58 to file: 14:53:58 Time: 14:53:59 to file: 14:53:59

6.2.3

Comentarii

Sar putea pune urmtoarea a ntrebare: de ce este nevoie de o astfel de redirectare de eveniment, cnd metoda Run() se poate aa direct pe ecran a n s sau ntrun ier informaia cerut? Avantajul abordrii anterioare este c s t a a a se pot crea oricte clase care s e noticate atunci cnd acest eveniment se a a a declaneaz. Clasele abonate nu trebuie s tie despre modul care lucreaz s a as n a clasa Clock, iar clasa Clock nu trebuie s tie despre clasele care vor subscrie as la evenimentul su. Similar, un buton poate s publice un eveniment OnClick a a i orice numr de obiecte pot subscrie la acest eveniment, primind o noticare s a atunci cnd butonul este apsat. a a Publicatorul i abonaii sunt decuplai. Clasa Clock poate s modice s t t a modalitatea de detectare a schimbrii de timp fr ca acest lucru s impun a aa a a o schimbare clasele abonate. De asemenea, clasele abonate pot s si n a modice modul de tratare a evenimentului, mod transparent faa de clasa n t Clock. Toate aceste caracteristici fac ntreinerea codului extrem de facil. t a

6.3. TRATAREA EXCEPTIILOR

161

6.3

Tratarea excepiilor t

C#, la fel ca alte limbaje, permite tratarea erorilor i a situaiilor des t osebite prin excepii. O excepie este un obiect care t t ncapsuleaz informaie a t despre o situaie anormal. Ea este folosit pentru a semnala contextul t a a n care apare situaia deosebit t a Un programator nu trebuie s confunde tratarea excepiilor cu erorile sau a t bugurile. Un bug este o eroare de programare care ar trebui s e xat a a nainte de livrarea codului. Excepiile nu sunt gndite pentru a preveni bug t a urile (cu toate c un bug poate s duc la apariia unei excepii), pentru c a a a t t a acestea din urm ar trebui s e eliminate. a a Chiar dac se scot toate bugurile, vor exista erori predictibile dar neprea venibile, precum deschiderea unui ier al crui nume este greit sau ariri s a s mp t la 0. Nu se pot preveni astfel de situaii, dar se pot manipula astfel at t nc nu vor duce la prbuirea programului. Cnd o metod alnete o situaie a s a a nt s t excepional, atunci se va arunca o excepie; cineva va trebui s sesizeze (s t a t a a prind) aceast excepie, sau eventual s lase o funcie de nivel superior s a a t a t a o trateze. Dac nimeni nu trateaz aceast excepie, atunci CLR o va face, a a a t dar aceasta duce la oprirea thread ului3 .

6.3.1

Tipul Exception

C# se pot arunca ca excepii obiecte de tip System.Exception sau In t derivate ale acestuia. Exist o ierarhie de excepii care se pot folosi, sau se a t pot crea propriile tipuri excepie. t Enumerm urmtoarele metode i proprieti relevante ale clasei Excepa a s at tion: public Exception(), public Exception(string), public Exception(string, Exception) - constructori; ultimul preia un obiect de tip Exception (sau de tip clas derivat) care va a a ncapsulat instana curent; o excepie n t a t poate deci s conin interiorul su o instana a altei excepii (cea a t a n a t t care a fost de fapt semnalat iniial). a t public virtual string HelpLink {get; set;} obine sau seteaz o legtur t a a a ctre un ier help asociat acestei excepii; poate de asemenea o a s t adresa Web (URL) public Exception InnerException {get;} returnez excepia care este a t ncorporat excepia curent a n t a
3

Si nu neaprat a a ntregului proces!

162

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII.

public virtual string Message {get;} obine un mesaj care descrie excepia t t curent a public virtual string Source {get; set;} obine sau seteaz numele aplicaiei t a t sau al obiectului care a cauzat eroarea public virtual string StackTrace {get;} obine o reprezetare string a t apelurilor de metode care au dus la apariia acestei excepii t t public MethodBase TargetSite {get;} obine metoda care a aruncat t 4 excepia curent t a

6.3.2

Aruncarea i prinderea excepiilor s t

Aruncarea cu throw Aruncarea unei excepii se face folosind instruciunea throw. Exemplu: t t throw new System.Exception(); Aruncarea unei excepii oprete execuia metodei curente, dup care CLR t s t a ncepe s caute un manipulator de excepie. Dac un handler de excepie a t a t nu este gsit metoda curent, atunci CLR va cura stiva, ajungnduse a n a at a la metoda apelant. Fie undeva lanul de metode care au fost apelate a n t se gsete un exception handler, e threadul curent este terminat de ctre a s a CLR. Exemplu: using System; public class Test { public static void Main( ) { Console.WriteLine(Enter Main...); Test t = new Test( ); t.Func1( ); Console.WriteLine(Exit Main...); } public void Func1( ) { Console.WriteLine(Enter Func1...);
MethodBase este o clas care pune la dispoziie informaii despre metodele i cona t t s structorii unei clase
4

6.3. TRATAREA EXCEPTIILOR Func2( ); Console.WriteLine(Exit Func1...); } public void Func2( ) { Console.WriteLine(Enter Func2...); throw new System.Exception( ); Console.WriteLine(Exit Func2...); } }

163

Se exemplic apelul de metode: Main() apeleaz Func1(), care apeleaz a a a Func2(); aceasta va arunca o excepie. Deoarece lipsete un event handler t s care s trateze aceast excepie, se va a a t ntrerupe threadul curent (i ind s singurul, i s ntregul proces) de ctre CLR, iar la ieire vom avea: a s Enter Main... Enter Func1... Enter Func2... Exception occurred: System.Exception: An exception of type System.Exception was thrown at Test.Func2( ) in ...Test.cs:line 24 at Test.Func1( ) in ...Test.cs:line 18 at Test.Main( ) in ...Test.cs:line 12 Deoarece este aruncat o excepie, metoda Func2() nu se va mai executa a t n ultima linie, ci CLRul va ncepe imediat cutarea event handlerului care a s trateze excepia. La fel, nu se execut nici ultima linie din Func1() sau a t a din Main(). Prinderea cu catch Prinderea i tratarea excepiei se poate face folosind un bloc catch, creat s t prin intermediul instruciunii catch. t Exemplu: using System; public class Test { public static void Main( ) {

164

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII. Console.WriteLine(Enter Main...); Test t = new Test( ); t.Func1( ); Console.WriteLine(Exit Main...);

} public void Func1( ) { Console.WriteLine(Enter Func1...); Func2( ); Console.WriteLine(Exit Func1...); } public void Func2( ) { Console.WriteLine(Enter Func2...); try { Console.WriteLine(Entering try block...); throw new System.Exception( ); Console.WriteLine(Exiting try block...); } catch { Console.WriteLine(Exception caught and handled.); } Console.WriteLine(Exit Func2...); } } Se observ c sa folosit un bloc try pentru a delimita instruciunile care a a t momentul care se arunc excepia, restul vor duce la apariia excepiei. In t t n a t instruciunilor din blocul try se ignor i controlul este preluat de ctre blocul t as a catch. Deoarece excepia a fost tratat, CLRul nu va mai opri procesul. La t a ieire se va aa: s s Enter Main... Enter Func1... Enter Func2... Entering try block... Exception caught and handled. Exit Func2... Exit Func1... Exit Main...

6.3. TRATAREA EXCEPTIILOR

165

Se observ c blocul catch nu sa specicat tipul de excepie care se a a n t prinde; asta nseamn c se va prinde orice excepie se va arunca, indiferent a a t de tipul ei. Chiar dac excepia este tratat, execuia nu se va relua de la a t a t instruciunea care a produs excepia, ci se continu cu instruciunea de dup t t a t a blocul catch. Uneori, prinderea i tratatarea excepiei nu se poate face funcia apelat, s t n t a ci doar funcia apelant. Exemplu: n t a using System; public class Test { public static void Main( ) { Console.WriteLine(Enter Main...); Test t = new Test( ); t.Func1( ); Console.WriteLine(Exit Main...); } public void Func1( ) { Console.WriteLine(Enter Func1...); try { Console.WriteLine(Entering try block...); Func2( ); Console.WriteLine(Exiting try block...); } catch { Console.WriteLine(Exception caught and handled.); } Console.WriteLine(Exit Func1...); } public void Func2( ) { Console.WriteLine(Enter Func2...); throw new System.Exception( ); Console.WriteLine(Exit Func2...); } } La ieire se va aa: s s

166

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII.

Enter Main... Enter Func1... Entering try block... Enter Func2... Exception caught and handled. Exit Func1... Exit Main... Este posibil ca ntro secvena de instruciuni s se arunce mai multe tipuri de t t a acest caz, prinderea excepiei excepii, funcie de natura strii aprute. In t n t a a t printrun bloc catch generic, ca mai sus, nu este util; am vrea ca funcie de a n t natura excepiei aruncate, s facem o tratare anume. Se sugereaza chiar s nu t a a se foloseasc aceast construcie de prindere generic, deoarece majoritatea a a t a n cazurilor este necesar s se cunoasc natura erorii (de exemplu pentru a a a scris a ntr-un ier de logging, pentru a consultat mai trziu). s a a Acest lucru se face specicnd tipul excepiei care ar trebui tratate a t n blocul catch: using System; public class Test { public static void Main( ) { Test t = new Test( ); t.TestFunc( ); } //incearca sa imparta doua numere public void TestFunc( ) { try { double a = 5; double b = 0; Console.WriteLine ({0} / {1} = {2}, a, b, DoDivide(a,b)); } //cel mai derivat tip de exceptie se specifica primul catch (System.DivideByZeroException) { Console.WriteLine(DivideByZeroException caught!); } catch (System.ArithmeticException) {

6.3. TRATAREA EXCEPTIILOR Console.WriteLine(ArithmeticException caught!); } //Tipul mai general de exceptie este ultimul catch { Console.WriteLine(Unknown exception caught); } } //efectueaza impartirea daca se poate public double DoDivide(double a, double b) { if (b == 0) throw new System.DivideByZeroException( ); if (a == 0) throw new System.ArithmeticException( ); return a/b; }

167

} exemplul de mai sus sa convenit ca o arire cu numitor 0 s duc la In mp t a a o execepie System.DivideByZeroException, iar o arire cu numrtor 0 t mp t aa s duc la apariia unei excepii de tip System.ArithmeticException. Este a a t t posibil specicarea mai multor blocuri de tratare a excepiilor. Aceste a t blocuri sunt parcurse ordinea care sunt specicate, iar primul tip care n n se potrivete cu excepia aruncat ( sensul c tipul excepie specicat este s t a n a t e exact tipul obiectului aruncat, e un tip de baz al acestuia - din cauz a a de upcasting) este cel care va face tratarea excepiei aprute. Ca atare, este t a important ca ordinea excepiilor tratate s e de la cel mai derivat la cel mai t a general. exemplul anterior, System.DivideByZeroException este derivat In din clasa System.ArithmeticException. Blocul nally Uneori, aruncarea unei excepii i golirea stivei pn la blocul de tratare t s a a a excepiei poate s nu e o idee bun. De exemplu, dac excepia apare t a a a t atunci cnd un ier este deschis (i a s s nchiderea lui se poate face doar n metoda curent), atunci ar util ca s se a a nchid ierul a s nainte ca s e a preluat controlul de ctre metoda apelant. Altfel spus, ar trebui s existe o a a a garanie c un anumit cod se va executa, indiferent dac totul merge normal t a a sau apare o excepie. Acest lucru se face prin intermediul blocului nally, t care se va executa orice situaie. Existena acestui bloc elimin necesitatea n t t a existenei blocurilor catch (cu toate c i acestea pot s apar). t as a a

168

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII. Exemplu:

using System; public class Test { public static void Main( ) { Test t = new Test( ); t.TestFunc( ); } public void TestFunc( ) { try { Console.WriteLine(Open file here); double a = 5; double b = 0; Console.WriteLine ({0} / {1} = {2}, a, b, DoDivide(a,b)); Console.WriteLine (This line may or may not print); } finally { Console.WriteLine (Close file here.); } } public double DoDivide(double a, double b) { if (b == 0) throw new System.DivideByZeroException( ); if (a == 0) throw new System.ArithmeticException( ); return a/b; } } exemplul de mai sus, mesajul Close le here se va aa indiferent de ce In s parametri se transmit metodei DoDivide(). La aruncarea unei excepii se poate particulariza obiectul care se arunc: t a if (b == 0) { DivideByZeroException e = new DivideByZeroException( );

6.3. TRATAREA EXCEPTIILOR e.HelpLink = http://www.greselifatale.com; throw e; } iar cnd excepia este prins, se poate prelucra informaia: a t a t catch (System.DivideByZeroException e) { Console.WriteLine( DivideByZeroException! goto {0} and read more, e.HelpLink); } Crearea propriilor excepii t

169

cazul care suita de excepii predenite nu este sucient, programIn n t a atorul si poate construi propriile tipuri. Se recomand ca acestea s e a a derivate din System.ApplicationException, care este derivat direct din Sysa tem.Exception. Se indic aceast derivare deoarece astfel se face distincie a a t ntre excepiile aplicaie i cele sistem (cele aruncate de ctre CLR). t t s a Exemplu: using System; public class MyCustomException : System.ApplicationException { public MyCustomException(string message): base(message) { } } public class Test { public static void Main( ) { Test t = new Test( ); t.TestFunc( ); } public void TestFunc( ) { try { double a = 0;

170

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII. double b = 5; Console.WriteLine ({0} / {1} = {2}, a, b, DoDivide(a,b)); Console.WriteLine (This line may or may not print); } catch (System.DivideByZeroException e) { Console.WriteLine(DivideByZeroException! Msg: {0}, e.Message); Console.WriteLine(HelpLink: {0}, e.HelpLink); } catch (MyCustomException e) { Console.WriteLine(\nMyCustomException! Msg: {0}, e.Message); Console.WriteLine(\nHelpLink: {0}\n, e.HelpLink); } catch { Console.WriteLine(Unknown exception caught); }

} public double DoDivide(double a, double b) { if (b == 0) { DivideByZeroException e = new DivideByZeroException( ); e.HelpLink= http://www.greselifatale.com; throw e; } if (a == 0) { MyCustomException e = new MyCustomException( Cant have zero divisor); e.HelpLink = http://www.greselifatale.com/NoZeroDivisor.htm; throw e; } return a/b; }

6.3. TRATAREA EXCEPTIILOR } Rearuncarea excepiilor t

171

Este perfect posibil ca ntrun bloc de tratare a excepiilor s se se fac t a a o tratare primar a excepiei, dup care s se arunce mai departe o alt a t a a a excepie, de acelai tip sau de tip diferit (sau chiar excepia original). Dac t s t a a se dorete ca aceast excepie s pstreze cumva interiorul ei excepia s a t a a n t original, atunci constructorul permite a nglobarea unei referine la aceasta; t aceast referina va accesibil prin intermediul proprietii InnerException: a t a at using System; public class MyCustomException : System.ApplicationException { public MyCustomException(string message,Exception inner): base(message,inner) { } } public class Test { public static void Main( ) { Test t = new Test( ); t.TestFunc( ); } public void TestFunc( ) { try { DangerousFunc1( ); } catch (MyCustomException e) { Console.WriteLine(\n{0}, e.Message); Console.WriteLine(Retrieving exception history...); Exception inner = e.InnerException; while (inner != null) { Console.WriteLine({0},inner.Message); inner = inner.InnerException;

172 }

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII.

} } public void DangerousFunc1( ) { try { DangerousFunc2( ); } catch(System.Exception e) { MyCustomException ex = new MyCustomException(E3 Custom Exception Situation!,e); throw ex; } } public void DangerousFunc2( ) { try { DangerousFunc3( ); } catch (System.DivideByZeroException e) { Exception ex = new Exception(E2 - Func2 caught divide by zero,e); throw ex; } } public void DangerousFunc3( ) { try { DangerousFunc4( ); } catch (System.ArithmeticException) { throw; } catch (System.Exception) {

6.3. TRATAREA EXCEPTIILOR Console.WriteLine(Exception handled here.);

173

} } public void DangerousFunc4( ) { throw new DivideByZeroException("E1 - DivideByZero Exception"); } }

6.3.3

Re ncercarea codului

Se poate pune ntrebarea: cum se procedeaz dac se dorecte revenirea a a s la codul care a produs excepia, dup tratarea ei? Exist destule situaii t a a t n care reexecutarea acestui cod este dorit: s ne gndim de exemplu la cazul a a a care n ntr-o fereastr de dialog se specic numele unuei ier ce trebuie a a s procesat, numele este introdus greit si se dorete ca s se permit corectarea s s a a numelui. Un alt exemplu clasic este cazul care autorul unei metode tie c n s a o operaie poate s eueze periodic de exemplu din cauza unui timeout pe t a s reea dar vrea s re t a ncerce operaia de n ori t nainte de a semnala eroare. aceast situaie se poate deni o etichet In a t a naintea blocului try la care s se permit salutl printrun goto. Urmtorul exemplu permite unui utia a a lizator s specice de maxim trei ori numele unui ier ce se proceseaz, cu a s a revenire cazul erorii. n using System; using System.IO; class Retry { static void Main() { StreamReader sr; int attempts = 0; int maxAttempts = 3; GetFile: Console.Write("\n[Attempt #{0}] Specify file " + "to open/read: ", attempts+1); string fileName = Console.ReadLine();

174

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII.

try { sr = new StreamReader(fileName); Console.WriteLine(); string s; while (null != (s = sr.ReadLine())) { Console.WriteLine(s); } sr.Close(); } catch(FileNotFoundException e) { Console.WriteLine(e.Message); if (++attempts < maxAttempts) { Console.Write("Do you want to select " + "another file: "); string response = Console.ReadLine(); response = response.ToUpper(); if (response == "Y") goto GetFile; } else { Console.Write("You have exceeded the maximum " + "retry limit ({0})", maxAttempts); }

} catch(Exception e) { Console.WriteLine(e.Message); } Console.ReadLine(); } }

6.3. TRATAREA EXCEPTIILOR

175

6.3.4

Compararea tehnicilor de manipulare a erorilor

Metoda standard de tratare a aerorilor a fost general returnarea unui n cod de eroare ctre metoda apelant. Ca atare, apelantul are sarcina de a a a descifra acest cod de eroare i s reacioneze consecina. a aa cum s a t n t Ins s se arat mai jos, tratarea excepiilor este superioar acestei tehnici din mai a t a multe motive.

Neconsiderarea codului de retur Apelul unei funcii care returneaz un cod de eroare poate fcut i fr t a a s aa a utiliza efectiv codul returnat, scriind doar numele funciei cu parametrii t de apel. Dac de exemplu pentru o anmit procesare se apeleaz metoda A a a a (de exemplu o deschidere de ier) dup care metoda B (citirea din ier), se s a s poate ca A s apar o eroare care nu este luat considerare; apelul lui B n a a a n este deja sortit eecului, pentru c buna sa funcionare depinde de efectele lui s a t A. Dac a metoda A arunc o excepie, atunci nici mcar nu se mai ajunge a ns a t a la apel de B, deoarece CLR-ul va pasa execuia unui bloc catch/finally. t Altfel spus, nu se permite o propagare a erorilor.

Manipularea erorii contextul adecvat n cazul care o metod A apeleaz alte metode B1 , . . . Bn , este posibil ca In n a a oricare din aceste n metode s cauzeze o eroare (i s returneze cod adecvat); a s a tratarea erorii din exteriorul lui A este dicil acest caz, deoarece ar trebui a n s se cerceteze toate codurile de eroare posibile pentru a determina motivul a apariiei erorii. Dac se mai adauga i apelul de metod Bn+1 interiorul t a s a n lui A, atunci orice apel al lui A trebuie s includ suplimentar i vericarea a a s pentru posibilitatea ca Bn+1 s cauzat o eroare. Ca atare, costul meninerii a t codului crete permanent, ceea ce are un impact negativ asupra TCO-ului5 s Folosind tratarea excepiilor, aruncnd excepii cu mesaje de eroare ext a t plicite sau excepii de un anumit tip (denit de programator) se poate trata t mult mai convenabil o situaie deosebit. Mai mult dect att, introduct a a a erea apelului lui Bn+1 interiorul lui A nu reclam modicare suplimenn a tar, deoarece tipul de excepie aruncat de Bn+1 este deja tratat (desigur, a t se presupune c se denete un tip excepie sau o ierarhie de excepii creat a s t t a convenabil).
5

Total Cost of Ownership.

176

CURS 6. METODE ANONIME. EVENIMENTE. EXCEPTII.

Uurina citirii codului s t Pentru comparaie, se poate scrie un cod care realizeaz procesarea coninutului t a t unui ier folosind coduri de eroare returnate sau excepii. primul caz, s t In soluia va conine cod de prelucrare a coninutului mixat cu cod de vericare t t t i reacie pentru diferitele cazuri de excepie. Codul al doilea caz este mult s t t n mai scurt, mai uor de teles, de meninut, de corectat i extins. s n t s Aruncarea de excepii din constructori t Nimic nu oprete ca o situaie deosebit s apar s t a a a ntrun apel de constructor. Tehnica vericrii codului de retur nu mai funcioneaz aici, deoarece a t a un constructor nu returneaz valori. Folosirea excepiilor este acest caz a t n aproape de ne nlocuit.

6.3.5

Sugestie pentru lucrul cu excepiile t

Java, programatorii trebuie s declare c o metod poate arunca o In a a a excepie i s o declare explicti t s a ntro list astfel at un apelant s tie c a nc as a se poate se poate atepta la primirea ei. Aceast cunoatere avans permite s a s n conceperea unui plan de lucru cu ecare dintre ele, preferabil dect s se a a cazul .NET se sugereaz prind oricare dintre ele cu un catch generic. In a a s se menin o documentaie cu excepiile care pot aruncate de ecare a t a t t metod. a

Curs 7 Colecii. Clase generice. t


7.1 Colecii t

Un vector reprezint cel mai simplu tip de colecie. Singura sa deciena a t t este faptul c trebuie cunoscut dinainte numrul de elemente coninute. a a t Spaiul de nume System.Collections pune la dispoziie un set de clase de t t tip colecie. Clasele din acest spaiu de nume reprezint containere de elet t a mente de tip Object care si gestioneaz singure necesarul de memorie (cresc a pe msur ce se adaug elemente; pot de asemenea s si reduc efectivul de a a a a a memorie alocat atunci cnd numrul de elemente coninute este prea mic). a a t Exemplu: ArrayList myCollection = new ArrayList(); myCollection.Add(client); client = myCollection[0] as Client; Remarcm conversia explicit pentru recuperarea unui element din colecie. a a t Elementele de baz pentru lucrul cu coleciile sunt un set de interfee a t t care ofer o mulime consistent de metode de lucru. Principalele colecii a t a t mpreun cu interfeele implementate sunt: a t ArrayList : IList, ICollection, IEnumerable, ICloneable SortedList : IDictionary, ICollection, IEnumerable, ICloneable Hashtable : IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback, ICloneable BitArray : ICollection, IEnumerable, ICloneable Queue : ICollection, IEnumerable, ICloneable 177

178

CURS 7. COLECTII. CLASE GENERICE.

Stack : ICollection, IEnumerable, ICloneable CollectionBase : IList, ICollection, IEnumerable DictionaryBase : IDictionary, ICollection, IEnumerable ReadOnlyCollectionBase : ICollection, IEnumerable IEnumerable Implementrile aceste interfee permit iterarea peste o colecie de elea t t mente. Unica metod declarat este metoda GetEnumerator : a a IEnumerator GetEnumerator () unde un obiect de tip IEnumerator este folosit pentru parcurgerea coleciei t un aa numit iterator. s ICollection Interfaa ICollection este tipul de baz pentru orice clas de tip colecie; t a a t se deriveaz din IEnumerable i prezint urmtoarele proprieti i metode: a s a a at s Count - proprietate de tip ntreg care returneaz numrul de elemente a a coninute colecie t n t IsSynchronized - proprietate logic ce indic dac colecia este sina a a t cronizat (sigur pentru accesarea de ctre mai multe re de execuie) a a a t SyncRoot - proprietate care returneaz un obiect care poate folosit a pentru a sincroniza accesul la colecie t CopyTo - metod care permite copierea coninutului coleciei a t t ntr-un vector IList Reprezint o colecie de obiecte care pot accesate individual printr-un a t index. Proprietile sunt: at IsFixedSize - returneaza o valoare logic indicnd dac lista are o dia a a mensiune x a IsReadOnly - returneaz o valoare logic indicnd dac lista poate a a a a doar citit a

7.1. COLECTII Item - returneaz sau seteaz elementul de la locaia specicat a a t a Metodele sunt: Add - adaug un obiect la o list a a Clear - golete lista s Contains - determin dac colecia conine o anumit valoare a a t t a IndexOf - determin poziia list a unei anumite valori a t n a Insert - insereaz o valoare la o anumit poziie a a t Remove - terge prima apariie a unui obiect din list s t a RemoveAt - sterge un obiect aat la o anumit locaie a t IDictionary

179

Interfaa IDictionary reprezint o colecie de perechi (cheie, valoare). t a t Permite indexarea unei colecii de elemente dup altceva dect indici t a a ntregi. Fiecare pereche trebuie s aib o cheie unic. a a a Proprietile sunt: at IsFixedSize, IsReadOnly - returneaz o valoare care precizeaz dac a a a colecia este cu dimensiune maxim xat, respectiv doar citibil t a a a Item - returneaz un element avnd o cheie specicat a a a Keys - returneaz o colecie de obiecte coninnd cheile din dicionar a t t a t Values - returneaz un obiect de tip colecie care conine toate valorile a t t din dicionar t Metodele sunt: Add - adaug o pereche (cheie, valoare) la dicionar; dac cheia exist a t a a deja, se va face suprascrierea valorii asociate Clear - se terge coninutul unui dicionar s t t Contains - determin dac dictionarul conine un element cu cheia a a t specicat a GetEnumerator - returneaz un obiect de tipul IDictionaryEnumerator a asociat Remove - terge elementul din dicionar avnd cheia specicat s t a a

180

CURS 7. COLECTII. CLASE GENERICE.

7.1.1

Iteratori pentru colecii t

Coleciile (att cele de tip list, ct i cele dictionar) motenesc interfaa t a a a s s t IEnumerable care permite construirea unui obiect de tip enumerator instana t a lui IEnumerable: interface IEnumerator { object Current {get;} bool MoveNext(); void Reset(); } Remarcm c un asemenea iterator este de tip forward-only. Proprietatea a a Current returneaz elementul curent al iterrii. Metoda MoveNext avanseaz a a a la urmtorul element al coleciei, returnnd true dac acest lucru s-a putut a t a a face i false caz contrar; trebuie s e apelat cel puin o dat s n a a t a naintea accesrii componentelor coleciei. Metoda Reset reiniializeaz iteratorul a t t a mutnd poziia curent a t a naintea primului obiect al coleciei. t Pentru ecare clas de tip colecie, enumeratorul este implementat ca a t o clas intern. Returnarea unui enumerator se face prin apelul metodei a a GetEnumerator. Exemplu: se va apela mod explicit metoda de returnare a iteratorului n i mai departe acesta se folosete pentru aarea elementelor. s s s ArrayList list = new ArrayList(); list.Add("One"); list.Add("Two"); list.Add("Three"); IEnumerator e = list.GetEnumerator(); while(e.MoveNext()) { Console.WriteLine(e.Current); } Apelul unui iterator este mecanismul esenial pentru funcionara instruciunii t t t foreach, care debuteaz prin a apela intern metoda GetEnumerator iar trea cerea la elementul urmtor se face cu metoda MoveNext. Altfel spus, exema plul de mai sus este echivalent cu: ArrayList list = new ArrayList(); list.Add("One"); list.Add("Two");

7.1. COLECTII list.Add("Three"); foreach(String s in list) { Console.WriteLine(s); }

181

7.1.2

Colecii de tip list t a

Coleciile de tip list sunt: ArrayList, BitArray, Stack, Queue i Collect a s tionBase. ArrayList Este o clas concret care stocheaz o colecie de elemente sub forma unui a a a t vector auto-redimensionabil. Suport mai muli cititori concureni i poate a t t s accesat exact ca un vector: ArrayList list = new ArrayList(); list.Add(...); Console.WriteLine(list[0]); list[0] = "abc"; BitArray Acest tip de colecie gestioneaz un vector de elemente binare reprezentate t a ca booleeni, unde true reprezint 1 iar false 0. Cele mai importante metode a sunt: And, Or, Xor - produce un nou BitArray care conine rezultatul aplicrii t a operanzilor respectivi pe elementele din colecia curent i alt colecie t as a t dat ca argument. Dac cele 2 colecii nu au acelai numr de elemente, a a t s a se arunc excepie a t Not - returneaz un obiect de tip BitArray care conine valorile negate a t din colecia curent t a Stack Stack reprezint o colecie ce permite lucrul conform principiului LIFO a t Last In, First Out.

182 Queue

CURS 7. COLECTII. CLASE GENERICE.

Clasa Queue este nou aprut versuinea 2.0 ce permite implementarea a a n politicii FIFO - First In, First Out. CollectionBase Clasa CollectionBase reprezint o clas abstract, baz pentru o colecie a a a a t puternic tipizat. Programatorii sunt a ncurajai s deriveze aceast clas t a a a dect s creeze una proprie de la zero. a a

7.1.3

Colecii de tip dicionar t t

Coleciile de tip dicionar (SortedList, Hashtable, DictionaryBase) conin t t t obiecte care se manipuleaz prin intermediul cheii asociate (care poate a altceva dect un indice numeric). Toate extind interfaa IDictionary, iar ca a t enumeratorul este de tip IDictionaryEnumerator : interface IDictionaryEnumerator : IEnumerator { DictionaryEntry Entry {get;} object Key {get;} object Value {get;} } unde DictionaryEntry este denit ca: struct DictionaryEntry { public DictionaryEntry(object key, object value) { ... } public object Key {get; set;} public object Value {get; set;} ... } Invocarea enumeratorului se poate face e explicit: Hashtable htable = new Hashtable(); htable.Add("A", "Chapter I"); htable.Add("B", "Chapter II"); htable.Add("App", "Appendix"); IDictionaryEnumerator e = htable.GetEnumerator(); for ( ; e.MoveNext() ; ) Console.WriteLine(e.Key); e implicit:

7.2. CREAREA UNEI COLECTII foreach (DictionaryEntry s in htable) Console.WriteLine(s.Key); Hashtable

183

Reprezint o colecie de perechi de tip (cheie, valoare) care este orgaa t nizat pe baza codului de dispersie (hashing) al cheii. O cheie nu poate a s e nul. Obiectele folosite pe post de chei trebuie s suprascrie metodele a a a Object.GetHashCode i Object.Equals. Obiectele folosite pe post de cheie tres buie sa e imuabile (s nu suporte schimbri de stare care s altereze valorile a a a returnate de cele 2 metode spuse anterior). SortedList Reprezint o colecie de perechi de tip (cheie, valoare) care sunt sortate a t dup cheie i se pot accesa dup cheie sau dup index. a s a a DictionaryBase Reprezint o clas de baz abstract pentru implementarea unui dicionar a a a a t utilizator puternic tipizat (valorile s nu e vzute ca object, ci ca tip specia a cat de programator).

7.2

Crearea unei colecii t

Vom exemplica aceast seciune modul care se denete o colecie n a t n s t ce poate iterat. Sunt prezentate 2 variante: specice versiunilor 1.1 i a s ambele cazuri clasa de tip colecie va respectiv 2.0 ale platformei .NET. In t implementa intefaa IEnumerable, dar va diferi modul de implementare. t

7.2.1

Colecie iterabil (stil vechi) t a

using System; using System.Collections; class MyCollection : IEnumerable { private int[] continut = {1, 2, 3}; public IEnumerator GetEnumerator() { return new MyEnumerator( this ); }

184

CURS 7. COLECTII. CLASE GENERICE.

private class MyEnumerator : IEnumerator { private MyCollection mc; private int index = -1; public MyEnumerator( MyCollection mc ) { this.mc = mc; } public object Current { get { if (index < 0 || index >= mc.continut.Length) { return null; } else return mc.continut[index]; } } public bool MoveNext() { index++; return index < mc.continut.Length; } public void Reset() { index = -1; } } } Remarcm c clasa imbricat primete prin constructor o referina la obiectul a a a s t de tip colecie, deoarece orice clas imbricat C# este automat i static, t a a n s a neavnd astfel acces la membrii nestatici ai clasei. a Demonstraia pentru iterarea clasei este: t class TestIterator

7.2. CREAREA UNEI COLECTII { static void Main() { MyCollection col = new MyCollection(); foreach(int i in col) { Console.WriteLine(s); } } }

185

Instruciunea foreach va apela iniial metoda GetEnumerator pentru a obine t t t obiectul de iterare i apoi pentru acest obiect se va apela metoda MoveNext s la ecare iteraie. Dac se returneaz true atunci se apeleaz automat i t a a a s metoda Current pentru obinerea elementului curent din colecie; dac se t t a returneaz false atunci execuia lui foreach se termin. a t a Implementarea de mai sus permite folosirea simultan a mai multor itera atori, cu pstrarea strii specice. a a Defectele majore ale acestei implementri sunt: a 1. Complexitatea codului (numrul mare de linii). Dei uor de teles a s s n i general acceptat (ind de fapt un design pattern), abordarea pres a supune scrierea multor linii de cod, motiv pentru care programatorii evita aceasta facilitate, prefernd mecanisme alternative precum indexa atorii. 2. Datorit semnturii metodei Current se returneaz de ecare dat un a a a a Object, pentru care se face e boxing i unboxing (dac in colecie s a t avem tip valoare - cazul de mai sus), e downcasting (de la Object la tipul declarat in prima parte a lui foreach, dac in colecie avem tip a t primul caz resursele suplimentare de memorie i ciclii proreferina). In t s cesor vor afecta negativ performana aplicaiei iar in al doilea caz apare t t o conversie explicit care duneaz performanei globale. Modalitatea a a a t de evitare a acestei probleme este ca s nu se implementeze interfeele a t IEnumerator i IEnumerable, ci scriind metoda Current astfel inct s s a a returneze direct tipul de date necesar (int in cazul nostru). Acest lucru duce ins la expunerea claselor imbricate (vzute ca nite clase auxila a s iare), ceea ce incalc principiul incapsularii. plus, cantitatea de cod a In rmane aceeai. a s Pentru prima problem vom da varianta de mai jos. Pentru cea de a doua, a rezolvarea se d sub forma claselor generice. a

186

CURS 7. COLECTII. CLASE GENERICE.

7.2.2

Colecie iterabil (stil nou) t a

Incepand cu C# 2.0 se poate deni un iterator mult mai simplu. Pentru aceasta se foloseste instruciunea yield. yield este folosit t a ntr-un bloc de iterare pentru a semnala valoarea ce urmeaz a returnat sau oprirea a a iterrii. Are formele: a yield return expression; yield break; prima form se precizeaz care va valoarea returnat; cea de-a doua In a a a n se precizeaz oprirea iterrii (sfritul secvenei de elemente de returnat). a a as t Pentru exemplicare, prezentm o metod al crei rezultat este folosit a a a pentru iterare. Valorile returnate de aceast metod sunt ptratele numerelor a a a de la 1 la valoarea argumentului using System; using System.Collections; using System.Text; namespace TestCollection { class Program { static IEnumerable Squares(int number) { for (int i = 1; i <= number; i++) { yield return i*i; } } static void Main(string[] args) { foreach (int iterate in Squares(10)) { Console.WriteLine(iterate.ToString()); } } } }

7.2. CREAREA UNEI COLECTII

187

Remarcm c are loc urmtorul efect: la ecare iteraie se returneaz urmtoarea a a a t a a valoare din colecie (colecia este denit de metoda Squares). Astfel, se t t a creeaz impresia c la ecare iterare din metoda Main se reia execuia din a a t metoda Squares de unde a rmas la apelul precedent; acest mecanism este a diferit de cel al rutinelor (metodelor) alnite pn acum, purtnd numele nt a a a de corutin. a Ca s facem evident acest mecanism de continuare a execuiei de la punca t tul de retur anterior, considerm exemplul: a using System; using System.Collections.Generic; namespace Iterator { class DemoCorutina { static IEnumerable<int> Numere() { Console.WriteLine("Returnare 1"); yield return 1; Console.WriteLine("Returnare 2"); yield return 2; Console.WriteLine("Returnare 3"); yield return 3; } static void Main(string[] args) { foreach(int valoare in Numere()) { Console.WriteLine(valoare.ToString()); } } } } pentru care rezultatul aat pe ecran este: s Returnare 1 1 Returnare 2 2

188 Returnare 3 3

CURS 7. COLECTII. CLASE GENERICE.

deci mod clar apelul pentru urmtoarea valoarea dat de ctre metoda n a a a Numere se continu de la punctul de ieire anterior. De fapt, compilatorul va a s genera automat o implementare de metod de tip IEnumerable (precum am a fcut manual seciunea 7.2.1), permianduse astfel programatorului s se a n t t a concentreze pe designul metodei i mai puin pe stufoasele detaliile interne. s t Un alt aspect demn de reinut este c secvena se construiete pe msur ce t a t s a a datele din enumerare sunt parcurse. Clasa MyCollection de mai sus s-ar rescrie astfel: class MyCollection : IEnumerable { private int[] continut = { 1, 2, 3 }; public IEnumerator GetEnumerator() { for(int i=0; i<continut.Length; i++) { yield return continut[i]; } } } Pentru a demonstra utilitatea acestui tip de implementare, mai jos dm rea zolvarea pentru urmtoarea problem: plecnduse de la un arbore binar a a a s se scrie iteratorii pentru parcurgerea inordine i preordine. Nu vom a n s prezenta construirea efectiv a arborelui, aceasta ind o problema separat. a a Practic, se va implementa mod recursiv o iterare peste arbore. n Pentru nceput, deniia tipului nod: t using System; namespace TestIterTree { class TreeNode<T> { private T value; private TreeNode<T> left, right; public T Value { get

7.2. CREAREA UNEI COLECTII { return value; } set { this.value = value; } } public TreeNode<T> Left { get { return left; } set { left = value; } } public TreeNode<T> Right { get { return right; } set { this.right = value; } } } } Urmeaz denirea arborelui i a celor doi iteratori: a s using System; using System.Collections.Generic; namespace TestIterTree {

189

190

CURS 7. COLECTII. CLASE GENERICE. class Tree<T> { private TreeNode<T> root; #region popularea arborelui cu valori public void AddValues(params T[] value) { Array.ForEach(value, Add); } #endregion #region tehnici de traversare public IEnumerable<T> InOrder() { return InOrder(root); } public IEnumerable<T> PreOrder() { return PreOrder(root); } #endregion #region Private helper methods private IEnumerable<T> InOrder(TreeNode<T> node) { if (node.Left != null) { foreach (T value in InOrder(node.Left)) { yield return value; } } yield return node.Value; if (node.Right != null) { foreach (T value in InOrder(node.Right)) { yield return value; } }

7.3. CLASE GENERICE } private IEnumerable<T> PreOrder(TreeNode<T> root) { yield return root.Value; if (root.Left != null) { foreach (T value in PreOrder(root.Left)) { yield return value; } } if (root.Right != null) { foreach (T value in PreOrder(root.Right)) { yield return value; } } } private void Add(T value) { //Implements adding a value to the tree } #endregion } }

191

Implementarea de mai sus sa fcut conform deniiilor recursive pentru a t cele dou tipuri de parcurgeri (al treilea tip de parcurgere se implementeaz a a analog). Invitm cititorul s compare aceste implementri cu cele iterative a a a clasice din teoria structurilor de date. Pe lng timpul scurt de implementare, a a se ctig claritate i uurina exploatare. as a n s s t n

7.3

Clase generice

Vom prezenta cele ce urmeaz suportul .NET 2.0 pentru clase i metode n a s generice; acestea sunt blocuri de cod parametrizate care permit scriere unui cod general, ce poate ulterior adaptat automat la cerintele specice ale programatorului.

192

CURS 7. COLECTII. CLASE GENERICE.

7.3.1

Metode generice

S presupunem c dorim s scriem o metod care s realizeze interschima a a a a barea valorilor a dou variabile. Variantele sunt: a 1. scrierea unei metode pentru ecare tip al variabilelor: neelegant, cod mult 2. scrierea unei metode care s foloseasc un Object pe post de variabil a a a auxiliar; dac se face apelul pentru 2 variabile de tip ir de caractere, a a s apare eroarea Cannot convert from ref string to ref object . plus, In antetul metodei ar permite apel pentru un parametru de tip string i s cellalt de tip int, ceea ce nu ar trebui s e admis la compilare. a a Singurul mod adecvat de rezolvare a problemei este folosirea unei metode generice, ca mai jos: void Swap<T>(ref T a, ref T b) { T aux; aux = a; a = b; b = aux; } Apelul acestei metode se face astfel: int x = 3, y=4; Swap<int>(ref x, ref y);//nu apare boxing/unboxing string a="a", b="b"; Swap<string>(ref a, ref b); Remarcm c apelul se face specicnd tipul efectiv pentru T. Aceast specia a a a care poate omis dac compilatorul poate deduce singur care este tipul a a efectiv T : bool b1=true, b2=false; Swap(ref b1, ref b2); Tipul generic T poate folosit i ca tip de retur. s

7.3. CLASE GENERICE

193

7.3.2

Tipuri generice

Mecanismul de genericitate poate extins la clase i structuri. Dm mai s a jos exemplu care modeleaz noiunea de punct a t ntrun spaiu bidimensional. t Genericitatea provine din faptul c coordonatele pot de tip a ntreg sau fracionare. t struct Point<T> { private T xPos; private T yPos; public Point(T xPos, T yPos) { this.xPos = xPos; this.yPos = yPos; } public T X { get { return xPos; } set { xPos = value; } } public T Y { get { return yPos; } set { yPos = value; } }

194

CURS 7. COLECTII. CLASE GENERICE.

public override string ToString() { return string.Format("({0}, {1})", xPos.ToString(), yPos.ToString()); } public void Reset() { xPos = default(T); yPos = default(T); } } Utilizarea efectiv ar putea : a Point<int> p = new Point(10, 10); Point<double> q = new Point(1.2, 3.4); Observm c: a a Metodele, dei cu caracter generic, nu se mai specic drept generice s a (nu se mai folosesc simbolurile < i >), acest lucru ind implicit s folosim o supra ncarcare a cuvntului cheie default pentru a aduce a cmpurile la valorile implicite ale tipului respectiv: 0 pentru numerice, a false pentru boolean, null pentru tipuri referina. t Mai adugm faptul c o clas poate avea mai mult de un tip generic drept a a a a parametru, exemplele clasice ind coleciile generice de tip dicionar pentru t t care se specic tipul cheii i al valorilor coninute. a s t

7.3.3

Constrngeri asupra parametrilor de genericitate a

Pentru structura de mai sus este posibil s se foloseasc o instaniere de a a t tipul: Point<StringBuilder> r = null; ceea ce este aberant din punct de vedere semantic. Am dori s putem face a restriionarea tipului parametrilor generici. Un asemenea mecanism exist i t as permite 5 tipuri de restricii: t

7.3. CLASE GENERICE where T:struct where T:class where T:new() where T:NameOfBaseClass where T:NameOfInterface Exemple:

195 T trebuie s e tip derivat din a System.ValueType T trebuie s nu e derivat din a System.ValueType T trebuie s aibe un constructor implicit a (fr parametri) aa T trebuie s e derivat (direct sau nu) a din NameOfBaseClass T trebuie s implementeze interfaa a t NameOfInterface

class MyGenericClass<T> where T:new() specic faptul c parametrul a a T trebuie s e un tip cu constructor implicit a class MyGenericClass<T> where T:class, IDrawable, new() specic a faptul c parametrul T trebuie s e de tip referina, s implementeze a a t a IDrawable i s posede constructor implicit s a class MyGenericClass<T>:MyBase, ICloneable where T:struct descrie o clas care este derivat din MyBase, implementeaz ICloneable iar a a a parametrul T este de tip valoare (structur sau enumerare). a Clasele generice pot de asemenea clase de baz pentru tipuri (generice a sau nu): class MyList<T>... class MyStringList : MyList<String>...

7.3.4

Interfee i delegai generici t s t

Interfeele i delegaii pot declarate ca ind generice; dei nu pezint t s t s a cerine sau particulariti faa de ceea ce s-a spus mai sus, le evideniem astfel t at t t doarece gradul nalt de abstractizare le face deosebit de utile modelarea n unui sistem soft complex. interface IMyFeature<T> { T MyService(T param1, T param2); } respectiv: delegate void MyGenericDelegate<T>(T arg);

196

CURS 7. COLECTII. CLASE GENERICE.

7.4
7.4.1

Colecii generice t
Probleme cu coleciile de obiecte t

Coleciile, aa cum au fost ele prezentate seciunea 7.1 sunt utile, dar t s n t au cteva puncte slabe. a 1. s presupunem c pornim cu o list de tip ArrayList la care adugm a a a a a elemente de tip ntreg: ArrayList al = new ArrayList(); al.Add(1); al.Add(2); int x = (int)al[0]; Secvena este corect din punct de vedere sintactic, dar la rulare sot a licit folosirea mecanismului de boxing i unboxing. Dei pentru colecii a s s t mici acest lucru nu are are efecte sesizabile, pentru un numr mare de a adugri sau accesri ale elementelor din list avem un impact negativ a a a a ce trebuie luat calcul. Am prefera ca tipurile colecie s suporte n t a lucrul cu tipuri valoare fr costul suplimentar introdus de boxing/unaa boxing. 2. problema tipului efectiv stocat colecie: s presupunem c n t a a ntro list adugm: a a a al.Add(new Dog("Miki")); al.Add(new Dog("Gogu")); al.Add(new Matrix(3, 5)); Dog dog = (Dog)al[2]; Secvena de sus este corect din punct de vedere sintactic, dar la rulare t a va determina aruncarea unei excepii de tipul InvalidCastException. E t de dorit ca la compilare s se poat semnala aceast greeal. a a a s a

7.4.2

Colecii generice t

Clasele generice mpreun cu coleciile au fost combinate biblioteca a t n .NET Framework, ducnd la apariia unui nou spaiu de nume, coninut a t t t System.Collections: System.Collections.Generic. Acesta conine tipurile: n t ICollection<T>, IComparer<T>, IDictionary<K, V>, IEnumerable<T>, IEnumerator<T>, IList<T>, Queue<T>, Stack<T>, LinkedList<T>, List<T>. Exemplu de utilizare:

7.5. ELEMENTE SPECIFICE C# 3.0 List<int> myInts = new List<int>(); myInts.Add(1); myInts.Add(2); //myInts.Add(new Complex());//eroare de compilare

197

Dei secvena de mai sus tipul listei este int, nu se apeleaz la boxing/uns n t a boxing, deoarece lista este compus din elemente de tip a ntreg i nu din s obiecte de tip Object.

7.5

Elemente specice C# 3.0

Seciunea conine o prezentare a elementelor noi introduse de versiunea t t 3.0 a limbajului C#, aspecte ce se pot raporta la ceea ce s-a prezentat pn a a acum. Prezentarea este completat capitolele ulterioare (LINQ, lambda a n expresii).

7.5.1

Proprieti implementate automat at

Considerm clasa: a class MyClass { private int myField; public int MyField { get { return myField; } set { myField = value; } } } Deseori se pune problema scrierii unor cmpuri private, pentru care accea sarea se face prin intermediul proprietilor. Este contraindicat s se expun at a a cmpurile ca ind publice, deoarece se sparge a ncapsularea i nu se poate face s databinding la cmpuri publice. Dac un cmp se expune ca ind public i a a a s

198

CURS 7. COLECTII. CLASE GENERICE.

un cod client ncepe s acceseze, este imposibil ca ulterior s se impun a l a a cod de validare pentru accesul la el. Deoarece codul de tipul celui scris mai sus apare foarte des, s-a pus problema simplicrii lui. C# 3.0 se scrie echivalent: a In class MyClass { public int MyField { get; set; } } Se folosete aici mecanismul de implementare automat a unei proprieti s a at care acioneaz asupra unor cmpuri private autodeclarate; aceast proprit a a a etate returneaz sau acceseaz direct cmpul asociat. a a a Particularitctile sunt urmtoarele: a a 1. nu se declar cmpul privat; acesta este creat automat de compilator, a a pe baza proprietii auto-implementate; at 2. nu se scriu implementri pentru get i set; corpul lor este caracterul a s ;. get acceseaz cmpul autodeclarat, set seteaz valoarea cmpului a a a a cu ce se a dreapta semnului egal; a n 3. nu se poate accesa cmpul autodeclarat altfel dect prin intermediul a a proprietii at 4. proprietatea nu poate doar read-only sau write-only, ci read-write. Dac ulterior se decide implementarea unui accesor, atunci i cellalt trebuie a s a implementat i cmpul privat trebuie declarat. Important este a c se s a ns a scrie un minim de cod pentru a genera un contract: cmp privat accesat prin a proprietate public. a

7.5.2

Iniializatori de obiecte t

S considerm clasa: a a class Person { private string firstName;

7.5. ELEMENTE SPECIFICE C# 3.0 public string FirstName { get { return firstName; } set { firstName = value; } } private string lastName; public string LastName { get { return lastName; } set { lastName = value; } } private int age; public int Age { get { return age; } set { age = value; } } }

199

Se poate scrie urmtoarea secvena de cod care iniializeaz o persoan cu a t t a a datele cuvenite: Person p = new Person(); p.FirstName = "Rafael"; p.Age = 25; p.LastName = "Popescu"; C# 3.0 se poate scrie mai succint: In Person p = new Person { FirstName = "Rafael", Age = 25, LastName = "Popescu" }; cu acelai rezultat1 ,2 . s Intr-un astfel de caz se face mai ai apelarea connt structorului implicit (indiferent de cine anume scrie - compilatorul sau l programatorul) i apoi se face accesarea proprietilor, ordinea scris la s at n a iniializator. Membrii pentru care se face iniializare trebuie s e publici; t t a n particular, ei pot i cmpuri, dar acest lucru nu este s a ncurajat de principiul
In unele lucrri se folosete: Person p = new Person(){FirstName="Rafael", a s Age=25, LastName="Popescu" }; deci cu paranteze rotunde dup numele clasei folosite a de operatorul new. 2 Cele dou secvene nu sunt totui echivalente, aa cum se arat a t s s a n http://community.bartdesmet.net/blogs/bart/archive/2007/11/22/ c-3-0-object-initializers-revisited.aspx
1

200

CURS 7. COLECTII. CLASE GENERICE.

ncapsulrii. Putem avea inclusiv proprieti auto-implementate (seciunea a at t 7.5.1). Dac se scrie un constructor care preia un parametru dar nu i unul care a s s e implicit, de exemplu: a public Person(String firstName) { FirstName = firstName; } atunci se poate a folosi mecansimul de iniializare: nc t Person r = new Person("Rafael") {LastName="Popescu", Age = 25 }; Exemplul se poate dezvolta mai departe prin exemplicarea construirii unor obiecte mai complexe: Person p = new Person{ FirstName = "Rafael", LastName = "Popescu", Age={25}, Address = new Address{ City = "Brasov", Country = "Romania", Street = "Iuliu Maniu" } }

7.5.3

Iniializatori de colecii t t

Se d secvena de cod: a t List<String> list = new List<String>(); list.Add("a"); list.Add("b"); list.Add("c"); C# 3.0 ea este echivalent cu: In a List<String> list = new List<String>(){"a", "b", "c"}; ceea ce aduce aminte de o trstur similar de la iniializarea tablourilor; aa a a t mai exact codul de mai jos:

7.5. ELEMENTE SPECIFICE C# 3.0 String[] x = new String[3]; x[0] = "a"; x[1] = "b"; x[2] = "c"; este a din prima versiune de C# echivalent cu: nc a String[] x = new String[]{"a", "b", "c"};

201

Iniializarea coleciilor vine s ofere acelai mecanism ca i cazul tablourilor. t t a s s n Exemplul poate completat cu popularea unei colecii de obiecte comt puse: List<Person> persons = new List<Person>(){ new Person{FirstName = "Rafael", LastName="Popescu", Age=25}, new Person{FirstName = "Ioana", LastName="Ionescu", Age=23} };

202

CURS 7. COLECTII. CLASE GENERICE.

Curs 8 ADO.NET
8.1 Ce reprezint ADO.NET? a

ADO.NET reprezint o parte component a lui .NET Framework ce pera a mite aducerea, manipularea i modicarea datelor. mod normal, o surs s In a de date poate s e o baz de date, dar de asemenea un ier text sau Exa a s cel sau XML sau Access. Lucrul se poate face e conectat, e deconectat de la sursa de date. Dei exist variate modaliti de lucru cu bazele de s a at date, ADO.NET se impune tocmai prin faptul c permite lucrul deconectat a de la baza de date, integrarea cu XML, reprezentarea comun a datelor cu a posibilitatea de a combina date din variate surse, toate pe baza unor clase .NET. Faptul c se permite lucrul deconectat de la sursa de date rezolv urmtoarele a a a probleme: meninerea conexiunilor la baza de date este o operaie costisitoare. t t O bun parte a limii de band este meninut ocupat pentru nite a at a t a a s procesri care nu necesit neaprat conectare continu a a a a probleme legate de scalabilitatea aplicaiei: se poate ca serverul de baze t de date s lucreze uor cu 5-10 conexiuni meninute, dar dac numrul a s t a a acestora crete aplicaia poate s reacioneze extrem de lent s t a t pentru unele servere se impun clauze asupra numrului de conexiuni ce a se pot folosi simultan. Toate acestea fac ca ADO.NET s e o tehnologie mai potrivit pentru deza a voltarea aplicaiilor Internet dect cele precedente (e.g. ADO, ODBC). t a Pentru o prezentare a metodelor de lucru cu surse de date sub platform a Windows se poate consulta [7]. 203

204

CURS 8. ADO.NET

Vom exemplica cele ce urmeaz preponderent pe baz de date Min a a crosoft SQL 2005 Express Edition, ce se poate descrca gratuit de pe site-ul a Microsoft.

8.2

Furnizori de date ADO.NET n

Din cauza existenei mai multor tipuri de surse de date (de exemplu, a t mai multor productori de servere de baze de date) e nevoie ca pentru ecare a tip major de protocol de comunicare s se foloseasc o bibliotec specializat a a a a de clase. Toate aceste clase implementeaz nite interfee bine stabilite, ca a s t atare trecerea de la un SGBD la altul se face cu eforturi minore (dac codul a este scris innd cont de principiile programrii orientate pe obiecte). t a a 1 Exist urmtorii furnizori de date (lista nu este complet): a a a Tabelul 8.1: Furnizori de date. Nume furnizor Prex API ODBC Data Odbc Provider OleDb Data OleDb Provider Oracle Data Provider SQL Data Provider Borland Data Provider MySql Oracle Sql Bdp MySql Descriere Surse de date cu interfaa ODBC t (baze de date vechi) Surse de date care expun o interfaa t OleDb, de exemplu Access i Excel sau s SQL Sever versiune mai veche de 7.0 SGBD Oracle Pentru interaciune cu Microsoft SQL t Server 7.0, 2000, 2005 Acces generic la SGBD-uri precum Interbase, SQL Server, IBM DB2, Oracle SGBD MySql

Prexele trecute coloana "Prex" sunt folosite pentru clasele de lucru n specice unui anumit furnizor ADO.NET: de exemplu, pentru o connexiune SQL Server se va folosi clasa SqlConnection.

8.3

Componentele unui furnizor de date

Fiecare furnizor de date ADO.NET const patru componente: Cona n nection, Command, DataReader, DataAdapter. Arhitectura ADO.NET este
1

Engl: Data Providers

8.3. COMPONENTELE UNUI FURNIZOR DE DATE prezentat gura 8.1 a n

205

Figura 8.1: Principalele clase ADO.NET

Mai jos sunt date descrieri succinte ale claselor cel mai des utilizate.

8.3.1

Clasele Connection

Sunt folosite pentru a reprezenta o conexiune la surse de date. Ele conin t date specice conexiunii, cum ar locaia sursei de date, numele i parola t s contului de acces, etc. plus au metode pentru deschiderea i In s nchiderea conexiunilor, pornirea unei tranzacii sau setarea perioadei de time-out. Stau t la baza oricrei accesri de servicii de pe server. a a

206

CURS 8. ADO.NET

8.3.2

Clasele Command

Sunt folosite pentru a executa diferite comenzi pe baza de date (SELECT, INSERT, UPDATE, DELETE) i pentru a furniza un obiect de tip s DataReader sau pentru a umple un DataSet prin intermediul unui obiect DataAdapter. Pot folosite pentru apelarea de proceduri stocate aate pe server. Ele permit scrierea de interogri SQL parametrizate sau specicarea a parametrilor pentru procedurile stocate.

8.3.3

Clasele DataReader

Permit navigarea de tip forwardonly, readonly mod conectat la sursa n de date. Se obin pe baza unui obiect de tip Command prin apelul metodei t ExecuteReader(). Accesul rezultat este extrem de rapid cu minim de resurse consumate.

8.3.4

Clasele DataAdapter

Ultima component principal a unui furnizor de date .NET este DataAdapter. a a Funcioneaz ca o punte t a ntre sursa de date i obiecte de tip DataSet deconecs tate, permiand efectuarea operaiilor pe baza de date. Conin referine ctre t t t t a obiecte de tip Connection i deschid / s nchid singure conexiunea la baza de plus, un DataAdapter conine referine ctre patru comenzi pentru date. In t t a selectare, tergere, modicare i adugare la baza de date. s s a

8.3.5

Clasa DataSet

Aceast clas nu este parte a unui furnizor de date .NET ci independent a a a de particularitile de conectare i lucru cu o baz de date anume. Prezint at s a a marele avantaj c poate sa lucreze deconectat de la sursa de date, facilitnd a a stocarea i modicarea datelor local, apoi reectarea acestor modicri s a n baza de date. Un obiect DataSet este de fapt un container de tabele i relaii s t ntre tabele. Folosete serviciile unui obiect de tip DataAdapter pentru as i procura datele i a trimite modicrile s s a napoi ctre baza de date. Datele a sunt stocate de un DataSet format XML; acelai format este folosit pentru n s transportul datelor.

8.4

Obiecte Connection

Clasele de tip Connection pun la dispoziie tot ceea ce e necesar pentru t conectarea la baze de date. Este primul obiect cu care un programator ia

8.4. OBIECTE CONNECTION

207

contact atunci cnd a ncearc s foloseasc un furnizor de date .NET. a a a Inainte ca o comand s e executat pe o baz de date trebuie stabilite datele de a a a a conectare i deschis conexiunea. s a Orice clas de tip conexiune (din orice furnizor de date) implementeaz a a intefaa IDbConnection. De exemplu, clasele SqlConnection (folosit pent a tru conectare la server Microsoft SQL Server 2000) sau OleDbConnection (folosit pentru conectare la iere .mdb din Access sau Excel) implementeaz a s a IDbConnection. Pentru deschiderea unei conexiuni se poate proceda ca mai jos: using System.Data.SqlClient;//spatiul de nume SqlClient ... SqlConnection cn = new SqlConnection(@"Data Source= localhost\sqlexpress;Database=Northwind;User ID=sa; Password=parola"); cn.Open(); ... Mai sus sa specicat numele calculatorului pe care se a instalat severul a SQL (localhost) precum i al numelui de instana pentru acest server (exs t press), baza de date la care se face conectarea (Northwind), contul SQL cu care se face accesul (sa) i parola pentru acest cont (parola). s Pentru conectarea la un ier Access Northwind.mdb aat directorul s n c:\lucru se folosete un obiect de tipul OleDbConnection sub forma: s using System.Data.OleDb;//spatiul de nume OleDb ... OleDbConnection cn = new OleDbConnection( @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source= C:\Lucru\Northwind.mdb"); cn.Open(); ... S-a specicat furnizorul de date (Microsoft.Jet.OLEDB.4.0 pentru ier Acs cess) precum i locul unde se a sursa de date (C:\Lucru\Northwind.mdb). s a Vom enumera principalele proprieti, metode i evenimente pentru un at s obiect de tip Connection.

8.4.1

Proprieti at

1. ConnectionString: de tip String, cu accesori de get i set; aceast pros a prietate denete un string care permite identicarea tipului i locaiei s s t

208

CURS 8. ADO.NET sursei de date la care se face conectarea i eventual contul i parola de s s acces. Acest string conine lista de parametri necesari pentru conectare t sub forma numeParametru=valoare, separai prin punct i virgul. t s a Parametrii sunt: provider : se specic furnizorul de date pentru conectarea la sursa a de date. Acest furnizor trebuie precizat doar dac se folosete OLE a s DB .NET Data Provider, a nu se specic pentru conectare la ns a SQL Server. Data Source (sinonim cu server ): se specic numele serverului a de baze de date sau numele ierului de date. s Initial Catalog (sinonim cu Database): specic numele baze de a date. Baza de date trebuie s se gseasc pe serverul dat Data a a a n Source. User ID (sinonim cu uid ): specica un nume de utilizator care are acces de loginare la server. Password (sinonim cu pwd ): specic parola contului de mai sus. a

2. ConnectionTimeout: de tip int, cu accesor de get, valoare implicit a 15; specic numrul de secunde pentru care un obiect de conexiune a a ar trebui s atepte pentru realizarea conectrii la server a s a nainte de a se genera o excepie. Se poate specica o valoare diferit de 15 t a n ConnectionString folosind parametrul Connect Timeout: SqlConnection cn = new SqlConnection("Data Source=serverBD; Database=Northwind;User ID=sa;Password=parola; Connect Timeout=30"); Se poate specica pentru Connect Timeout valoarea 0 cu semnicaia t "ateapt orict", dar se sugereaz s nu se procedeze acest mod. s a a a a n 3. Database: atribut de tip string, read-only, returneaz numele bazei de a date la care sa fcut conectarea. Folosit pentru a arta unui utilizator a a a care este baza de date pe care se face operarea. 4. Provider : atribut de tip string, read-only, returneaz numele furnizorua lui OLE DB. 5. ServerVersion: atribut de tip string, read-only, returneaz versiunea a de server la care sa fcut conectarea. a

8.4. OBIECTE CONNECTION

209

6. State: atribut de tip enumerare ConnectionState , read-only, returneaz a starea curent a conexiunii. Valorile posibile sunt: Broken, Closed, a Connecting, Executing, Fetching, Open.

8.4.2

Metode

1. Open(): deschide o conexiune la baza de date 2. Close(), Dispose(): nchid conexiunea i elibereaz toate resursele alos a cate pentru ea 3. BeginTransaction(): pentru executarea unei tranzacii pe baza de date; t la sfrit se apeleaz Commit() sau Rollback(). as a 4. ChangeDatabase(): se modic baza de date la care se vor face conexiua nile. Noua baz de date trebuie s existe pe acelai server ca precedenta. a a s 5. CreateCommand(): creeaz un obiect de tip Command valid (care ima plementeaz interfaa IDbCommand ) asociat cu conexiunea curent. a t a

8.4.3

Evenimente

Un obiect de tip conexiune poate semnala dou evenimente: a evenimentul StateChange: apare atunci cnd se schimb starea conexa a iunii. Event-handlerul este de tipul delegat StateChangeEventHandler, care spune care sunt strile a ntre care sa fcut tranziia. a t evenimentul InfoMessage: apare atunci cnd furnizorul trimite un avera tisment sau un mesaj informaional ctre client. t a

8.4.4

Stocarea stringului de conexiune ier de conn s gurare

general este contraindicat ca stringul de conexiune s e scris direct In a n cod; modicarea datelor de conectare (de exemplu parola pe cont sau sursa de date) ar nsemna recompilarea codului. .NET Framework permite meninerea unor perechi de tipul chei-valoare, t specice aplicaiei; ierul este de tip XML. Pentru aplicaiile Web ierul se t s t s numete web.cong, pentru aplicaiile de tip consol ierul de congurare are s t a s extensia cong i numele aplicaiei, iar pentru aplicaiile Windows acest ier s t t s

210

CURS 8. ADO.NET

nu exist implicit, dar se adaug: Project->Add new item->Application Cona a guration File, implicit acesta avnd numele App.cong. Elementul rdcin a a a a mpreun cu declaraia de XML sunt: a t <?xml version="1.0" encoding="utf-8" ?> <configuration> </configuration> interiorul elementului rdcin conguration se va introduce elementul In a a a appSettings, care va conine oricte perechi cheie-valoare interiorul unui t a n atribut numit add, precum mai jos: <?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="constring" value="Data Source=localhost\sqlexpress;database=Northwind; User ID=sa;pwd=parola"/> </appSettings> </configuration> Clasele neceasre pentru accesarea ierului de congurare se gsesc spaiul s a n t de nume System.Conguration. Pentru a se putea folosi acest spaiu de t nume trebuie s se adauge o referina la assembly-ul care conine deaceast a t t a clas: din Solutin explorer click dreapta pe proiect->Add reference. . . ->se a alege tab-ul .NET i de acolo System.Conguration. Utilizarea stringului de s conexiune denit anterior se face astfel: using System; using System.Data; using System.Data.SqlClient; using System.Configuration; public class UsingConfigSettings { public static void Main() { SqlConnection con = new SqlConnection( ConfigurationManager.AppSettings["constring"]; //se lucreaza cu conexiunea con.Close(); } }

8.4. OBIECTE CONNECTION

211

Este posibil ca ntr-o aplicaie s se foloseasc mai multe conexiuni, motiv t a a pentru care se sugereaz ca loc de varianta precedent s se foloseasc a n a a a elementul XML <connectionStrings>: <configuration> <appSettings>...</appSettings> <connectionStrings> <add name ="SqlProviderPubs" connectionString = "Data Source=localhost\sqlexpress;uid=sa;pwd=; Initial Catalog=Pubs"/> <add name ="OleDbProviderPubs" connectionString = "Provider=SQLOLEDB.1;Data Source=localhost;uid=sa;pwd=; Initial Catalog=Pubs"/> </connectionStrings> </configuration> Preluarea unui string de conexiune se face prin: string cnStr = ConfigurationManager.ConnectionStrings["SqlProviderPubs"] .ConnectionString;

8.4.5

Gruparea conexiunilor

Gruparea conexiunilor2 reprezint reutilizarea resurselor de tip conexia une la o baz de date. Atunci cnd se creeaz o grupare de conexiuni se a a a genereaz automat mai multe obiecte de tip conexiune, acest numr ind a a egal cu minimul setat pentru gruparea respectiv. O nou conexiune este a a creat dac toate conexiunile sunt ocupate i se cere conectare. Dac dia a s a mensiunea maxim setat a gruprii este atins, atunci nu se va mai crea a a a a o conexiune nou, ci se va pune a ntro coad de ateptare. Dac ateparea a s a s dureaz mai mult dect este precizat valoarea de Timeout se va arunca a a n o excepie. Pentru a returna o conexiune la grupare trebuie apelat metoda t a Close() sau Dispose() pentru acea conexiune. Sursele de date .NET administreaz automat gruparea de conexiuni, dea grevndu-l pe programator de aceast aspect ce nu ine de logica aplicaiei. La a t t dorina comportamentul implicit se poate modica prin intemediul coninutului t t stringului de conectare.
2

Engl: connection pooling

212

CURS 8. ADO.NET

8.4.6

Mod de lucru cu conexiunile

Se cere ca o conexiune s e a ntotdeauna nchis (i dac se poate, ct a s a a mai repede posibil). Ca atare, este de preferat ca s se aplice o schem de a a lucru de tipul: IDBConnection con = ... try { //deschidere conexiune //lucru pe baza } catch(Exception e) { //tratare de exceptie } finally { con.Close(); } Deoarece se garanteaz ca blocul nally este executat indiferent dac apare a a sau nu o excepie, cazul de mai sus se va t n nchide mod garantat conexn iunea. acelai scop se mai poate folosi i instruciunea using (seciunea In s s t t 3.5.8).

8.5

Obiecte Command

Un clas de tip Command dat de un furnizor .NET trebuie s implea a a menteze interfaa IDbCommand, ca atare toate vor asigura un set de servicii t bine specicat. Un asemenea obiect este folosit pentru a executa comenzi pe baza de date: SELECT, INSERT, DELETE, UPDATE sau apel de proceduri stocate (dac SGBD-ul respectiv tie acest lucru). Comanda se poate a s executa numai dac s-a deschis o conexiune la baza de date. a Exemplu: SqlConnection con = new SqlConnection( ConfigurationManager.ConnectionStrings["constring"] .ConnectionString; SqlCommand cmd = new SqlCommand("SELECT * FROM Customers", con);

8.5. OBIECTE COMMAND

213

Codul care utilizeaz o comand pentru lucrul cu siere mdb sau xls ar a a foarte asemntor, cu diferena c loc de SqlCommand se folosete OleDa a t a n s bCommand, din spaiul de nume System.Data.OleDb. Nu trebuie modicat t altceva, doarece locaia sursei de date se specic doar la conexiune. t a Se observ c obiectul de conexiune este furnizat comenzii create. Enua a merm mai jos principalele proprieti i metode ale unui obiect de tip coa at s mand. a

8.5.1

Proprieti at

1. CommandText: de tip String, cu ambii accesori; conine comanda SQL t sau numele procedurii stocate care se execut pe sursa de date. a 2. CommandTimeout: de tip int, cu ambii accesori; reprezint numrul a a de secunde care trebuie s e ateptat pentru executarea interogrii. a s a Dac se depeste acest timp, atunci se arunc o excepie. a as a t 3. CommandType: de tip CommandType (enumerare), cu ambii accesori; reprezint tipul de comand care se execut pe sursa de date. Valorile a a a pot : CommandType.StoredProcedure - interpreteaz comanda coninut a t a proprietatea CommandText ca o un apel de procedur stocat n a a denit baza de date. a n CommandType.Text - interpreteaz comanda ca ind o comand a a SQL clasic; este valoarea implicit. a a CommandType.TableDirect - momentan disponibil numai pentru furnizorul OLE DB (deci pentru obiect de tip OleDbCommand); dac proprietatea CommandType are aceast valoare, atunci proa a prietatea CommandText este interpretat ca numele unui tabel a pentru care se aduc toate liniile i coloanele la momentul exes cutrii. a 4. Connection - proprietate de tip System. Data. [.NET Data Provider]. PrexConnection, cu ambii accesori; conine obiectul de tip conexiune t folosit pentru legarea la sursa de date; Prex este prexul asociat furnizorului respectiv (a se vedea tabelul 8.1). 5. Parameters - proprietate de tip System.Data.[.NET Data Provider]. PrexParameterCollection, read-only; returneaz o colecie de parametri a t care s-au transmis comenzii; aceast list a fost creat prin apelul a a a metodei CreateParameter().Prex reprezint acelasi lucru ca mai sus. a

214

CURS 8. ADO.NET

6. Transaction - proprietate de tip System.Data.[.NET Data Provider]. PrexTransaction, read-write; permite accesul la obiectul de tip tranzacie t care se cere a executat pe sursa de date.

8.5.2

Metode

1. Constructori - un obiect de tip comand poate creat i prin intera s mediul apelului de constructor; de exemplu un obiect SqlCommand se poate obine astfel: t SqlCommand cmd; cmd = new SqlCommand(); cmd = new SqlCommand(string CommandText); cmd = new SqlCommand(string CommandText, SqlConnection con ); cmd = new SqlCommand(string CommandText, SqlConnection con, SqlTransaction trans); 2. Cancel() - ncearc s opreasc o comand dac ea se a execuie. a a a a a a n t Dac nu se a execuie, sau aceast a a n t a ncercare nu are nici un efect nu se intmpl nimic. a a 3. Dispose() - distruge obiectul comand. a 4. ExecuteNonQuery() - execut o comand care nu returneaz un set de a a a date din baza de date; dac comanda a fost de tip INSERT, UPDATE, a DELETE, se returneaz numrul de a a nregistrri afectate. Dac nu este a a denit conexiunea la baza de date sau aveasta nu este deschis, se a a arunc o excepie de tip InvalidOperationException. a t Exemplu: SqlConnection con = new SqlConnection( ConfigurationManager.ConnectionStrings["constring"] .ConnectionString); SqlCommand cmd = new SqlCommand(); cmd.CommandText = "DELETE FROM Customers WHERE CustomerID = SEVEN"; cmd.Connection = con; con.Open(); Console.WriteLine(cmd.ExecuteNonQuery().ToString()); con.Close() exemplul de mai sus se returneaz numrul de In a a nregistrri care au a fost terse. s

8.5. OBIECTE COMMAND

215

5. ExecuteReader() - execut comanda coninut proprietatea Coma t a n mandText i se returneaz un obiect de tip IDataReader (e.g. Sqls a DataReader sau OleDbDataReader ). Exemplu: se obine coninutul tabelei Customers t t ntrun obiect de tip SqlDataReader (se presupune c baza de date se stocheaz pe un server a a MSSQL): SqlConnection con = new SqlConnection( ConfigurationManager.ConnectionStrings["constring"] .ConnectionString); SqlCommand cmd = new SqlCommand(); cmd.CommandText = "SELECT * FROM Customers"; cmd.Connection = con; con.Open(); SqlDataReader reader = cmd.ExecuteReader(); while(reader.Read()) { Console.WriteLine("{0} - {1}", reader.GetString(0), reader.GetString(1)); } reader.Close(); con.Close(); Metoda ExecuteReader() mai poate lua un argument opional de tip t enumerare CommandBehavior care descrie rezultatele i efectul asupra s bazei de date: CommandBehavior.CloseConnection - conexiunea este nchis atunci a cnd obiectul de tip IDataReader este a nchis. CommandBehavior.KeyInfo - comanda returnez metadate despre a coloane i cheia primar. s a CommandBehavior.SchemaOnly - comanda returnez doar informaie a t despre coloane. CommandBehavior.SequentialAccess - d posibilitatea unui DataReader a s manipuleze a nregistrri care conin cmpuri cu valori binare de a t a mare ntindere. Acest mod permite arcarea sub forma unui ux nc de date folosind GetChars() sau GetBytes(). CommandBehavior.SingleResult - se returneaz un singur set de a rezultate

216

CURS 8. ADO.NET CommandBehavior.SingleRow - se returneaz o singur linie. De a a exemplu, dac codul anterior a n nainte de while obinerea obiect tului reader sar face cu: SqlDataReader reader = cmd.ExecuteReader( CommandBehavior.SingleRow); atunci sar returna doar prima nregistrare din setul de date.

6. ExecuteScalar() - execut comanda coninut proprietatea Coma t a n mandText; se returneaz valoarea primei coloane de pe primul rnd a a a setului de date rezultat; folosit pentru obinerea unor rezultate de tip t agregat (SELECT COUNT(*) FROM CUSTOMERS, de exemplu). 7. ExecuteXmlReader() - returneaz un obiect de tipul XmlReader obinut a t din rezultatul interogrii pe sursa de date. a Exemplu: SqlCommand custCMD=new SqlCommand("SELECT * FROM Customers FOR XML AUTO, ELEMENTS", con); System.Xml.XmlReader myXR = custCMD.ExecuteXmlReader();

8.5.3

Utilizarea unei comenzi cu o procedur stocat a a

Pentru a se executa pe server o procedur stocat denit baza respeca a a n tiv, este necesar ca obiectul comand s aibe proprietatea CommandType la a a a valoarea CommandType.StoredProcedure iar proprietatea CommandText s a conin numele procedurii stocate: t a SqlConnection con = new SqlConnection( ConfigurationManager.ConnectionStrings["constring"] .ConnectionString); SqlCommand cmd = new SqlCommand("Ten Most Expensive Products", con); cmd.CommandType = CommandType.StoredProcedure; con.Open(); SqlDataReader reader = cmd.ExecuteReader(); while(reader.Read()) { Console.WriteLine("{0} - {1}", reader.GetString(0), reader.GetDecimal(1)); } reader.Close(); con.Close();

8.5. OBIECTE COMMAND

217

Observaie: toate exemplele de mai sus faptul ecare conexiune se t n nchide manual, printr-un apel de tipul con.Close(). Daca conexiunea a fost folosit a pentru un obiect de tip DataRead atunci acesta din urm trebuie s e i a a s el nchis, naintea nchiderii conexiunii. Dac nu se face acest apel atunci a conexiunea va inut ocupat i nu va putea reutilizat. t a as a

8.5.4

Folosirea comenzilor parametrizate

Exist posibilitatea de a rula cod SQL (interogri sau proceduri stocate) a a pe server care s e parametrizate. Orice furnizor de date .NET permite a crearea obiectelor parametru care pot adugate la o colecie de parametri a t ai comenzii. Valoarea acestor parametri se specic e prin numele lor (cazul a SqlParameter ), e prin poziia lor (cazul OleDbParameter ). t Exemplu: vom aduce din tabela Customers toate nregistrrile care au a n cmpul Country valoarea USA. a SqlConnection con = new SqlConnection( ConfigurationManager.ConnectionStrings["constring"] .ConnectionString); SqlCommand cmd = new SqlCommand("SELECT * FROM Customers WHERE Country=@country",con); SqlParameter param = new SqlParameter("@country", SqlDbType.VarChar); param.Value = "USA"; cmd.Parameters.Add( param ); con.Open(); SqlDataReader reader = cmd.ExecuteReader(); while(reader.Read()) { Console.WriteLine("{0} - {1}", reader.GetString(0), reader.GetString(1)); } reader.Close(); con.Close(); Pentru parametrul creat sa setat tipul lui (ca ind tip ir de caractere SQL) s i valoarea. De reinut faptul c numele parametrului se prexeaz cu cars t a a acterul @ cazul lucrului cu SQL Server. n cazul care un parametru este de ieire, acest lucru trebuie spus In n s explicit folosind proprietatea Direction a parametrului respectiv: SqlCommand cmd = new SqlCommand( "SELECT * FROM Customers WHERE Country = @country; " +

218

CURS 8. ADO.NET

"SELECT @count = COUNT(*) FROM Customers WHERE Country = @country", con); SqlParameter param = new SqlParameter("@country", SqlDbType.VarChar); param.Value = "USA"; cmd.Parameters.Add( param ); cmd.Parameters.Add(new SqlParameter("@count", SqlDbType.Int)); cmd.Parameters["@count"].Direction = ParameterDirection.Output; con.Open(); SqlDataReader reader = cmd.ExecuteReader(); while(reader.Read()) { Console.WriteLine("{0} - {1}", reader.GetString(0), reader.GetString(1)); } reader.Close(); Console.WriteLine("{0} - {1}", "Count", cmd.Parameters["@count"].Value.ToString()); con.Close(); Remarcm urmtoarele: a a este posibil ca ntro comand s se execute mai multe interogri a a a pentru parametrul de ieire numit @count trebuie fcut declarare de s a a direcie; implicit un parametru este de intrare t parametrii de ieire sunt accesibili doar dup s a nchiderea obiectului de tip DataReader

8.6

Obiecte DataReader

Un obiect de tip DataReader este folosit pentru a citi date dintr-o surs a de date. Caracteristicile unei asemenea clase sunt: 1. implementeaz a ntotdeauna interfaa IDataReader t 2. se lucreaz conectat la sursa de date - pe toat perioada ct este accesat a a a un DataReader necesit conexiune activ a a 3. este de tip read-only; dac se dorete modicarea datelor se poate folosi a s un DataSet + DataAdapter sau comenzi INSERT, DELETE sau UPDATE trimise prin obiect de tip Command ;

8.6. OBIECTE DATAREADER

219

4. este de tip forward-only - metoda de modicare a poziiei curente este t doar direcia n t nainte; orice re ntoarcere presupune reluarea nregistrrilor a (dac programatorul nu implementeaz el singur un mecanism de coad) a a a Avantajele utilizrii acestui tip de obiecte sunt: accesul conectat, performanele a t bune, consumul mic de resurse i tipizarea puternic. s a

8.6.1

Proprieti at

1. IsClosed - proprietate read-only, returneaz true daca obiectul este a deschis, false altfel 2. HasRows - proprietate boolean read-only care spune dac readerul a a conine cel puin o t t nregistrare 3. Item - indexator care d acces la cmpurile unei a a nregistrri a 4. FieldCount - d numrul de cmpuri din a a a nregistrarea curent a

8.6.2

Metode

1. Close() - nchide obiectul de citire i elibereaz resursele client. Este s a obligatoriu apelul acestei metode naintea nchiderii conexiunii. 2. GetBoolean(), GetByte(), GetChar(), GetDateTime(), GetDecimal(), GetDouble(), GetFloat(), GetInt16(), GetInt32(), GetInt64(), GetValue(), GetString() returneaz valorile cmpurilor din a a nergistrarea curent. a Preiau ca parametru indicele coloanei a crei valoare se cere. Geta Value() returneaz un obiect de tip Object, pentru celelalte tipul retura nat este descris de numele metodelor. 3. GetBytes(), GetChars() - returneaz numrul de octei / caractere citii a a t t dintrun cmp ce stocheaz o structur de dimensiuni mari; primete a a a s ca parametri indicele de coloan (int), poziia din acea coloan de unde a t a se va ncepe citirea, vectorul care se face citirea, poziia buer de n t n la care se depun datele citite, numrul de octei/caractere ce urmeaz a t a a citii. t 4. GetDataTypeName() - returneaz tipul coloanei specicat prin indice a 5. GetName() - returneaz numele coloanei a 6. IsDBNull() - returneaz true dac cmpul specicat prin index este a a n a o valoare de NULL (din baza de date)

220

CURS 8. ADO.NET

7. NextResult() - determin trecerea la urmtorul rezultat, dac aceasta a a a exist; acest caz returneaz true, altfel false (este posibil ca a n a ntr un DataReader s vin mai multe rezultate, provenind din interogri a a a diferite) 8. Read() - determin trecerea la urmtoarea a a nregistrare, dac aceasta exa ista; acest caz ea returneaz true. Metoda trebuie chemat cel puin a n a a t o dat, deoarece iniial poziia curent este a t t a naintea primei nregistrri. a

8.6.3

Crearea i utilizarea unui DataReader s

Nu se creaz un obiect de tip DataReader prin apel de constructor, ci prin a intermediul unui obiect de tip Command, folosind apelul ExecuteReader() (a se vedea seciunea 8.3.2). Pentru comanda respectiv se specic instruciunea t a a t care determin returnarea setului de date precum i obiectul de conexiune. a s Aceast conexiune trebuie s e deschis a a a naintea apelului ExecuteReader(). Trecerea la urmtoarea a nregistrare se face folosind metoda Read(). Cnd a se dorete s ncetarea lucrului se nchide readerul i conexiunea. Omiterea s nchiderii obiectului de tip reader va duce la imposibilitatea reutilizrii conexa iunii iniiale. Dup ce se t a nchide acest DataReader este necesar i chiderea a s n explicit a conexiunii (acest lucru nu mai e mandatoriu doar dac la apelul a a metodei ExecuteReader sa specicat CommandBehavior.CloseConnection). Dac se a ncearc refolosirea conexiunii fr ca readerul s fost a aa a nchis se va arunca o excepie InvalidOperationException. t Exemplu: SqlConnection conn = new SqlConnection ( ConfigurationManager.ConnectionStrings["constring"] .ConnectionString); SqlCommand selectCommand = new SqlCommand("select * from ORDERS", conn); conn.Open (); OleDbDataReader reader = selectCommand.ExecuteReader ( ); while ( reader.Read () ) { object id = reader["OrderID"]; object date = reader["OrderDate"]; object freight = reader["Freight"]; Console.WriteLine ( "{0}\t{1}\t\t{2}", id, date, freight ); } reader.Close (); conn.Close ();

8.6. OBIECTE DATAREADER

221

Este perfect posibil ca un obiect de tip DataReader s aduc datele prin a a apelul unei proceduri stocate (de fapt invocarea acestei proceduri este fcut a a de ctre obiectul de tip Command ). a Urmtoarele observaii trebuie luate considerare atunci cnd se lua t n a creaz cu un obiect DataReader : a Metoda Read() trebuie s e a ntotdeauna apelat a naintea oricrui acces a la date; poziia curent la deschidere este t a naintea primei nregistrri. a Intotdeauna apelai metoda Close() pe un DataReader i pe conexiunea t s asociat ct mai repede posibil; caz contrar conexiunea nu poate a a n reutilizat a Procesarea datelor citite trebuie s se fac dup a a a nchiderea conexiunii; felul acesta conexiunea se las liber pentru a putea reutilizat. n a a a

8.6.4

Utilizarea de seturi de date multiple

Este posibil ca ntrun DataReader s se aduc mai multe seturi de date. a a Acest lucru ar micora numrul de apeluri pentru deschiderea de conexiuni s a la stratul de date. Obiectul care permite acest lucru este chiar cel de tip Command : string select = "select * from Categories; select * from customers"; SqlCommand command = new SqlCommand ( select, conn ); conn.Open (); SqlDataReader reader = command.ExecuteReader (); Trecerea de la un set de date la altul se face cu metoda NextResult() a obiectului de tip Reader : do { while ( reader.Read () ) { Console.WriteLine ( "{0}\t\t{1}", reader[0], reader[1] ); } }while ( reader.NextResult () );

8.6.5

Accesarea datelor ntro manier sigur din punct a a de vedere a tipului

S considerm urmtoarea secvena de cod: a a a t

222

CURS 8. ADO.NET

while ( reader.Read () ) { object id = reader["OrderID"]; object date = reader["OrderDate"]; object freight = reader["Freight"]; Console.WriteLine ( "{0}\t{1}\t\t{2}", id, date, freight ); } Dup cum se observ, este posibil ca valorile cmpurilor dintro a a a nregistrare s e accesate prin intermediul numelui coloanei (sau a indicelui ei, pornind a de la 0). Dezavantajul acestei metode este c tipul datelor returnate este a pierdut (ind returnate obiecte de tip Object), trebuind fcut un downcasta a ing pentru a utiliza din plin facilitile tipului respectiv. Pentru ca acest at lucru s nu se ample se pot folosi metodele GetXY care returneaz un tip a nt a specic de date: while ( reader.Read () ) { int id = reader.GetInt32 ( 0 ); DateTime date = reader.GetDateTime ( 3 ); decimal freight = reader.GetDecimal ( 7 ); Console.WriteLine ( "{0}\t{1}\t\t{2}", id, date, freight ); } Avantajul secvenei anterioare este c dac se t a a ncearc aducerea valorii unui a cmp pentru care tipul nu este dat corect se arunc o excepie InvalidCasa a t tException; altfel spus, accesul la date se face sigur din punct de verere al tipului datelor. Pentru a evita folosirea unor constante magice ca indici de coloan a (precum mai sus: 0, 3, 7), se poate folosi urmtoarea strategie: indicii se a obin folosind apel de metod GetOrdinal la care se specic numele coloanei t a a dorite: private int OrderID; private int OrderDate; private int Freight; ... OrderID = reader.GetOrdinal("OrderID"); OrderDate = reader.GetOrdinal("OrderDate"); Freight = reader.GetOrdinal("Freight"); ... reader.GetDecimal ( Freight ); ...

Curs 9 ADO.NET (2)


9.1 Obiecte DataAdapter

La fel ca i Connection, Command, DataReader, obiectele de tip DataAdapter s fac parte din furnizorul de date .NET specic ecrui tip de surs de date. a a Scopul clasei este s permit umplerea unui obiect DataSet cu date i rea a s ectarea schimbrilor efectuate asupra acestuia a napoi baza de date (DataSet n permite lucrul deconectat de la baza de date). Orice clas de tipul DataAdapter (de ex SqlDataAdapter i OleDbDataAdapter) a s este derivat din clasa DbDataAdapter (clas abstract). Pentru orice obiect a a a de acest tip trebuie specicat minim comanda de tip SELECT care s popa a uleze un obiect de tip DataSet; acest lucru este stabilit prin intermediul proprietii SelectCommand de tip Command (SqlCommand, OleDbCommand, at . . . ). cazul care se dorete i modicarea informaiilor din sursa de date In n s s t (inserare, modicare, tergere) trebuie specicate obiecte de tip comand via s a proprietile: InsertCommand, UpdateCommand, DeleteCommand. at Exemplu: mai jos se preiau nregistrrile din 2 tabele: Authors i TitleAua s thor i se trec s ntrun obiect de tip DataSet pentru a procesate ulterior. using System; using System.Data; using System.Data.SqlClient; class DemoDataSource { static void Main() { SqlConnection conn = new SqlConnection( ConfigurationManager.ConnectionStrings["constring"] 223

224

CURS 9. ADO.NET (2) .ConnectionString); DataSet ds = new DataSet(); SqlDataAdapter daAuthors = new SqlDataAdapter("SELECT au_id, au_fname, au_lname FROM authors",conn); daAuthors.Fill(ds,"Author"); SqlDataAdapter daTitleAuthor = new SqlDataAdapter("SELECT au_id, title_id FROM titleauthor", conn); daTitleAuthor.Fill(ds,"TitleAuthor");

} } Prezentm mai jos cele mai importante componente ale unei clase de tip a DataAdapter.

9.1.1

Metode

1. Constructori de la cei implicii pn la cei care se specic o comand t a a n a a de tip SELECT i conexiunea la sursa de date. Pentru un obiect de s tip SqlDataAdapter se poate crea o instana urmtoarele moduri: t n a SqlDataAdapter da = new SqlDataAdapter(); sau: SqlCommand cmd = new SqlCommand("SELECT * FROM Employees"); SqlDataAdapter da = new SqlDataAdapter(cmd); sau: String strCmd = "SELECT * FROM Employees"; String strConn = "..."; SqlDataAdapter da = new SqlDataAdapter(strCmd, strConn);

2. Fill() metod polimorc, permiand umplerea unei tabele dintrun a a t obiect de tip DataSet cu date. Permite specicarea obiectului DataSet care se depun datele, eventual a numelui tablei din acest DataSet, n numrul de a nregistrare cu care s se a nceap popularea (prima avnd a a indicele 0) i numrul de s a nregistrri care urmeaz a aduse. Rea a turneaz de ecare dat numrul de a a a nregistrri care au fost aduse din a clipa care se apeleaz Fill() se procedeaz astfel: baz. In a n a a

9.2. CLASA DATASET (a) Se deschide conexiunea (dac ea nu a fost explicit deschis) a a

225

(b) Se aduc datele i se populeaz un obiect de tip DataTable din s a DataSet (c) Se nchide conexiunea (dac ea nu a fost explicit deschis!) a a De remarcat c un DataAdapter si poate deschide i a s nchide singur conexiunea, dar dac aceasta a fost deschis a a naintea metodei Fill() atunci tot programatorul trebuie s o a nchid. a 3. Update() metod polimorc, permiand reectarea modicrilor efeca a t a tuate ntreun DataSet. Pentru a funciona are nevoie de obiecte de t tip comand adecvate: proprietile InsertCommand, DeleteCommand a at i UpdateCommand trebuie s indice ctre comenzi valide. Returneaz s a a a de ecare dat numrul de a a nregistrri afectate. a

9.1.2

Proprieti at

1. DeleteCommand, InsertCommand, SelectCommand, UpdateCommand de tip Command, conin comenzile ce se execut pentru selectarea t a sau modicarea datelor sursa de date. Mcar proprietatea Selectn a Command trebuie s indice ctre un obiect valid, pentru a se putea a a face popularea setului de date. 2. MissingSchemaAction de tip enumerare MissingSchemaAction, determin ce se face atunci cnd datele care sunt aduse nu se potrivesc a a peste schema tablei care sunt depuse. Poate avea urmtoarele valori: n a Add - implicit, DataAdapter adaug coloana la schema tablei a

AddWithKey - ca mai sus, dar adaug i informaii relativ la cine as t este cheia primar a Ignore - se ignor lipsa coloanei respective, ceea ce duce la pierdere a de date pe DataSet

Error - se genereaz o excepie de tipul InvalidOperationExcepa t tion.

9.2

Clasa DataSet

Clasa DataSet nu mai face parte din biblioteca unui furnizor de date ADO.NET, ind parte din .NET Framework. Ea poate s conin reprezentri a t a a tabelare ale datelor din baz precum i diferite restricii i relaii existente. a s t s t

226

CURS 9. ADO.NET (2)

Marele ei avantaj este faptul c permite lucrul deconectat de la sursa de date, a eliminnd necesitatea unei conexiuni permanente precum la DataReader. a felul acesta, un server de aplicaii sau un client oarecare poate apela In t la serverul de baze de date doar cnd preia datele sau cnd se dorete a a s salvarea lor. Funcioneaz strns legtur cu clasa DataAdapter care t a n a a a a acioneaz ca o punte t a ntre un DataSet i sursa de date. Remarcabil este faps tul c un DataSet poate face abstracie de sursa de date, procesarea datelor a t desfurnduse independent de ea. as a Figura 9.1 conine o vedere parial asupra clasei DataSet. t t a

Figura 9.1: Structura unui DataSet

9.2.1

Coninut t

Prezentm succint coninutul unui DataSet: a t 1. Colecia Tables conine 0 sau mai multe obiecte DataTable. Fiecare t t DataTable este compus dintr-o colecie de linii i coloane. a t s 2. Colecia Relations conine 0 sau mai multe obiecte de tip DataRelation, t t folosite pentru marcarea legturilor printecopil. a a 3. Colecia ExtendedProperties conine proprieti denite de utilizator. t t at

9.2.2

Clasa DataTable

Datele sunt coninute t ntr-un DataSet sub forma unor tabele de tip DataTable. Aceste obiecte pot folosite att independent, ct i interiorul unui a a s n

9.2. CLASA DATASET

227

DataSet ca elemente ale coleciei Tables. Un DataTable conine o colecie t t t Columns de coloane, Rows de linii i Constraints de constrngeri. s a DataColumn Un obiect DataColumn denete numele i tipul unei coloane care face s s parte sau se adaug unui obiect DataTable. Un obiect de acest tip se obine a t prin apel de constructor sau pe baza metodei DataTable.Columns.Add. Exemplu: DataColumn myColumn = new DataColumn("title", Type.GetType("System.String")); Denirea unei coloane ca ind de tip autonumber ( vederea stabilirii ei n ca i cheie pe o tabel) se face astfel: s a DataColumn idColumn = new DataColumn("ID", Type.GetType("System.Int32")); idColumn.AutoIncrement = true; idColumn.AutoIncrementSeed = 1; idColumn.AutoIncrementStep = 1; idColumn.ReadOnly = true; DataRow Un obiect de tip DataRow reprezint o linie dintr-un obiect DataTable. a Orice obiect DataTable conine o proprietate Rows ce da acces la colecia de t t obiecte DataRow coninut. Pentru crearea unei linii se poate apela metoda t a NewRow pentru o tabel a crei schem se cunoate. Mai jos este dat a a a s a secvena de cod care creeaz o linie nou pentru o tabel i o adaug acestesia: t a a as a DataRow tempRow; tempRow = myTable.NewRow(); tempRow["Name"] = "Book"; tempRow["Category"] = 1; myTable.Rows.Add(tempRow); Constrngeri a Constrngerile sunt folosite pentru a descrie anumite restricii aplicate a t asupra valorilor din coloane. ADO.NET exist dou tipuri de constrngeri: In a a a de unicitate i de cheie strin. Toate obiectele de constrngere se a s a a a a n colecia Constraints a unei tabele. t

228

CURS 9. ADO.NET (2)

UniqueConstraint - precizeaz c a a ntr-o anumit coloan valorile sunt a a unice. Incercarea de a seta valori duplicate pe o coloana pentru care s-a precizat restricia duce la aruncarea unei excepii. Este necesar t t a o asemenea coloan clipa care se folosete metoda Find pentru a n n s proprietatea Rows: acest caz trebuie s se specice o coloan pe care n a a avem unicitate. ForeignKeyConstraint - specic aciunea care se va efectua atunci a t cnd se terge sau modic valoarea dintro anumit coloan. De exa s a a a emplu se poate decide c dac se sterge o a a nregistrare dintr-o tabel a atunci s se tearg i a s a s nregistrrile copil. Valorile care se pot seta a pentru o asemenea constrngere se specic proprietile ForeignKa a n at eyConstraint.DeleteRule i ForeignKeyConstraint.UpdateRule: s Rule.Cascade - aciunea implicit, terge sau modic t a s a nregistrrile a afectate Rule.SetNull - se seteaz valoare de null pentru a nregistrrile afeca tate Rule.SetDefault - se seteaz valoarea implicit denit baz a a a n a pentru cmpul respectiv a Rule.None - nu se execut nimic a Exemplu: ForeignKeyConstraint custOrderFK=new ForeignKeyConstraint ("CustOrderFK",custDS.Tables["CustTable"].Columns["CustomerID"], custDS.Tables["OrdersTable"].Columns["CustomerID"]); custOrderFK.DeleteRule = Rule.None; //Nu se poate sterge un client care are comenzi facute custDS.Tables["OrdersTable"].Constraints.Add(custOrderFK); Mai sus s-a declarat o relaie de tip cheie strin t a a ntre dou tabele a (CustTable i OrdersTable, care fac parte dintr-un DataSet). Restricia s t se adaug la tabla copil. a Stabilirea cheii primare O cheie primar se denete ca un vector de coloane care se atribuie a s proprietii PrimaryKey a unei tabele (obiect DataTable). at DataColumn[] pk = new DataColumn[1]; pk[0] = myTable.Columns["ID"]; myTable.PrimaryKey = pk;

9.2. CLASA DATASET

229

Proprietatea Rows a clasei DataTable permite cutarea unei anumite linii a din colecia coninut dac se specic un obiect sau un array de obiecte t t a a a folosite pe post de cheie: object key = 17;//cheia dupa care se face cautarea DataRow line = myTable.Rows.Find(key); if ( line != null ) //proceseaza linia

9.2.3

Relaii t ntre tabele

Proprietatea Relations a unui obiect de tip DataSet conine o colecie de t t obiecte de tip DataRelation folosite pentru a gura relaiile de tip printe t a copil ntre dou tabele. Aceste relaii se precizeaz esena ca niste perechi a t a n t de array-uri de coloane sau chiar coloane simple din cele dou tabele care se a relaioneaz, de exemplu sub forma: t a myDataSet.Relations.Add(DataColumn, DataColumn); //sau myDataSet.Relations.Add(DataColumn[], DataColumn[]); concret: myDataSet.Relations.Add( myDataSet.Tables["Customers"].Columns["CustomerID"], myDataSet.Tables["Orders"].Columns["CustomerID"]);

9.2.4

Popularea unui DataSet

Dei un obiect DataSet se poate popula prin crearea dinamic a obiectelor s a DataTable, cazul cel mai des alnit este acela care se populeaz prin nt n a intermediul unui obiect DataAdapter. O dat obinut un asemenea obiect a t (care conine cel puin o comand de tiop SELECT ) se poate apela metoda t t a Fill() care primete ca parametru DataSet-ul care se umple i opional numele s s t tabelei care va conine datele: t //defineste comanda de selectare din baza de date String mySqlStmt ="SELECT * FROM Customers"; String myConString = ConfigurationManager.ConnectionStrings["constring"] .ConnectionString; //Construieste obiectele de conexiune + comanda SELECT SqlConnection myConnection = new SqlConnection(myConString); SqlCommand myCommand = new SqlCommand(mySqlStmt, myConnection);

230

CURS 9. ADO.NET (2)

//Construieste obiectul DataAdapter SqlDataAdapter myDataAdapter = new SqlDataAdapter(); //seteaza proprietatea SelectCommand pentru DataAdapter myDataAdapter.SelectCommand = myCommand; //construieste obiectul DataSet si il umple cu date DataSet myDataSet = new DataSet(); myDataAdapter.Fill(myDataSet, "Customers"); Datele aduse mai sus sunt depuse ntr-un obiect de tip DataTable din interiorul lui DataSet, numit "Customers". Accesul la acest tabel se face prin construcia t myDataSet.Tables["Customers"] sau folosind indici ntregi (prima tabel are indicele 0). Acelai DataSet se a s poate popula continuare cu alte tabele pe baza aceluiai sau a altor obiecte n s DataAdapter.

9.2.5

Clasa DataTableReader

Incepnd cu versiunea 2.0 a lui ADO.NET s-a introdus clasa DataTa ableReader care permite manipularea unui obiect de tip DataTable ca i cum s ar un DataReader : ntr-o manier forward-only i read-only. Crearea unui a s obiect de tip DataTableReader se face prin: DataTableReader dtReader = dt.CreateDataReader(); iar folosirea lui: while (dtReader.Read()) { for (int i = 0; i < dtReader.FieldCount; i++) { Console.Write("{0} = {1} ", dtReader.GetName(i), dtReader.GetValue(i).ToString().Trim()); } Console.WriteLine(); } dtReader.Close();

9.2. CLASA DATASET

231

9.2.6

Propagarea modicrilor ctre baza de date a a

Pentru a propaga modicrile efectuate asupra coninutului tabelelor a t dintr-un DataSet ctre baza de date este nevoie s se deneasc adecvat a a a obiecte comand de tip INSERT, UPDATE, DELETE. Pentru cazuri sima ple se poate folosi clasa SqlCommandBuilder care va construi singur aceste a comenzi. Clasa CommandBuilder Un obiect de tip CommandBuilder (ce provine din furnizorul de date) va analiza comanda SELECT care a adus datele DataSet i va construi cele n s 3 comenzi de update funcie de aceasta. E nevoie s se satisfac 2 condiii n t a a t atunci cnd se uzeaz de un astfel de obiect: a a 1. Trebuie specicat o comand de tip SELECT care s aduc datele a a a a dintr-o singur tabel a a 2. Trebuie specicat cel puin cheia primar sau o coloan cu constrngere a t a a a de unicitate comanda SELECT. n Pentru cea de a doua condiie se poate proceda felul urmtor: comanda t n a n SELECT se specic i aducerea cheii, iar pentru obiectul DataAdapter care as face aducerea din baz se seteaz proprietatea MissingSchemaAction pe vala a oarea MissingSchemaAction.AddWithKey (implicit este doar Add ). Fiecare linie modicat din colecia Rows a unei tabele va avea moda t icat valoarea proprietii RowState astfel: DataRowState.Added pentru a at o linie nou adugat, DataRowState.Deleted dac e tears i DataRowa a a a s a s State.Modied dac a fost modicat. Apelul de update pe un dataReader a a va apela comanda necesar pentru ecare linie care a fost modicat, a a n funcie de starea ei. t Artm mai jos modul de utilizare a clasei SqlCommandBuilder pentru aa adugarea, modicarea, tergerea de a s nregistrri din baza de date. a SqlConnection conn = new SqlConnection( ConfigurationManager.ConnectionStrings["constring"] .ConnectionString); da = new SqlDataAdapter("SELECT id, name, address FROM customers",conn); da.MissingSchemaAction = MissingSchemaAction.AddWithKey; da.Fill(ds); SqlCommandBuilder cb = new SqlCommandBuilder(da);

232 //determina liniile care au fost schimbate DataSet dsChanges = ds.GetChanges(); if (dsChanges != null) { // modifica baza de date da.Update(dsChanges); //accepta schimbarile din dataset ds.AcceptChanges(); }

CURS 9. ADO.NET (2)

clipa care se creeaz obiectul SqlCommandBuilder automat se vor comIn n a pleta proprietile InsertCommand, DeleteCommand, UpdateCommand ale at dataAdapter-ului. Se determin apoi liniile care au fost modicate (prin a interogarea strii lor) i se obine un nou DataSet care le va conine doar a s t t pe acestea. Comanda de Update se d doar pentru acest set de modicri, a a reducnd astfel tracul spre serverul de baze de date. a Update folosind comenzi SQL Atunci cnd interogrile de aducere a datelor sunt mai complexe (de a a exemplu datele sunt aduse din mai multe table, printr-un join) se pot specica propriile comenzi SQL prin intermediul proprietilor InsertCommand, at DeleteCommand UpdateCommand ale obiectului DataAdapter. Pentru ecare linie dintr-o tabel care este modicat/adugat/tears se va apela coa a a a s a manda SQL corespunztoare.Aceste comenzi pot fraze SQL parametrizate a sau pot denumi proceduri stocate aate baz. n a S presupunem c s-a denit un DataAdapter legat la o baz de date. a a a Instruciunea de selecie este t t SELECT CompanyName, Address, Country, CustomerID FROM Customers unde CustomerID este cheia. Pentru inserarea unei noi nregistrri sar putea a scrie codul de mai jos: //da=obiect DataAdapter da.InsertCommand.CommandText = @"INSERT INTO Customers ( CompanyName, Address, Country) VALUES (@CompanyName, @Address, @Country); SELECT CompanyName, Address, Country, CustomerID FROM Customers WHERE (CustomerID = scope_indentity())"; Update-ul efectiv se face prin prima instruciune de tip Update. Valorile t pentru aceti parametri se vor da la runtime, de exemplu prin alegerea lor s

9.3. TRANZACTII ADO.NET IN

233

dintr-un tabel. Valoarea pentru cheia CustomerID nu s-a specicat, deoarece ( acest caz) ea este de tip AutoNumber (SGBD-ul este cel care face managen mentul valorilor acestor cmpuri, nu programatorul). scope_indentity() este a o funcie predenit ce returneaz id-ul noii t a a nregistrri adugate tabel. a a n a Ultima instruciune va duce la reactualizarea obiectului DataSet, pentru ca t acesta s conin modicarea efectuat (de exemplu ar putea aduce valorile a t a a implicite puse pe anumite coloane). Pentru modicarea coninutului unei linii se poate declara instruciunea t t de UPDATE astfel: da.UpdateCommand.CommandText = @"UPDATE Customers SET CompanyName = @CompanyName, Address = @Address, Country = @Country WHERE (CustomerID = @ID)";

9.3

Tranzacii ADO.NET t n

O tranzacie este un set de operaii care se efectueaz e t t a n ntregime, e deloc. S presupunem c se dorete trecerea unei anumite sume de bani a a s dintr-un cont altul. Operaia presupune 2 pai: n t s 1. scade suma din primul cont 2. adaug suma la al doilea cont a Este inadmisibil ca primul pas s reueasc iar al doilea s eueze. Tranzaciile a s a a s t satisfac nite proprieti strnse sub numele ACID: s at a atomicitate - toate operaiile din tranzacie ar trebui s aib succes t t a a sau s eueze a s mpreun a consisten - tranzacia duce baza de date dintr-o stare stabil alta ta t a n izolare - nici o tranzacie nu ar trebui s afecteze o alta care ruleaz t a a acelai timp n s durabilitate - schimbrile care apar tipul tranzaciei sunt permaa n t nent stocate pe un mediu. Sunt trei comenzi care se folosesc context de tranzacii: n t BEGIN - nainte de executarea unei comenzi SQL sub o tranzacie, t aceasta trebuie s e iniializat a t a COMMIT - se spune c o tranzacie este terminat cnd toate schimbrile a t a a a cerute sunt trecute baza de date n

234

CURS 9. ADO.NET (2)

ROLLBACK - dac o parte a tranzaciei eueaz, atunci toate operaiile a t s a t efectuate de la nceputul tranzaciei vor neglijate t Schema de lucru cu tranzaciile sub ADO.NET este: t 1. deschide conexiunea la baza de date 2. ncepe tranzacia t 3. execut comenzi pentru tranzacie a t 4. dac tranzacia se poate efectua (nu sunt excepii sau anumite condiii a t t t sunt ndeplinite), efectueaz COMMIT, altfel efectueaz ROLLBACK a a 5. nchide conexiunea la baza de date Sub ADO.NET acest lucru s-ar face astfel: SqlConnection myConnection = new SqlConnection(myConnString); myConnection.Open(); SqlCommand myCommand1 = myConnection.CreateCommand(); SqlCommand myCommand2 = myConnection.CreateCommand(); SqlTransaction myTrans; myTrans = myConnection.BeginTransaction(); //Trebuie asignate ambele obiecte: conexiune si tranzactie //unui obiect de tip comanda care va participa la tranzactie myCommand1.Transaction = myTrans; myCommand2.Transaction = myTrans; try { myCommand1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, Description)"; myCommand1.ExecuteNonQuery(); myCommand2.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, Description)"; myCommand2.ExecuteNonQuery(); myTrans.Commit(); Console.WriteLine("Ambele inregistrari au fost scrise."); } catch(Exception e)

9.4. LUCRUL GENERIC CU FURNIZORI DE DATE { myTrans.Rollback(); } finally { myConnection.Close(); }

235

Comanda de ROLLBACK se poate executa i alte situaii, de exemplul s n t comanda efectuat deptete stocul disponibil. a as s

9.4

Lucrul generic cu furnizori de date

cele expuse pn acum, s-a lucrat cu un furnizor de date specic pentru In a a general e de dorit s se scrie cod care s funcioneze fr SQL Server 2005. In a a t aa modicri majore pentru orice furnizor de date; mai exact, am prefera s nu a a e nevoie de rescrierea sau recompilarea codului. Incepnd cu versiunea 2.0 a a lui ADO.NET se poate face acest lucru uor, prin intermediul unei clase s DbProviderFactory (un Abstract factory). Mecanismul se bazeaz pe faptul c avem urmtoarele clase de baz pena a a a tru tipurile folosite ntr-un furnizor de date: DbCommand : clas de baz abstract pentru obiectele de tip Coma a a mand DbConnection: clas de baz abstract pentru obiectele de tip Cona a a nection DbDataAdapter : clas de baz abstract pentru obiectele de tip DataAdapter a a a DbDataReader : clas de baz abstract pentru obiectele de tip DataReader a a a DbParameter : clas de baz abstract pentru obiectele de tip parametru a a a DbTransaction: clas de baz abstract pentru obiectele de tip tranzacie a a a t Crearea de obiecte specice (de exemplu obiect SqlCommand ) se face folosind clase derivate din DbProviderFactory; o schia a acestei clase este: t public abstract class DbProviderFactory { ... public virtual DbCommand CreateCommand();

236 public public public public public public } virtual virtual virtual virtual virtual virtual

CURS 9. ADO.NET (2) DbCommandBuilder CreateCommandBuilder(); DbConnection CreateConnection(); DbConnectionStringBuilder CreateConnectionStringBuilder(); DbDataAdapter CreateDataAdapter(); DbDataSourceEnumerator CreateDataSourceEnumerator(); DbParameter CreateParameter();

Tot ceea ce trebuie fcut este s se obin o clas concret derivat din a a t a a a a DbProviderFactory i care la apeluri de tip Create... s returneze obiecte s a concrete, adecvate pentru lucrul cu sursa de date. Concret: static void Main(string[] args) { // Obtine un producator pentru SqlServer DbProviderFactory sqlFactory = DbProviderFactories.GetFactory("System.Data.SqlClient"); ... // Obtine un producator pentru Oracle DbProviderFactory oracleFactory = DbProviderFactories.GetFactory("System.Data.OracleClient"); ... } Se va evita, reste, codicarea numelui furnizorului de date cod (precum n mai sus) si se vor integra iere de congurare. Aceste iruri de caractere n s s exemplicate mai sus sunt denite ierul machine.cong din directorul n s unde s-a fcut instalarea de .NET (%windir%\Microsoft.Net\Framework\ a v2.0.50727\cong). Exemplu: ierul de congurare este: s <configuration> <appSettings> <!-- Provider --> <add key="provider" value="System.Data.SqlClient" /> <!-- String de conexiune --> <add key="cnStr" value= "Data Source=localhost;uid=sa;pwd=;Initial Catalog=Pubs"/> </appSettings> </configuration> Codul C#:

9.5. TIPURI NULABILE

237

static void Main(string[] args) { string dp = ConfigurationManager.AppSettings["provider"]; string cnStr = ConfigurationManager.ConnectionStrings["constring"] .ConnectionString; DbProviderFactory df = DbProviderFactories.GetFactory(dp); DbConnection cn = df.CreateConnection(); cn.ConnectionString = cnStr; cn.Open(); DbCommand cmd = df.CreateCommand(); cmd.Connection = cn; cmd.CommandText = "Select * From Authors"; DbDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); while (dr.Read()) Console.WriteLine("-> {0}, {1}", dr["au_lname"], dr["au_fname"]); dr.Close();

9.5

Tipuri nulabile

Pentru tipurile valoare este mandatorie stabiilirea unei valori; o variabil a de tip valoare nu poate s rein null. Altfel spus, codul urmtor va genera a t a a eroare de compilare static void Main(string[] args) { bool myBool = null; int myInt = null; } contextul lucrului cu baze de date este perfect posibil ca rezulattul unei In interogri s aduc null pentru un anumit cmp. Pentru a rezolva aceast a a a a a incompatibilitate (tipuri cu valoare nenul C# care trebuie s poat lua n a a cra cu null-urile provenite din baz), s-au introdus tipurile nulabile. Acesta a reprezint un mecansim de extindere a tipurilor de tip valoare astfel at s a nc a suporte i valoarea de nul. s De exemplu, pentru a putea declara o variabil de tip int care s poat a a a avea i valoare nul se va scrie: s a

238 int? intNulabil = null; i=3; i=null;

CURS 9. ADO.NET (2)

Nu vom putea deni ca nulabile tipurile referina, deoarece acestea suport t a implicti null: //eroare de compilare string? s = null Lucrul cu tipurile nulabile se face exact ca i cu tipurile valoare: s class DatabaseReader { //campuri nulabile public int? numbericValue; public bool? boolValue = true; public int? GetIntFromDatabase() { return numbericValue; } public bool? GetBoolFromDatabase() { return boolValue; } } contextul tipurilor nulabile s-a introdus operatorul ?? care permite asignarea In unei valori pentru o variabil de tip nulabil dac valoarea returnat este nul: a a a a static void Main(string[] args) { DatabaseReader dr = new DatabaseReader(); int? myData = dr.GetIntFromDatabase() ?? 100; Console.WriteLine("Value of myData: {0}", myData); }

Curs 10 LINQ (I)


10.1 Generaliti at

Language Integrated Query (LINQ, pronunat precum link) permite t interogarea unor colecii de date folosind o sintax integrat platforma t a a n .NET. Prin intermediul unor operatori se pot interoga colecii de forma: vect tori, colecii, clase enumerabile, documente XML, baze de date relaionale. t t Datele rezultate sunt vzute ca obiecte; are loc o mapare (asociere, traduca ere) a unor date neobiectuale ntrun format uor de folosit limbajele s n obiectuale din cadrul plaformei. Trebuie menionat c sintaxa este unitar, t a a independent de natura sursei de date. Listing 10.1: Interogare LINQ var query = from e in employees where e . i d == 1 s e l e c t e . name sau: Listing 10.2: Alt interogare LINQ a var r e s u l t s = from product in p r o d u c t s C o l l e c t i o n where product . U n i t P r i c e < 100 s e l e c t new { product . ProductName , product . U n i t P r i c e } ;

foreach ( var r e s u l t in r e s u l t s ) { Console . WriteLine ( r e s u l t ) ; }

239

240

CURS 10. LINQ (I)

Este introdus interfaa IQueryable<T>, care permite implementarea a t unor furnizori de date modul specic sursei de date considerate. Exn presia folosit pentru interogare este tradus a a ntr-un arbore de expresie. Dac colecia implementeaz IEnumerable<T>, atunci se folosete motorul a t a s de execuie LINQ local, integrat platform; dac colecia implementeaz t n a a t a IQueryable<T>, atunci se folosete implementarea bazat pe arborele de exs a presie; aceast implementare este dat de ctre furnizoare de LINQ. Furnia a a zoarele de LINQ sunt scrise mod specic ecrei surse de date, dar datorit n a a respectrii unor interfee specicate, detaliile de implementare sunt irelevante a t pentru cel care folosete cod LINQ interogare. s n LINQ a fost introdus versiunea 3.5 a lui .NET Framework. Const n a ntrun set de unelte care sunt folosite pentru lucrul cu date i extensii aduse s limbajului. Este alctuit din: a LINQ to Objects se aduc date din colecii care implementeaz IEnumerable<T>; t a datele interogate sunt deja memoria procesului; n LINQ to XML convertete documentele XML s ntro colecie de obiecte t de tip XElement; LINQ to SQL permite convertirea interogrilor LINQ comenzi a n SQL; LINQ to DataSets spre deosebire de LINQ to SQL care a venit iniial doar cu suport pentru SQL Server, LINQ to DataSets folosete t s ADO.NET pentru comunicarea cu baze de date; LINQ to Entities soluie Object/Relational Mapping de la Microsoft t ce permite utilizarea de Entities introduse ADO.NET 3.0 pentru n a specica declarativ structura obiectelor ce modeleaz domeniul i a s folosete LINQ pentru interogare. s La ora actual exist urmtorii furnizori de date LINQ: a a a 1. LINQ to MySQL, PostgreSQL, Oracle, Ingres, SQLite i Microsoft SQL s Server 2. LINQ to CSV 3. LINQ to Google 4. LINQ to NHibernate 5. LINQ to System Search

10.2. MOTIVATIE

241

Se lucreaz la PLINQ, Parallel LINQ, un motor ce folosete paralelizarea de a s cod pentru executare mai rapid a interogrilor, cazul unui sistem multia a n nucleu sau multiprocesor.

10.2

Motivaie t

Vom prezenta dou motive pentru care LINQ este util: a codul stufos, neproductiv utilizat pentru accesarea modul clasic a n datelor; nepotrivirea paradigmelor obiectualrelaionale. t

10.2.1

Codul clasic ADO.NET

Pentru accesarea datelor dintr-o baz de date relaional, folosind ADO.NET a t a se scrie de regul un cod de forma: a Listing 10.3: Interogare clasic folosind ADO.NET a using ( S q l C o n n e c t i o n c o n n e c t i o n = new S q l C o n n e c t i o n ( " . . . " ) ) { c o n n e c t i o n . Open ( ) ; SqlCommand command = c o n n e c t i o n . CreateCommand ( ) ; command . CommandText = @"SELECT Name , Country FROM Customers WHERE City = @City" ; command . Parameters . AddWithValue ( "@City" , " P a r i s " ) ; using ( SqlDataReader r e a d e r = command . ExecuteReader ( ) ) { while ( r e a d e r . Read ( ) ) { string name = r e a d e r . G e t S t r i n g ( 0 ) ; string c o u n t r y = r e a d e r . G e t S t r i n g ( 1 ) ; ... } } } Se remarc urmtoarele: a a cantitatea de cod scris; codul de sus este unul des folosit, scrierea lui a repetate rnduri este contraproductiv; n a a

242

CURS 10. LINQ (I)

interogrile sunt exprimate prin intermediul unei fraze scrise a ntre ghilimele, ca ir de caractere, deci automat nu se poate verica prin compilarea s corectitudinea codului SQL coninut; este adevrat, a, c se pot t a ns a folosi aici proceduri stocate care sunt compilate deja pe server; slaba tipizare a parametrilor: dac tipul acestora nu coincide cu ce se a a baza de date? dac numrul de parametri este incorect (asta a n a a se semnaleaz numai la rulare, ca eroare; de preferat ar fost s se a a depisteze acest lucru la compilare); de cele mai multe ori trebui folosit un dialect de SQL specic productorului serverului de baze de date; codul SQL nu este portabil; a totodat: mixarea de limbaje C# i SQL face codul greu de urmrit. a s a O expresie LINQ care care demonstreaz depirea acestor probleme este: a as Listing 10.4: Cod LINQ from customer in c u s t o m e r s where customer . Name . S t a r t s W i t h ( "A" ) && customer . Orders . Count > 10 o r d e r b y customer . Name s e l e c t new { customer . Name , customer . Orders }

10.2.2

Nepotrivirea de paradigme

Paradigma este o constructie mental larg acceptat, care ofer unei a a a comuniti sau unei societi pe perioad at at a ndelungat o baz pentru crearea a a unei identiti de sine (a activitii de cercetare de exemplu) si astfel pentru at at rezolvarea unor probleme sau sarcini 1 . Exist o diferena sesizabil a t a ntre programarea orientat pe obiecte, utia lizat cadrul limbajelor folosite pentru implementarea aplicaiilor i modul a n t s de stocare i reprezentare a datelor: XML sau baze de date relaionale. s t Translatarea unui graf de obiecte din reprezentarea obiectual a ntro alt a reprezentare este greoaie: programatorul trebuie s teleag i particua n a s laritile structurilor de date folosite pentru persistarea lor, pe lng cunoaterea at a a s limbajului care lucreaz. n a Problema pe care LINQ o abordeaz este rezolvarea urmtoarelor inea a galiti: at Data != Objects
1

Wikipedia

10.3. LINQ TO OBJECTS: EXEMPLIFICARE Relational data != Objects XML data != Objects XML data != Relational data

243

Toate aceste nepotriviri necesit efort de adaptare din partea programaa torului. Modelarea obiectualrelaional este problema cea mai des alnit, t a nt a cu urmtoarele aspecte: a 1. tipurile de date folosite de ctre modelele relaionale i modelele obieca t s tuale nu sunt aceleai; de exemplu, multitudinea de tipuri ir de caracs s tere folosite specicarea coloanelor, timp ce .NET exist doar n n n a tipul String; 2. modelele relaionale folosesc normalizarea (pentru eliminarea redundanei t t i a anomaliilor de inserare, tergere, modicare), timp ce modes s n n larea obiectual nu trebuie s treac prin aa ceva; schimb, modelarea a a a s n obiectual folosete agregarea sau motenirea, mecanisme care nu si au a s s un echivalent direct modelarea relaional n t a 3. modele de programare diferite: pentru SQL se folosete un limbaj s declarativ care specic ce se prelucreaz, timp ce limbajele de proa a n gramare folosite sunt de regul imperative arat cum se face prelua a crarea 4. ncapsulare ascunderea detaliilor i legarea laolalt datelor cu metodele s a care prelucreaz datele; a Toate aceste probleme se manifest a ncepnd cu maparea a ntre obiecte i datele persistate. Aceeai problem apare dac ne referim la XML, care s s a a favorizeaz un model ierarhic, semistructurat. Programatorul trebuie s scrie a a mereu un cod care s faciliteze legarea acestor universuri diferite. LINQ vine a cu o propunere de rezolvare.

10.3

LINQ to Objects: exemplicare

LINQ to Objects este folosit pentru interogarea datelor care se a deja a memorie. Un prim exemplu este: n using System ; using System . Linq ; s t a t i c c l a s s HelloWorld

244 {

CURS 10. LINQ (I)

s t a t i c void Main ( ) { string [ ] words = { " h e l l o " , " w o n d e r f u l " , " l i n q " , " b e a u t i f u l " , " world " } ; var shortWords = from word in words where word . Length <= 5 s e l e c t word ; foreach ( var word in shortWords ) Console . WriteLine ( word ) ; } } Un exemplu mai complex este: Listing 10.5: LINQ peste colecie generic, folosind expresie LINQ t a L i s t <Person> p e o p l e = new L i s t <Person> { new Person ( ) { ID = 1 , IDRole = 1 , LastName = " Anderson " , FirstName = "Brad" }, new Person ( ) { ID = 2 , IDRole = 2 , LastName = "Gray" , FirstName = "Tom" } }; var query = from p in p e o p l e where p . ID == 1 s e l e c t new { p . FirstName , p . LastName } ; Interogarea din nal poate scris i altfel: as Listing 10.6: LINQ peste colecie generic, folosind apeluri de metode t a var query = p e o p l e . Where ( p => p . ID == 1 ) . S e l e c t ( p => new { p . FirstName , p . LastName } ) ;

10.4. MECANISME UTILIZATE DE LINQ

245

10.4

Mecanisme utilizate de LINQ

Pentru a putea folosit, LINQ a necesitat extinderea limbajelor din cadrul platformei.

10.4.1

Inferena tipului t

Considerm metoda: a void f() { int x = 3; MyClass y = new MyClass(); bool z = true; .... } Pentru ecare din declaraiile cu iniializare de mai sus se poate spune c t t a valoarea asociat d sucient informaie despre tipul de date corespunztor a a a t a variabilelor. Tocmai din acest motiv C# 3.0 se poate scrie astfel: n void f() { var x = 3; var y = new MyClass(); var z = true; .... } var nu reprezint un nou tip de date, ci pur i simplu arat c la compilare a s a a se poate deduce tipul actual al variabilelor locale respective. Ca atare, var este de fapt o scurttur, prin care se obine ceea ce s-a scris prima oar. a a t a Dac dup declarare i iniializare se scrie: a a s t x = false; compilatorul semnaleaz eroare, deoarece s-a fcut deja inferarea tipului de a a dat pentru x, i anume int, iar false nu este compatibil cu int. Limbajul a s rmne deci puternic tipizat, ecare variabil avnd un tip asignat. a a a a Folosirea acestui nou cuvnt cheie se supune condiiilor: a t se poate folosi doar pentru variabile locale

246

CURS 10. LINQ (I)

se poate folosi atunci cnd compilatorul poate s infereze tipul de dat a a a asociat variabilei; acest lucru se ampl conform situaiilor de mai nt a t sus, sau pentru o iterare de forma: int[] sir = {1, 2, 3}; foreach(var i in sir) { ... } odat ce compilatorul determin tipul de date, acesta nu mai poate a a schimbat. Mecanismul nu reprezint la prima vedere un mare pas, dar este cu a adevrat util atunci cnd se lucreaz cu alte mecanisme din C# 3.0: tipuri a a a anonime i programarea bazat pe LINQ. s a

10.4.2

Tipuri anonime

Acest mecanism permite declararea unor variabile de un tip care nu este denit aprioric. Se omite declararea numelui de clas i a componenei acesas t teia. Exemplu: var p = new {FirstName="Rafael", Age=25}; Pentru proprietile care sunt pomenite expresia de iniializare se face at n t deducerea tipului mod automat (pe baza aceluiai mecanism de la variabile n s anonime). Proprietile sunt publice i read-only. at s

10.4.3

Metode pariale t

Metodele pariale reprezint metodele care sunt declarate mai multe t a n pri ale unei clase. Clasa conintoare poate s e sau nu parial. Cele at t a a t a dou pri sunt una care se denete metoda parial (cu tip de retur i a at n s t a s parametri, dar fr corp) i alta care se implementeaz (cu corp complet). aa s n a Exemplu: //fisierul MyClass1.cs partial class MyClass { //definitie de metoda partial void f(int x);

10.4. MECANISME UTILIZATE DE LINQ } //fisierul MyClass2.cs partial class MyClass { //implementare de metoda partial void f(int x) { Console.WriteLine(x.ToString()); } }

247

Compilatorul va pune la un loc cele dou declaraii de metode pariale i va a t t s rezulta o metod a ntreag. Regulile care trebuie respectate pentru crearea a de metode pariale sunt: t 1. metoda pentru care se folosete implementare parial trebuie s res t a a turneze void; 2. parametrii nu pot de tip output; 3. metoda nu poate avea specicatori de acces; ea este privat a 4. metoda trebuie s e declarat att la denire ct i la implementare a a a a s ca ind parial. t a Adgm c o astfel de metod parial poate s apar a a a a t a a a ntro structur sau a ntro clas (declarate ca pariale). Nu este obligatoriu ca ambele declaraii a t t s apar pri diferite ale clasei. Dac o implementare de metod parial a a n a t a a t a lipsete, atunci orice apel la ea este ignorat. s

10.4.4

Metode de extensie

Metodele de extensie permit scrierea de metode asociate cu clase, alte clase dect cele care sunt denite. a n S considerm o clas, cazul creia ecare obiect menine un ir de a a a n a t s numere. O posibil deniie ar : a t class LotOfNumbers { private int[] numbers; public LotOfNumbers(int[] numbers)

248 {

CURS 10. LINQ (I)

this.numbers = new int[numbers.Length]; numbers.CopyTo(this.numbers, 0); } public int NumbersNo { get { if (numbers == null) { return 0; } else { return numbers.Length; } } } public int Sum() { if (numbers == null) { throw new Exception("No number inside"); } int result = 0; foreach (int x in numbers) { result += x; } return result; } } Ne propunem s adugm o metod la aceast clas, care s returneze media a a a a a a a elementelor irului. S presupunem c nu avem acces la sursa codului C#. s a a Singura modalitate de a extinde clasa LotOfNumbers este ca s se scrie o a metod de extensie: a static class ExtendsLotOfNumbers {

10.4. MECANISME UTILIZATE DE LINQ public static double Average(this LotOfNumbers data) { return (double)data.Sum() / data.NumbersNo; } } Utilizarea metodei de extensie se face cu:

249

class Program { static void Main() { LotOfNumbers numbers = new LotOfNumbers(new int[]{1, 2, 3}); Console.WriteLine(numbers.Average().ToString()); } } Am reusit astfel s strecurm o metod interirorul unei clase al crei cod a a a n a surs nu este accesibil. Putem utiliza acest mecanism dac: a a 1. clasa care se face implementarea metodei de extensie este static; n a 2. metoda care implementeaz extensia este static a a 3. metoda de extensie are primul parametru de tipul clasei pentru care se face extinderea, iar tipul parametrului formal este prexat cu this. Este posibil ca o metod de extensie s aibe mai mult de un parametru. a a

10.4.5

Expresii lambda

Aceste expresii simplic scrierea delegailor i a metodelor anonime. a t s Intrun exemplu anterior sa folosit: Where ( p => p . ID == 1 ) care sar citi: obiectul p produce expresia logic p.ID egal cu 1. Ar putea a rescris echivalent, astfel: a Listing 10.7: Expresie lambda rescris cu delegai a t Func<Person , bool> f i l t e r = delegate ( Person p ) { return p . ID == 1 ; } ; var query = p e o p l e . Where ( f i l t e r ) . S e l e c t ( p => new { p . FirstName , p . LastName } ) ;

250

CURS 10. LINQ (I)

10.5

Operatori LINQ
Tabelul 10.1: Operatori LINQ.

Operaie t Aggregate

Descriere Aplic o funcie peste o secvena a t t Calculeaz media peste o secvena a t calculeaz numrul de elemente a a dintr-o secvena t Max, Min, Sum Calculeaz maximul, minimul, suma a elementelor dintr-o secvena t Concatenare Concat Concateneaz elementele a dou secvene a a t Conversie AsEnumerable Convertete o secvena s t la un IEnumerable<T> AsQueryable Convertete o secvena la un s t IQueryable<T> Cast convertete un element al unei s secvene la un tip specicat t OfType Filtreaz elementele unei secvene, a t returnndule doar pe cele care au un a tip specicat ToArray transform vector a n ToDictionary transform dicionar a n t ToList transform colecie a n t ToLookup creeaz un obiect de tip a Lookup<K, T> dintr-o secvena t ToSequence returneaz argumentul transformat a ntrun IEnumerable<T> Obinere de element DefaultIfEmpty t d un element implicit dac secvena a a t este goal a ElementAt returneaz elementul de la a poziia specicat t a ElementAtOrDefault returneaz elementul de la poziia a t specicat sau o valoare implicit, dac a a a la poziia specicat nu se a nimic t a a First, Last primul, respectiv ultimul element dintro secvena t FirstOrDefault ca mai sus, dar cu returnare de valoare LastOrDefault implicit dac primul, respectiv ultimul elem a a

Operator Aggregate Average Count/LongCount

10.5. OPERATORI LINQ

251 Tabelul 10.1 (continuare)

Operaie t

Operator Single SingleOrDefault

Egalitate Generare

SequenceEqual Empty Range Repeat

Grupare Jonciune t

GroupBy GroupJoin Join OrderBy OrderByDescending Reverse ThenBy, ThenByDescending Skip SkipWhile Take TakeWhile

Sortare

Partiionare t

Proieie t

Select SelectMany

Cuanticatori

All Any

Descriere din colecie nu este diponibil t returneaz elementul din colecie, a t presupus a format dintrun singur element a ca mai sus, sau element implicit dac a elementul singular nu este gsit secvena a n t veric dac dou secvene sunt egale a a a t returneaz o secvena goal de tipul specicat a t a Genereaz o secvena de numere aate a t ntre dou capete specicate a Genereaz o secvena prin repetarea de un a t numr de ori specicat a unei valori specicate a Grupeaz elementele unei secvene a t O joniune grupat a dou secvene pe baza t a a t unor chei care se potrivesc Joniune interioar a dou secvene t a a t Ordoneaz elementele unei secvene pe baza a t unora sau a mai multo chei Ca mai sus, dar cu sortare descresctoare a inverseaz ordinea elementelor dintr-o secvena a t pentru specicarea de chei suplimentare de sortare Produce elementele unei secvene care t se a dup o anumit poziie a a a t Sare peste elementele unei secvene care t ndeplinesc o anumit condiie a t Ia primele elemente dintr-o secvena t Ia elementele de la nceputul unei secvene, t atta timp ct ele respect o a a a anumit condiie a t denete elementele care s se iau secvena n t Preia elemente dintr-o secvena t coninnd secvene t a t Veric dac toate elementele a a unei secvene satisfac o condiie dat t t a Veric dac vreun elementl al unei a a secvene satisface o condiie dat t t a

252

CURS 10. LINQ (I)

Tabelul 10.1 (continua Operaie t Operator Contains Where Distinct Except Intersect Union Descriere Veric dac o secvena conine a a t t un element Filtreaz o secvena pe baza a t unei condiii t Returneaz elementele distincte a dintro colecie t Efectueaz diferena a dou secvene a t a t Returneaz intersecia a dou mulimi a t a t Produce reuniunea a dou secvene a t

Restricie t Mulime t

10.6

LINQ to Objects

s Exemplele de mai jos sunt preluate din [8] i sunt folosite pentru exemplicarea codului ce folosete LINQ. Se pleac de la clase Person, Role i s a s Salary, denite ca mai jos: Listing 10.8: Clase pentru exemplicarea LINQ-ului c l a s s Person { public int ID { get ; set ; } public int IDRole { get ; set ; } public string LastName { get ; set ; } public string FirstName

10.6. LINQ TO OBJECTS { get ; set ; } } c l a s s Role { public int ID { get ; set ; } public string R o l e D e s c r i p t i o n { get ; set ; } } class Salary { public int IDPerson { get ; set ; } public int Year { get ; set ; } public double S a l a r y Y e a r { get ; set ; } }

253

254

CURS 10. LINQ (I)

10.6.1

Filtarea cu Where

Pentru operatorul Where sau denit dou metode de extensie: a public s t a t i c IEnumerable<T> Where<T>( t h i s IEnumerable<T> s o u r c e , Func<T, bool> p r e d i c a t e ) ; public s t a t i c IEnumerable<T> Where<T>( t h i s IEnumerable<T> s o u r c e , Func<T, int , bool> p r e d i c a t e ) ; Prima form folosete un predicat (condiie) care pentru un obiect de tipul a s t T returneaz un boolean, iar al doilea caz se folosete la condiie obiectul a n s t i indicele su secvena. s a n t S presupunem c avem o colecie de obiecte de tip Person construit a a t a astfel: L i s t <Person> p e o p l e = new L i s t <Person> { new Person { ID = 1 , IDRole = 1 , LastName = " Anderson " , FirstName = "Brad" }, new Person { ID = 2 , IDRole = 2 , LastName = "Gray" , FirstName = "Tom" }, new Person { ID = 3 , IDRole = 2 , LastName = " Grant " , FirstName = "Mary" }, new Person { ID = 4 , IDRole = 3 , LastName = "Cops" , FirstName = "Gary" } };

10.6. LINQ TO OBJECTS

255

Obinerea obiectelor de tip Person care au prenumele Brad se face cu t expresia LINQ: var query = from p in p e o p l e where p . FirstName == "Brad" select p; Elementele care sunt aduse de ctre interogarea anterioar pot iterate cu: a a foreach ( Person x in query ) { Console . WriteLine ( " { 0 } , {1} " , x . FirstName , x . LastName ) ; } Pentru expresia de interogare de mai sus se poate scrie echivalent: var query = p e o p l e . Where ( p => p . FirstName == "Brad" ) ; adic o variant care se folosesc metodele de extensie. a a n Dac se dorete folosirea cadrul condiiei de ltrare a poziiei elemena s n t t tului curent lista people, se poate scrie: n var query = p e o p l e . Where ( ( p , i n d e x ) => p . IDRole == 1 && i n d e x % 2 == 0 ) ; i utilizarea metodei de extensie este singura posibilitate de a referi indicele s d epoziie. t

10.6.2

Operatorul de proiecie t

Pentru a determina ce conine ecare element care compune o secvena, t t se folosete operatorul Select, denit de metodele de extensie: s public s t a t i c IEnumerable<S> S e l e c t <T, S>( t h i s IEnumerable<T> s o u r c e , Func<T, S> s e l e c t o r ) ; public s t a t i c IEnumerable<S> S e l e c t <T, S>( t h i s IEnumerable<T> s o u r c e , Func<T, int , S> s e l e c t o r ) ; Selectarea se poate face cu: var query = from p in p e o p l e s e l e c t p . FirstName ; sau: var query = p e o p l e . S e l e c t ( p => p . FirstName ) ; Se poate de asemenea ca la ecare element selectat s se produc un obiect a a de un tip nou, chiar tip anonim:

256 var query = p e o p l e . Select ( ( p , i n d e x ) => new { P o s i t i o n=index , p . FirstName , p . LastName } );

CURS 10. LINQ (I)

10.6.3

Operatorul let

Let permite evaluarea unei expresii i atribuirea rezultatului ctre o varis a abil care poate utilizat cadrul interogrii ce o conine. a a n a t s t a t i c void Main ( string [ ] a r g s ) { // code from DevCurry . com var a r r = new [ ] { 5 , 3 , 4 , 2 , 6 , 7 } ; var sq = from int num in a r r l e t s q u a r e = num num where s q u a r e > 10 s e l e c t new { num , s q u a r e } ; foreach ( var a in sq ) Console . WriteLine ( a ) ; Console . ReadLine ( ) ; } Rezultat: { { { { num num num num = = = = 5, 4, 6, 7, square square square square = = = = 25 16 36 49 } } } }

10.6.4

Operatorul SelectMany

Are metodele de extensie: public s t a t i c IEnumerable<S> SelectMany<T, S>( t h i s IEnumerable<T> s o u r c e ,

10.6. LINQ TO OBJECTS Func<T, IEnumerable<S>> s e l e c t o r ) ; public s t a t i c IEnumerable<S> SelectMany<T, S>( t h i s IEnumerable<T> s o u r c e , Func<T, int , IEnumerable<S>> s e l e c t o r ) ; S presupunem c specicm i nite obiecte rol: a a a s s L i s t <Role> r o l e s = new L i s t <Role> { new Role { ID = 1 , R o l e D e s c r i p t i o n = "Manager" } , new Role { ID = 2 , R o l e D e s c r i p t i o n = " D e v e l o p e r " } } ; Dac dorim rolul persoanei avnd id-ul 1, atunci putem scrie: a a var query = from p in p e o p l e where p . ID == 1 from r in r o l e s where r . ID == p . IDRole s e l e c t new { p . FirstName , p . LastName , r . RoleDescription }; Echivalent, se poate folosi metoda SelectMany: var query = p e o p l e . Where ( p => p . ID > 1 ) . SelectMany ( p => r o l e s . Where ( r => r . ID == p . RoleID ) . S e l e c t ( r => new { p . FirstName , p . LastName , r . RoleDescription } ) );

257

SelectMany permite gestiunea unei alte secvene elemente; dac s-ar folosi t a Select loc de SelectMany, atunci sar obine o secvena de elemente de n t t tip IEnumerable<T>.

258

CURS 10. LINQ (I)

10.6.5

Jonciuni t

Operatorul Join poate folosit pentru a aduce date din dou colecii, a t date care sunt puse corespondena pe baza unei chei. Se bazeaz pe metoda n t a de extensie: public s t a t i c IEnumerable<V> Join<T, U, K, V>( t h i s IEnumerable<T> o u t e r , IEnumerable<U> i n n e r , Func<T, K o u t e r K e y S e l e c t o r , > Func<U, K i n n e r K e y S e l e c t o r , > Func<T, U, V> r e s u l t S e l e c t o r ) ; De exemplu, pentru a aduce lista numelor, prenumelor i a descrierilor de s roluri pentru coleciile persons i roles putem folosi: t s var query = from p in p e o p l e j o i n r in r o l e s on p . IDRole e q u a l s r . ID s e l e c t new { p . FirstName , p . LastName , r . RoleDescription }; Folosind operatori (metode) LINQ, se poate scrie: query = p e o p l e . J o i n ( r o l e s , p => p . IDRole , r o l e => r o l e . ID , ( p , r ) => new { p . FirstName , p . LastName , r . RoleDescription } );

10.6.6

Grupare

Gruparea datelor se face cu GroupBy; se grupeaz elementele unei secvene a t pe baza unui selector. Exemplu: plecnd de la a var query = from m in typeof ( int ) . GetMethods ( ) s e l e c t m. Name ;

10.6. LINQ TO OBJECTS

259

Dac se aeaz elementele din colecia query, se poate observa c metoda a s a t a ToString este aat de 4 ori, Equals de cou ori etc. Gruparea acestora2 s a a se poate face astfel: var q = from m in typeof ( int ) . GetMethods ( ) group m by m. Name i n t o gb s e l e c t new {Name = gb . Key } ; Dac dorim s aam i o valoare agregat la nivel de grup, atunci putem a a s s a specica suplimentar operatorul de selecie: n t var q = from m in typeof ( int ) . GetMethods ( ) group m by m. Name i n t o gb s e l e c t new {Name = gb . Key , O v e r l o a d s = gb . Count ( ) } ; Pentru grupare se poate specica i un comparator (implementare de interfaa s t IEqualityComparer) care s spun care este criteriul de determinare a elea a mentelor egale ce formeaz un grup. a

10.6.7

Ordonare

Sunt folosii operatorii: OrderBy, OrderByDescending, ThenBy, ThenByDescending t i Reverse. s Operatorii OrderBy i OrderByDescending produc sortarea cresctoare, s a respectiv descresctoare a unor secvene, pe baza unei chei de sortare. Exa t emplu: var q = from m in typeof ( int ) . GetMethods ( ) o r d e r b y m. Name s e l e c t new { Name = m. Name } ; Folosind metode se poate scrie: q = typeof ( int ) . GetMethods ( ) . OrderBy ( method => method . Name ) . S e l e c t ( method => new { Name = method . Name } ) ; Pentru sortarea descresctoare expresia LINQ este: a var q = from m in typeof ( int ) . GetMethods ( ) o r d e r b y m. Name d e s c e n d i n g s e l e c t new { Name = m. Name } ; sau prin operatori LINQ:
2

A nu se confunda exemplul cu eliminarea duplicatelor.

260

CURS 10. LINQ (I)

q = typeof ( int ) . GetMethods ( ) . OrderByDescending ( method => method . Name ) . S e l e c t ( method => new { Name = method . Name } ) ; Dac se dorete specicarea mai multor chei dup care s se fac sortarea a s a a a ordine lexicograc, atunci se poate specica prin ThenBy i ThenByDescending n a s apelate ca metode care sunt criteriile suplimentare de sortare. Dac se a folosete expresie LINQ, atunci se poate scrie mai simplu: s var query = from p in p e o p l e o r d e r b y p . FirstName , p . LastName select p; Exprimarea echivalent cu operatori LINQ este: a var query = p e o p l e . OrderBy ( p => p . FirstName ) . ThenBy ( p => p . LastName ) ; Pentru personalizarea sortrii, se poate folosi o implementare de interfaa a t IComparer<T>. Inversarea ordinii elementelor dintro secvena se face cu Reverse, apelat t a ca metod: a var q = ( from m in typeof ( int ) . GetMethods ( ) s e l e c t new { Name = m. Name } ) . R e ve r s e ( ) ; sau: var q = typeof ( int ) . GetMethods ( ) . S e l e c t ( method => new { Name = method . Name } ) . R e ve r s e ( ) ;

10.6.8

Agregare

Exist operatorii de agregare: Count, LongCount, Sum, Min, Max, Average, a Aggregate. Metoda Count() returneaz un a ntreg, iar LongCount() un long (intreg pe 64 de bii, cu semn) reprezentnd numrul de elemente din secvena. t a a t Metoda Sum calculeaz suma unor valori numerice dintro secvena. Metodele a t sunt: public s t a t i c Numeric Sum( t h i s IEnumerable<Numeric> s o u r c e ) ;

10.6. LINQ TO OBJECTS public s t a t i c Numeric Sum<T>( t h i s IEnumerable<T> s o u r c e , Func<T, Numeric> s e l e c t o r ) ;

261

unde Numeric se refer la un tip de date de forma: int, int?, long, a long?, double, double?, decimal, sau decimal?. Exemplu: int [ ] numbers = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ; var query = numbers . Sum ( ) ; Console . WriteLine ( query . ToString ( ) ) ; Dac dorim s determinm care este suma salariilor anuale primite de a a a ctre ecare salariat parte, atunci putem forma prima oar grupuri bazate a n a pe numele de familie al salariatului i apoi se poate face suma salariilor de-a s lungul anilor. Presupunem c salariile sunt date astfel: a L i s t <S a l a r y > s a l a r i e s = new L i s t <S a l a r y > { new S a l a r y { IDPerson = 1 , Year = 2 0 0 4 , SalaryYear = 10000.00 } , new S a l a r y { IDPerson = 1 , Year = 2 0 0 5 , SalaryYear = 15000.00 } }; var query = from p in p e o p l e j o i n s in s a l a r i e s on p . ID e q u a l s s . IDPerson s e l e c t new {p . FirstName , p . LastName , s . SalaryYear }; Apoi: var querySum = from q in query group q by q . LastName i n t o gp s e l e c t new { LastName = gp . Key , T o t a l S a l a r y = gp . Sum( q => q . S a l a r y Y e a r ) };

262

CURS 10. LINQ (I)

Pentru operatorii Min, Max, Average, acetia trebuie apelai pentru o s t colecie de valori numerice. t Operatorul Aggregate permite denirea unei metode care s e folosit a a pentru sumarizarea datelor. Declaraia metodei este: t public s t a t i c T Aggregate<T>( t h i s IEnumerable<T> s o u r c e , Func<T, T, T> f u n c ) ; public s t a t i c U Aggregate<T, U>( t h i s IEnumerable<T> s o u r c e , U seed , Func<U, T, U> f u n c ) ; A doua metod sepcic prin intermediul valorii seed care este valoarea de a a nceput cu care se pornete agregarea. Dac nu se specic nicio valoare s a a pentru seed, atunci primul element din colecie este luat drept seed. t Pentru nmulirea valorilor cuprinse t ntrun tablou se poate proceda astfel: int [ ] numbers = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ; var query = numbers . Aggregate ( ( a , b ) => a b ) ;

Valoarea aat este 9! = 362880. Specicarea unei valori pentru seed se s a face (exemplu): int [ ] numbers = { 9 , 3 , 5 , 4 , 2 , 6 , 7 , 1 , 8 } ; var query = numbers . Aggregate ( 5 , ( a , b ) => ( ( a < b ) ? ( a b ) : a ) )

10.6.9

Partiionare t

Se folosesc pentru extragerea unei anumite pri dintr-o secvena. Opat t eratorii sunt: Take, Skip, TakeWhile, SkipWhile. Vom exemplica doar o parte din acetia: s int var var var var

[ ] numbers = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ; query = numbers . Take ( 5 ) ; // s e c v e n t a 1 , 2 , 3 , 4 , 5 query2 = numbers . Skip ( 5 ) ; // s e c v e n t a 6 , 7 , 8 , 9 query3 = numbers . TakeWhile ( ( n , i n d e x ) => n + i n d e x < 4 ) ; // 1 , 2 query4 = numbers . SkipWhile ( ( n , i n d e x ) => n + i n d e x < 4 ) ; // 3 , 4 ,

10.6.10

Concatenarea

Se face cu Concat, care preia dou secvene i produce o a treia, reprezentnd a t s a concatenarea lor, fr eliminarea duplicatelor: aa

10.6. LINQ TO OBJECTS int [ ] x = { 1 , 2 , 3 } ; int [ ] y = { 3 , 4 , 5 } ; var c o n c a t = x . Concat ( y ) ; // c o n c a t = 1 , 2 , 3 , 3 , 4 , 5

263

10.6.11

Referirea de elemente din secvene t

Se pot folosi: First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAt, ElementAtOrDefault i DefaultIfEmpty. s Pentru First i derivaii lui exist formele: s t a public s t a t i c T F i r s t <T>( t h i s IEnumerable<T> s o u r c e ) ; public s t a t i c T F i r s t <T>( t h i s IEnumerable<T> s o u r c e , Func<T, bool> p r e d i c a t e ) ; public s t a t i c T F i r s t O r D e f a u l t <T>( t h i s IEnumerable<T> s o u r c e ) ; public s t a t i c T F i r s t O r D e f a u l t <T>( t h i s IEnumerable<T> s o u r c e , Func<T, bool> p r e d i c a t e ) ; Dac nu se specic predicatul, atunci se va returna primul element din a a secvena, altfel primul element din secvena care satisface condiia exprimat t t t a prin predicat. int [ ] numbers = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ; var query = numbers . F i r s t ( ) ; query = numbers . F i r s t ( n => n % 2 == 0 ) ; Varianta cu OrDefault este util dac se crede c predicatul poate s nu e a a a a satisfcut de niciun element al secvenei. Se returneaz valoarea implicit a t a a pentru tipul de date considerat: 0 pentru tip numeric, false pentru boolean, null pentru tip referina. Dac se folosete varianta fr OrDefault, atunci t a s aa e posibil ca aplicaia s se opreasc cu eroare asta cazul care niciun t a a n n element al coleciei nu respect condiia. t a t

264

CURS 10. LINQ (I)

Curs 11 LINQ (II): Linq to SQL


Linq to SQL vine cu o soluie pentru accesarea datelor stocate t ntr-un server de baze de date SQL Server 2000, 2005 sau 2008; se permite i imples mentarea unor furnizori de Linq pentru alte servere de baze de date1 . Linq to SQL denete o asociere s ntre tabelele din baza de date i clase C#, clase s numite entiti. Suplimentar, se pune la dispoziie o clas DataContext care at t a mediaz comunicarea a ntre baza de date i clasele entitate. DataContext s menine starea obiectelor, transform dintr-un obiect t a ntro nregistrare i s invers.

11.1

Obinerea unui context de date t

Obinerea obiectului de tip DataContext se poate face adugnd un t a a obiect de tip LINQ to SQL Classes, prin Add new item la nivel de a s aplicaie, precum gura 11.1. Va rezulta adugarea unui ier cu extensia t n dbml la aplicaie; acesta conine o clas derivat din DataContext. t t a a Prin adugarea tabelelor din fereastra de Server Explorer se vor crea i a s clasele entitate aferente tabelelor (gurile 11.2 i 11.3). s Obinerea unui obiect asociate tabelei Person se face prin: t PersonDataContext pdc = new PersonDataContext ( ) ; Trebuie avut vedere faptul c clasa asociat contextului de date implen a a menteaz interfaa IDisposable, deci un asemenea obiect poate i ar trebui a t s s e disponibilizat de a ndat ce nu mai este nevoie de serviciile lui. Acest a lucru se face e prin instruciunea using, e prin bloc try-finally: t using ( PersonDataContext pdc = new PersonDataContext ( ) ) {
1

Vezi de exemplu : furnizor pentru MySql, Oracle, PostgreSQL.

265

266

CURS 11. LINQ (II): LINQ TO SQL

Figura 11.1: Adgarea unui ier DBML la aplicaie a s t

Figura 11.2: Selectarea tabelor pe baza crora se va construi ierul DBML a s

11.1. OBTINEREA UNUI CONTEXT DE DATE

267

Figura 11.3: Clasele C# rezultate dup adugarea de tabele pe suprafaa a a t ierului DBML s Table<Person> p e r s o n T a b l e = pdc . GetTable<Person > ( ) ; } // sau PersonDataContext pdc = new PersonDataContext ( ) ; try { ... } finally { pdc . D i s p o s e ( ) ; } Obinerea unui obiect asociat tabelei People se face prin: t Table<Person> p e r s o n T a b l e = pdc . GetTable<Person > ( ) ; iar obinerea de obiecte de tip Person, cte unul asociat ecrei t a a nregistrri a se face cu: var a l l P e r s o n s = from p in p e r s o n T a b l e select p; Mai mult, orice interogare efectuat cu expresii Linq sau operatori Linq poate a efectuat asupra tabelelor. De exemplu, pentru o jonciune a t ntre tabelele Person i Salary putem scrie: s var p e r s o n s W i t h S a l a r i e s = from p in pdc . P e r s o n s j o i n s in pdc . S a l a r i e s on p . i d e q u a l s s . IDPerson

268

CURS 11. LINQ (II): LINQ TO SQL s e l e c t new { p . FirstName , p . LastName , s . Year , s . S a l a r y Y e a r } ;

foreach ( var i t e r in p e r s o n s W i t h S a l a r i e s ) { Console . WriteLine ( " FirstName : {0} LastName : {1} Year : {2} S a l a r y Y e a r : {3} " , i t e r . FirstName , i t e r . LastName , i t e r . Year . ToString ( ) , i t e r . S a l a r y Y e a r . ToString ( ) ) ; } Pentru a vizualiza interogarea SQL care este formulat spre a se trimite ctre a a server, se poate scrie: pdc . Log = Console . Out ; O alt variant este folosirea obiectului de context de date conjuncie cu a a n t metoda GetCommand care primete interogarea pentru care se vrea vizualizarea s comenzii SQL: Console . WriteLine ( pdc . GetCommand ( p e r s o n s W i t h S a l a r i e s ) . CommandText ) ;

11.2
11.2.1

Adugarea, modicarea i tergerea de a s s nregistrri a tabel n a


Adugarea a

Pentru adugarea unei noi a nregistrri, se poate crea un obiect de tipul a entitate - clas furnizat automat de ctre DBML - iar apoi apelarea metodei a a a InsertOnSubmit pentru tabela care se dorecte inserarea. Adugarea la n s a setul de nregistrri se face numai cnd se apeleaz metoda SubmitChanges a a a pentru contextul de date: using ( PersonDataContext pdc = new PersonDataContext ( ) ) { Person p = new Person { FirstName = " i n s e r t e d " , LastName = " from code " , IDRole = 1 } ; pdc . P e r s o n s . InsertOnSubmit ( p ) ; pdc . SubmitChanges ( ) ; } Dac pentru obiectul p se stabilete rolul nu prin asigmare direct a valorii a s a cheii strine, ci prin crearea unui obiect nou de tip Role, atunci se face a

IN 11.2. ADAUGAREA, MODIFICAREA SI STERGEREA DE INREGISTRARI TABELA269 inserare automat a acestui nou obiect de rol i apoi inserarea a s nregistrrii a de persoan, cu referina ctre rolul creat: a t a using ( PersonDataContext pdc = new PersonDataContext ( ) ) { pdc . Log = Console . Out ; Person p = new Person { FirstName = " i n s e r t e d " , LastName = " from code " , Role = new Role { R o l e D e s c r i p t i o n = " t e s t e r " } } ; pdc . P e r s o n s . InsertOnSubmit ( p ) ; pdc . SubmitChanges ( ) ; }

11.2.2

Modicarea unei nregistrri a

Modicarea unei nregistrri se poate face prin obinerea obiectului care a t va modicat, se modic proprietile lui i nal apelul metodei SubmitChanges a at s n pentru contextul de date: using ( PersonDataContext pdc = new PersonDataContext ( ) ) { pdc . Log = Console . Out ; Person p e r s o n = pdc . P e r s o n s . Where ( p => p . i d == 1 ) . S i n g l e ( ) ; p e r s o n . LastName = " M o d i f i e d " ; pdc . SubmitChanges ( ) ; } Menionm doar c se poate face modicarea unui obiect care a fost adus t a a ntrun context deschis i s nchis anterior, dar cu reataare la context. s

11.2.3

Stergerea unei nregistrri a

Printro manevr asemntoare cu cea de mai sus, se preia a a a nregistrarea care trebuie tears i apoi pentru obiectul rezultat se face tergerea via s a s s metoda DeleteOnSubmit pentru obiectul reprezentnd tabelul din care se a terge: s using ( PersonDataContext pdc = new PersonDataContext ( ) ) { pdc . Log = Console . Out ; Person p e r s o n = pdc . P e r s o n s . Where ( p => p . i d == 9 ) . DefaultIfEmpty ( ) . S i n g l e ( ) ; i f ( p e r s o n != null ) {

270

CURS 11. LINQ (II): LINQ TO SQL pdc . P e r s o n s . DeleteOnSubmit ( p e r s o n ) ;

} pdc . SubmitChanges ( ) ; }

11.3

Opiuni de arcare a datelor t nc

S presupunem c dorim s aducem lista de rolurim iar pentru ecare rol a a a persoanele care fac parte din el: using ( PersonDataContext pdc = new PersonDataContext ( ) ) { pdc . Log = Console . Out ; var q u e r y R o l e s = from r o l e in pdc . R o l e s select role ; foreach ( var r o l e in q u e r y R o l e s ) { foreach ( var p e r s o n in r o l e . P e r s o n s ) { Console . WriteLine ( " p e r s o n : FirstName { 0 } , LastName {1} " , p e r s o n . FirstName , p e r s o n . LastName ) ; } } } Se poate observa c la ecare executare a ciclului foreach exterior se va a apela cte o instruciune SQL care aduce persoanele care fac parte din rolul a t respectiv. Dat ind abilitatea SQL-ului de a face jonciuni, ne dorim s a t a mbuntcim acest ecomportament. Se poate obine o arcare complet a aa t t nc a datelor prin utilizarea obiectului DataLoadOptions: using ( PersonDataContext pdc = new PersonDataContext ( ) ) { pdc . Log = Console . Out ; DataLoadOptions d l o = new DataLoadOptions ( ) ; d l o . LoadWith<Role >( r o l e => r o l e . P e r s o n s ) ; pdc . LoadOptions = d l o ; var q u e r y R o l e s = from r o l e in pdc . R o l e s select role ; foreach ( var r o l e in q u e r y R o l e s ) { foreach ( var p e r s o n in r o l e . P e r s o n s )

11.3. OPTIUNI DE ARCARE A DATELOR INC {

271

Console . WriteLine ( " r o l e : {0} p e r s o n : FirstName { 1 } , LastName {2} " , r o l e . R o l e D e s c r i p t i o n , p e r s o n . FirstName , p e r s o n . LastName ) ; } } }

272

CURS 11. LINQ (II): LINQ TO SQL

Curs 12 Atribute. Fire de execuie t


12.1 Atribute

O aplicaie .NET conine cod, date i metadate. Metadata este o dat t t s a despre assembly (versiune, producator, etc), tipuri de date coninute, etc t stocate mpreun cu codul compilat. a Atributele sunt folosite pentru a da o extra-informaie compilatorului t de .NET. Java folosete o combinaie de semne /** i @ pentru a include s t s informaie relativ la clase, metode, cmpuri sau parametri. Aceste comentarii t a a nu vor incluse bytecod-ul nal, doar eventuala documentaie. ns n n t Folosind atributele, aceast informaie poate stocat codul compilat i a t a n s reutilizat ulterior la runtime. a Unde ar utile aceste atribute? Un exemplu de utilizare a lor ar urmrirea bugurilor care exist a a ntrun sistem sau urmrirea stadiului proieca tului. De asemenea, anumite atribute predenite sunt utilizate pentru a specica dac un tip este sau nu serializabil, care sunt poriunile de cod a t scoase din circulaie, informaii despre versiunea assembly-ului, etc. t t

12.1.1

Generaliti at

Atributele sunt de dou feluri: intrinseci (predenite) i denite de utia s lizator. Cele intrinseci sunt integrate platforma .NET i sunt recunoscute n s de CLR. Atributele denite de utilizator sunt create funcie de dorinele n t t acestuia. Atributele se pot specica pentru: assemblyuri, clase, constructori, delegai, t enumerri, evenimente, cmpuri, interfee, metode, module, parametri, proa a t prieti, valori de retur, structuri. at Tinta unui atribut specic cui anume i se va aplica un atribut anume. a Tabelul 12.1 conine intele posibile i o scurt descriere a lor. t t s a 273

274

CURS 12. ATRIBUTE. FIRE DE EXECUTIE Tabelul 12.1: Tintele atributelor. Tint a All Assembly ClassMembers Class Constructor Delegate Enum Event Field Interface Method Module Parameter Property ReturnValue Struct Descriere Orice element Un assembly Orice membru al unei clase O clas a Un constructor Un delegat O enumerare Un eveniment Un cmp a O interfaa t O metod a Un modul Un parametru O proprietate O valoare de retur O structur a

Aplicarea atributelor se face prin specicarea numelui lor ntre paranteze drepte; mai multe atribute e se specicunul deasupra celuilalt, e a n interiorul acelorai paranteze, desprite prin virgul. Exemplu: s at a [Serializable] [Webservice] echivalent cu: [Serializable, Webservice] cazul atributelor ce se specic pentru un assembly, forma lor este: In a [assembly:AssemblyDelaySign(false)] cazul acestor din urm atribute, specicarea lor se face dup toate declaraiile in a a t general, specicarea unui atribut se face using, dar naintea oricrui cod. In a prin scrierea lui imediat naintea elementului asupra cruia se aplic: a a using System; [Serializable] class ClasaSerializabila { //definitia clasei }

12.1. ATRIBUTE

275

12.1.2

Atribute predenite
Tabelul 12.2: Atribute predenite.

a a Tabelul 12.2 prezint cteva dintre ele:

Atribut System.SerializableAttribute [Serializable] System.NonSerializedAttribute [NonSerialized] System.Web.Services.WebServiceAttribute [WebService] System.Web.Services.WebMethodAttribute [WebMethod] System.AttributeUsageAttribute [AttributeUsage] System.ObsoleteAttribute [Obsolete] System.Reection.AssemblyVersionAttribute [AssemblyVersion] System.Attribute.CLSCompliant [CLSCompliant] System.Runtime.InteropServices. DllImportAttribute [DllImport]

Descriere Permite unei clase s e serializat a a pe disc sau ntro reea t Permite unor membri s nu e a salvai pe reea sau pe disc t t Permite specicarea unui nume i a s unei descrieri pentru un serviciu Web Marcheaz o metod ca ind expus a a a ca parte a unui serviciu Web Denete parametrii de utilizare s pentru atribute Marcheaz o secvena ca ind scoas a t a din uz Specic numrul de versiune al a a unui assembly Indic dac un element de program este a a compatibil cu CLS specic locaia DLL care conine a t t implementarea pentru o metod extern a a

Intre parantezele drepte se arat cum se specic acel atribut. a a Exemplul de mai jos folosete atributul System.ObsoleteAttribute. s using System; namespace AttributeSample1 { class Class1 { [STAThread] static void Main(string[] args) { int s1 = AddTwoNumbers(2, 3); int s2 = AddNumbers(2, 3); int s3 = AddNumbers(2, 3, 4);

276 }

CURS 12. ATRIBUTE. FIRE DE EXECUTIE

[Obsolete("obsolete: use AddNumbers instead")] static int AddTwoNumbers(int a, int b) { return a + b; } static int AddNumbers(params int[] numbers) { int result = 0; foreach(int number in numbers) result += number; return result; } } } La compilarea codului se genereaz un avertisment care semnalizeaz faptul a a c se utilizeaz o metod care este scoas din uz. Mesajul specicat ca a a a a parametru al atributului este aat ca mesaj al avertismentului. Suplimentar, s pentru atributul Obsolete se poate specica dac respectivul avertisment este a sau nu interpretat ca o eroare, adugnd un parametru de tip bool: false ca la a a compilare s se genereze avertisment, true pentru ca utilizarea s e tratat a a a ca o eroare. Exemplul de mai jos exemplic serializarea i deserializarea unui obiect: a s using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; [Serializable] class Point2D { public int X; public int Y; } class MyMainClass { public static void Main() { Point2D My2DPoint = new Point2D(); My2DPoint.X = 100;

12.1. ATRIBUTE

277

My2DPoint.Y = 200; Stream WriteStream = File.Create("Point2D.bin"); BinaryFormatter BinaryWrite = new BinaryFormatter(); BinaryWrite.Serialize(WriteStream, My2DPoint); WriteStream.Close(); Point2D ANewPoint = new Point2D(); Console.WriteLine("New Point Before Deserialization: ({0}, {1})", ANewPoint.X, ANewPoint.Y); Stream ReadStream = File.OpenRead("Point2D.bin"); BinaryFormatter BinaryRead = new BinaryFormatter(); ANewPoint = (Point2D)BinaryRead.Deserialize(ReadStream); ReadStream.Close(); Console.WriteLine("New Point After Deserialization: ({0}, {1})", ANewPoint.X, ANewPoint.Y); } }

12.1.3

Exemplicarea altor atribute predenite

Atributul Conditional Acest atribut se ataeaz la o metod pentru care se dorete ca atunci s a a s cnd compilatorul o alnete la un apel, dac un anumit simbol nu e denit a nt s a atunci nu va chemat. Este folosit pentru a omite anumite apeluri de a a metode ( de exemplu cele folosite la etapa de debugging). Exemplu: using System; using System.Diagnostics; namespace CondAttrib { class Thing { private string name; public Thing(string name) { this.name = name; SomeDebugFunc(); SomeFunc(); } public void SomeFunc()

278

CURS 12. ATRIBUTE. FIRE DE EXECUTIE { Console.WriteLine("SomeFunc"); } [Conditional("DEBUG")] public void SomeDebugFunc() { Console.WriteLine("SomeDebugFunc"); }

} public class Class1 { [STAThread] static void Main(string[] args) { Thing t = new Thing("T1"); } } } Denirea unui anumit simbol (de exemplu DEBUG) se poate face dou n a moduri: prin folosirea unei directive de preprocesor de tipul #dene: #define DEBUG prin folosirea parametrilor din linia de comanda sau a posibilitilor at mediului integrat de dezvoltare. De exemplu, pentru a se deni un anumit simbolul din Visual Studio se procedeaz astfel: clic dreapta a Solution Explorer pe numele proiectului, selectare Properties apoi n Conguration Properties. Pe linia Conditional Compilation Constants se adaug simbolul dorit. Acest lucru va avea ca efect folosirea opiunii a t dene a compilatorului. Dac la compilare simbolul nu este denit atunci compilatorul va ignora a apelurile de metode calicate cu atributul Conditional. Acest lucru se poate verica att urmrind execuia programului, ct i din inspecia codului IL a a t a s t rezultat la compilare. Atributul CLSCompliant Acest atribut se aplic pe assemblyuri. Dac un assembly este marcat a a ca ind CLSCompliant, orice tip expus public assembly care nu este comn patibil CLS trebuie s e marcat cu CLSCompliant(false). De exmplu, CLS a

12.1. ATRIBUTE

279

prevede faptul c tipurile de date a ntregi ar trebui s e cu semn. O clas a a poate s conin membri (cmpuri, metode) de tip unsigned, dar dac rea t a a a spectiva clas este declarat ca ind CLSCompliant atunci acestea ar trebui a a declarate ca ind ne-vizibile din afara assemblyului. Dac ele sunt expuse a n afara assemblyului, unele limbaje .NET s-ar putea s nu le poat folosi efea a tiv. Pentru a ajuta dezvoltatorii .NET s evite asemenea situaii, platforma a t pune la dispoziie atributul CLSCompliant cu care se controleaz rspunsul t a a compilatorului la expunerea entitilor ne-compatibile cu CLS; astfel, se va at genera o eroare sau se vor ignora asemenea cazuri. Exemplu: [assembly:CLSCompliant(true)] namespace DemoCLS { public class ComplianceTest { //Tipul uint nu ste compatibil CLS //deaorece acest camp este privat, regulile CLS nu se aplica private uint a = 4; //deoarece acest camp uint este public, avem //incompatibilitate CLS public uint B = 5; //Acesta este modul corect de expunere a unui uint: //tipul long este compatibil CLS public long A { get { return a; } } } } Dac se compileaz assembly-ul de mai sus atunci apare o eroare: a a Type of DemoCLS.ComplianceTest.B is not CLS-compliant Pentru ca aceast eroare s nu mai apar putem s declarm B ca ind a a a a a invizibil din exteriorul assembly-ului sau s adugm inaintea lui B atributul: a a a [CLSCompliant(false)] Meritul acestui atribut este c anuna programatorul despre eventualele neconcordane a t t cu Common Language Specications.

280

CURS 12. ATRIBUTE. FIRE DE EXECUTIE

12.1.4

Atribute denite de utilizator

general, atributele predenite acoper marea majoritate a situaiilor In a t care cer utilizarea de atribute. Eventualizatea ca utilizatorul s doreasc a a crearea propriilor sale atribute este prevzut de ctre platforma .NET, a a a dnduse posibilitatea denirii lor. a Exist cteva situaii care e benec denirea de noi atribute. Exema a t n a plul cel mai des alnit este acela care pentru a se menine informaii nt n t t despre un cod la care lucreaz mai multe echipe, se denete un atribut care a s s serveasc la pasarea de informaie relativ la poriuni din cod. Un alt a a t t exemplu este utilizarea unui sistem de urmrire a stadiului de dezvoltare a a codului, care ar folosi informaia stocat atribute. t a n Un atribut utilizator este o clas denit ca pn acum. Se cere a a a a a derivat din System.Attribute e direct, e indirect. Sunt civa pai care a at s trebuie parcuri pentru realizara unui atribut: specicarea intei, derivarea s t adecvat a clasei atribut, denumirea clasei conformitate cu anumite reguli a n (recomandat, dar nu obligatoriu) i denirea clasei. s Pasul 1. Declararea intei t Primul pas crearea unui atribut utilizator este specicarea domeniului n su de aplicabilitate, adic a elementelor crora li se poate ataa. Tintele sunt a a a s cele din tabelul 12.1, care sunt de fapt valori din enumerarea AttributeTargets. Specicarea intei se face prin intermediul unui (meta)atribut AttributeUsage. t Pe lng inta propriuzis, se mai pot specica valorile proprietilor Inhera at a at ited i AllowMultiple. s Proprietatea Inherited este de tip boolean i precizeaz dac atributul s a a poate motenit de ctre clasele derivate din cele creia i se aplic. Valoarea s a a a implicit este true. a Proprietatea AllowMultiple este de asemenea de tip boolean i specic s a dac se pot utiliza mai multe instane ale atributului pe un acelai element. a t s Valoarea implicit este false. a Vom deni mai jos inta atributului pe care vom construi ca ind t l assemblyul, clasa, metoda, cu posibilitate de repetare a sa: [AttributeUsage(AttributeTargets.Assembly|AttributeTargets.Class | AttributeTargets.Method, AllowMultiple=true)] Pasul 2. Declararea unei clase atribut Pentru declarearea unei clase atribut, urmtoarele reguli trebuie s e a a avute vedere: n

12.1. ATRIBUTE

281

(obligatoriu) O clas atribut trebuie s deriveze direct sau indirect Sysa a tem.Attribute (obligatoriu) O clas atribut trebuie s e declarat ca ind public a a a a (recomandat) Un atribut ar trebui s aib suxul Attribute. a a Vom declara clasa atribut CodeTrackerAttribute. Acest atribut s-ar putea folosi cazul care s-ar dori urmrirea dezvoltrii codului cadrul unui n n a a n proiect de dimensiuni foarte mari, la care particip mai multe echipe. Acest a atribut utilizator va inclus codul compilat i distribuit altor echipe (care n s nu vor avea acces la cod). O alternativ ar folosirea documentatiei XML a t generate dup comentariile din cod. a [AttributeUsage(AttributeTargets.Assembly|AttributeTargets.Class | AttributeTargets.Method, AllowMultiple=true)] public class CodeTrackerAttribute : System.Attribute { //cod } Pasul 3. Declararea constructorilor i a proprietilor s at Acest pas const denirea efectiv membrilor clasei. Vom considera a n a ctributul pe care crem va purta trei informaii: numele programatorua l a t lui care a acionat asupra respectivei uniti de cod, faza care se a, t at n a opional note. Primele dou atribute sunt mandatorii i vor preluate prin t a s constructor, al treilea poate setat prin intermediul unei proprieti. at using System; [AttributeUsage(AttributeTargets.Assembly|AttributeTargets.Class | AttributeTargets.Method, AllowMultiple=true)] public class CodeTrackerAttribute : System.Attribute { private string name; private string phase; private string notes; public CodeTrackerAttribute(string name, string phase) { this.name = name; this.phase = phase; }

282

CURS 12. ATRIBUTE. FIRE DE EXECUTIE

public virtual string Name { get{return name;} } public virtual string Phase { get{return phase;} } public virtual string Notes { get{return notes;} set{notes=value;} } } Pasul 4. Utilizarea atributului utilizator Atributele denite de utilizator sunt folosite acelai mod ca i cele n s s implicite. Codul de mai jso exemplic acest lucru: a [CodeTracker("Lucian Sasu", "implementing specification", Notes = "First attempt")] class AttribTest { public AttribTest() { Console.WriteLine("AttribTest instance"); } [CodeTracker("Lucian Sasu", "April 1st 2005")] public void SayHello(String message) { Console.WriteLine(message); } } O inspeie a codului IL rezultat arat c aceste atribute s-au salvat t a a n codul compilat. Obinerea acestor atribute i a valorilor lor se poate face t s foarte uor prin reectare. s

12.2. FIRE DE EXECUTIE

283

12.2

Fire de execuie t

Firele de execuie1 sunt responsabile cu multitaskingul interiorul unei t n aplicaii. Clasele i interfeele responsabile pentru crearea aplicaiilor mult s t t tir se gsesc spaiul de nume System.Threading. Vom discuta cele ce a n t n urmeaz despre managementul threadurilor i despre sincronizare. a s De cele mai multe ori crearea de threaduri este necesar pentru a da a utilizatorului impresia c programul execut mai multe aciuni simultan, a a t n cadrul unei aplicaii. Pentru ca o interfaa utilizator s poat folosit fr t t a a a aa a se atepta s ncheierea unei anumite secvene de instruciuni, e nevoie de t t mai multe re de execuie (unele pentru procesarea efectiv a informaiei, t a t altele care s rspund aciunilor utilizatorului). Pentru probleme de calcul a a a t numeric, exprimarea procesului de calcul se poate face foarte natural sub form de re de execie. De asemenea, strategia de rezolvare divide et a t impera se preteaz natural la o lucrul cu re de execuie. sfrit, se poate a t In a s benecia de sisteme cu procesoare hyperthreading, multiprocesor, multicore sau combinaii ale acestora. t

12.3
12.3.1

Managementul threadurilor
Pornirea threadurilor

Cea mai simpl metod de a crea un r de execie este de a crea o instana a a t t a clasei Thread, al crei constructor preia un singur argument de tip delea gat. BCL este denit tipul delegat ThreadStart, care este folosit ca proIn totip pentru orice metod care se vrea a lansat a a ntrun r de execuie. t Declaraia de ThreadStart este: t public delegate void ThreadStart(); Crearea unui r de execuie se face pe baza unei metode care returneaz t a void i nu preia nici un parametru: s ThreadStart ts = new ThreadStart(MyFunc); Thread myThread = new Thread( ts ); Un exemplu simplu este: using System; using System.Threading;
1

Engl: threads; vom folosi alternativ termenii thread i r de execuie s t

284

CURS 12. ATRIBUTE. FIRE DE EXECUTIE

class SimpleThreadApp { public static void WorkerThreadMethod() { Console.WriteLine("[WorkerThreadMethod] Worker " + "thread started"); } public static void Main() { ThreadStart worker = new ThreadStart(WorkerThreadMethod); Console.WriteLine("[Main] Creating worker thread"); Thread t = new Thread(worker); t.Start(); Console.WriteLine( "[Main] Have requested the start of worker thread"); Console.ReadLine(); } } Pn la linia t.Start() exist un singur thread: cel dat de pornirea metodei a a a Main. Dup t.Start() sunt 2 threaduri: cel anterior i t. Primul esaj din a s Main va tiprit a naintea celui din threadul t; funcie de cum anume n t se planic threadurile pentru execuie, mesajul de dup t.Start() poate s a t a a apar a nainte sau dup mesajul tiprit de metoda WorkerThreadMethod(). a a De remarcat c simpla creere a rului de execuie nu determin i pornirea a t as lui: acest lucru se ampl dupa apelul metodei Start denit clasa nt a a n Thread. Exemplul urmtor pornete dou re de execuie. Funciile care conin a s a t t t codul ce se va executa cte un thread sunt Increment() i Decrement(): n a s using System; using System.Threading; class Tester { static void Main( ) { Tester t = new Tester( ); t.DoTest( ); } public void DoTest( )

12.3. MANAGEMENTUL THREADURILOR { // creeaza un thread pentru Thread t1 = new Thread( new // creeaza un thread pentru Thread t2 = new Thread( new // porneste threadurile t1.Start( ); t2.Start( ); } public void Incrementer( ) { for (int i = 1;i<=1000;i++) { Console.WriteLine( "Incrementer: {0}", i); } } public void Decrementer( ) { for (int i = 1000;i>0;i--) { Console.WriteLine("Decrementer: {0}", i); } } } Incrementer ThreadStart(Incrementer) ); Decrementer ThreadStart(Decrementer) );

285

La ieire se vor mixa mesajele tiprite de primul thread cu cele tiprite s a a de cel deal doilea thread: ... Incrementer: Incrementer: Incrementer: Incrementer: Incrementer: Decrementer: Decrementer: Decrementer: Decrementer: ...

102 103 104 105 106 1000 999 998 997

286

CURS 12. ATRIBUTE. FIRE DE EXECUTIE

Perioada de timp alocat ecrui thread este determinat de ctre plania a a a 2 catorul de re de execuie i depinde de factori precum viteza procesorului, t s gradul lui de ocupare, etc. O alt modalitate de obinere a unei referine la un thread este prin apelul a t t proprietii statice Thread.CurrentThread pentru rul de execuie curent. at t exemplul de mai jos se obine obiectul Thread asociat rului de execuie In t t curent, i se seteaz numele (proprietate read/write de tip String) i se aeaz a s s a pe ecran: Thread current = Thread.CurrentThread; current.Name = "My name"; Console.WriteLine("nume={0}", current.Name );

12.3.2

Metoda Join()

Exist situaii care, a t n naintea unei instruciuni trebuie s se asigure fapt a tul c un alt r de execuie t s-a terminat; acest lucru se va face folosind apelul a t t.Join() naintea instruciunii cauz; acest moment rul de execuie care t n a n t a apelat t.Join() intr ateptare. a n s Dac de exemplu metoda Main se lanseaz o colecie de threaduri a n a t (stocat myThreads), atunci pentru a se continua execuia numai dup ce a n t a toate rele din colecie sau terminat, se procedeaz astfel: t a foreach( Thread myThread in myThreads ) { myThread.Join(); } Console.WriteLine(All my threads are done); Mesajul nal se va tipri doar cnd toate rele de execuie sau terminat. a a t

12.3.3

Suspendarea relor de execuie t

Se poate ca anumite cazuri s se doreasc suspendarea unui r de n a a execuie pentru o scurt perioad de timp. Clasa Thread ofer o metod t a a a a static supra arcat Sleep(), care poate prelua un parametru de tip int a nc a reprezentnd milisecundele de adormire, iar a doua variant preia un ara a gument de tip TimeSpan, care reprezint cea mai mic unitate de timp a a care poate specicat, egal cu 100 nanosecunde. Pentru a cere rului a a de execuie curent s se suspende pentru o secund, se execut cadrul t a a a n acestuia:
2

Engl: thread scheduler

12.3. MANAGEMENTUL THREADURILOR Thread.Sleep(1000);

287

acest fel se semnaleaz planicatorului de re de execuie c poate In a t a lansa un alt thread. Dac exemplul de mai sus se adaug un apel Thread.Sleep(1) dup a n a a ecare WriteLine(), atunci ieirea se schimb dramatic: s a Iesire (extras) Incrementer: 0 Incrementer: 1 Decrementer: 1000 Incrementer: 2 Decrementer: 999 Incrementer: 3 Decrementer: 998 Incrementer: 4 Decrementer: 997 Incrementer: 5 Decrementer: 996 Incrementer: 6 Decrementer: 995

12.3.4

Omorrea threadurilor a

De obicei, un r de execuie moare dup ce se termin de executat. Se t a a poate totui cere unui r de execuie s si s t a nceteze execuia folosind metoda t Abort(). Acest lucru va duce la aruncarea unei excepii interiorul rului t n de execuie cruia i se cere suspendarea: ThreadAbortedException, pe care t a rul respectiv o poate prinde i procesa, permiandui eliberarea de resurse s t alocate. Ca atare, se recomand ca o metod care se va lansa ca r de a a execuie s se compun dintrun bloc try care conine instruciunile utile, t a a t t dup care un bloc catch i/sau nally care vor efectua eliberarea de resurse. a s Exemplu: using System; using System.Threading; class Tester { static void Main( ) { Tester t = new Tester( ); t.DoTest( );

288

CURS 12. ATRIBUTE. FIRE DE EXECUTIE

} public void DoTest( ) { // creeaza un vector de threaduri Thread[] myThreads = { new Thread( new ThreadStart(Decrementer) ), new Thread( new ThreadStart(Incrementer) ), new Thread( new ThreadStart(Incrementer) ) }; // porneste fiecare thread int ctr = 1; foreach (Thread myThread in myThreads) { myThread.IsBackground=true; myThread.Start( ); myThread.Name = "Thread" + ctr.ToString( ); ctr++; Console.WriteLine("Started thread {0}", myThread.Name); Thread.Sleep(50); } // dupa ce firele se pornesc, // comanda oprirea threadului 1 myThreads[1].Abort( ); // asteapta ca fiecare thread sa se termine foreach (Thread myThread in myThreads) { myThread.Join( ); } Console.WriteLine("All my threads are done."); } // numara descrescator de la 1000 public void Decrementer( ) { try { for (int i = 1000;i>0;i--) { Console.WriteLine(Thread {0}. Decrementer: {1}, Thread.CurrentThread.Name, i); Thread.Sleep(1);

12.3. MANAGEMENTUL THREADURILOR } } catch (ThreadAbortedException) { Console.WriteLine( Thread {0} interrupted! Cleaning up..., Thread.CurrentThread.Name); } finally { Console.WriteLine(Thread {0} Exiting. , Thread.CurrentThread.Name); }

289

} // numara cresacator pana la 1000 public void Incrementer( ) { try { for (int i =1;i<=1000;i++) { Console.WriteLine(Thread {0}. Incrementer: {1}, Thread.CurrentThread.Name, i); Thread.Sleep(1); } } catch (ThreadAbortedException) { Console.WriteLine( Thread {0} interrupted! Cleaning up..., Thread.CurrentThread.Name); } finally { Console.WriteLine( Thread {0} Exiting. , Thread.CurrentThread.Name); } } } Ieire: s Started thread Thread1

290

CURS 12. ATRIBUTE. FIRE DE EXECUTIE

Thread Thread1. Decrementer: 1000 Thread Thread1. Decrementer: 999 Thread Thread1. Decrementer: 998 Started thread Thread2 Thread Thread1. Decrementer: 997 Thread Thread2. Incrementer: 0 Thread Thread1. Decrementer: 996 Thread Thread2. Incrementer: 1 Thread Thread1. Decrementer: 995 Thread Thread2. Incrementer: 2 Thread Thread1. Decrementer: 994 Thread Thread2. Incrementer: 3 Started thread Thread3 Thread Thread1. Decrementer: 993 Thread Thread2. Incrementer: 4 Thread Thread2. Incrementer: 5 Thread Thread1. Decrementer: 992 Thread Thread2. Incrementer: 6 Thread Thread1. Decrementer: 991 Thread Thread3. Incrementer: 0 Thread Thread2. Incrementer: 7 Thread Thread1. Decrementer: 990 Thread Thread3. Incrementer: 1 Thread Thread2 interrupted! Cleaning up... Thread Thread2 Exiting. Thread Thread1. Decrementer: 989 Thread Thread3. Incrementer: 2 Thread Thread1. Decrementer: 988 Thread Thread3. Incrementer: 3 Thread Thread1. Decrementer: 987 Thread Thread3. Incrementer: 4 Thread Thread1. Decrementer: 986 Thread Thread3. Incrementer: 5 // ... Thread Thread1. Decrementer: 1 Thread Thread3. Incrementer: 997

12.3.5

Sugerarea prioritilor relor de execuie at t

Un r de execuie se lanseaz implicit cu prioritatea ThreadPriorityLevel.Normal. t a Dar schedulerul poate inuenat activitatea sa prin setarea de diferite t n

12.3. MANAGEMENTUL THREADURILOR

291

nivele de prioritate pentru re; aceste nivele fac parte din enumerarea ThreadPriorityLevel : ThreadPriorityLevel.TimeCritical, ThreadPriorityLevel.Highest, ThreadPriorityLevel.AboveNormal, ThreadPriorityLevel.Normal, ThreadPriorityLevel.BelowNormal, ThreadPriorityLevel.Lowest, ThreadPriorityLevel.Idle. Prioritatea este descresctoare lista prezentat. Pe baza prioritii proa n a at cesului care conine rele de execuie i a prioritii relor, se calculeaz un t t s at a nivel de prioritate (de ex. pe maini Intel valori s ntre 0 i 31) care determin s a prioritatea ansamblul sistemului de operare a rului respectiv. n Setarea unei anumite prioriti se face folosind proprietatea Priority: at myThread.Priority = ThreadPriorityLevel.Highest;

12.3.6

Fire fundal i re prim-plan n s n

Relativ la proprietatea boolean IsBackground, trebuie fcut precizarea a a a c un r de execuie poate s se execute fundal (background) sau prim a t a n n plan (foreground). Diferena dintre cele dou posibiliti o constituie faptul t a at c dac un proces are mcar un r de execuie foreground, CLR va menine a a a t n t aplicaia execuie. O dat ce toate rele de execuie de tip foreground t n t a t se termin, CLR va executa Abort() pentru ecare r de execuie de tip a t background (dac mai exist aa ceva) i termin procesul. a a s s a Exemplu: using System; using System.Threading; class Test { static void Main() { BackgroundTest shortTest = new BackgroundTest(10); Thread foregroundThread = new Thread(new ThreadStart(shortTest.RunLoop)); foregroundThread.Name = "ForegroundThread"; BackgroundTest longTest = new BackgroundTest(50); Thread backgroundThread = new Thread(new ThreadStart(longTest.RunLoop)); backgroundThread.Name = "BackgroundThread"; backgroundThread.IsBackground = true; foregroundThread.Start();

292

CURS 12. ATRIBUTE. FIRE DE EXECUTIE backgroundThread.Start(); }

} class BackgroundTest { int maxIterations; public BackgroundTest(int maxIterations) { this.maxIterations = maxIterations; } public void RunLoop() { String threadName = Thread.CurrentThread.Name; for(int i = 0; i < maxIterations; i++) { Console.WriteLine("{0} count: {1}", threadName, i.ToString()); Thread.Sleep(250); } Console.WriteLine("{0} finished counting.", threadName); } } Firul din foreground va menine procesul execuie pn cnd se termin t n t a a a a ciclul su while. Cnd acesta se termin, procesul este oprit, chiar dac a a a a ciclul while din rul de execuie din background nu i-a terminat execuia. t s t

12.4

Sincronizarea

Sincronizarea se ocup cu controlarea accesului la resurse partajate de mai a multe re de execuie. De exemplu, se poate cere ca utilizarea unei resurse t anume s se fac la un moment dat de ctre un singur r de execuie. Vom a a a t discuta aici trei mecanisme de sincronizare: clasa Interlock, instruciunea C# t lock i clasa Monitor. Exemplele se vor baza pe acces la o resurs partajat, s a a ca mai jos: public void Incrementer( )

12.4. SINCRONIZAREA {

293

try { while (counter < 1000) { int temp = counter; temp++; // increment // simuleza o sarcina oarecare in acest thread Thread.Sleep(1); // atribuie valoarea incrementata // variabilei counter // si afiseaza rezultatul counter = temp; Console.WriteLine( Thread {0}. Incrementer: {1}, Thread.CurrentThread.Name, counter); } } catch (ThreadAbortedException) { Console.WriteLine( Thread {0} interrupted! Cleaning up..., Thread.CurrentThread.Name); } finally { Console.WriteLine( Thread {0} Exiting. , Thread.CurrentThread.Name); } } Cmpul counter se iniializeaz cu 0. S presupunem c pornim dou a t a a a a re de execuie pe baza metodei Incrementer() de mai sus. Este posibil t s se ample urmtoarele: primul thread va citi valoarea lui counter (0) a nt a i o va atribui unei variabile temporare, pe care o va incrementa apoi. Al s doilea r se activeaz i el, va citi valoarea (nemodicat) lui counter i va as a s atribui aceast valoare unei variabile temporare. Primul r de execuie si a t termin munca, apoi asigneaz valoarea variabilei temporare (1) lui counter a a i o aeaz. Al doilea thread face exact acelai lucru. Se tiprete astfel 1, s s a s a s 1. La urmtoarele iteraii se va aa 2, 2, 3, 3, etc, locul lui 1, 2, 3, 4. a t s n Exemplu: using System; using System.Threading;

294

CURS 12. ATRIBUTE. FIRE DE EXECUTIE

class Tester { private int counter = 0; static void Main( ) { Tester t = new Tester( ); t.DoTest( ); } public void DoTest( ) { Thread t1 = new Thread( new ThreadStart(this.Incrementer) ); t1.IsBackground=true; t1.Name = ThreadOne; t1.Start( ); Console.WriteLine(Started thread {0}, t1.Name); Thread t2 = new Thread( new ThreadStart(this.Incrementer) ); t2.IsBackground=true; t2.Name = ThreadTwo; t2.Start( ); Console.WriteLine(Started thread {0}, t2.Name); t1.Join( ); t2.Join( ); } // numara crescator pana la 1000 public void Incrementer( ) { //la fel ca la inceputul sectiunii } } Ieire: s Started thread ThreadOne Started thread ThreadTwo Thread ThreadOne. Incrementer: Thread ThreadOne. Incrementer: Thread ThreadOne. Incrementer: Thread ThreadTwo. Incrementer: Thread ThreadTwo. Incrementer: Thread ThreadOne. Incrementer: Thread ThreadTwo. Incrementer: Thread ThreadOne. Incrementer:

1 2 3 3 4 4 5 5

12.4. SINCRONIZAREA Thread ThreadTwo. Incrementer: 6 Thread ThreadOne. Incrementer: 6

295

Trebuie deci s se realizeze o excludere reciproc a threadurilor pentru aca a cesul la counter.

12.4.1

Clasa Interlocked

Incrementarea i decrementarea unei valori este o situaie att de des s t a alnit, at C# pune la dispoziie o clas special Interlocked pentru o nt a nc t a a rezolvare rapid. Clasa include dou metode statice, Increment() i Decrea a s ment(), care incrementeaz sau decrementeaz o valoare, a sub un control a a ns sincronizat. Putem modica metoda Incrementer() de mai sus astfel: public void Incrementer( ) { try { while (counter < 1000) { Interlocked.Increment(ref counter); // simuleaza o sarcina in aceasta metoda Thread.Sleep(1); // asigura valoarea decrementata // si afiseaza rezultatul Console.WriteLine( "Thread {0}. Incrementer: {1}", Thread.CurrentThread.Name, counter); } } //blocurile catch si finally raman neschimbate } Ieirea este cea dorit: s a Started thread ThreadOne Started thread ThreadTwo Thread ThreadOne. Incrementer: 1 Thread ThreadTwo. Incrementer: 2 Thread ThreadOne. Incrementer: 3

296 Thread Thread Thread Thread Thread ThreadTwo. ThreadOne. ThreadTwo. ThreadOne. ThreadTwo.

CURS 12. ATRIBUTE. FIRE DE EXECUTIE Incrementer: Incrementer: Incrementer: Incrementer: Incrementer: 4 5 6 7 8

12.4.2

Instruciunea lock t

Exist situaii cnd vrem s blocm alte variabile dect cele de tip int. a t a a a a Un lock marcheaz o seciune critic a codului, producnd astfel sincronizare a t a a pentru un obiect. La utilizare, se specic un obiect pentru care se stabilete a s un lock, dup care o instruiune sau un grup de instruciuni. Lockul este a t t aturat la sfritul instruciunii/blocului de instruiuni. Sintaxa este: nl as t t lock(expresie) { instructiuni } Exemplu: metoda Incrementer() se va modica dup cum urmeaz: a a public void Incrementer( ) { try { while (counter < 1000) { lock (this) { int temp = counter; temp++; Thread.Sleep(1); counter = temp; } // atribuie valoarea decrementata // si afiseaza rezultatul Console.WriteLine( "Thread {0}. Incrementer: {1}", Thread.CurrentThread.Name, counter); } } //blocurile catch si finally raman neschimbate } Rezultatele sunt aate exact ca la seciunea 12.4.1. s t

12.4. SINCRONIZAREA

297

12.4.3

Clasa Monitor

Clasa Monitor conine metode pentru a controla sincronizarea relor de t execuie, permiand declararea unei zone critice care la un moment dat t t n doar un thread trebuie s opereze. a Atunci cnd se dorete s se a s a nceap sincronizarea, se va apela metoda a Enter, dnd obiectul pentru care se va face blocarea: a Monitor.Enter( obiect ); Dac monitorul este nedisponibil, atunci a nseamn c un alt thread este a a ntro regiune critic a obiectului respectiv. Se mai poate folosi de asemenea a metoda Wait(), care elibereaz monitorul, dar blocheaz threadul, informnd a a a CLR c atunci cnd monitorul devine din nou liber, threadul curent ar vrea a a s si continue execuia (este adugat a t a ntro coad de ateptare format din a s a re de execuie blocate pe obiect). Terminarea zonei critice se face folosind t metoda Exit() a clasei Monitor. Metoda Pulse() semnaleaz c a avut loc a a o schimbare de stare, urma creia este posibil ca un alt r de execuie n a t care ateapt va putea s e continuat (ordinea de selectare a relor ce vor s a a executate ind ordinea introducerii coada de ateptare). n s Inrudit este a metoda PulseAll care anuna toate obiectele blocate pe un anumit obiect de t schimbarea de stare. S presupunem c avem o clas MessageBoard unde re individuale pot a a a citi i scrie mesaje. Vom sincroniza accesul la aceast clas astfel at doar s a a nc un thread s poat aciona la un moment dat. Clasa MessageBoard va avea a a t o metod Reader() i una Writer(). a s Metoda Reader() determin dac stringul message conine vreun mesaj a a t valabil, iar metoda Writer() va scrie acest string. Dac nu sunt mesaje n a timpul citirii, threadul Reader() va intra stare de ateptare folosind n n s Wait() pn cnd metoda Writer() scrie un mesaj i transmite un mesaj via a a a s Pulse() pentru a trezi alte threaduri. using System; using System.Threading; class MessageBoard { private String messages = no messages ; public void Reader() { try { Monitor.Enter(this);

298

CURS 12. ATRIBUTE. FIRE DE EXECUTIE //daca nu e nici un mesaj atunci asteapta if (messages == no messages) { Console.WriteLine({0} {1}, Thread.CurrentThread.Name, messages); Console.WriteLine({0} waiting..., Thread.CurrentThread.Name); Monitor.Wait(this); } //inseamna ca mesajul s-a schimbat Console.WriteLine({0} {1}, Thread.CurrentThread.Name, messages); } finally { Monitor.Exit(this); }

} public void Writer() { try { Monitor.Enter(this); messages = Greetings Caroline and Marianne!; Console.WriteLine({0} Done writing message..., Thread.CurrentThread.Name); //semnaleaza threadului de asteptare ca s-a schimbat mesajul Monitor.Pulse(this); } finally { Monitor.Exit(this); } } public static void Main() { MessageBoard myMessageBoard = new MessageBoard(); Thread reader = new Thread(new ThreadStart(myMessageBoard.Reader)); reader.Name = ReaderThread:; Thread writer = new Thread( new

12.4. SINCRONIZAREA ThreadStart(myMessageBoard.Writer)); writer.Name = WriterThread:; reader.Start(); writer.Start(); } } Ieirea este: s ReadrThread: no messages ReaderThread: waiting... WriterThread: Done writing message... ReaderThread: Greetings Caroline and Marianne!

299

Dup cum se vede mai sus, threadul reader este pornit primul, el blocheaz a a clasa obeictul de tip MessageBoard, ceea ce nseamn c are acces exclusiv la a a variabila message. Dar deoarece message nu conine nici un mesaj, thread-ul t reader elibereaz obiectul pe care la blocat mai a nainte prin apelul Wait(). Threadul writer va putea acum s blocheze obiectul pentru a scrie mesajul. a Apoi el cheam metoda Pulse() pentru a semnala rului reader c a aprut a a a o schimbare de stare a obiectului indicat wde this.

300

CURS 12. ATRIBUTE. FIRE DE EXECUTIE

Curs 13 Nouti C# 4.0 at n


13.1 Parallel Linq

TODO: de inclus TPL 1, TPL 2, TPL 3. Parallel Linq (PLINQ) permite executarea de interogri mod paralel, a n pentru stituaiile care exist un sistem de tip multioprocesor, multicore t n a sau cu suport de hyperthreading. Pentru a transforma o interogare clasic a ntruna paralelizat, trebuie adugat specicarea AsParallel la sursa de a a a date peste care se execut interogarea. Mai exact, plecnd de la interogarea a a Linq: var result = from x in source where [conditie] select [ceva] se obine interogarea PLINQ: t var result = from x in source.AsParallel() where [conditie] select [ceva] Echivalent, se poate face transformarea folosind metode Linq: //varianta var result //varianta var result neparalela = source.Where(x => [conditie]).Select(x => [ceva]); cu paralelism = source.AsParallel().Where(x => [conditie]).Select(x => [ceva]); 301

302

IN CURS 13. NOUTATI C# 4.0

Prin adugarea acestei metode AsParallel() se folosesc metode din clasa a ParallelEnumerable, timp ce Linq-ul clasic se folosesc metode din n n clasa Enumerable. Prezentm urmtorul exemplu preluat din [9]: a a using using using using using using System; System.Collections; System.Collections.Generic; System.Linq; System.Text; System.Diagnostics;

namespace TestPLINQ { class Program { static void Main() { Stopwatch sw = new Stopwatch(); sw.Start(); DoIt(); sw.Stop(); Console.WriteLine("Elapsed = " + sw.ElapsedMilliseconds.ToString()); } private static bool isPrime(int p) { int upperBound = (int)Math.Sqrt(p); for (int i = 2; i <= upperBound; i++) { if (p % i == 0) return false; } return true; } static void DoIt() { IEnumerable<int> arr = Enumerable.Range(2, 10000000); var q = from n in arr

13.2. PARAMETRI CU NUME SI PARAMETRI OPTIONALI where isPrime(n) select n.ToString(); IList list = q.ToList(); Console.WriteLine(list.Count.ToString()); } } }

303

Pentru un sistem oarecare, timpul mediu de rulare este de aproximativ 17 secunde; rescriind interogarea pentru obinerea variabilei q astfel: t var q = from n in arr.AsParallel() where isPrime(n) select n.ToString(); timpul mediu obinut este de aproximativ 10 secunde. t Se pot congura aspecte precum gradul de paralelism, controlul ordinii, opiuni de utilizare de buere, dac anumite poriuni s se ruleze secvenial t a t a t etc prin folosirea metodelor de extensie precum AsOrdered, AsUnordered sau prin folosirea enumerrii ParallelQueryMergeOptions. a

13.2

Parametri cu nume i parametri opionali s t

Parametrii opionali permit precizarea unor valori implicite pentru parametrii t unor metode; dac acetia nu sunt precizai la apel, atunci valorile trimise a s t metodei sunt cele declarate implicit. Exemplu: class Program { static void Main(string[] args) { MyMethod(3); MyMethod(3, 4); } private static void MyMethod(int p, int q = 100) { Console.WriteLine("p= {0}, q={1}", p, q); } }

304

IN CURS 13. NOUTATI C# 4.0

Dei avem o singur metod, aceasta suport cele dou apeluri. Se poate s a a a a chiar s avem mai muli de un parametru cu valoare implicit: a t a private static void MyMethod(int p=1, int q = 100) { Console.WriteLine("p= {0}, q={1}", p, q); } cazul care avem mcar un astfel de parametru cu valoarea implicit, In n a a acesta trebuie s e prezent dup parametrii cu valori obligatorii. Astfel, a a ncercarea de a scrie: private void MyMethod(int p=1, int q){...} duce la apariia erorii de compilare: t Optional parameters must appear after all required parameters Valorile furnizate pentru parametrii cu valori implicite trebuie s e constante a sau s aibe valori de forma default(T). a ceea ce privete folosirea parametrilor cu nume, s presupunem c avem In s a a urmtoarea metod: a a public void M(int x, int y = 5, int z = 7){...} Dac vrem ca la un apel s nu precizm valoarea lui y, dar s o precizm pe a a a a a a lui z, am tentai s folosim: t a M(1, ,-1) ceea ce cazul care ar permis, ar duce la un cod greu de citit, care n n n abilitatea de numrare a virgulelor ar crucial. locul acestei variante, a a In s-a introdus posibilitatea de a preciza parametrii prin nume: M(1, z:3); //sau M(x:1, z:3) //sau chiar: M(z:3, x:1) Ordinea de evaluare a expresiilor date pentru parametri este dat de ordinea a de precizare a numelor parametrilor, deci ultimul exemplu expresia 3 este n evaluat a nainte de expresia 1. Mai precizm i c parametrii opionali i cu a s a t s nume se pot folosi i pentru constructori sau indexatori. s

13.3. TIPURI DE DATE DINAMICE

305

13.3

Tipuri de date dinamice

Tipurile dinamice permit tratarea unui obiect fr a crispai de proveniena aa t t acestuia: obiect creat clasic, sau prin COM sau prin reectare. Unui astfel de obiect i s pot transmite mesaje (=apeluri de metode), iar legitimitatea acestor apeluri este vericat la rulare. Altfel zis, pentru tipurile de date dia namice se renuna la tipizarea static specic platformelor .NET de versiune t a a anterioar, dar cu riscul de a primi erori doar la rularea aplicaiei. a t Acest tip de date dinamic se declar prin cuvntul cheie dynamic. Plecnd a a a de la un astfel de obiect, se pot apela diferite metode: dynamic x = MyMethod(); x.AnotherMethod(3); La rulare se veric dac tipul de date aferent variabilei x are metoda AnotherMethod a a care s permit apel cu un parametru de tip a a ntreg. Exemplu: static void Main(string[] args) { dynamic x = "abc"; x = 3; Console.WriteLine(x.CompareTo(10));//metoda CompareTo este din //tipul System.Int32 } Remarcm c tipul unei variabile dinamice nu este setat odat pentru tota a a deauna. Alt exemplu este: class ExampleClass { public ExampleClass() { } public ExampleClass(int v) { } public void exampleMethod1(int i) { } public void exampleMethod2(string str) { } } //.... static void Main(string[] args) { ExampleClass ec = new ExampleClass();

306

IN CURS 13. NOUTATI C# 4.0 // The following line causes a compiler error if exampleMethod1 has only // one parameter. //ec.exampleMethod1(10, 4); dynamic dynamic_ec = new ExampleClass(); // The following line is not identified as an error by the // compiler, but it causes a run-time exception. dynamic_ec.exampleMethod1(10, 4); // The following calls also do not cause compiler errors, whether // appropriate methods exist or not. dynamic_ec.someMethod("some argument", 7, null); dynamic_ec.nonexistentMethod();

} Se pot declara ca ind dinamici si parametrii unei metode: public static void Log(dynamic x) { Console.WriteLine(x.ToString()); } static void Main(string[] args) { var x = 3; string y = "abc"; Log(x); Log(y); }

13.4

COM Interop

COM Interop este o facilitate existent versiuni mai vechi ale lui .NET a n framework care permite codului managed s apeleze cod unmanaged de tip a Component Object Model, cum ar aplicaiile Word sau Excel. De exemplu, t pentru utilizarea unei celule de Excel, varianta veche de cod ce trebuia scris a era: ((Excel.Range)excel.Cells[1, 1]).Value2 = "Hello";

13.4. COM INTEROP pe cnd .NET 4 a se scrie mai inteligibil astfel: a n excel.Cells[1, 1].Value = "Hello"; sau loc de n Excel.Range range = (Excel.Range)excel.Cells[1, 1]; se scrie: Excel.Range range = excel.Cells[1, 1]; Exemplu: using using using using using System; System.Collections.Generic; System.Linq; System.Text; Excel = Microsoft.Office.Interop.Excel;

307

namespace TestOffice { class Program { static void Main(string[] args) { var excel = new Excel.Application(); // Make the object visible. excel.Visible = true;

// Create a new, empty workbook and add it to the collection returned // by property Workbooks. The new workbook becomes the active workbook. // Add has an optional parameter for specifying a praticular template. // Because no argument is sent in this example, Add creates a new workbo excel.Workbooks.Add(); excel.Cells[1, 1].Value = "Hello"; var processes = Process.GetProcesses() .OrderByDescending(p => p.WorkingSet64) .Take(10); int i = 2; foreach (var p in processes)

308 {

IN CURS 13. NOUTATI C# 4.0

excel.Cells[i, 1].Value = p.ProcessName; // no casts excel.Cells[i, 2].Value = p.WorkingSet64; // no casts i++; }

Excel.Range range = excel.Cells[1, 1]; // no casts dynamic chart = excel.ActiveWorkbook.Charts. Add(After: excel.ActiveSheet); // named and optional chart.ChartWizard( Source: range.CurrentRegion, Title: "Memory Usage in " + Environment.MachineName); //named chart.ChartStyle = 45; chart.CopyPicture(Excel.XlPictureAppearance.xlScreen, Excel.XlCopyPictureFormat.xlBitmap, Excel.XlPictureAppearance.xlScreen); } } }

13.5

Covariana i contravariana t s t

S presupunem c avem secvena de cod: a a t var strList = new List<string>(); List<object> objList = strList; Linia a doua, dac ar permis, ar predispune la erori, deoarece s-ar permite a a mai departe: objList.Add(new MyClass()); deci sar alca punctul de plecare pentru colecie: elementele ar trebui s nc t a e toate de tip String. Ca atare, acest lucru nu ar avea sens s e permis. a Incepnd cu .NET 4, se permite a conversia ctre interfee generice sau a ns a t delegai generici pentru care tipul generic este mai general dect argumentul t a generic dinspre care se face conversie. De exemplu, se poate scrie: IEnumerable<object> myCollection = strList; Aceasta este covariana i se obine prin declaraiile: t s t t

13.5. COVARIANTA SI CONTRAVARIANTA public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); } public interface IEnumerator<out T> : IEnumerator { bool MoveNext(); T Current { get; } }

309

unde cuvntul out are un alt sens dect la transmiterea de parametri: a a n C# 4.0, acest context, semnic faptul c tipul T poate s apar doar n a a a a n poziia de ieire a unei metode din interfaa. Interfaa IEnumerable devine t s t t astfel covariant T i se poate face conversie de la IEnumerable<B> la a n s IEnumerable<A> pentru tipul B derivat din A. Acest lucru este util pentru o situaie de genul: t var result = strings.Union(objects); unde se face reuniune ntre o coleie de stringuri i una de obiecte. t s Contravariana permite conversii sens invers ca la covariana. De ext n t emplu, prin declaraia: t public interface IComparer<in T> { public int Compare(T left, T right); } prin arat contravariana a tipului T, deci se poate face ca un IComparer<object> a t s e considerat drept un IComparer<String>. Are sens, deoarece dac un a a IComparer poate s compare orice dou obiecte, atunci cu sigurana poate a a t s compare i doua string-uri. a s

310

IN CURS 13. NOUTATI C# 4.0

Curs 14 Fluxuri
Pentru aplicaiile reale, datele care se prelucreaz nu se mai preiau din t a tastatur, ci se acceseaz din iere de date. C#, precum alte limbaje antea a s rioare, pune la dispoziie o abstractizare numit ux 1 care permite manipt a ularea datelor, indiferent de sursa acestora. Intrun astfel de ux, pachete de date urmeaz unele dup altele. a a C# ierele i directoarele se manipuleaz prin intermediul unor clase In s s a predenite. Acestea permit crearea, redenumirea, manipularea, tergerea s ierelor i a directoarelor. Manipularea coninutului ierelor se face prin s s t s intermediul stream-urilor cu sau fr buer; de asemenea exist un suport aa a puternic pentru streamuri asincrone (prelucrarea unui ier se face de ctre s a un r de execuie creat automat). Datorit abstractizrii, nu exist diferene t a a a t mari ntre lucrul cu iere aate pe discul local i datele aate pe reea; ca s s t atare se va vorbi despre uxuri bazate pe protocoale TCP/IP sau web. In sfrit, serializarea este legat de uxuri, as a ntruct permit tarea unui a nge obiect (care poate urmat de transportul lui pe reea). a t

14.1

Sistemul de iere s

Clasele care se folosesc pentru manipularea ierelor i a directoarelor s s se a spaiul de nume System.IO. Funcionalitatea lor este aceeai cu a a n t t s comenzilor disponibile ntrun sistem de operare: creare, tegere, redenumire, s mutare de iere sau directoare, listarea coninutului unui director, listarea s t atributelor sau a diferiilor timpi pentru iere sau directoare, etc. t s Clasele pe care vom folosi sunt: Directory, DirectoryInfo, File, FileInfo.
1

Engl: Stream

311

312

CURS 14. FLUXURI

14.1.1

Lucrul cu directoarele: clasele Directory i Dis rectoryInfo

Clasa Directory conine metode statice pentru crearea, mutarea, explot rarea directoarelor. Clasa DirectoryInfo conine doar membri nestatici i t s permite aarea diferitelor informaii pentru un director anume. t Tabelul 14.1 conine principalele metode ale clasei Directory, iar tabelul t 14.2 conine metodele notabile din clasa DirectoryInfo. t Tabelul 14.1: Metode ale clasei Directory. Metoda CreateDirectory() Explicaie t Creeaz directoarele i subdirectoarele a s specicate prin parametru Delete() Sterge un director i coninutul su s t a Exists() Returneaz true dac stringul specicat a a reprezint numele unui director existent a GetCreationTime() Returneaz / seteaz data i timpul a a s SetCreationTime() crerii unui director a GetCurrentDirectory() returneaz / seteaz directorul curent a a SetCurrentDirectory() GetDirectories() Returneaz un ir de nume de subdirectoare a s GetDirectoryRoot() Returneaz numele rdcinii unui director specicat a a a GetFiles() Returneaz un ir de stringuri care conine numele a s t ierelor din directorul specicat s GetLastAccesTime() returneaz / seteaz timpul ultimului acces a a SetLastAccesTime() pentru un director GetLastWriteTime() Returneaz / seteaz timpul cnd directorul a a a SetLastWriteTime() specicat a fost ultima oar modicat a GetLogicalDrives() Returneaz numele tuturor unitilor logice a at sub forma drive:\ GetParent() Returneaz directorul curent pentru calea specicat a a Move() Mut un director (cu coninut) a t ntro cale specicat a Tabelul 14.2: Metode ale clasei DirectoryInfo. Metoda sau proprietate Attributes CreationTime Explicaie t Returneaz sau seteaz atributele ierului curent a a s Returneaz sau seteaz timpul de creare al a a

14.1. SISTEMUL DE FI IERE S

313 Tabelul 14.2 (continuare)

Metoda sau proprietate Exists Extension FullName LastAccessTime LastWriteTime Parent Root Create() CreateSubdirectory() Delete() GetDirectories() GetFiles() MoveTo()

Explicaie t ierului curent s true dac directorul exist a a Extensia directorului Returneaz calea absolut a ierului sau a a a s directorului Returneaz sau seteaz timpul ultimului acces a a Returneaz sau seteaz timpul ultimei scrieri a a Directorul printe al directorului specicat a Rdcina cii corespunztoare a a a a Creeaz un director a Creeaz un subdirector calea specicatu a a n Sterge un DirectoryInfo i coninutul su s t a Returneaz un vector de tip DirectoryInfo cu a subdirectoare Returneaz lista ierelor din director a s Mut un DirectoryInfo i coninutul su a s t a ntrun nou loc

Exemplul urmtor folosete clasa DirectoryInfo pentru a realiza exploa s rarea recursiv a unui director cu enumerarea tuturor subdirectoarelor coninute. a t Se creeaz un obiect de tipul pomenit pe baza numelui de director de la care a se va ncepe explorarea. O metod va aa numele directorului la care s a s a ajuns, dup care se apeleaz recursiv metoda pentru ecare subdirector a a (obinut via GetDirectories()). t using System; using System.IO; class Tester { public static void Main( ) { Tester t = new Tester( ); //choose the initial subdirectory string theDirectory = @c:\WinNT; // call the method to explore the directory, // displaying its access date and all // subdirectories DirectoryInfo dir = new DirectoryInfo(theDirectory);

314

CURS 14. FLUXURI t.ExploreDirectory(dir); // completed. print the statistics Console.WriteLine(\n\n{0} directories found.\n, dirCounter);

} // Set it running with a directoryInfo object // for each directory it finds, it will call // itself recursively private void ExploreDirectory(DirectoryInfo dir) { indentLevel++; // push a directory level // create indentation for subdirectories for (int i = 0; i < indentLevel; i++) Console.Write(" "); // two spaces per level // print the directory and the time last accessed Console.WriteLine("[{0}] {1} [{2}]\n", indentLevel, dir.Name, dir.LastAccessTime); // get all the directories in the current directory // and call this method recursively on each DirectoryInfo[] directories = dir.GetDirectories( ); foreach (DirectoryInfo newDir in directories) { dirCounter++; // increment the counter ExploreDirectory(newDir); } indentLevel--; // pop a directory level } // static member variables to keep track of totals // and indentation level static int dirCounter = 1; static int indentLevel = -1; // so first push = 0 }

14.1.2

Lucrul cu ierele: clasele FileInfo i File s s

Un obiect DirectoryInfo poate de asemenea s returneze o colecie a tua t turor ierelor coninute, sub forma unor obiecte de tip FileInfo. s t Inrudit a cu clasa FileInfo (care are membri nestatici) este clasa File (care are doar membri statici). Tabelele 14.3 i 14.4 conin metodele pentru ecare clas: s t a

14.1. SISTEMUL DE FI IERE S Tabelul 14.3: Metode ale clasei File. Metoda AppendText()

315

Explicaie t Creeaz un obiect StreamWriter care adaug a a text la ierul specicat s Copy() Copiaz un ier existent a s ntrun alt ier s Create() Creeaz un ier calea specicat a s n a CreateText() Creeaz un StreamWriter care scrie un nou ier text a s Delete() Sterge ierul specicat s Exists() Returneaz true dac ierul corespunztor exist a a s a a GetAttributes() Returneaz / seteaz FileAttributes pentru ierul a a s SetAttributes() specicat GetCreationTime() Returneaz / seteaz data i timpul crerii pentru ierul a a s a s SetCreationTime() specicat GetLastAccessTime() Returneaz sau seteaz timpul ultimului acces la ier a a s SetLastAccessFile() GetLastWriteTime() Returneaz / seteaz timpul ultimei modicri a ierului a a a s SetLastAccessTime() Move() Mut un ier la o nou locaie; poate folosit pentru a s a t redenumire OpenRead() Metod returnnd un FileStream pe un ier a a s OpenWrite() Creaz un Stream de citire / scriere a Tabelul 14.4: Metode ale clasei FileInfo. Metoda Attibutes CreationTime Directory Exists Extension FullName LastAccessTime LastWriteTime Length Name AppendText Explicaie t Returneaz sau seteaz atributele ierului curent a a s Returneaz sau seteaz timpul de creare al a a ierului curent s Returneaz o instana a directorului curent a t true dac ierul exist a s a Returneaz extensia ierului a s Calea absolut pn la ierul curent a a a s Returneaz sau seteaz timpul ultimului acces a a Returneaz sau seteaz timpul cnd sa modicat a a a ultima oar ierul curent a s Returneaz dimensiunea ierului a s Returneaz numele instanei curente a t Creaz un StreamWriter care va permite adugarea a a

316

CURS 14. FLUXURI Tabelul 14.4 (continuare)

Metoda sau proprietatea CopyTo() Create() Delete() MoveTo() OpenRead() OpenText() OpenWrite()

Explicaie t la ier s Copieaz ierul curent a s ntrun alt ier s Creaz un nou ier a s Sterge un ier s Mut un ier la o locaie specicat; poate a s t a folosit pentru redenumire a Creaz un ier readonly a s respectiv StreamReader(text) sau FileStream(readwrite)

Exemplul anterior este modicat pentru a aa informaii despre iere: s t s numele, dimensiunea, data ultimei modicri: a using System; using System.IO; class Tester { public static void Main( ) { Tester t = new Tester( ); // choose the initial subdirectory string theDirectory = @c:\WinNT; // call the method to explore the directory, // displaying its access date and all // subdirectories DirectoryInfo dir = new DirectoryInfo(theDirectory); t.ExploreDirectory(dir); // completed. print the statistics Console.WriteLine(\n\n{0} files in {1} directories found.\n, fileCounter,dirCounter); } // Set it running with a directoryInfo object // for each directory it finds, it will call // itself recursively private void ExploreDirectory(DirectoryInfo dir) { indentLevel++; // push a directory level // create indentation for subdirectories for (int i = 0; i < indentLevel; i++)

14.1. SISTEMUL DE FI IERE S

317

Console.Write( ); // two spaces per level // print the directory and the time last accessed Console.WriteLine([{0}] {1} [{2}]\n, indentLevel, dir.Name, dir.LastAccessTime); // get all the files in the directory and // print their name, last access time, and size FileInfo[] filesInDir = dir.GetFiles( ); foreach (FileInfo file in filesInDir) { // indent once extra to put files // under their directory for (int i = 0; i < indentLevel+1; i++) Console.Write( ); // two spaces per level Console.WriteLine({0} [{1}] Size: {2} bytes, file.Name, file.LastWriteTime file.Length); fileCounter++; } // get all the directories in the current directory // and call this method recursively on each DirectoryInfo[] directories = dir.GetDirectories( ); foreach (DirectoryInfo newDir in directories) { dirCounter++; // increment the counter ExploreDirectory(newDir); } indentLevel--; // pop a directory level } // static member variables to keep track of totals // and indentation level static int dirCounter = 1; static int indentLevel = -1; // so first push = 0 static int fileCounter = 0; } Exemplul urmtor nu introduce clase noi, ci doar exemplic crearea unui a a director, copierea de iere el, tergerea unora dinre ele i nal tergerea s n s s n s directorului: using System; using System.IO; class Tester {

318

CURS 14. FLUXURI

public static void Main( ) { // make an instance and run it Tester t = new Tester( ); string theDirectory = @c:\test\media; DirectoryInfo dir = new DirectoryInfo(theDirectory); t.ExploreDirectory(dir); } // Set it running with a directory name private void ExploreDirectory(DirectoryInfo dir) { // make a new subdirectory string newDirectory = newTest; DirectoryInfo newSubDir = dir.CreateSubdirectory(newDirectory); / get all the files in the directory and // copy them to the new directory FileInfo[] filesInDir = dir.GetFiles( ); foreach (FileInfo file in filesInDir) { string fullName = newSubDir.FullName + \\ + file.Name; file.CopyTo(fullName); Console.WriteLine({0} copied to newTest, file.FullName); } // get a collection of the files copied in filesInDir = newSubDir.GetFiles( ); // delete some and rename others int counter = 0; foreach (FileInfo file in filesInDir) { string fullName = file.FullName; if (counter++ %2 == 0) { file.MoveTo(fullName + .bak); Console.WriteLine({0} renamed to {1}, fullName,file.FullName); } else { file.Delete( ); Console.WriteLine({0} deleted.,

14.2. CITIREA SI SCRIEREA DATELOR fullName); } } newSubDir.Delete(true); // delete the subdirectory } }

319

14.2

Citirea i scrierea datelor s


Tabelul 14.5: Clase pentru lucrul cu streamuri

Clasele disponibile pentru lucrul cu streamuri sunt:

Clasa Stream BinaryReader

Descriere Manipulare generic a unei secvene de octei; clas abstract a t t a a Citete tipuri de date primitive ca voalori binare s ntro codicare specic a BinaryWriter Scrie tipuri de date primitive ntrun ux binar; de asemenea scrie stringuri folosind o anumit codicare a BueredStream Ataeaz un buer unui stream de intrare / ieire. Clas sealed s a s a FileStream Ataeaz un stream unui ier, permiand operaii sincrone s a s t t sau asincrone de citire i scriere. s MemoryStream Creaz un stream pentru care citirea / stocarea de date se face a memorie n NetworkStream Creeaz un stream folosind TCP/IP a TextReader Permite citire de caractere, mod secvenial. Clas abstract. n t a a TextWriter Permite scriere secvenial t a ntrun ier. Clas abstract. s a a StreamReader Implementeaz o clas TextReader care citete caractere a a s dintrun stream folosind o codicare particular a StreamWriter Implementeaz o clas TextWriter care scrie caractere a a ntrun stream folosind o codicare particular a StringReader Implementeaz un TextReader care citete dintrun string a s StringWriter Scrie informaie t ntrun string. Informaia este stocat t a ntrun StringBuilder

14.2.1

Clasa Stream

Clasa Stream este o clas abstract din care se deriveaz toate celelalte a a a clase de lucru cu streamuri. Metodele coninute permit citire de octei, t t

320

CURS 14. FLUXURI

nchidere, golire de buer, etc. Pe lng acestea, clasa Stream permite att a a a operaii sincrone, ct i asincrone. Intro operaie de intrare / ieire sincron, t a s t s a dac se a ncepe o operaie de citire sau scriere dintrun / t ntrun ux, atunci programul va trebui s atepte pn cnd aceast operaie se termin. Sub a s a a a a t a platforma .NET se poate face a op operaie de intrare / ieire mod ns t s n asincron, astfel permiand altor re de execuie s se execute. t t a Metodele folosite pentru a cepe o astfel de intrare asincron sunt: n a BeginRead(), BeginWrite(), EndRead(), EndWrite(). O dat ce o astfel de a operaie se termin, o metod callback se va executa automat. t a a O list a metodelor i proprietilor care sunt denite clasa Stream este a s at n dat tabelul 14.6. a n Tabelul 14.6: Metode i proprieti ale clasei Stream s at Clasa Descriere Incepe o citire asincron a BeginRead() a BeginWrite() Incepe o scriere asincron Inchide streamul curent i elibereaz resursele asociate cu el s a Close() (socketuri, le handles, etc) EndRead() Ateapt pentru o terminare de citire asincron. s a a EndWrite() Ateapt pentru o terminare de scriere asincron. s a a Flush() Cnd este suprascris tro clas derivat, golete a a n a a s buerele asociate straemului curent i determin scrierea lor. s a Read() Cnd este suprascris a a ntro clas derivat, citete o a a s secvena de octei i incrementeaz indicatorul de poziie t t s a t curent stream a n ReadByte() Citete un byte din stream i incrementeaz indicatorul de s s a poziie curent; dac este la sfrit de ier, returneaz -1 t a as s a Seek() Cnd este suprascris a a ntro clas derivat, seteaz a a a poziia curent interiorul streamului t a n SetLength() Cnd este suprascris a a ntro clas derivat, seteaz a a a lungimea streamului curent Write() Cnd este suprascris a a ntro clas derivat, scrie o a a secvena de octei streamul curent i incrementeaz t t n s a corespunztor indicatorul poziiei curente stream a t n WriteByte() Scrie un byte la poziia curent din stream i incrementeaz t a s a indicatorul de poziie curent t a CanRead() Cnd este suprascris a a ntro clas derivat, returneaz a a a o valoarea care indic dac streamul curent poate citit a a CanWrite() Cnd este suprascris a a ntro clas derivat, returneaz a a a

14.2. CITIREA SI SCRIEREA DATELOR

321 Tabelul 14.6 (continuare)

Metoda CanSeek

Length Position

Descriere o valoarea care indic dac streamul curent suport scriere a a a Cnd este suprascris a a ntro clas derivat, returneaz o a a a valoarea care indic dac se poate face poziionare aleatoare a a t n streamul curent Cnd este suprascris a a ntro clas derivat, returneaz a a a dimensiunea octei a ierului n t s Cnd este suprascris a a ntro clas derivat, returneaz a a a sau seteaz poziia curent interiorul streamului a t a n

14.2.2

Clasa FileStream

Exist mai multe metode de obinere a unui obiect de tip FileStream. a t Clasa prezint nou constructori supra arcai. Enumerarea FileMode este a a nc t folosit pentru a se specica modul de deschidere a unui stream: (Append, a Create, CreateNew, Open, OpenOrCreate, Truncate). Exemplu: mai jos se creeaz un ier nou (dac nu exist) sau se suprascrie a s a a unul existent: FileStream f = new FileStream( @C:\temp\a.dat, FileMode.Create ); De asemenea se mai poate obine o instana a clasei FileStream din clasa t t File: FileStream g = File.OpenWrite(@c:\temp\test.dat); //deschidere doar pentru citire Asemntor, se poate folosi o instana a clasei FileInfo: a a t FileInfo fi = new FileInfo(@c:\temp\test.dat); FileStream fs = fi.OpenRead(); //deschidere doar pentru citire

14.2.3

Clasa MemoryStream

Un MemoryStream si ia datele din memorie, vzut ca un vector de a a octei. Exist apte constructori pentru aceast clas, dar care pot grupai t as a a t dou categorii. n a Primul tip de constructor preia un array de octei pentru care poate t face citire i opional scriere. Caracteristic este c tabloul nu va putea s t a redimensionat:

322 byte[] b = {1, 2, 3, 4}; MemoryStream mem = new MemoryStream(b);

CURS 14. FLUXURI

O alt variant de constructor nu primete un vector de octei, dar va a a s t putea scrie ntrun tablou redimensionabil. Opional, se specic un int ca t a parametru al constructorului care determin dimensiunea iniial a tabloului a t a de octei. Datele sunt adugate folosind metoda Write(): t a using System; using System.IO; public class MemTest { public static void Main() { MemoryStream mem = new MemoryStream(); byte[] bs = {1, 2, 3, 4, 5, 6}; mem.Write(bs, 0, bs.Length); mem.Seek(3, SeekOrigin.Begin); byte b = (byte)mem.ReadByte(); Console.WriteLine(Value: {0}, b.ToString()); //se va afisa 4 } } E de preferat s se utilizeze obiecte de tip MemoryStream scopul de a a n accesa informaia din memoria RAM, mai degrab dect de pe disc sau din t a a reea. De exemplu se poate arca un ier de pe disc memorie, astfel t nc s n at analiza lui se poate face mai repede. nc

14.2.4

Clasa BueredStream

C# pune la dispoziie o clas BueredStream pentru a asigura o zon t a a tampon cazul operaiilor de intrareieire. Constructorul acestei clase n t s primete o instana a clasei Stream. s t Exemplu: FileStream fs = new FileStream(@c:\temp\a.dat, FileMode.Open); BufferedStream bs = new BufferedStream(fs); De menionat aici metoda Flush(), care foreaz golirea buerului asociat t t a streamului.

14.2. CITIREA SI SCRIEREA DATELOR

323

14.2.5

Clasele BinaryReader i BinaryWriter s

Cele dou clase sunt folosite pentru a accesa date mai complexe dect un a a byte: de exemplu, pentru manipularea datelor de tip boolean, sau Decimal, sau int cu semn pe 64 de bii. t Tabelul 14.7 conine metodele puse la dispoziie de ctre clasa Binaryt t a Writer : Tabelul 14.7: Metodele clasei BinaryReader Metoda PeekChar() Read() ReadBoolean() ReadBytes() ReadChar() ReadChars() Descriere Returneaz urmtorul caracter disponibil, fr a avansa a a aa indicatorul de poziie curent t a Citete caractere din ux i avanseaz poziia curent s s a t a din acel stream Citete un Boolean din stream i avanseaz poziia curent s s a t a cu un byte Citete un numr precizat de octei s a t ntrun vector i s avanseaz poziia curent a t a Citete urmtorul caracter i avanseaz poziia curent s a s a t a corespunztor cu codicarea folosit pentru caracter a a Citete mai multe caractere s ntrun vector i avanseaz poziia s a t curent cu numrul de caractere dimensiunea de reprezentare a a pentru caracter Citete un decimal i avanseaz poziia curent cu 16 octei s s a t a t Citete o variabil virgul mobil i avanseaz cu 8 octei s an a as a t Citete un s ntreg cu semn pe 16 bii i avanseaz cu 2 octei t s a t Citete un s ntreg cu semn pe 32 de bii i avanseaz cu 4 octei t s a t Citete un s ntreg cu semn pe 64 de bii i avanseaz cu 8 octei t s a t Citete un byte cu semn i avanseaz cu un byte s s a Citete o valoare virgul mobil pe 4 octei i avanseaz s n a a t s a poziia curent cu 4 octei t a t Citete un string, prexat cu lungimea sa, codicat ca un s a ntreg reprezentat pe grupe de c&ate 7 bii (MSDN) t Citete un s ntreg fr semn reprezentat pe 16 bii i avanseaz cu aa t s a 2 octei t Citete un s ntreg fr semn reprezentat pe 32 de bii i avanseaz aa t s a cu 4 octei t Citete un s ntreg fr semn reprezentat pe 64 de bii i avanseaz aa t s a cu 8 octei t

readDecimal() ReadDouble() ReadInt16() ReadInt32() ReadInt64() ReadSByte() ReadSingle() ReadString() ReadUInt16 ReadUInt32 ReadUInt64

324

CURS 14. FLUXURI

Clasa BinaryWriter are o metod Write() supra arcat, care poate a nc a apelat pentru scrierea diferitelor tipuri de date. O meniune la scrierea de a t stringuri i de caractere / vectori de caractere: caracterele pot codicate s mai multe moduri (ex: ASCII, UNICODE, UTF7, UTF8), codicare care n se poate transmite ca argument pentru constructor. A se vedea MSDN i s RFCurile de pe Interner.

14.2.6

Clasele TextReader, TextWriter i descendentele s lor

Pentru manipularea irurilor de caractere aate iere, dar nu numai, s n s C# pune la dispoziie clasele abstracte TextReader, TextWriter. Clasa Text tReader are subclasele neabstracte StreamReader i StringReader. Clasa Texs tWriter are subclasele neabstracte StreamWriter, StringWriter, System.Web.HttpWriter, System.Web.UI.HtmlTextWriter, System.CodeDom.Compiler.IndentedTextWriter. Descrieri i exemple sunt date mai jos: s 1. Clasele StreamReader i StreamWriter - sunt folosite pentru a cit sau s scrie iruri de caractere. Un obiect de tip StreamReader se poate obine s t via un constructor: StreamReader sr = new StreamReader(@C:\temp\siruri.txt); sau pe baza unui obeiect de tip FileInfo: FileInfo fi = new FileInfo(@c:\temp\siruri.txt); StreamReader sr = fi.OpenText(); Obiectele de tip StreamReader pot citi cte o linie la un moment dat a folosind metoda ReadLine(). Exemplu: using System; using System.IO; class Test { static void Main() { StreamReader sr = new StreamReader(c:\temp\siruri.txt); String line; do

14.2. CITIREA SI SCRIEREA DATELOR {

325

line = sr.ReadLine(); Console.WriteLine(line); //daca line==null, atunci se va afisa o linie vida }while( line!=null); } } 2. Clasele StringReader i StringWriter - permit ataarea unor uxuri la s s iruri de caractere, folosite pe post de surse de date. s Exemplu: string myString = 1234567890; StringReader sr = new StringReader(myString); using System; using System.IO; using System.Xml; class Test { static void Main() { XmlDocument doc = new XmlDocument(); String entry = <book genre=biography + ISBN=12345678><title>Yeager</title> + </book>; doc.LoadXml(entry);//salvare in doc. xml StringWriter writer = new StringWriter(); doc.Save(writer); string strXml = writer.ToString(); Console.WriteLine(strXml); } } va aa: s <?xml version=1.0 encoding=utf-16> <book genre=biography ISBN=12345678> <title>Yeager</title> </book>

326

CURS 14. FLUXURI loc ca salvarea din documentul xml s se fac In a a ntrun ier text, se s face ntrun obiect de tip StringWriter(), al crui coninut se va aa. a t s

3. IndentedTextWriter denete metode care insereaz taburi i pstreaz s a s a a evidena niveluilui de identare. Este folosit de deriuvri ale claseiCodeDom, t a folosit pentru generare de cod. a 4. HtmlTextWriter scrie o secvena HTML pe o pagin Web. Este folosit t a de exemplu script-uri C#, cazul aplicaiilor ASP.NET n n t 5. HttpWriter - prea puin informaie!!! t a t

14.3

Operare sincron i asincron as a

Exemplele folosite pn acum au folosit un mod de operare sincron, adic a a a atunci cnd se face o operaie de intrare/ieire, a t s ntregul program este blocat pn cnd se tranziteaz toate datele specicate. Streamurile C# permit a a a a i acces asincron, permiand altor re de execuie s e rulate. Pentru sems t t a nalarea nceputului unei operaii de citire sau scriere asincrone, se folosesc t BeginRead() i BeginWrite(). s Metoda BeginRead are prototipul: public override IAsyncResult BeginRead( byte[] array, int offset, int numBytes, AsyncCallback userCallback, object stateObject ); Vectorul de bytes reprezint buerul care se vor citi datele; al doilea i al a n s treilea parametru determin byteul la care s se va scrie, respectiv numrul a a a maxim de octei care se vor citi. Al patrulea parametru este un delegat, t folosit pentru a semnala (mai exact, a face nite prelucrri) sfritul citirii. s a as Se poate transmite null pentru acest parametru, dar programul nu va noticat de terminarea citirii. Ultimul parametru este un obiect care va folosit pentru a distinge ntre aceast cerere de citire asincron i altele. a as using System; using System.IO; using System.Threading; using System.Text; public class AsynchIOTester { private Stream inputStream;

14.3. OPERARE SINCRONA SI ASINCRONA // delegated method private AsyncCallback myCallBack; // buffer to hold the read data private byte[] buffer; // the size of the buffer const int BufferSize = 256; // constructor AsynchIOTester( ) { // open the input stream inputStream = File.OpenRead( @"C:\test\source\AskTim.txt"); // allocate a buffer buffer = new byte[BufferSize]; // assign the call back myCallBack = new AsyncCallback(this.OnCompletedRead); } public static void Main( ) { // create an instance of AsynchIOTester // which invokes the constructor AsynchIOTester theApp = new AsynchIOTester( ); // call the instance method theApp.Run( ); } void Run( ) { inputStream.BeginRead( buffer, // holds the results 0, // offset buffer.Length, // (BufferSize) myCallBack, // call back delegate null); // local state object // do some work while data is read for (long i = 0; i < 500000; i++) { if (i%1000 == 0) {

327

328

CURS 14. FLUXURI

Console.WriteLine("i: {0}", i); } } } // call back method void OnCompletedRead(IAsyncResult asyncResult) { int bytesRead = inputStream.EndRead(asyncResult); // if we got bytes, make them a string // and display them, then start up again. // Otherwise, were done. if (bytesRead > 0) { String s = Encoding.ASCII.GetString(buffer, 0, bytesRead); Console.WriteLine(s); inputStream.BeginRead( buffer, 0, buffer.Length, myCallBack, null); } } } Ieirea ar : s i: 47000 i: 48000 i: 49000 Date: January 2001 From: Dave Heisler To: Ask Tim Subject: Questions About OReilly Dear Tim, Ive been a programmer for about ten years. I had heard of OReilly books,then... Dave, You might be amazed at how many requests for help with school projects I get; i: 50000 i: 51000 i: 52000 Cele dou re de executie lucreaz deci concurent. a c a

14.4. STREAMURI WEB

329

14.4

Streamuri Web

C# conine clase gndite pentru a uura interoperarea cu webul. Aduct a s erea informaiei de pe web se face doi pai: primul pas const a face o t n s a n cerere de conectare la un server; dac cererea se poate face, atunci pasul a n al doilea const obinerea informaiei propriuzise de la server. Cei doi a n t t pai se fac respectiv cu clasele HttpWebRequest, respectiv HttpWebResponse s Un obiect HttpWebRequest poate obinut prin metoda static Create() t a din clasa WebRequest: string page = http://www.cetus-links.org/index.html; HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(page); Pe baza obiectului HttpWebRequest obinut se va crea un obiect HttpWebRet sponse: HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); nal, se obine un stream prin metoda GetResponseStream(): In t StreamReader streamReader = new StreamReader(webResponse.GetResponseStream(), Encoding.ASCII);

14.5

Serializarea

Serializarea reprezint posibilitatea de a trimite un obiect printrun stream. a Pentru aceasta, C# folosete metodele Serialize() i Deserialize() ale clasei s s BinaryFormatter. Metoda Serialize() are nevoie de 2 parametri: un stream care s scrie i n a s obiectul pe care s serializeze. Metoda de deserializare cere doar un stream a l din care s citeasc, i din care s refac un object (care poate convertit la a a s a a tipul corespunztor). a

14.5.1

Crearea unui obiect serializabil

Pentru ca o clas denit de utilizator s suport serializarea, este nevoie a a a de a preciza atributul [Serializable] faa declaraiei clasei respective, atribut n t t denit clasa System.SerializableAttribute. Tipurile primitive sunt automat n serializabile, iar dac tipul denit de utilizator conine alte tipuri, atunci a t acestea la rndul lor trebuie s poat serializate. a a a Exemplu:

330 using System; [Serializable] public class BookRecord { public String title; public int asin; public BookRecord(String title, int asin) { this.title = title; this.asin = asin; } }

CURS 14. FLUXURI

14.5.2

Serializarea

Codul pentru serializarea unui obiect de tipul declarat mai sus este: using System; using System.Runtime.Serialization.Formatters.Binary; using System.IO; public class SerializeObject { public static void Main() { BookRecord book = new BookRecord( "Building Robots with Lego Mindstorms", 1928994679); FileStream stream = new FileStream(@"book.obj", FileMode.Create); BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(stream, book); stream.Close(); } }

14.5.3

Deserializarea unui obiect

Deserializarea se face astfel: using System; using System.Runtime.Serialization.Formatters.Binary; using System.IO;

14.5. SERIALIZAREA public class DeserializeObject { public static void Main() { FileStream streamIn = new FileStream( @"book.obj", FileMode.Open); BinaryFormatter bf = new BinaryFormatter(); BookRecord book = (BookRecord)bf.Deserialize(streamIn); streamIn.Close(); Console.Write(book.title + " " + book.asin); } }

331

14.5.4

Date tranziente

Uneori este nevoie ca anumite cmpuri ale unui obiect s nu e salvate: a a parola unui utilizator, numrul de cont al unui client, etc. Acest lucru se face a specicnd atributul [NonSerialized] pentru cmpul respectiv: a a [NonSerialized] int secretKey;

14.5.5

Operaii la deserializare t

Uneori este nevoie ca o deserializare s e automat urmat de o anumit a a a operaie. De exemplu, un cmp care nu a fost salvat (tranzient) va trebui t a s e refcut mod calculat. Pentru acest lucru, C# pune la dispoziie a a n t interfaa IDeserializationCallback, pentru care trebuie s se implementeze t a metoda OnDeserialization. Aceast metod va apelat automat de ctre a a a a CLR atunci cnd se va face deserializarea obiectului. a Exemplul de mai jos ilustreaz acest mecanism: a using System; using System.Runtime.Serialization; [Serializable] public class BookRecord: IDeserializationCallback { public String title; public int asin; [NonSerialized] public int sales_rank; public BookRecord(String title, int asin) {

332 this.title = title; this.asin = asin; sales_rank = GetSalesRank(); } public int GetSalesRank() { Random r = new Random(); return r.Next(5000); } public void OnDeserialization(Object o) { sales_rank = GetSalesRank(); } }

CURS 14. FLUXURI

Mecanismul poate folosit special atunci cnd serializarea unui anumit n a cmp, care ar duce la mult spaiu de stocare consumat, ar putea calculat a t mod automat la deserializare (spaiu vs. procesor). n t

Bibliograe
[1] C# in depth, Manning, Jon Skeet, 2008 [2] Programming C#, OReilly, Jesse Liberty, 4th edition, 2005 [3] Microsoft C# 2005 Step by Step, John Sharp, Microsoft Press, 2005 [4] Core C# and .NET, Stephen C. Perry, Prentice Hall, 2005 [5] Pro ASP.NET in C# 2005, Mathew MacDonald and Mario Szpuszta, Apress, 2005 [6] C# Language Specication, ECMA TC39/TG2, Octombrie 2002 [7] Professional ADO.NET Programming, Wrox, 2001 [8] LINQ for Visual C# 2008, Fabio Claudio Ferracchiati, Apress, 2008 [9] PLINQ, Daniel Moth, 2009.

333