Concepte .NET

Stack & Heap

Cand se declara o variabila intr-o aplicatie .NET, aceasta aloca o zona de memorie pentru a stoca respectiva variabila. O aplicatie .NET cunoaste doua tipuri de memorie, memoria statica (stack memory sau mai simplu stack sau stiva) si memoria dinamica (heap memory sau mai simplu heap).

Variabilele de tip valoare sunt alocate pe stiva iar cele de tip referinta pe heap. Daca insa, o variabila de tip valoare este membra a unei valori referinta, atunci ea este alocata tot pe heap.

Parametrii unei metode precum si toate variabilele locale sunt alocate pe stiva. In cazul variabilelor de tip referinta, se aloca pe stiva doar un pointer spre o zona in heap, unde va fi stocata variabila respectiva. Acest lucru poate fi urmarit in imaginea de mai jos.

In imaginea de mai sus putem observa cum evolueaza stiva si heap-ul, in functie de executarea codului linie cu linie. In prima line de cod se declara si initializeaza o variabila 'a' de tip intreg. Acest lucru duce la alocarea pe stiva a unei zone de memorie suficient de mare incat sa poate fi stocata o valoare de tip intreg iar in aceasta zona se va stoca valoarea 1. In linia a doua, se declara o alta valoare de tip intreg, 'b', care este initializata la valoarea 2. La fel ca mai inainte, se va aloca pe stiva o zona de memorie care va contine valoarea 2. In linia a treia lucrurile incep sa se complice. Se declara o variabila de tip referinta si se instantiaza. Prin declararea respectivei variabile se va aloca pe stiva o zona de memorie suficient de mare, care sa contina un adresa unei alte zone de memorie (heap). In momentul in care se instantiaza respectiva variabila (prin intermediul operatorului new), se va aloca in memoria heap o zona suficient de mare incat sa poate fi stocata o variabila de tip Cls iar pointer-ul de pe stiva va contine adresa acestei zone de memorie dinamica. Cand se iese din scop, adica la terminarea metodei, elementele din stiva vor fi sterse insa heap-ul ramane nemodificat pana cand garbage collector-ul va sterge obiectul de tip Cls.



Tipuri valoare si referinta

Tipurile valoare sunt tipuri care stocheaza datele sale la aceeasi locatie cu declaratia sa. Tipurile valoare sunt alocate de regula pe stiva, exceptie facand doar tipurile valoare care sunt parte a unui tip referinta, care sunt alocate pe heap impreuna cu tipul referinta care il gazduieste. Nu stiu daca definitia data este cea mai buna, insa vom vedea din urmatoarea figura cum sta treaba.
Se poate observa din figura de mai jos, ca am declarat doua variabile de tip intreg, a si b. Ambele contin valoarea 1. Tipul int fiind un tip valoare, este alocat pe stiva. In ambele cazuri, si a si b vor avea rezervata pe stiva propria lor zona de memorie care contine valoarea 1. Deci, stiva va contine doua intrari distincte, care contin propria copie a valoarii 1. Astfel, daca variabilei b i se va atribui mai tarziu valoarea 2, variabila a va contine in continuare valoarea 1.

In cazul tipului referinta lucrurile stau un pic altfel. Tipul referinta este alocat in heap iar pe stiva se afla doar o referinta spre zona alocata in heap. Astfel, putem avea doua sau mai multe declaratii de tipuri referinta, care pointeaza spre aceeasi zona in heap, lucru care poate fi observat in figura de mai jos. Se observa in figura de mai jos, ca am declarat un tip referinta, cls1, si l-am instantiat. In urmatoarea linie de cod avem o noua declaratie a tipului referinta Cls, initializata cu instanta din prima linie de cod. Astfel, avem pe stiva cele doua referinte de tip Cls, care pointeaza ambele spre aceeasi zona de memorie dinamica (heap) unde sunt stocate datele obiectului de tip Cls. Astfel, daca obiectul cls1 se modifica, aceste modificari vor fi vizibile si-n obiectul cls2 deoarece modificarile se produc pe datele interne ale obiectului (campuri/fields sau proprietati/properties ale obiectului), care sunt alocate in heap iar cls1 si cls2 pointeaza spre aceasta zona de memorie.



Boxing & Unboxing

Boxing-ul si unboxing-ul permit unui tip valoare sa fie tratat ca un tip referinta, care este stocat in memoria heap. Boxing-ul reprezinta mecanismul de conversie a unui tip valoare intr-un tip referinta iar unboxing-ul este mecanismul invers, de extragere a unui tip valoare dintr-un tip referinta. Aceste doua mecanisme se realizeaza cu costuri mari de performanta datorita conversiei din tipul valoare in tipul referinta si viceversa. Aceasta conversie se realizeaza prin alocarea unei zone de memorie heap si crearea unei copii a tipului valoare in zona nou alocata, nascandu-se astfel un nou obiect de tip referinta. Aceasta "mutare" a obiectului de pe stiva in heap si viceversa este costisitoare din punct de vedere al performantei si ar trebui evitata.



Delegates

Delegate e un tip referinta care incapsuleaza o metoda (defineste semnatura unei metode). Delegates sunt similare pointerilor la functii din C++ dar sunt type-safe. Un delegate poate fi instantiat prin asocierea cu o metoda care are aceeasi semnatura cu delegate-ul. Prin instantierea unui delegate i se asociaza instantei acestuia o metoda cu semnatura compatibila cu definitia delegate-ului, iar mai apoi, metoda asociata poate fi invocata prin intermediul acestui delegate (invocand delegate-ul ca si cum am invoca direct metoda asociata).

Sa vedem acum un exemplu de delegate, instantierea si invocarea acestuia.

// Definire delegate
delegate int CalculationDelegate(int x, int y);

class Program
{
    static void Main(string[] args)
    {
        // Instantiere delegate prin asociarea metodei Add
        var subtract = new CalculationDelegate(Add);

        // Invocare delegate
        var result = subtract(90, 10);

        Console.WriteLine("Result: {0}", result);
    }

    static int Add(int x, int y)
    {
        return x + y;
    }
}

In exemplul de mai sus putem observa ca semnatura delegate-ului este identica cu semnatura metodei asociate Add. Delegate-ul este identificat de cuvantul cheie delegate, care prefixeaza declaratia.
Urmeaza instantierea delegate-ului prin asocierea cu metoda Add si invocarea instantei acestuia ca si cum am apela direct metoda Add.

Delegates sunt folositi ca sa se paseze metode ca argumentul altor metode. Metodele asociate pot fi metode statice sau metode instanta (in exemplul de mai sus am ales sa fie statica).
Delegates se mai folosesc la definirea metodelor callback.
Unui delegate i se poate asocia si o metoda anonima (anonymous method, metoda fara nume).



Metode anonime (Anonymous methods)

Odata cu versiunea C# 2.0, Microsoft a introdus conceptul de metoda anonima (anonymous method) prin care se poate defini o metoda anonima ca metoda asociata unui delegate. Astfel, se evita declararea unei metode asociate unui delegate, inserandu-se direct un bloc de cod la instantierea delegate-ului. Metodele anonime contribuie la reducerea complexitatii codului prin limitarea metodelor suplimentare asociate delegate-urilor.

Metodele anonime se declara folosind cuvantul cheie delegate. Mai jos prezint cateva exemple de metode anonime, luate don MSDN:

Exemplul 1:
// Creaza un handler pentru evenimentul Click
button1.Click += delegate(object sender, EventArgs e) { MessageBox.Show("Click"); };

Exemplul 2:
// Creaza un delegate
delegate void Del(string s);

// Instantiaza delegate-ul asociindu-i o metoda anonima
Del d = delegate(string s) { Console.Write("Hello " + s); };

Exemplul 3:
void StartThread()
{
    var t = new Thread (delegate()
                {
                    Console.Write("Hello, ");
                    Console.WriteLine("World!");
                });
    t.Start();
}



Expresii Lambda (Lambda Expressions)

In C# 3.0, Microsoft a introdus conceptul de expresie lambda (lambda expression). Expresia lambda este o functie anonima folosita pentru a crea delegate-uri sau expresii. Cu ajutorul expresiilor lambda se pot scrie functii locale care pot fi pasate ca argument sau pot fi folosite ca valoare returnata a unor apeluri de metode.

O expresie lambda consta dintr-o lista de parametrii (dupa caz, pot lipsi), operatorul lambda => si o expresie:

(lista_parametrii) => expresie_sau_bloc_declarativ

lista_parametrii contine o insiruire de nume de parametrii delimitate prin virgula. Parantezele pot lipsi doar daca expresia lambda are un singur parametru. Lista de parametrii poate contine si tipurile parametrilor.
Mai jos, prezint cateva exemple de expresii lambda.

(x, y) => x == y

(int x, string s) => s.Length > x

param => SomeMethod(param)

() => OtherMethod()

(x) => { var result = x * x; Console.WriteLine("{0}", result); return result; }

button.Click += (sender,args) => { /* alte declaratii si apeluri de metode */ }

Niciun comentariu:

Trimiteți un comentariu