select new { Category = cat , Name = prod.Name } ;
In exemplul de mai sus,se vor reuni elementele cat din categories cu
cele prod din products,daca respecta conditia cat=prod.Category si se va
forma o secventa noua de elemente de tip var in care se va arhiva pentru
fiecare element: Category=cat si Name = prod.Name.
Operatiile de tip join sunt aparent simple,dar pot produce numeroase
erori si exceptii,datorate operatiilor cu tipuri diferite de data.Din
acest motiv,este recomandabil sa fie evitate de catre incepatori,sa sa
fie utilizate formule standard,transformate pentru necesitatile din
program (cautati exemple in care se prelucreaza acelasi tip de date si
se utilizeaza acelasi gen de selectie cu cel dorit).Daca construiti
singuri aceste expresii,verificati cu atentie toate valorile posibile din
domeniul de definitie al fiecarui tip de data utilizat.Acest gen de
operatii este principalul generator de erori de executie si de dureri de
cap pentru programator.
Daca este absolut necesar,expresiile pot contine si selectii intricate.
Fiecare noua formula de selectie va incepe cu clauza from si se va termina
cu clauza select.Si acest gen de solutie este mai bine sa fie evitat,
deoarece verificarea si depanarea sunt extrem de greu de controlat.In
toate cazurile este mai simplu daca se utilizeaza doua selectii succesive,
decat o expresie cu doua selectii intricate.
-58-
Un exemplu complet de selectie si reformatare a datelor va contine
obiecte cu mai multe proprietati si metode si o formula de selectare a
celor ce respecta o anumita conditie.
EXEMPLU:
using System;
using System.Linq;
using System.Collections.Generic;
public class StudentClass { public string Nume { get; set; }
public List Punctaj; }
protected static List students = new List {
new Student { Nume = "Albu Mihai",
Punctaj = new List{ 79,82,81,79 }},
new Student { Nume = "Pop Maria",
Punctaj = new List{ 99,76,90,94 }},
new Student { Nume = "Pop Victor",
Punctaj = new List{ 93,72,80,87 }},
new Student { Nume = "Georgescu Lucia",
Punctaj = new List{ 97,79,85,82 }},
new Student { Nume = "Ionescu Ion",
Punctaj = new List{ 85,92,91,90 }},
new Student { Nume = "Barbu Constantin",
Punctaj = new List{ 96,75,91,60 }}
};
public void Rezultate(int exam,int score) {
var selectie = from student in students
where student.Punctaj[exam] > score
select new { n1 = student.Nume,Nota = student.Punctaj[exam]};
foreach (var item in selectie)
{ Console.WriteLine("{0,-25}{1}",item.n1,item.Nota); }
}}
public class Program {
public static void Main() {
StudentClass sc = new StudentClass();
Console.WriteLine("Studentii promovati la primul examen sunt: ");
sc.Rezultate(0,90);
Console.WriteLine("Studentii promovati la al doilea examen sunt:");
sc.Rezultate(1,80);
Console.ReadLine();
}}
Salvati fila cu numele Linq5.cs si compilati.
In toate exemplele de mai sus,datele au fost preluate direct din
memoria de operare.In aplicatiile reale,datele sunt preluate din file
text,XML,HTML sau din tabele si baze de date.In acest caz,sunt necesare
si un set de operatii auxiliare: localizarea resursei in retea,crearea
unei conexiuni fixe cu resursa,deschiderea filei,citirea filei,conversia
sau reformatarea datelor,formarea de colectii primare...etc.
Tehnologia LINQ permite solutii extrem de versatile pentru orice tip
de data,preluat din orice tip de resursa.In plus,aceasta tehnologie
confera si un plus de siguranta in exploatare,daca formula de preluare
si selectie a datelor se arhiveaza intr-o fila DLL,la care utilizatorul
nu are acces direct.
-59-
NAMESPACES
In cadrul activitatii de operare si programare a calculatoarelor,cea
mai importanta componenta este organizarea si gestiunea memoriei.Anual
se produc zeci de mii de resurse software,ce ocupa volume din ce in ce
mai mari de memorie inscriptibila.Organizarea tuturor acestor resurse se
face prin blocuri de memorie denumite,cunoscute sub numele de namespaces.
Fiecare program,aplicatie,resursa software,driver sau container de date,
este inclus intr-un astfel de spatiu de memorie denumit.Chiar si atunci
cand procesorul opereza asupra datelor,creaza automat un astfel de bloc
de memorie temporar,cu un nume de cod generat automat,in care se vor face
toate operatiile de gestiune interna a proceselor.Aceste spatii au o
importanta vitala.Ori de cate ori un bloc de date este mai mare decat
tamponul de memorie alocat in memoria de operare,procesorul va trebui sa
fragmenteze acest bloc in calupuri mai mici,ce vor fi procesate succesiv.
Daca se lucreaza in mediu de multiprocesare,atunci procesorul va avea de
gestionat mai multe astfel de fragmente de cod,ce vor fi prelucrate in
functie de prioritatea thread-ului de executie.Pentru a simplifica la
maximum aceste operatii,programatorii experimentati prefera sa fragmenteze
ei codul sursa,in module astfel dimensionate,incat sa poata fi executate
global.Astfel,cunoscand dimensiunea medie a tampoanelor de memorie din
procesor si din sistemul de operare,se poate calcula dimensiuea optima a
modulelor din program.Exemplul cel mai simplu este date de tabelele si
bazele de date.Presupunand ca memoria RAM este de 32 Mb si baza de date
este o arhiva de 800 Mb,se poate crea un singur tabel de 800 Mb,sau se
pot crea 100 de tabele a cate 8 Mb.In cel de al doilea caz,incarcarea
unui tabel se va face instantaneu,in timp ce in primul caz,incarcarea
datelor poate sa treneze pana cand procesorul creaza un tampon temporar
in care fragmenteaza datele,pe care le prelucreaza apoi succesiv.
In concluzie,cu cat programul este format din blocuri functionale mai
mici,cu atat executia va fi mai rapida.Pentru a fragmenta programul se
utilizeaza spatii denumite.Aceste spatii vor forma fie biblioteci de
resurse de tip DLL,fie module executabile (assemblies).Tot namespaces se
utilizeaza si pentru a fragmenta memoria interna a unui program,atunci
cand se doreste impachetarea datelor in spatii cu vizibilitate redusa.
Crearea unui astfel de bloc de memorie se face cu ajutorul cuvantului
cheie "namespace" urmat de un identificator.
EXEMPLU: namespace BibliotecaMea { ... }
Toate datele incluse in blocul respectiv de memorie vor fi incluse
intre acolade.Intr-un astfel de bloc de memorie se pot include: un alt
namespace,clase,interfete,structuri,enumerari,clase delegat.Toate spatiile
denumite au implicit accesibilitate de tip public,ce nu poate fi
modificata sau restrictionata.Actualizarea datelor se poate face prin
operatii succesive.
EXEMPLU: namespace MyCompany.Proj1 { class MyClass { ...} }
namespace MyCompany.Proj1 { class MyClass1 { ...} }
Dupa cele doua declaratii succesive,blocul MyCompany.Proj1 va include
ambele clase: MyClass si MyClass1.
In mod similar,atunci cand se construieste o biblioteca DLL,se pot
adauga ulterior nenumarate clase,cu conditia sa fie declarate in acelasi
spatiu denumit (vezi exemplul de la optiunile de compilare).
-60-
Un astfel de bloc de memorie denumit,se incarca in memorie utilizand
cuvantul cheie "using".
EXEMPLU: using System;
incarca in memorie biblioteca DLL ce contine datele identificate prin
System.Datele incarcate cu using au vizibilitate doar in interiorul filei
din care s-a facut apelul (nu au vizibilitate in tot spatiul de memorie
alocat programului).Pentru a apela o clasa sau o structura din blocul de
memorie se utilizeaza operatorul punct (.) urmat de identificatorul
clasei respective:
EXENPLU: System.Console se refera la clasa Console din System.
Atunci cand blocul de memorie contine numeroase structuri de date
intricate,se poate crea o denumire alias,pentru a simplifica accesul la
date:
EXEMPLU: using m1 = Biblioteca1.Clasa1.Subclasa2.Metoda1;
in continuare se poate lucra in program cu identificatorul m1.
Daca un bloc de memorie contine mai multe spatii de memorie intricate,
apelul prin using,nu ofera acces la spatiile de memorie intricate ci doar
la datele din blocul respectiv de memorie.Pentru a avea acces si la
blocurile intricate,acestea vor trebui sa fie incarcate explicit:
EXEMPLU: using System;
using System.Collections;
Pentru a vizualiza mai usor aceasta situatie considerati ca fiecare
namespace este un folder.Pentru a avea acces la sub-foldere este necesar
sa se specifice calea completa de acces.
Exista doua feluri de namespaces:
1. -user defined -cele declarate de programator
2. -system defined - cele create automat de sistem,pentru controlul si
gestiunea interna a proceselor (au un cod alfanumeric aleator).
In mod normal,sistemul de operare elibereaza automat toate aceste
blocuri de memorie create automat,dar exista si numeroase situatii in
care sistemul este intrerupt inainte de a putea elibera memoria si
aceste blocuri de memorie paraziteaza memoria cu date inutile.Din acest
motiv,este recomandabil ca sistemul sa fie "deparazitat" (debugg) din
cand in cand.Exista si programatori care prefera sa creeze rutine speciale
prin care controleaza strict eliberarea memoriei,dupa fiecare executie.
Pentru a putea avea acces la intregul spatiu de memorie alocat unui
program se poate utiliza cuvantul cheie global.Orice referinta spre
spatiul global de memorie,sau spre un namespace se face prin operatorul
(::) in loc de (.).
Exemplu: global::System.Console.WriteLine("text");
sau class TestClass : global::TestApplicatie ;
Cu alte cuvinte,operatorul :: se utilizeaza pentru namespaces si pentru
spatiul global de memorie,in timp ce operatorul punct se utilizeaza pentru
tipurile de data sau membrii inclusi intr-un namespace (clase,structuri,
interfete....etc.).Cea mai frecventa utilizare pentru operatorul :: este
pentru a implementa o interfata.
Modul in care sunt gestionate spatiile de memorie,defineste cel mai
clar maturitatea si experienta unui programator.Nu se pot formula "reguli
de aur".Solutiile optime difera atat in functie de sistemul de operare si
configuratia hardware,cat si in functie de aplicatia propriu zisa : mono
sau multiprocesare,procesare paralela,networking...etc.
-61-
NULLABLE TYPES
In limbaj C#,toate tipurile valorice sunt stricte,adica nu pot accepta
decat valori incluse in domeniul de reprezentare.Exemplu: tipul Int32
poate accepta orice valoare cuprinsa intre -2147483648 si 2147483647,dar
nu poate accepta valoarea null.
EXEMPLU: int x = null;
Console.WriteLine("x= {0}",x);
va genera o eroare de compilare de genul:
error CS0037: Cannot convert null to int because it is a non-nullable type
Exista insa si numeroase situatii in care este esential ca o variabila
de tip valoric sa poata accepta si tipul null.De exemplu,atunci cand se
citesc valori dintr-o baza de date sau dintr-o resursa oarecare,exista si
posibilitatea ca datele citite sa nu existe.In acest caz,functia prin
care se citesc datele va returna o valoare null.
Pentru a corecta aceasta limitare,C# introduce o structura speciala
denumita System.Nullable.Orice tip de data derivat din System.Nullable
este o data de tip nullable si poate accepta si valoarea null.Pentru a
declara un astfel de tip,se poate utiliza o formula de genul:
System.Nullable variabila
sau prescurtat
T? variabila
Cu alte cuvinte,daca se adauga semnul intrebarii dupa tipul de baza,
data creata va fi de tip nullable.
EXEMPLU:
using System;
class ProgramTest {
static void Main() {
System.Type type = typeof(int?);
Console.WriteLine(" type este din tipul: {0}",type);
int? a = 10;
int? b = null;
Console.WriteLine("a= {0}",a);
Console.WriteLine("b= {0}",b);
a=a+b;
Console.WriteLine("a+b= {0}",a);
Console.ReadLine();
}}
Salvati fila cu numele Nullable1.cs si compilati.
Datele din tipul int? vor accepta toate valorile tipului Int32,la care
se adauga si valoarea null.Daca se fac operatii,tipul null are precedenta.
Conversia din tipul ordinar in tipul nullable,este implicita:
EXEMPLU: bool? a = null;
bool b = true;
a = b; // a primeste valoarea True
Daca se compara intre ele date de tip nullable,orice valoare null va
returna false.Acest lucru este esential atunci cand se construiesc bucle
de tip IF( nullable ).
EXEMPLU: int? num1 = 10;
int? num2 = null;
if (num1 >= num2) { .....} // este evaluata false
-62-
Daca se compara doua date de tip nullable care au valoarea null,se va
returna valoarea True.
Operatorul ?? se utilizeaza pentru a specifica valoarea default ce se
returneaza atunci cand o valoare de tip nullable se atribuie unei date
de tip non-nullable.
EXEMPLU: int? x = null;
int? y = null;
int z = x ?? y ?? 0;
Unde ultima expresie se citeste astfel: z primeste valoarea lui x,sau
valoarea lui y,sau valoarea zero daca atat x cat si y sunt null.
Este esential sa fie desemnata o valoare default compatibila,atunci
se face conversia din tipul nullable in tipul ordinar.
Structura System.Nullable are urmatorii membrii: Nullable,HasValue,
Value,Equals,GetHashCode,GetType,GetValueOrDefault.Metodele se pot apela
pentru diverse operatii sau conversii:
EXEMPLU:
using System;
class ExempluTest {
public static void Main() {
float? a = 12.34f;
float? b = -1.0f;
Console.WriteLine("Valoarea versus valoarea default: ");
Display("A1",a,b);
b = a.GetValueOrDefault();
Display("A2",a,b);
a = null;
b = a.GetValueOrDefault();
Display("A3",a,b);
a = 12.34f;
b = -1.0f;
Console.WriteLine("Valoarea sau valoarea default atribuita: ");
Display("B1",a,b);
b = a.GetValueOrDefault(-222.22f);
Display("B2",a,b);
a = null;
b = a.GetValueOrDefault(-333.33f);
Display("B3",a,b);
Console.ReadLine();
}
public static void Display(string title,float? x,float? y)
{ Console.WriteLine("{0}) a= [{1}], b= [{2}]",title,x,y); }
}
Salvati fila cu numele Nullable2.cs si compilati.
In exemplul de mai sus,valoarea default atribuita a fost fie zero,daca
nu se specifica nici o valoare in GetValueOrDefault() fie o valoare data.
Tipul nullable este esential atunci cand o variabila primeste in
timpul executiei valori ce nu sunt intotdeauna definite.Prin acest tip
de data,se extinde foarte mult aria de executie a functiilor de cautare
a unui anumit tip de data,intr-o resursa,atunci cand rezultatul cautarii
este inpredictibil.Pentru detalii,consultati intreaga documentatie pentru
structura System.Nullable.
-63-
UNSAFE CODE
In timpul executiei unui program,pot sa apara si situatii ce nu pot fi
anticipate in momentul compilarii.Toate codurile ce pot genera astfel de
situatii neprevazute au fost denumite generic coduri nesigure,sau "unsafe
code".Aplicatiile platformei .Net sunt proiectate mai ales pentru a putea
fi utilizate in mediu de retea,in regim de multiprocesare,unde cea mai
mica eroare de executie se poate amplifica exponential,sau poate afecta
un numar oarecare de utilizatori.Din acest motiv,proiectantii limbajului
C# au decis sa excluda de la compilare toate codurile ce se incadreaza in
aceasta categorie.Cele mai frecvente situatii de acest gen,sunt generate
de pointeri si opertiile cu pointeri.Exemplu: se creeaza automat pointeri
spre diferite adrese si dupa utilizarea lor nu sunt eliminati din memorie
(in C# eliberarea memoriei se face automat doar pentru obiecte).Astfel
memoria va fi fragmentata cu numeroase variabile parazitare.In alte
situatii,se ridica un pointer spre o adresa de memorie,apoi se elibereaza
resursa si ramane un pointer spre o adresa care nu exista.Orice apel al
unui astfel de pointer va determina o bucla infinita de cautare a adresei.
Totusi,exista si numeroase situatii de programare in care este foarte
comod sa se opereze cu si asupra pointerilor.In plus,exista numeroase
coduri scrise in C++,ce pot fi extrapolate si in C#.Pentru a rezolva toate
aceste situatii,codurile nesigure se marcheaza prin cuvantul cheie UNSAFE,
apoi se compileaza adaugand si optiunea /unsafe la comanda de compilare.
Daca se utilizeaza cuvantul cheie UNSAFE in fata unei clase,intreaga
clasa va fi compilata ca si cand ar contine coduri nesigure.In mod similar
se poate marca o interfata,o clasa delegat,o metoda,o proprietate,un
eveniment,un indexator,un operator,un constructor sau un destructor,sau
un bloc oarecare de date inclus intre doua acolade.
Daca se utilizeza blocuri unsafe,se pot utiliza si date de tip
pointer ce se declara adaugand la tipul de data un asterix (*).
Exemple: byte* = pointer to byte int* = pointer to int ...etc.
Daca se utilizeaza doua asterixuri este un pointer spre alt pointer:
EXEMPLU: int** = pointer spre pointer spre int
Practic se utilizeaza aceleasi conventii ca si pentru limbajul C++.
Orice cod preluat din C++,poate fi inclus intr-o bucla unsafe.
EXEMPLU: using System;
class Conversie {
static void Main() {
long numar = 1024;
unsafe {
for (int x=0; x<15; x++) {
numar *= 3;
byte* p = (byte*)&numar;
Console.Write("Numarul: {0} ",numar);
Console.Write(" :conversia in bytes:");
for (int i =0; i < sizeof(long); ++i)
{ Console.Write(" {0:X2}",*p); p++; }
Console.WriteLine(); }
Console.ReadLine()}
}}}
Salvati ca Unsafe1.cs si compliati cu: csc /unsafe Unsafe1.cs
-64-
La executie,puteti observa ca la ultima bucla,valoarea rezultata este
in afara domeniului de reprezentare,astfel ca valoarea returnata nu este
corecta.Un astfel de cod nesigur nu poate fi implementat decat intr-o
bucla UNSAFE.
Exemplul de mai sus,contine un simplu bloc de date "unsfe".Sunt insa
si situatii in care se declara o clasa intreaga unsafe:
EXEMPLU: using System;
unsafe class TestCaracter {
static void Main() {
char c1 = "|";
char* pChar = &c1;
void* pVoid = pChar;
int* pInt = (int*)pVoid;
Console.WriteLine("Caracterul este = {0}",c1);
Console.WriteLine("Adresa este = {0:X2}",(int)pChar);
Console.WriteLine("Valoarea pointerului = {0}",*pChar);
Console.WriteLine("Numeric = {0}",*pInt);
Console.ReadLine();
}}
Salvati ca Unsafe2.cs si compilati cu: csc /unsafe Unsafe2.cs
In acest exemplu,nu exista nici un cod nesigur,dar se utilizeaza mai
multi pointeri si operatii cu pointeri.Toate operatiile de acest gen,
trebuie incluse in bucle UNSAFE,pentru a atrage atentia depanatorului de
sistem,asupra unei eventuale cauze de malfunctie.Pentru a prelua adresa
unei expresii,se utilizeaza operatorul ampersand(&).Un alt exemplu tipic,
implica utilizarea pointerilor pentru a prelua valori de la o adresa:
EXEMPLU: using System;
class TestAdresa {
static void Main(){
int numar;
unsafe {
int* p = &numar;
*p = 0x2a3f;
Console.WriteLine(" p pointeaza valoarea: {0:X},*p);
Console.WriteLine(" la adresa: {0}",p->ToString());
}
Console.WriteLine("Numarul pointat este: {0}",numar);
Console.ReadLine();
}}
Salvati ca Unsafe3.cs si compilati cu: csc /unsafe Unsafe3.cs
In acest caz,codul este nesigur deoarece se poate atribui pentru *p
o valoare situata in afara domeniului de reprezentare pentru int.
(EXEMPLU: *p = 0xFFFFFFFF ).Daca valoarea este introdusa inainte de
compilare,va genera o eroare de compilare,dar daca este introdusa de
catre utilizator,in timpul executiei,va genera o eroare de executie.
Mai observati in acest exemplu,ca apelul unui membru al pointerului
se face prin operatorul -> in loc de punct. (Exemplu: p->ToString() )
Nu este obligatoriu ca un cod nesigur sa returneze date nereprezentabile.
Este suficient daca se creaza conditii potentiale pentru astfel de
situatii.Blocurile UNSAFE nu se utilizeaza de rutina,ci sunt o solutie
extrema,pentru situatii experimentale sau exceptionale.
-65-
DOCUMENTATIA XML
Daca programele sunt mai ample,sau contin coduri cu operatii complexe,
este recomandabil sa fie adaugate si scurte comentarii explicative,ce vor
fi utile pentru orice alt programator care depaneaza sau modernizeaza
programul.Aceste comentarii trebuie sa explice cat mai scut si clar,ce
contine codul respectiv.
Comentariile simple nu sunt suficiente in toate situatiile.Daca fila
respectiva urmeaza sa fie impachetata sub forma de biblioteca DLL sau sub
forma de executabil si nu doriti sa distribuiti si codul sursa,se poate
crea o fila speciala de tip XML,in care includeti toate informatiile
necesare.Aceasta fila,poate fi creata automat in timpul compilarii,daca
se utilizeaza si optiunea /doc si daca fila sursa contine niste comentarii
speciale.Aceste comentarii speciale se introduc prin:
/// - urmate de comentariul dorit pe o singura line
sau prin /** urmat de comentariu pe mai multe linii
iar comentariul se incheie prin grupul */
Fiind vorba despre o fila in format XML,se pot adauga si tag-uri,penrtu
a facilita selectia datelor dorite,dupa o anumita formula CSS.Se pot
utiliza orice fel de tag-uri,dar exista un set de tag-uri conventionale ce
pot fi recunoscute de orice alt programator.Aceste tag-uri conventionale
sunt: ,
, , ,
, , , ,
, , ,
, , ,
, , si .
Fiecare tag se utilizeaza intr-un anumit context.De exemplu,se poate
utiliza text pentru a delimita in cadrul textului o linie de cod
oarecare,sau se poate utiliza text.... pentru a delimita
mai multe linii de cod.Daca se utilizeaza aceasta conventie,utilizatorul
va putea extrage din documentatie doar codurile exemplificative,selectand
doar tag-urile ... .
EXEMPLU:
using System;
// compilare cu csc Xmldoc1.cs /doc:Documentatie.xml
/// text pentru clasa TestClass
public class TestClass
{
/// DoWork este o metoda din clasa TestClass
Dostları ilə paylaş: |