Sunteți pe pagina 1din 307

Paradigme de programare

= un set de concepte, modele şi practici care descriu esenţa programării


• programare imperativă => este descris fluxul de control al întregii
procesări => se descrie modul în care se execută programul,
programele fiind o succesiune de comenzi pentru care ordinea de
execuție este foarte importantă și care utilizează atât date mutabile,
cât și imutabile (C++, Java, C#, etc.) => folosește instrucțiuni care
schimbă starea unui program
– programare procedurală (structurată) = un program este
conceput pe baza unei secvenţe de funcţii care cooperează între
ele în atingerea scopului dorit
– programare orientata obiect = programele sunt vazute ca fiind
nişte colecţii de obiecte care interacţionează unele cu altele în
atingerea scopului dorit
Paradigme de programare
• programare declarativă => exprimă pur și simplu logica procesării
=> scopul principal este de a descrie rezultatul dorit fără a explicita
în mod direct modalitatea de obținere a acestuia, utilizând doar tipuri
imutabile (Haskell, Prolog, Erlang, etc.) => un alt mod de a gândi
procesul de codificare care exprimă logica unui calcul fără a descrie
fluxul său de control
- programare logică
- programare funcţională
• unele limbaje se încadrează categoria limbajelor funcționale/logice
și sunt specifice domeniului (DSL): XML, HTML, SQL, CSS, etc.
Programarea procedurală
• se bazează pe descompunerea funcţională şi abordarea top-down
(de sus în jos)
• este focalizată aproape în întregime pe producerea de instrucţiuni
(porţiuni de cod) necesare rezolvării unei anumite probleme;
proiectarea structurilor de date nu reprezintă o prioritate în această
abordare
• funcţiile care manipulează aceleaşi structuri de date pot diferi ca şi
convenţii de numire, listă de parametri, etc. făcînd dificilă utilizarea
acestora iar codul astfel rezultat este greu de înţeles existînd de
multe ori porţiuni semnificative de cod duplicat
• codul rezultat este difícil de reutilizat în alte proiecte: datorită faptului
că proiectarea începe pornind de la o problemă concretă dată,
divizînd-o în părţi componente, tendinţa este aceea de a rezulta cod
specific acelei probleme particulare
• extinderea ulterioară a aplicaţiilor este dificilă
Programarea orientată pe obiecte
• reprezintă o tehnică de programare alternativă la programarea
structurată care se bazează pe conceptul modular de obiect. Un
obiect este un fascicul de informaţie care modelează un concept de
nivel înalt din domeniul problemei pe care o implementeză.
• abordare bottom-up (de sus in jos)
• programarea orientată pe obiecte, având la bază conceptul de
obiect şi conceptele fundamentale de încapsulare, moştenire şi
polimorfism, prezintă următoarele avantaje :
– paradigma transmiterii de mesaje între obiecte, caracteristică
programării orientate pe obiecte furnizează o sintaxă clară şi
consistentă pentru manipularea obiectelor;
– obiectele sunt prevăzute cu un set complet de funcţii necesare şi
suficiente pentru manipularea acestora
– încapsularea previne accesul neautorizat şi necontrolat asupra datelor
– moştenirea permite definirea de noi structuri de date pe baza altora
existente, reutilizând codul existent (şi testat)
Concepte fundamentale

• Incapsularea = previne accesul neautorizat asupra datelor =>


ascunderea informatiei
=> mentenanta mai simplă
• Mostenirea = permite definirea de noi structuri de date pe baza
celor existente, prin adaugarea de noi caracteristici si facilitati
=> promoveaza reutilizarea codului
• Polimorfismul = permite crearea de programe mult mai generale
=> promoveaza reutilizarea codului
=> utilizarea componentelor software in alte proiecte,
cu impact semnificativ asupra efortului si costurilor de dezvoltare
ale aplicaţiei
Exemplu – definirea unei clase cu
câmpuri de date şi proprietăţi
//Program.cs
Punct testpt = new Punct();
//Punct.cs
//setarea proprietatii X
internal class Punct
testpt.X = 5;
{
//setarea proprietatii Y
//campuri de date
testpt.Y = 7;
int x;
//afisare proprietati
int y;
Console.WriteLine

//proprietati get/set (" Coordonata x = "+testpt.X+


public int X { get => x; " Coordonata y = "+testpt.Y);
set => x = value;
}
public int Y { get => y;
set => y = value;
}
}
Proprietăţi
• în loc de a avea metode specifice de tipul set/get care să
seteze/returneze valoarea câmpurilor de date corespunzătoare ale
clasei, C# defineşte o sintaxa specifică pentru proprietăţi: de regulă
cu nume similar câmpului asociat dar cu literă mare
• sintaxa generalǎ pentru o proprietate de tipul read/write este
urmǎtoarea:
modificator-acces tip_return NumeProprietate
{
get { aici se returneaza o valoare }
set { aici se atribuie o valoare }
}
• modificator acces: proprietǎţile pot fi private sau publice
• proprietǎţile pot fi de tip read/write (get/set), read-only (doar get) sau
write-only (doar set)
• în C#, proprietǎţile nu pot avea parametri
Proprietăţi
• diferă conceptual faţă de câmpuri chiar dacă seamănă din
perspectiva utilizării: proprietǎţile oferǎ doar acces la date, dar nu
reprezintǎ datele propriu-zise
• proprietǎţile par sǎ atenueze diferenţa între cod şi date: pentru un
program care utilizeazǎ o clasǎ, proprietǎţile aratǎ ca nişte câmpuri
de date, chiar dacǎ ele reprezintǎ de fapt cod. În multe situaţii, o
proprietate publicǎ asigurǎ altor clase accesul la un câmp privat al
clasei
• proprietatea are, spre deosebire de câmp, avantajul cǎ poate face în
plus şi verificǎri de validitate
Proprietati auto-implementate
• valabile începând versiunea
C# 3.0
• proprietatile auto-implementate
furnizeazǎ o manierǎ mai
simplǎ si concisǎ de a de a internal class Punct
defini proprietati, atunci când {
implementarea acestora NU //proprietati auto implementate
necesitǎ cod aditional public int X { get; set; }
• compilatorul crează în spatele public int Y { get; set; }
proprietăţii un câmp privat care }
însă poate fi accesat doar prin
intermediul proprietǎtii (get/set)
Metode
• metodele unei clase reprezintă funcţii declarate în cadrul clasei şi care, de regulă,
realizează prelucrări asupra câmpurilor clasei. Sintaxa generală este:
modificator_acces tip_retur Nume_metoda(lista parametri)
{ …instructiuni… }
public void Afisare()
{ Console.WriteLine(X); Console.WriteLine(Y); }
• dacă implementarea metodei constă dintr-o singură instrucţiune, C# furnizează
aşa numitele “expression body methods”, care oferă o alternativă simplificată de
definire a metodelor: acoladele şi instrucţiunea return nu mai sunt necesare a fi
scrise explicit
modificator_acces tip_retur Nume_metoda(lista parametri) => instructiune;
public void AfisareX() => Console.WriteLine(X);
Structura generală a unei clase
• câmpuri de date
• proprietăţi
• metode – inclusiv constructori şi destructor
• elemente de indexare sau indexatori (indexers)
• evenimente/delegaţi
• operatori
• alte tipuri: eventual, alte clase încuibate
Spaţii de nume - namespace (1)
• un spaţiu de nume în MicroSoft .NET este asemănător cu un
container care permite identificarea unor entitǎți într-un anumit
context => existența unor entitǎți cu acelaşi nume, dar în contexte
diferite
• poate conţine o serie de elemente precum: clase, structuri,
interfeţe, enumeratori, delegaţi
• utilizat pentru organizarea ierarhică a programelor, care are ca şi
consecinţă directă evitarea conflictelor de nume în cadrul unui
proiect complex (nume de clase, funcţii, variabile) => spaţiile de
nume permit de asemenea atribuirea de nume existente deja în
.NET Framework în cadrul programelor dezvoltate
• conceptul de spaţiu de nume (namespace) faciliteazǎ de
asemenea utilizarea bibliotecilor de clase de la diverşi furnizori şi
care ar putea conţine clase cu nume similare.
Spaţii de nume - namespace (2)
namespace Compania1.BibliotecaUtila
{
using UnAltSpatiuDeNume;
..
class ClasaUtila {....}
}
namespace Compania2.BibliotecaExtinsa
{
..
class ClasaUtila {....}
}
Compania1.BibliotecaUtila.ClasaUtila
Compania2.BibliotecaExtinsa.ClasaUtilǎ
• directiva using utilizată pentru a se specifica, la începutul programului, la
ce spaţiu sau spaţii de nume se face referire (se doreşte utilizarea claselor
incluse în cadrul acelor spaţii de nume)
using Compania1.BibliotecaUtila
• spațiile de nume pot utiliza, la rândul lor, alte spații de nume:
using UnAltSpatiuDeNume;
Spaţii de nume - namespace (3)
• în MicroSoft .NET, fiecare program este creat împreună cu un spaţiu de
nume implicit, aşa numitul spaţiu de nume global. În cadrul programului însă
pot fi create şi alte spaţii de nume, fiecare cu un nume distinct, şi conţinînd
propriile clase, funcţii, variable sau chiar alte spaţii de nume, al căror nume
trebuie să fie unic în cadrul spaţiului de nume respectiv

• MicroSoft .NET defineşte peste 90 de spaţii de nume care încep cu cuvîntul


System şi 5 spaţii de nume care încep cu cuvîntul MicroSoft: System,
System.Drawing, System.Windows.Forms, etc. - fiecare clasǎ definitǎ în
cadrul unui proiect va fi plasatǎ în cadrul unui spaţiu de nume propriu
proiectului şi se specificǎ prin directiva using ce spaţii de nume utilizeazǎ
clasa respectiva

• nu existǎ neapǎrat corespondențǎ directǎ între fişier şi spațiul de nume: un


fişier poate include mai multe spații de nume:
fisier.cs => namespace A {}
namespace B {}
namespace C {}
Spaţii de nume - namespace (4)
• .NET folosește spații de nume pentru a-și organiza numeroasele
clase din biblioteca de clase => fiecare tip de aplicaţie dezvoltată pe
platforma .NET utilizează biblioteci specifice (spaţii de nume) care
sunt incluse în cadrul proiectului prin directive using.
• spaţiile de nume se utilizează şi pentru declararea propriilor spații de
nume prin intermediul cărora se poate controla domeniul de aplicare
al numelor de clase și metode în proiecte de programare mai mari
=> fiecare proiect se crează implicit într-un spaţiu de nume propriu
Includerea spaţiilor de nume -
directiva using
//clasa Program cu metoda Main generate in .NET Framework style
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleAppNetFramework
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Includerea spaţiilor de nume -
implicit
// clasa Program generata in .NET 6
Console.WriteLine("Hello, World!");

• noua variantă folosește caracteristici recente C# care simplifică codul


• cele două forme reprezintă același program => ambele sunt valide cu
C# 10.0.
• când se utilizeaza versiunea mai nouă, trebuie doar scris corpul
metodei Main => compilatorul sintetizează o clasă Program cu o
metodă Main și plasează toate instrucțiunile scrise în acea metodă
Main; compilatorul generează includerea celelorlalte elemente
• se utilizează de asemenea directive using implicite (implicit using
directives) care sunt adăugate de către compilator în mod specific cu
tipul de proiect creat (using System; using System.IO;using
System.Collections.Generic;….)
Includerea spaţiilor de nume -
implicit

La crearea unui proiect nou, la pagina


de configurare a informațiilor
suplimentare => Do not use top-level
statements => pentru utilizare varianta
nesimplificată
Includerea spaţiilor de nume -
implicit
• dacă este necesară utilizarea de directive using care nu sunt incluse
implicit, se pot adăuga la fișierul .cs corespunzator
• dacă se doreşte eliminarea acestui comportament implicit și controlul
manual a spațiilor de nume din proiect => prin configurarea proiectului
– directiva ImplicitUsings (enable/disable):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Ansamble (assembly)
• un ansamblu (assembly) reprezintă un bloc funcţional al unei aplicaţii .NET,
unitatea logică care conţine cod destinat platformei .NET => formează
unitatea fundamentală (structura) de distribuire, versionare, reutilizare a
codului => un ansamblu este un fișier care este generat de compilator la
compilarea cu succes a unei aplicaţii .NET.
• ansamblele sunt stocate ȋn fişiere .exe => <OutputType>Exe</OutputType
sau .dll (Dynamic Link Library)
• aplicațiile simple sunt structurate sub forma:
1 ansamblu = 1 spațiu de nume (namespace) = 1 program
• pentru aplicații mai complexe existǎ însǎ variante:
1 ansamblu = mai multe spații de nume
1 spațiu de nume poate sǎ rezide în mai multe ansamble
• în momentul execuţiei, un tip de date există în interiorul unui ansamblu (şi nu
poate exista în exteriorul acestuia) => ansamblul conţine şi metadate ce
descriu tipurile respective şi care sunt folosite de către CLR (Common
Language Runtime)
• scopul ansamblelor este să se asigure dezvoltarea softului în mod plug-and-
play
Încapsularea
şi controlul accesului (1)
• încapsularea implică faptul că structura internă a obiectului este
ascunsă total sau parţial de obiectele din exteriorul său; accesul se
realizează prin intermediul aşa numitei interfeţe publice a obiectului
respectiv în mod controlat şi sigur.
• este de dorit să se ascundă starea internă a unui obiect faţă de
accesul direct din afara clasei prin intermediul aşa numiţilor membri
wrapper (metode, proprietăţi) care garantează consistenţa
informaţiilor accesate, controlul accesului putȃnd fi realizat prin
intermediul modificatorilor de acces asociaţi membrilor clasei
• încapsularea presupune reuniunea în cadrul claselor a structurilor de
date împreună cu codul necesar prelucrării acestor structuri
• implică modularitate, şi reprezintă mecanismul prin care se combină
câmpurile de date cu funcţii (metode, proprietăţi) care manipulează
câmpurile de date într-o manieră sigură şi controlată => încapsulare
într-o singură unitate=clasa
Încapsularea
şi controlul accesului (2)
Modificatorii de acces (access modifier) private, protected, public,
internal controlează accesul la membri (elementele) unei clase
astfel:
• private - elementele sunt direct accesibile numai din interiorul
clasei din care fac parte; în mod automat, dacǎ nu se specificǎ
nimic în acest sens, in C# elementele respective (câmpuri de
date, proprietăţi sau metode) se considerǎ implicit private; totuşi
este indicată precizarea explicită a acestuia
• internal – elementele sunt accesibile doar în cadrul ansamblului
din care fac parte
• protected - mai putin restrictiv decit private, permite în plus şi
membrilor claselor derivate să aibe acces direct la elementele
protected ale claselor strămoşi
Încapsularea
şi controlul accesului (3)
• public - în mod normal recomandat a fi utilizat numai pentru
metode sau proprietǎţi (nu şi câmpuri de date – încapsularea!), şi
care conferă elementelor proprietatea de a putea fi accesate in
mod direct, din orice secvenţă de cod (atât din interiorul clasei cât
şi din afara acesteia)
• protected internal - reprezintă o combinaţie a celor doi
modificatori adică şi protected şi internal: permite accesul din
cadrul aceluiaşi ansamblu dar şi din clase derivate care nu fac
parte din acelaşi ansamblu
• modificatorii de acces pot fi aplicaţi şi tipurilor de date (claselor)
însă tipurile de date acceptă doar doi modificatori de acces:
public şi internal (implicit); internal implică accesul numai la
tipurile definite în cadrul aceluiaşi ansamblu
Constructori
• un constructor este un bloc de cod care se executǎ atunci când se utilizeazǎ
cuvîntul cheie new pentru a crea o instanţǎ a unei clase
• rezultatul apelǎrii constructorului îl reprezintǎ crearea (alocarea) unei variabile
de tip referinţǎ
• nu este obligatoriu să se definească un constructor în mod explicit în cadrul unei
clase => atunci când nici un constructor nu este definit în cadrul unei clase,
compilatorul genereazǎ un constructor implicit, ca metodǎ publicǎ
• constructorii trebuie sa aibe acelaşi nume cu clasa însăşi a obiectului
• se definesc şi se utilizează în principiu ca funcţiile membre obişnuite; faţă de
acestea însă, constructorii sunt implicit apelaţi atunci când se crează obiecte ale
clasei respective
• constructorii nu returneazǎ nici un fel de valoare, nici macar de tipul void
• asemănător metodelor, constructorii pot avea multiple definitii, rezultând ceea ce
se numeste supraîncărcarea constructorilor (overloading); deosebirea dintre
aceştia şi selecţia efectivǎ a constructorului apelat la un moment dat este
realizatǎ de cǎtre compilator pe baza numǎrului şi/sau tipul parametrilor actuali
furnizaţi în momentul apelului
• constructorii nu se moştenesc de către descendenti ca alte metode
Constructori- lazy loading
• amânarea creării (alocării) şi inițializării unor obiecte care nu sunt
necesare până la momentul utilizării efective a lor => lazy loading
• alternativa implicită => eager loading => obiectele sunt încărcate în
memorie imediat după crearea lor
• avantaje lazy-loading:
– se minimizeaza timpul de start-up a unei aplicaţii
– aplicaţia consumă mai puţină memorie deoarece încărcarea se
face la solicitare
• implementare .NET: tipul Lazy<T> care poate fi utilizat pentru lazy
loading: o clasă wrapper care furnizează iniţializare lazy pentru orice
clasă
Constructorii impliciţi/fără parametri
internal class Punct
{ public int X { get; set; }
public int Y { get; set; }
}

• se pot obţine:
– prin implementarea explicită de către utilizator al unui constructor
simplu, fără nici un parametru; constructorii fără parametri implementaţi
explicit în cadrul clasei pot include (sau nu!) anumite secvenţe de
instrucţiuni (de regulă, iniţalizări)
//constructor fără parametri implementat explicit
public Punct() { …. }

– fie prin generarea implicită a acestuia de către compilator, atunci când


clasa respectivă nu are implementat explicit nici un constructor
//constructor generat implicit
public Punct() { }
Constructori cu parametri
• iniţializarea câmpurilor/proprietăţilor de date se face pe baza valorilor primite ca
şi parametri de către constructor
public Punct(int x, int y)
{
X = x;
Y = y;
}
• se pot utiliza şi în cazul constructorilor “expression body methods” dacă
implementarea constructorului constă dintr-o singură instrucţiune, constructorii
fiind de fapt un caz particular de metode; în mod particular însă, este permisă în
cazul costructorilor iniţializarea mai multor proprietăţi într-o singură
expresie/instrucţiune folosind sintaxa bazată pe tuple:
public Punct(int x, int y) => (X, Y) = (x, y);

//creare obiect folosind constructorul cu parametri


Punct testpt = new Punct(5,7);
Constructori de copiere
• atunci când se copiază un obiect într-un alt obiect, C# copiază de fapt
referinţa primului obiect în al doilea, ceea ce conduce la crearea a două
referinţe către acelaşi obiect
• dacă se doreşte însă crearea unei instanţe-copii ale unui obiect existent se
poate utiliza un constructor de copiere
• constructorul de copiere primeşte ca parametru un obiect din clasa
respectivă şi crează un al doilea obiect ca o copie a obiectului primit ca
parametru
public Punct(Punct p)
{
this.X = p.X;
this.Y = p.Y;
}
Exemplu
//cream un obiect Punct utilizind constructorul cu paramtri
Punct pt = new Punct(20, 30);
//cream un obiect Punct utilizind constructorul de copiere
Punct pt_copie = new Punct(pt);
//definim o referinta la Punct
Punct pt_alta_copie;
//atribuire de referinte
pt_alta_copie = pt;
//modificam valorile X si Y pentru pt
pt.X = 10;
pt.Y = 15;
Console.WriteLine("Punctul original de coordonate X = "
+ pt.X + " Y = " + pt.Y);
Console.WriteLine("Punctul copiat de coordonate X = {0} Y= {1} ",
pt_copie.X, pt_copie.Y);
Console.WriteLine("Punctul atribuit de coordonate X = {0} Y= {1} ",
pt_alta_copie.X, pt_alta_copie.Y);
Exemplu

pt pt_copie

x 20 x
y 30 y

pt_alta_copie ???
Exemplu – Laboratorul 1
student_01: Student date: Date_univ

nume
prenume
adresa
an_nastere
date_univ

student_01.date_univ = date;
student_02: Student student_02.date_univ = date;
nume
prenume
adresa
an_nastere student_02.date_univ = new Date_univ(date);
date_univ
Finalizers - Destructori
• Finalizers (cunoscuţi anterior ca destructori) sunt utilizaţi în procesul de
distrugere a unei instanţe a unui obiect => efectuează operaţii necesare de
“curăţare” a memoriei
• destructorii nu se moştenesc; de fapt, se poate defini un singur destructor
pentru o clasǎ, fǎrǎ parametri
• destructorii în C# nu pot fi apelaţi în mod explicit, ei sunt apelaţi automat;
• programatorul nu are nici un control asupra momentului cînd destructorul
este apelat pentru cǎ acest lucru este realizat de cǎtre colectorul de deşeuri
(garbage collector) care, dacǎ considerǎ cǎ un obiect poate fi distrus,
apeleazǎ destructorul (dacǎ existǎ).
• Garbage Collector => situațiile în care trebuie implementat un destructor
sunt destul de rare
~Punct()
{
//afisare in fereastra output
System.Diagnostics.Debug.WriteLine
("Acum s-a apelat destructorul clasei Punct!");
}
Destructorii şi metoda Finalize
• destructorii C# sunt convertiţi către un apel la metoda Finalize
(care nu poate fi apelată direct din C#) a clasei System.Object
~Nume_destructor()
{
// cod destructor
}

protected override void Finalize()


{ try
{
//cod destructor
}
finally
{
base.Finalize();
}
}
Finalizers - Destructori
• se va evita definirea de destructori fără cod => apel Garbage Collector =>
penalizare de performanţă => indiferent de faptul ca prezintă sau nu cod
care să realizeze eliberarea de resurse, dacă o clasă prezinta un destructor
(finalizer), atunci colectorul de deşeuri va rezerva un spatiu de memorie şi un
timp de execuţie necesar apelării destructorului
• pentru tipurile de date .NET - tipuri de date standard, sau tipuri proprii
scrise exclusiv folosind C# şi obiecte .NET nu este necesar (contraindicat)
crearea unui destructor
Mecanismul de colectare a deşeurilor
(garbage collection)
• dacǎ nimic dintr-un program nu mai face referire la blocul de memorie iniţial,
acesta devine candidat pentru operaţia de colectare a deşeurilor (sau eliberare a
memoriei neutilizate – garbage collection).
• MicroSoft .NET gestioneazǎ memoria prin intermediul unei rutine de colectare a
deşeurilor: tipurile referinţǎ sunt alocate în memoria heap gestionatǎ de
instrumentul Common Language Runtime CLR.
• rutina de colectare a deşeurilor urmǎreşte utilizarea obiectelor: când nu mai
existǎ referinţe cǎtre un obiect, aceasta va marca memoria utilizatǎ de obiectul
respectiv ca disponibilǎ pentru reutilizare, iar, ulterior, va curǎţa memoria heap şi
va realiza anumite operaţiuni de compactare a acesteia (deplasarea obiectelor
existente în memorie (!))
Mecanismul de colectare a deşeurilor
(garbage collection)
• rutina de colectare a deşeurilor utilizeazǎ un mecanism de tip generaţie în cadrul
cǎruia obiectele care “supravieţuiesc” unei colectǎri sunt trecute într-o generaţie
mai veche.
• folosirea mecanismului de colectare a deşeurilor izoleazǎ programatorul de
detaliile alocǎrii memoriei şi îl scuteşte de responsabilitatea eliberǎrii acesteia
• teoretic, programatorul nu cunoaşte momentul exact cînd rutina de colectare a
deşeurilor va fi lansată în execuţie; practic, se poate forţa lansarea în execuţie a
acesteia printr-un apel explicit la System.GC.Collect; un astfel de apel trebuie
realizat cu precauţie deoarece poate avea consecinţe nedorite asupra vitezei de
execuţie
Tipuri referinţã şi tipuri valoare
Tipuri valoare Tipuri referinţă
Sunt alocate în stivă (stack) Sunt alocate în heap şi sunt distruse
O variabilǎ de tip valoare se creazǎ în mod automat
atunci când se declarǎ o variabilǎ: de către colectorul de deşeuri
• de un tip numeric (int, uint, long, O variabilǎ de tip referinţǎ se creazǎ
double, float …) atunci când se declarǎ:
• de un tip boolean (bool) • un obiect dintr-o anumitǎ clasǎ
inclusiv clasa Object
• de tip enumerare (enum)
• o variabilă de tip interfaţǎ (interface)
• de tip structurǎ (struct)
• un tablou (array)
• un şir (string)
Practic, majoritatea tipurilor simple
• un delegat (delegatul este de fapt
în C# sunt de tip valoare: un obiect care conţine informaţii
bool, char, byte, despre o metodǎ care va fi apelatǎ
la apariţia unui anumit eveniment
sbyte, short, ushort, int, uint,
long, ulong, float, double, • o variabilă referinţă care un referă
decimal. nimic poate fi asignată cu valoarea
null (null reference)
Tipuri referinţã şi tipuri valoare
Tipuri nullable (Nullable types)
• Tipuri valoare nullable (nullable value types) => în versiunile iniţiale ale limbajului
C# nu era posibil ca un tip valoare să fie assignat cu valoarea null, acest lucru fiind
posibil doar pentru tipurile de tip referinţă => în anumite situaţii acest lucru poate
simplifica codificarea (fisiere XML sau JSON).
• pentru tipurile valoare nullable, Nullable nu adaugă overhead-ul aferent unui tip
referinţă (stocarea în heap, garbage collection) fiind în esenţă un struct.
int? x1 = null;
echivalent cu:
Nullable<int> x2 = null;
Tipuri nullable (Nullable types)
• Tipuri referinţă nullable (nullable reference types) => datorită faptului că multe
excepţii în aplicaţiile .NET erau de tipul NullReferenceException, începând cu
versiunea C# 8.0 au fost introduse aşa numitele tipuri referinţă nullable prin care se
evită aruncarea unor astfel de excepţii deoarece se verifică în prealabil valoarea
referinţei
string? s1 = null;
//aici nu se obtine exceptie!!
string? s2 = s1?.ToUpper();
echivalent cu:
string? s1 = null;
//verificare referinta s1 pentru a nu se genera o exceptie
if (s1 is not null) s2=s1.ToUpper();
• dacă în cazul tipurilor valoare se utilizează tipul Nullable<T>, în cazul tipurilor
referinţă compilatorul adaugă adnotării pentru tipurile nullable => atributul
Nullable asociat.
• setarea opţiunii de a avea tipuri referinţă nullable se poate realiza din fişierul
asociat proiectului: <Nullable>enable</Nullable>
Obiecte (tipuri) immutable
• un obiect (tip) imutabil este un obiect a cǎrui stare nu poate fi modificatǎ =>
un obiect de tip imutabil conţine doar membri a căror stare (valoare) nu
poate fi modificată după ce aceasta a fost iniţializată
• câmpurile unui obiect imutabil sunt de obicei declarate readonly şi sunt
inițializate prin constructor la crearea obiectului
• dupǎ aceastǎ inițializare valorile acestora nu mai pot fi modificate
• modificarea valorilor obiectului implicǎ de fapt crearea şi alocarea unui nou
obiect
• exemplu: tipul string
https://learn.microsoft.com/en-us/dotnet/api/system.string?view=net-7.0
Membri de tip const si readonly
• ȋn cadrul unei clase, pe lȃnga cȃmpurile de date variabile, mai pot exista
cȃmpuri de date constante const sau câmpuri de tip readonly
• constante (const) = nu ȋşi modifica valoarea
• readonly = valoarea constantă a câmpului respectiv nu este cunoscută şi nu
poate fi iniţializată de la început ca o constantǎ => iniţializarea unui cȃmp
readonly se poate face doar ȋntr-un constructor

internal class Persoana


{
//camp de date readonly
readonly DateTime data_nasterii;
//camp de date constant
const string CNP = "2780512054672";
//initializare camp date readonly - constructor
public Persoana(DateTime dn)
{ data_nasterii = dn; }
}
Clasa Object (System)
• Equals - indică faptul că două instanţe de tip Object sunt egale.
Implementarea implicită testează însă doar egalitatea referinţei (şi returnează
true dacă ambele referinţe punctează către acelaşi obiect => apelează
ReferenceEquals; pentru tipurile valoare este testată egalitatea la nivel de bit
=> clasele derivate trebuie să redefinească metoda pentru a-şi defini propria
modalitate de testare a egalităţii pentru obiectele proprii
• GetHashCode – utilizată pentru a implementa o funcţie de dispersie
(hashing) care returnează o valoare (cod) de hashing pentru obiectul curent;
de cele mai multe ori, mai multe câmpuri sunt implicate în calculul acesteia
(de exemplu SAU exclusive -XOR între acestea) => se generează un număr,
care ar putea fi utilizat în cadrul unei tabele de dispersie
• GetType - returnează tipul tipul curent, la rulare, al instanței curente
(de tipul Type)
• ReferenceEquals - determină dacă două referinţe indică spre aceleaşi obiect
• ToString - returnează un String care este reprezentarea sub formă de şir de
caractere a obiectului curent
• MemberwiseClone – creaza o copie superficială (shallow) a obiectului curent
https://learn.microsoft.com/en-
us/dotnet/api/system.object.memberwiseclone?view=net-7.0
Mecanismul împachetare/despachetare
(boxing/unboxing)
• împachetarea reprezintǎ transformarea unui tip valoare în tip referinţǎ,
creînd un nou obiect în memoria heap gestionatǎ care are aceleaşi cîmpuri
ca şi tipul valoare; instrumentul Common Language Runtime (CLR) creazǎ
un astfel de obiect şi îl iniţializeazǎ cu cîmpurile şi valorile din tipul valoare.
int i = 10;
i.ToString();
• compilatorul crează o instanţă a clasei Object care conţine valoarea
întreagă i (împachetare implicită). Apoi se realizează apelul către metoda
ToString folosindu-se instanţa creată a obiectului
int i = 10;
Object obj = i;
obj.ToString();
• despachetarea implicǎ operaţia inversǎ: adicǎ crearea unui tip valoare
dintr-un tip referinţǎ; despachetarea însă presupune transformarea
explicită dintr-un obiect într-un tip valoare:
int j = (int)obj;
Exemplu
//definire struct – tip valoare
public struct Valoare
{
public Valoare(int x, int y) { this.x = x; this.y = y; }
public int x { get; set; }
public int y { get; set; }
}

Valoare a = new Valoare(1,2);


//boxing - impachetare
Object o = a;
Console.WriteLine("Tipul variabilei o este" + o.GetType());
Console.WriteLine("Tipul variabilei a este" + a.GetType());
//unboxing - despachetare
if ((((Valoare)o).x == a.x) && (((Valoare)o).y == a.y))
Console.WriteLine("Impachetare/despachetare reusita!");
Mecanismul împachetare/despachetare
(boxing/unboxing)
• obiectul împachetat acţioneazǎ ca un container în cadrul cǎruia se
regaseşte o copie a tipului valoare pe care îl împacheteazǎ;
utilizarea funcţiei GetType() va returna acelaşi tip atât pentru
obiectele neîmpachetate cât şi pentru cele împachetate.
• în general, GetType() returneazǎ un obiect de tipul Type, o clasǎ
definitǎ în spaţiul de nume System.GetType()
• clasa Type permite obţinerea de informaţii despre un anumit
obiect, inclusiv metodele, cîmpurile de date şi proprietǎţile
acestuia
• operatorul typeof returneazǎ de asemenea un obiect de tipul
Type; diferenţa dintre cele douǎ constǎ din aceea cǎ GetType()
este aplicatǎ unui obiect iar typeof se aplicǎ unei clase
Împachetarea şi
conversiile de tip
• împachetarea şi despachetarea reprezintă transformări între tipul
valoare şi referinţă (obiect) în timp ce conversiile de tip transformă
tipul (aparent) al obiectului
• tipurile valoare sunt memorate în stivă în timp ce obiectele sunt
memorate în heap
• împachetarea preia o copie a tipului valoare de pe stivă în heap iar
despachetarea repune tipul valoare pe stivă
• conversiile de tip pe de altă parte nu realizează nici un fel de mutări
fizice ale obiectelor, doar schimbă modul în care acestea sunt
tratate de program prin modificarea tipului referinţei
Clase partiale
• ȋn mod tipic, o clasă rezidă ȋn general ȋntr-un singur fişier
• utilizȃnd partial este posibilă crearea unor clase (valabil şi pentru
structuri, metode sau interfeţe) care să se ȋntindă pe mai multe fişiere =>
compilatorul va genera ȋn final o singură clasă, cu conţinutul comun al
claselor parţiale, utilizarea membrilor acestora realizȃndu-se ca şi cȃnd
aceştia ar rezida ȋn acelaşi fişier

internal partial class Clasa_partiala


{
public void Metoda1()
{
Console.WriteLine("Metoda 1!");
}
Clasa_partiala c = new Clasa_partiala();
}
c.Metoda1();
//in alt fisier
c.Metoda2();
internal partial class Clasa_partiala
{
public void Metoda2()
{
Console.WriteLine("Metoda 2!");
}
}
Tablouri in C# (1)
Tablouri unidimensionale
int[] tab_uni = new int[2];
tab_uni[0] = 1;
tab_uni[1] = 2;
// alte posibilitati de initializare a tabloului
int[] tab_uni = new int[] { 1, 2 };
int[] tab_uni = { 1, 2 };

Obs. variabila tablou creată dispune de o serie de metode (clasa System.Array) prin care
se pot realiza diferite operaţii asupra elementelor tabloului.
Console.WriteLine( "Dimensiunea tabloului =
"+tab_uni.GetLength(0));

Tablourile multidimensionale rectangulare - reprezintă tablouri care au mai multe


dimensiuni fixate prin declaraţia tabloului :
//declarare tablou multidimensional rectangular 2x3 fara initializare
int[,] tab_bi_0 = new int[2, 3];
//declarare tablou bidimensional cu initializare
int[,] tab_bi = { { 1, 2, 3 }, { 4, 5, 6 } };
for (int i = 0; i < tab_bi.GetLength(0); i++)
for (int j = 0; j < tab_bi.GetLength(1); j++)
Console.WriteLine(tab_bi[i, j]);
Tablouri in C# (2)
Tablourile multidimensionale (jagged) - sunt de fapt tablouri multidimesionale dar cu
dimensiuni neregulate. Flexibilitatea în acest caz derivă din aceea că tablourile sunt
imlementate ca tablouri de tablouri

//creare tablou jagged fara initializare


int[][] tab_jag = new int[2][];
tab_jag[0] = new int[4];
tab_jag[1] = new int[6];

//creare tablou jagged cu initializare


int[][] tab_jag = new int[][]
{ new int[] { 1, 2, 3, 4 },
new int[] { 5, 6, 7, 8, 9, 10 } };

//accesare elemente tablou utilizind Length


for (int i = 0; i < tab_jag.Length; i++)
for (int j = 0; j < tab_jag[i].Length; j++)
Console.WriteLine(tab_jag[i][j]);
Membri statici
versus membri de instanţă
• un membru de instanţă este orice membru dintr-o clasă care NU este
marcat ca static: câmpuri, proprietăți, metode
• un membru de instanţă poate fi utilizat numai după ce a fost creată o
instanță a clasei respective: acest lucru se datorează faptului că membrii de
instanţă aparțin obiectului, deci un obiect trebuie să existe în prelabil
• un mebru static se crează marcându-l cu cuvântul cheie static
• câmpuri de date statice: acest lucru înseamnă că toate instanţele claselor
respective împart acelaşi membru static, practic se alocǎ o singurǎ datǎ
memorie, o singură zonă comună tuturor obiectelor clasei; fiecare instanţǎ a
clasei are acces la copia unicǎ a datelor statice.
• proprietǎţile şi metodele pot fi la rândul lor declarate statice: acestea sunt
de regulǎ utilizate pentru a prelucra datele statice ale clasei
• un membru static este apelabil chiar și atunci când nu a fost creată nicio
instanță a clasei => accesat de numele clasei, nu prin numele unei instanţe
create în prealabil
Utilizarea membrilor statici (1)
• proprietǎţile şi metodele statice (inclusiv constructorii) prezintǎ dezavantajul
cǎ nu pot utiliza în cadrul implementǎrii decât câmpurile declarate statice
ale clasei din care fac parte, neavând acces la niciuna din datele instanţelor
clasei respective (dacǎ existǎ vreuna)
• în cazul constructorilor declaraţi static, aceştia sunt apelaţi înainte ca orice
membru static al clasei respective să fie referit, astfel încât aceştia pot fi
utilizaţi la iniţializarea clasei, nu doar a obiectelor; constructorii declaraţi
static nu pot avea parametri
• constructorii declaraţi static nu pot avea modificator de acces şi nu pot fi
supraȋncărcati; constructorul va fi executat doar o singură dată spre
deosebire de constructorii de instanţă care se execută la crearea fiecărei
instanţe a clasei => anumite câmpuri statice care trebuie iniţializate dintr-o
sursă externă înainte de prima utilizare a clasei (setări/configurări iniţiale)
• metodele statice pot fi supraîncărcate (overloadiing), dar nu suprascrise
(overriding), deoarece aparțin clasei și nu unei instanțăe a clasei
• o clasă non-statică poate conține metode statice, câmpuri, proprietăți sau
evenimente
Utilizarea membrilor statici (2)
• membri statici se caracterizeazǎ deci prin faptul cǎ aparţin clasei propriu-
zise şi nu unui obiect al unei anumite clase => datele statice pot fi iniţializate
chiar dacā nu existǎ încǎ obiecte instanţiate, prin intermediul clasei; se
aplicǎ regulile generale de acces.
nume_clasa.membru_static
nume_clasa.nume_metoda_statica
• există clase, precum clasa System.Math, care conţin numai membri statici
• o clasa poate fi declaratǎ ca fiind staticǎ:
– toti membrii clasei trebuie sǎ fie statici (fie ca e vorba de campuri, proprietati sau
metode)
– clasa nu mai poate fi instanțiatǎ folosind new()
– nu se pot defini constructori de instanțǎ (doar constructori statici)
– clasa nu poate fi derivatǎ
• declararea unor membri statici este utilă, deoarece ea reprezintă soluţia
optimă în cazul în care obiectele unei clase folosesc date în comun,
reducîndu-se astfel numărul de variabile globale.
• utilizarea membrilor statici rezolvǎ numeroase situaţii în programare, însǎ
trebuie folosiţi cu precauţie
Exemplu (1)
internal class Punct
{
public int X { get; set; }
public int Y { get; set; }
public static Punct Centru { get; set; } = new Punct(0, 0);
public Punct()
{ }
public Punct(int x, int y)
{
this.X = x; this.Y = y;
}
//membru (metoda) de instanta
public int DistantaLaPatrat()
{
int distx = Centru.X - X; int disty = Centru.Y - Y;
return (distx * distx) + (disty * disty);
}
public static void ModificaCentru(int xc, int yc)
{
Centru.X = xc; Centru.Y = yc;
}
}
Exemplu (2)
Punct testpt = new Punct(5, 7);

//calcul distanta fata de centrul (0,0)


Console.WriteLine("Distanta la patrat fata de centru =
"+testpt.DistantaLaPatrat());

//modificare valoare camp static utilizand proprietati


Punct.Centru.X = 2;
Punct.Centru.Y = 2;
//calcul distanta fata de (2,2)
Console.WriteLine("Distanta la patrat fata de centru = " +
testpt.DistantaLaPatrat());

//modificare camp static prin intermediul metodei statice


Punct.ModificaCentru(3, 4);
//calcul distanta fata de (3,4)
Console.WriteLine("Distanta la patrat fata de centru = " +
testpt.DistantaLaPatrat());
This
• cuvântul cheie this este o variabilă locală care apartine oricarei instanţe ne-
statice a unui obiect şi reprezintă o referinţǎ spre obiectul însuşi; în
consecinţă this are sens şi poate apǎrea numai în definiţia unei metode sau
proprietǎţi non-statice definite în cadrul unei clase
• this nu necesită declarare şi rareori este referit in mod explicit; el este
utilizat în mod implicit pentru referinte catre membrii obiectului şi conţine
adresa instanţei curente a obiectului respectiv
• referirea la membri unui obiect prin intermediul lui this se poate face din
interiorul clasei prin:
this.metoda sau this.proprietatea
Moştenirea şi ierarhia de clase –
concepte de bază
• moştenirea reprezintă mecanismul care • la nivel de clase, C# suportă numai
permite crearea unei ierarhii de obiecte moştenire simplă: acest lucru
cu descendenţi, care moştenesc implică faptul că o clasă poate să fie
derivată dintr-o singură clasă de
accesul şi structurile de date ale bază
strămoşilor.
• prin intermediul moştenirii se pot X = clasa
reutiliza şi extinde clasele existente, de baza (parinte)
fără a rescrie codul original; diferenţa
fundamentală între limbajele de Partea mostenita
programare procedurale şi cele de la X
orientate pe obiecte o reprezintă tocmai Y = clasa
utilizarea conceptului de moştenire derivata din X
Partea specifica
(copil)
lui Y
• în programarea obiectuală se admite
compatibilitatea între tipul clasei
derivate şi tipul clasei de bază,
reciproca nefiind însă adevărată
Modificatorii abstract şi sealed
• obligă respectiv se opun procesului de moştenire
• o clasă declarată abstractă (abstract) trebuie neapărat derivată, pentru că
ea însăşi nu poate fi instanţiată
abstract class Clasa_abstracta
{ ... }

• o clasă declarată ca sigilată (sealed) nu mai poate fi derivată ulterior,


practic ea trebuie văzută ca o clasă terminală din cadrul ierarhiei; derivarea
unei clase dintr-o clasă sigilată, compilatorul va genera o eroare
sealed class Clasa_terminala
{ ... }

• o metodă declarată abstractă nu are implementare (corp) şi trebuie


redefinită (override) în toate clasele non-abstracte derivate din clasa din
care face parte metoda
Moştenirea şi apelul constructorilor
• întotdeauna clasa de bază este instanţiată înaintea clasei derivate:
constructorul clasei de bază este apelat înaintea celui din clasa
derivată => lanţ de apeluri
• întotdeauna la crearea unui obiect dintr-o clasă derivată se apeleaza
întâi constructorul clasei de bază
• dacă nu se specifică nicio clasă de bază în definirea unei clase, implicit
aceasta se consideră derivată din clasa de bază System.Object =>
toate clasele din C# şi .NET sunt derivate implicit din System.Object
sau dintr-o clasǎ derivatǎ din clasa Object

https://learn.microsoft.com/en-us/dotnet/api/system.object?view=net-6.0
Exemplu - clasa Baza
Baza
internal class Baza
{
public int? Camp_Baza { get; set; }
public Baza() Derivata
{
Camp_Baza = 0;
Console.WriteLine("Constructorul clasei Baza fara parametru");
}
public Baza(int p)
{
Camp_Baza = p;
Console.WriteLine("Constructorul clasei Baza cu parametru int {0} ", p);
}
public void Afisare() => Console.WriteLine("Clasa Baza cu parametrul
Camp_Baza {0}", Camp_Baza);
public void Metoda_Baza() => Console.WriteLine("Metoda proprie a clasei Baza");
}
Exemplu - clasa Derivata
internal class Derivata:Baza
{
int? Camp_Derivata { get; set; }
public Derivata()
{
Camp_Derivata = 0;
Console.WriteLine("Constructorul clasei Derivata fara parametru");
}
public Derivata(int b, int d) : base(b)
{
Camp_Derivata = d;
Console.WriteLine("Constructorul clasei Derivata
cu parametri {0} {1}", b,d);
}
public void Afisare()
{
base.Afisare();
Console.WriteLine("Clasa derivata cu Camp_Derivata {0}", Camp_Derivata);
}
public void Metoda_Derivata() => Console.WriteLine("Metoda proprie
a clasei Derivata");
}
Exemplu – Main
//cream obiecte
Baza obj_baza1 = new Baza();
Baza obj_baza2 = new Baza(5);
Derivata obj_deriv1 = new Derivata();
Derivata obj_deriv2 = new Derivata(7, 8);

Console.WriteLine();
//apelam metode
obj_baza2.Afisare();
obj_deriv2.Afisare();
obj_deriv1.Metoda_Derivata();
obj_deriv1.Metoda_Baza();

//?? care afisare?


Console.WriteLine();
Baza obj_baza3 = new Derivata(10, 15);
obj_baza3.Afisare();
Moştenirea şi implementarea
constructorilor
• deoarece scopul claselor derivate este acela de a adăuga noi caracteristici
claselor de bază, în mod evident constructorii acestora sunt mai complecşi
• abordarea utilizată în acest sens este ca aceştia să apeleze în mod
implicit sau explicit constructorii claselor de bază ori de cîte ori este posibil
• regula este următoarea: în cazul constructorilor, se apelează mai întîi
constructorii claselor de bază, apoi se execută acţiuni specifice
constructorului clasei derivate.

Punct

Patrat Cerc
Dreptunghi
Exemplu – clasa Punct
internal class Punct
{
//proprietatea X
public int X { get; set; }
//proprietatea Y
public int Y { get; set; }
//constructori pentru clasa Punct
public Punct()
{ }
public Punct(int x, int y)
{
this.X = x;
this.Y = y;
}
//metoda cu acelasi nume!!!
public void Deseneaza() => Console.WriteLine("Coordonatele punctului
desenat sunt: x={0} , y={1}", this.X, this.Y);
}
Exemplu – clasa Patrat
internal class Patrat:Punct
{
public int Latura { get; set; }
//constructori pentru clasa Patrat
public Patrat()
{ }
public Patrat
(int x, int y, int latura) : base(x, y)
{
this.Latura = latura;
}
//metoda cu acelasi nume!!
public void Deseneaza()
=> Console.WriteLine("Coordonatele patratului desenat sunt:
x={0} , y={1}, latura={2}\n", this.X, this.Y, this.Latura);

}
Exemplu – clasa Dreptunghi
internal class Dreptunghi:Punct
{
public int Lungime { get; set; }
public int Latime { get; set; }
//constructori pentru clasa Dreptunghi
public Dreptunghi()
{ }
public Dreptunghi(int x, int y, int lungime, int latime) : base(x, y)
{
this.Lungime = lungime;
this.Latime = latime;
}
//metoda cu acelasi nume!!
public void Deseneaza()
=> Console.WriteLine("Coordonatele dreptunghiului desenat sunt:
x={0} , y={1}, lungime={2} latime={3}\n",
this.X, this.Y, this.Lungime, this.Latime);
}
Exemplu – clasa Cerc
internal class Cerc:Punct
{
public int Raza { get; set; }
public Cerc()
{ }
public Cerc(int x, int y, int raza) : base(x, y)
{
this.Raza = raza;
}
//metoda cu acelasi nume!!
public void Deseneaza()
=> Console.WriteLine("Coordonatele cercului desenat sunt:
x={0} , y={1}, raza={2}\n",
this.X, this.Y, this.Raza);
}
Exemplu – Main
//cream obiecte
//apel implicit la constructorul fara parametru al clasei Punct
Patrat p0 = new Patrat();
Cerc c0 = new Cerc();
Dreptunghi d0 = new Dreptunghi();

//apel explicit la constructorul cu parametru al clasei Punct


Patrat p = new Patrat(8, 3, 20);
Cerc c = new Cerc(7, 10, 40);
Dreptunghi d = new Dreptunghi(2, 7, 30, 50);

//apel metode cu acelasi nume ??


p.Deseneaza();
c.Deseneaza();
d.Deseneaza();
Polimorfismul
Polimorfismul => capacitatea unui obiect de a se comporta ca şi
un obiect de alt tip, sau abilitatea unei metode cu acelasi nume de
a desfǎşura acțiuni diferite, functie de contextul în care este
apelatǎ sau de clasa derivatǎ în care se aflǎ.
– Polimorfism de tip: utilizarea unui obiect de un anumit tip
(de regulă un obiect derivat) ca şi cum ar fi de un alt tip (de
regulă clasa de bază din care derivǎ)
– Polimorfism de metodă: redefinirea comportamentului
unei metode într-o clasă derivatǎ (prin mostenire) sau într-o
alta clasǎ (prin implementare interfațǎ => implementări
diferite pentru metodele care sunt apelate cu acelaşi nume.
Mecanismul polimorfismului (1)
class Baza
• mecanismul care stă la baza {
polimorfismului îl reprezintă public void Metoda_Baza() { ... }
posibilitatea de a referi obiecte- public void Metoda_Comuna() { ... }
derivate prin intermediul unor referinţe }
către clasa de bază => este corectă class Derivata: Baza
asignarea: {
public void Metoda_Derivata() { ...
Baza b = new Derivata(); }
public void Metoda_Comuna() { ... }
• obiectul de tip Derivata este tratat ca }
un obiect de tip Baza, ceea ce este
• dacă însă se încearcă accesul la metoda
corect întrucît Derivata este un sub-
comună a celor două clase, compilatorul va
tip sau tip derivat a lui Baza:
genera un warning prin care ne atenţionează
b.Metoda_Baza(); //corect că Metoda_comuna din Derivata ascunde
b.Metoda_Derivata(); //eroare Metoda_comuna moştenita de la clasa
Baza.
• accesul la metoda proprie a clasei
Baza este corect; în schimb, metoda • in acest caz, se va afişa totuşi metoda
proprie a clasei Derivata nu este Metoda_comuna a clasei Baza:
accesibilă deoarece b este tratat ca Baza b = new Derivata();
fiind de tip Baza. b.Metoda_Comuna();
Mecanismul polimorfismului (2)
class Derivata: Baza class Baza
{ {
public void Metoda_Derivata() public void Metoda_Baza()
{ ... } { ... }
public override void public virtual void
Metoda_Comuna() { ... } Metoda_Comuna() { ... }
} }
• în cazul metodelor virtuale definite în class Derivata: Baza
cadrul claselor de bază şi redefinite cu {
override în cazul claselor derivate, public void Metoda_Derivata()
metoda din clasa de bază este { ... }
suprascrisa în cadrul clasei derivate =>
compilatorul va aplica legarea dinamică public new void Metoda_Comuna()
(dynamic binding) şi în consecinţă va { ... }
“vedea” tipul obiectului numai în }
momentul execuţiei
• CLR (Common Language Runtime) va • utilizarea lui new în loc de override în
considera în acest caz tipul obiectului cu exemplul anterior ar însemna cǎ
care a fost asignată referinţa în momentul metoda din clasa derivatǎ este nouǎ,
execuţiei si nu tipului de care a fost aşa cǎ este ascunsǎ dacǎ este
declarată referinţa => se va apela apelatǎ prin intermediul unui pointer la
metoda corespunzătoare clasei Derivata clasa de bazǎ => se va apela metoda
=> comportament polimorfic din clasa Baza
Baza b = new Derivata(); Baza b = new Derivata();
b.Metoda_Comuna(); b.Metoda_Comuna();
Mecanismul polimorfismului (3)
• reprezintă concept fundamental în programarea orientată pe obiecte
alături de încapsulare şi moştenire
• ideea polimorfismului în acest context (care, în sens etimologic al
cuvȃntului înseamnă mai multe forme) porneşte de la faptul că este
posibilă apelarea metodelor claselor derivate prin intermediul unei
referinţe la clasa de bază, în momentul execuţiei
• conform definiţiei, polimorfismul reprezintă “abilitatea claselor de a furniza
implementări diferite pentru metodele care sunt apelate cu acelaşi nume”
=> se bazează pe utilizarea metodelor virtuale şi redefinirea acestora
în clasele derivate cu override (moştenire)
• metodele abstracte sunt implicit virtuale => redefinirea (suprascrierea)
acestora în clasele derivate se realizează tot cu overrride
• conceptul de virtual se aplică doar metodelor de instanţă nu şi
metotodelor statice
Mecanismul polimorfismului (4)
• metodele declarate virtuale sau abstracte stau la baza polimorfismului în
programarea orientată pe obiecte => decizia privind care metodă va fi
efectiv apelată este întârziată şi are loc doar în momentul execuţiei
(runtime) iar selecţia metodei se face în aces caz pe baza tipului existent
din momentul execuţiei şi nu pe baza celui declarat în momentul
compilării
• din considerente de performanţă, metodele în C# nu sunt toate virtuale în
mod implicit
• până la versiunea C#9 exista o regulă prin care se impunea ca
semnătura metodelor redefinite cu override să fie identică cu cea a
metodei pe care o redefinesc din clasa de bază; începând cu C#9
această cerinţă a limbajului s-a mai relaxat în sensul că tipul valorii
returnate de metodă poate fi identic sau derivat din tipul declarat la clasa
de bază
Polimorfismul si conversiile de tip
• conversii de la tipul de bază către cel derivat (down-casting) şi invers
(up-casting).
• conversia de tip de la tipul derivat către bază (up-casting) este sigură,
simplă şi implicită:
Baza b = new Derivata1(); //conversie implicita up-casting
Baza b = new Derivata2();
• conversia de la bază către tipul derivat (down-casting) nu este sigură (în
sensul că pot apărea excepţii dacă se referă ulterior, de exemplu, un
membru propriu al clasei derivate care nu mai este accesibil deoarece în
urma conversiei obiectul este tratat ca fiind de tipul de bază) şi trebuie
realizată explicit; chiar şi atunci conversia poate să nu fie realizabilă,
datorită incompatibilităţii între tipuri:
Baza [] b = {new Derivata1(), new Derivata2()};
//corect dar nesigur
Derivata1 d = (Derivata1) b[0];
//exceptie - InvalidCastException
Derivata1 d1 = (Derivata1) b[1];
Operatorii is si as pentru testarea
tipului obiectelor
• verificarea tipurilor utilizând is:
Console.WriteLine (b[0] is Derivata1); //returneaza true
Console.WriteLine (b[1] is Derivata1); //returneaza false
• verificarea tipurilor utilizând as:

Baza [] b = {new Derivata1(), new Derivata2()};


Derivata1 d1 = b[1] as Derivata1;
if (d1 != null)
Console.WriteLine (Conversie de tip corecta!);
else
Console.WriteLine(Conversie de tip incorecta!);
• chiar dacă prezintă o funcţionalitate asemănătoare, is nu face decît să
verifice tipul în momentul execuţiei pe când as, în plus, realizează şi
conversia către tipul specificat.
Exemplu – clasa Punct
internal class Punct
{
//proprietatea X
public int X { get; set; }
//proprietatea Y
public int Y { get; set; }
//constructori pentru clasa Punct
public Punct()
{ }
public Punct(int x, int y)
{
this.X = x;
this.Y = y;
}
public virtual string IsA { get { return "Punct"; } }
public virtual void Deseneaza() => Console.WriteLine("Coordonatele
punctului desesenat sunt: x={0} , y={1}", this.X, this.Y);
}
Exemplu – clasa Patrat
internal class Patrat:Punct
{
public int Latura { get; set; }
//constructori pentru clasa Patrat
public Patrat()
{ }
public Patrat
(int x, int y, int latura) : base(x, y)
{
this.Latura = latura;
}
public override string IsA {get { return "Patrat"; }}
public override void Deseneaza()
=> Console.WriteLine("Coordonatele patratului desenat sunt:
x={0} , y={1}, latura={2}\n", this.X, this.Y, this.Latura);

}
Exemplu – clasa Dreptunghi
internal class Dreptunghi:Punct
{
public int Lungime { get; set; }
public int Latime { get; set; }
//constructori pentru clasa Dreptunghi
public Dreptunghi()
{ }
public Dreptunghi(int x, int y, int lungime, int latime) : base(x, y)
{
this.Lungime = lungime;
this.Latime = latime;
}
public override string IsA { get { return "Dreptunghi"; }}
public override void Deseneaza()
=> Console.WriteLine("Coordonatele dreptunghiului desenat sunt:
x={0} , y={1}, lungime={2} latime={3}\n",
this.X, this.Y, this.Lungime, this.Latime);
}
Exemplu – clasa Cerc
internal class Cerc:Punct
{
public int Raza { get; set; }
public Cerc()
{ }
public Cerc(int x, int y, int raza) : base(x, y)
{
this.Raza = raza;
}
public override string IsA { get { return “Cerc"; }}
public override void Deseneaza()
=> Console.WriteLine("Coordonatele cercului desenat sunt:
x={0} , y={1}, raza={2}\n",
this.X, this.Y, this.Raza);
}
Polimorfism - exemplu
//cream obiecte
Punct pt = new Punct(5, 7); Patrat patrat = new Patrat();
Cerc cerc = new Cerc(); Dreptunghi dreptunghi = new Dreptunghi();

//apel metode si proprietati => care?


pt.Deseneaza();
Console.WriteLine("Obiect din clasa "+pt.IsA);
patrat.Deseneaza();
Console.WriteLine("Obiect din clasa "+patrat.IsA);
dreptunghi.Deseneaza();
Console.WriteLine("Obiect din clasa " + dreptunghi.IsA);

//polimorfism => care metoda Deseneaza va fi apelata?


Punct p;
p = new Patrat(8, 3, 20);
p.Deseneaza();

p = new Cerc(7, 10, 40);


p.Deseneaza();

p = new Dreptunghi(2, 7, 30, 50);


p.Deseneaza();
Concluzii - polimorfism
• dacǎ dorim sǎ redefinim o metodǎ definitǎ într-o clasǎ de bazǎ ca virtualǎ în
cadrul claselor derivate, aceasta trebuie marcatǎ cu cuvintele cheie
override sau new
• override înseamnǎ cǎ metoda din clasa derivatǎ trebuie sǎ anuleze metoda
din clasa de bazǎ, ceea ce conduce la un comportament polimorfic
• utilizarea lui new în loc de override în exemplul anterior ar însemna cǎ
metoda din clasa derivatǎ este nouǎ, aşa cǎ este ascunsǎ dacǎ este
apelatǎ prin intermediul unui pointer la clasa de bazǎ
• obtinerea polimorfismului implica legarea dinamicǎ (dynamic binding) care
este realizatǎ doar în faza de execuţie a programului, şi selecţia metodei
efectiv apelate se realizeazǎ pe baza tipului cu care a fost asignat efectiv
obiectul în momentul execuţiei
• mecanismul funcţiilor virtuale/abstracte asigurǎ un comportament polimorfic
al metodelor definite virtuale/abstracte în cadrul unor clase de bazǎ şi
redefinite în cadrul claselor derivate cu override, atunci când acestea sunt
apelate prin intermediul unor referinţe cǎtre clasa de bazǎ
Supraîncǎrcarea (overloading)
• supraîncǎrcarea (overloading) reprezintă mecanismul prin care este permis
ca un anumit operator sau o anumită metodǎ (funcţie) să poată fi definiți în
mai multe moduri => mai multe metode partajează (au) același nume de
metodă dar cu o semnătură diferită în clasă
• selecţia metodei efectiv apelatate se realizează funcţie de argumentele
utilizate (parametri) => metodele sunt diferențiate prin numărul și tipul
argumentelor/parametrilor metodei
• supraîncărcarea metodelor reprezintă o altă formă de polimorfism:
polimorfism la compilare (compile time polimorfism)
Supraîncǎrcarea metodelor
• în C#, metodele supraîncǎrcate au acelaşi nume dar trebuie sa conţină o
listǎ de parametri diferitǎ (adica numarul şi/sau tipul sau ordinea
parametrilor metodelor să fie diferit)
• tipul valorii returnate de o metodǎ supraîncǎrcatǎ NU reprezintǎ factor
distinctiv în cazul metodelor supraincarcate.
• în cazul supraîncǎrcǎrii metodelor, comportarea codului e determinată la
compilare: funcţie de numărul şi/sau tipul parametrilor utilizaţi în momentul
apelului, se identifică de către compilator funcţia care va fi utilizată, încă din
faza de compilare => acest lucru este diferit şi NU trebuie confundat cu
utilizarea funcţiilor virtuale (polimorfice) din cadrul unei ierarhii de clase, în
cazul cărora determinarea funcţiei utilizate efectiv se face doar în
momentul execuţiei (runtime polimorfism)
• utilizarea metodelor supraîncǎrcate simplificǎ interfaţa claselor: astfel, dacǎ
avem operaţii similare dar care necesitǎ parametri diferiţi precum şi
implementǎri diferite, utilizarea metodelor supraîncǎrcate reprezintǎ o
soluţie elegantǎ
Supraîncǎrcarea metodelor
internal class Carte
{
public string? Autor { get; set; }
public string? Titlu { get; set; }
//suprincarcare constructori
public Carte() { }
public Carte(string titlu, string autor)
{ this.Titlu = titlu; this.Autor = autor; }
public Carte(Carte c)
{ this.Titlu = c.Titlu; this.Autor = c.Autor; }
//supraincarcare metoda Afisare in cadrul clasei
public void Afisare()
{
Console.WriteLine("Cartea cu titlul " + this.Titlu
+ " de " + this.Autor);
}
public void Afisare(string text1, string text2)
{
Console.WriteLine(text1 + this.Titlu + text2 + this.Autor);
}
}
Supraîncǎrcarea metodelor
şi moştenirea
internal class ColectieBaza
{
//o colectie de carti
protected Carte[] colectie_carti = new Carte[100];
protected int Count { get; set; } = 0;
//supraincarcare metoda adauga
public Carte adauga(string titlu, string autor)
{
Carte c = new Carte(titlu, autor);
colectie_carti[Count++] = c;
return c;
}
}
internal class ColectieDerivata:ColectieBaza
{ //supraincarcare metoda adauga
public void adauga(Carte c)
{
colectie_carti[Count++] = c;
}
}
Supraîncǎrcarea metodelor
şi moştenirea
Carte carte1 = new Carte("C# and .NET", "Christian Nagel");
Carte carte2 = new Carte("Design Patterns in .NET", "Dimitri Nesteruk");

//apel metode supraincarcate clasa Carte


carte1.Afisare();
carte2.Afisare("Titlul cartii este ", " si este scrisa de: ");

ColectieDerivata colectie = new ColectieDerivata();

//ape metode supraincarcate ierarhie clase ColectieBaza-ColectieDerivata


colectie.adauga(carte1);
Carte carte3 = colectie.adauga("Adaptive Code via C#", "Gary McLean Hall");
carte3.Afisare();
Overloading versus overriding
Suprascrierea (redefinirea) metodelor
Supraîncărcarea metodelor (overloading)
(overriding)
Polimorfism la compilare Polimorfism la execuţie
(compile-time polymorphism) (run-time polymorphism)

Utilizată pentru a crea implementări specifice


Ajută la creșterea lizibilității/clarităţii
pentru metode care sunt deja furnizate de
programului
clasa părinte

Apare în general în cadrul clasei => Sunt implicate două clase cu relație de
supraîncărcarea metodei poate necesita sau moștenire => suprascrierea metodei necesită
nu moștenire întotdeauna moștenire

La supraîncărcarea metodelor, metodele În cazul suprascrierii metodelor, acestea


trebuie să aibă același nume și semnături trebuie să aibă același nume și aceeași
diferite => lista de argumente trebuie să fie semnătură => lista de argumente trebuie să fie
diferită aceeași

La supraîncărcarea metodei, tipul returnat În suprascrierea metodei, tipul de returnare


poate sau nu poate fi același trebuie să fie același sau covariant

Se utilizează legarea dinamică


Se utilizează legarea statică (static binding)
(dynamic binding)

Performanţă mai scăzută Performanţă mai bună


Supraîncǎrcarea operatorilor
• pentru redefinirea (supraîncărcarea) operatorilor se utilizează numai
metode statice (şi publice): operatorii redefiniţi astfel aparţin clasei şi nu
unei anumite instanţe a acesteia.
• numele operatorilor supraincarcati trebuie să înceapă cu cuvântul cheie
operator, urmat de caracterele reprezentînd operatorul pe care îl
implementează.
• numărul de argumente pe care funcţiile care implementează operatori le pot
avea depind de pluralitatea operatorului (unar sau binar); operatorii binari
au întotdeauna doi parametri (nu neaparat de acelaşi tip, dar de cele mai
multe ori acest lucru se întâmplǎ), în timp ce cei unari au un singur
parametru
• tipul valorii returnate poate fi oricare, însă trebuie ales în funcţie de
modalitatea în care se doreşte ca operatorul să poată fi utilizat
Exemplu – clasa Punct (1)
internal class Punct
{
//proprietatea X
public int X { get; set; }
//proprietatea Y
public int Y { get; set; }
public Punct(int x, int y)
{
this.X = x; this.Y = y;
}
//redefinire operator ==
public static bool operator == (Punct p1, Punct p2)
=> ((p1.X == p2.X) && (p1.Y == p2.Y));
//redefinire operator!= este obligatorie
public static bool operator != (Punct p1, Punct p2)
=> !((p1.X == p2.X) && (p1.Y == p2.Y));
//redefinire operator binar +
public static Punct operator + (Punct p1, Punct p2)
=> new Punct(p1.X + p2.X, p1.Y + p2.Y);
Exemplu – clasa Punct (2)
//redefinire operator binar -
public static Punct operator - (Punct p1, Punct p2)
=> new Punct(p1.X - p2.X, p1.Y - p2.Y);
//redefinire operator unar -
public static Punct operator - (Punct p) => new Punct(-p.X, -p.Y);
//suprascrierea metodelor Equals si GetHashCode
//nu este obligatorie dar e recomandata
public override bool Equals(object obj)
=> ((obj is Punct) && (this == (Punct)obj));
public override int GetHashCode() => (this.X + this.Y);
// suprascrierea ToString
public override string? ToString()
{
return "Punct de coordonate X=" + X.ToString()
+ " Y=" + Y.ToString();
}
}
Observatii exemplu – clasa Punct

• în ceea ce priveşte supraîncǎrcarea operatorului ==, dacǎ acesta este


supraîncǎrcat în cadrul unei clase, atunci este obligatorie
supraîncǎrcarea şi a operatorului != (de obicei redefinirea acestuia se
realizeazǎ pe baza operatorului == definit în prealabil).
• acelaşi lucru este valabil şi în cazul operatorilor <= respectiv >=
• prin convenţie, este recomandatǎ (dar nu obligatorie) şi
supraîncǎrcarea metodelor Equals şi GetHashCode moştenite de la
Object, acest lucru asigurind o comportare consistentǎ a obiectelor din
clasa respectivǎ atunci cînd sunt implicate în operaţii de comparaţie
Exemplu – clasa Vector (1)
internal class Vector
{
public Punct X { get; set; }
public Punct Y { get; set; }
public Vector(Punct x, Punct y)
{
X = x; Y = y;
}
//se utilizeaza operatorul == redefinit pentru Puncte
public static bool operator == (Vector v1, Vector v2)
=> ((v1.X == v2.X) && (v1.Y == v2.Y));
//supraincarcarea operatorului != este obligatorie
public static bool operator != (Vector v1, Vector v2)
=> !((v1.X == v2.X) && (v1.Y == v2.Y));
//suprascrierea metodelor Equals si GetHashCode
//nu este obligatorie dar e recomandata
public override bool Equals(object obj)
=> ((obj is Vector) && (this == (Vector)obj));
Exemplu – clasa Vector (2)
public override int GetHashCode() => (this.X.X * this.Y.X);
//se utilizeaza operatorul + redefinit pentru Puncte
public static Vector operator + (Vector v1, Vector v2)
=> new Vector(v1.X + v2.X, v1.Y + v2.Y);
//se utilizeaza operatorul - binar redefinit pentru Puncte
public static Vector operator - (Vector v1, Vector v2)
=> new Vector(v1.X - v2.X, v1.Y - v2.Y);
//se utilizeaza operatorul - unar redefinit pentru Puncte
public static Vector operator - (Vector v)
=> new Vector(-v.X, -v.Y);
public void Afisare() => Console.Write("Vector de coordonate X = {0}
si Y = {1} ", X.ToString(), Y.ToString());
}
Utilizare – clasa Vector
Vector rezultat;
Punct p10 = new Punct(5, 7);
Punct p1 = new Punct(14, 17);
Punct p20 = new Punct(25, 37);
Punct p2 = new Punct(12, 54);

Vector v1 = new Vector(p10, p1);


Vector v2 = new Vector(p20, p2);

if (v1 != v2) rezultat = v1 + v2; else rezultat = v1 - v1;


rezultat.Afisare();
Concluzii privind supraîncărcarea
operatorilor
• NU toti operatorii pot fi redefiniţi, deoarece aceasta ar conduce la
interacţiuni nedorite cu limbajul C# însuşi, printre aceştia numǎrându-se
operatorii de atribuire adică = += -= şi *=. Operatori care pot fi redefiniţi sunt
în principiu urmǎtorii: + - ¡ ~ ++ -- true false (operatori unari) respectiv + - * /
% & | ^ << >> == != > < >= <=
• redefinirea operatorilor este valabilǎ numai pentru clasele definite de
programator; pentru tipurile standard, operatorii îşi păstrează definiţia
originală
• nu se permite alegerea pentru operatori a altor simboluri decât cele
existente deja
• nu se permite schimbarea pluralităţii operatorului prin redefinire
(de exemplu din unar în binar)
Polimorfismul
şi colecţiile de obiecte
MultimeGrafica
….
Punct Punct[] elemente

Patrat Cerc
Dreptunghi
Polimorfismul
şi colecţiile de obiecte
internal class MultimeGrafica
{ //dimensiune colectie
public static int DIM { get;} = 20;
//contor elemente colectie
public int Count { get; set; } = 0;
//structura de date interna care stocheaza elementele colectiei
Punct[] elemente = new Punct[DIM];
//adauga un element multimii
public void Adauga(Punct p)
{ elemente[Count++] = p; }
//returneza elementele colectiei ca string
public override string? ToString()
{
string? rez = string.Empty;
for (int i = 0; i < 20; i++)
if (elemente[i] is Punct) rez+=elemente[i].ToString();
return rez;
}
Polimorfismul
şi colecţiile de obiecte
//element de indexare (acces prin index int)
public Punct this[int index]
{ get
{
if ((index >= 0) && (index < DIM))
return elemente[index];
else return null;
}
set
{
if ((index >= 0) && (index < DIM))
{
elemente[index] = value;
Count++;
}
}
}
}
Elemente de indexare
• elementele de indexare sunt specifice • elementul de indexare C# nu are
colecţiilor de obiecte şi permit accesul nume: pentru a-l specifica se
mulţimilor de obiecte pe bază de utilizeazǎ cuvîntul cheie this =>
index, ca si când ar fi memorate valoarea indexatǎ este accesibilǎ
într-un tablou doar prin indexarea numelui
• mulţimile de obiecte pot fi instanţei.
reprezentate de un set finit de membri
ai unei clase, tablouri de obiecte sau public Nume_clasa this
structuri de date complexe [int index]
{
• indiferent de reprezentarea internă a get
clasei respective (structura de date {
utilizată), datele pot fi obţinute într-o //aici se returneaza o valoare
manieră consistentă prin intermediul }
elementelor de indexare set
{
• elementele de indexare pot fi //aici se seteaza o valoare
read/write, read-only, write/only }
}
Utilizarea elementelor de indexare
Patrat patrat = new Patrat(8, 3, 20);
Cerc cerc = new Cerc(7, 10, 40);
Dreptunghi dreptunghi = new Dreptunghi(2, 7, 30, 50);
//defineste un obiect MultimeGrafica
MultimeGrafica m_gr = new MultimeGrafica();
//adaugam elemente colectiei prin metoda Adauga
m_gr.Adauga(patrat);
m_gr.Adauga(cerc);
m_gr.Adauga(dreptunghi);
//afisam elementele colectiei
Console.WriteLine(m_gr.ToString());
//acceseaza pe baza de index elementele din MultimeGrafica
for (int i = 0; i < MultimeGrafica.DIM; i++)
{
Console.WriteLine((m_gr[i] is null) ?
string.Empty : " X= " + m_gr[i].X + " Y= " + m_gr[i].Y);
}
Utilizarea elementelor de indexare
Patrat altpatrat = new Patrat(10, 30, 15);
Cerc altcerc = new Cerc(9, 11, 20);
Dreptunghi altdreptunghi = new Dreptunghi(3, 5, 20, 35);
//adaugare elemente prin index
m_gr[4] = altpatrat;
m_gr[5] = altcerc;
m_gr[6] = altdreptunghi;
Console.WriteLine(m_gr.ToString());
for (int i = 0; i < MultimeGrafica.DIM; i++)
{
Console.WriteLine((m_gr[i] is null)
? string.Empty : m_gr[i].ToString());
}
Clasa MultimeGrafica revizuită
internal class MultimeGrafica
{ //contor elemente colectie
public int Count { get; set; } = 0;
//structura de date interna care stocheaza elementele colectiei
//(Punct, Patrat, Dreptunghi, Cerc)
Punct[] elemente;
//initializare prin constructor
public MultimeGrafica(int count, Punct[] elemente)
{
this.elemente = elemente;
this.Count = count;
}
//adauga un element multimii
public void Adauga(Punct p)
{
elemente[Count++] = p;
}
Clasa MultimeGrafica revizuită
//returneza elementele colectiei ca string
public override string? ToString()
{
string? rez = string.Empty;
foreach(var elem in elemente) rez+=elem.ToString()+"\n";
return rez;
}
//returneaza toate elementele pentru care isA returneaza s
// index string!
public IEnumerable<Punct> this[string s]
{
get => elemente.Where(elem => elem.IsA == s);

}
}
Utilizare clasa MultimeGrafica
revizuită
Punct[] elemente_initial =
{ new Punct(5, 7), new Dreptunghi(20, 7, 3,3),
new Patrat(5, 7, 10), new Cerc(20, 3, 7), new Punct(21, 7) };
MultimeGrafica m_gr = new MultimeGrafica(5, elemente_initial);
//afiseaza colectia creata initial
Console.WriteLine(m_gr.ToString());
//doar elementele pentru care isA returneaza sirul "Punct"
foreach (Punct p in m_gr["Punct"]) p.Deseneaza();
Colecţii în .NET
• în majoritatea cazurilor se vor utiliza clasele de colecţie predefinite în .NET
în loc de a defini propria clasă de colecţie
• există două tipuri de colecții disponibile în C#: colecții non-generice și
colecții generice.
• spațiul de nume System.Collections conține tipurile de colecții non-
generice, iar spațiul de nume System.Collections.Generic include
tipuri de colecții generice
Colecţii non-generice .NET
Colecţii non-generice .NET
• ArrayList – tablou a cǎrui dimensiune creşte pe mǎsurǎ ce este necesar
• Hashtable – o colecție de perechi cheie/valoare organizate pe baza unei
tabele de dispersie (memorare pe baza cheii hashcode)
• Queue – o colecție de obiecte de tipul FIFO (coadǎ)
• SortedList – o colecție de perechi cheie/valoare sortate pe baza cheii şi
accesibile pe baza cheii şi pe baza unui index
• Stack - o colecție de obiecte de tipul LIFO (stivǎ)
• ......
• https://docs.microsoft.com/en-
us/dotnet/api/system.collections?view=net-6.0
Colecţii non-generice .NET
ArrayList
• ArrayList este unul din tipurile aşa numite de colecţie non-generice puse la
dispoziţie de .NET
• public class ArrayList : IList, ICollection,
IEnumerable, ICloneable
• constructori:
public ArrayList ()
public ArrayList (ICollection c);
public ArrayList (Int32 capacity);
• proprietăţi: Capacity, Count, Item, etc.
• metode (parţial):
public virtual int Add (Object value);
public virtual int BinarySearch (Object value);
public virtual bool Contains (Object item);
public virtual bool Equals (Object obj);
public virtual void Remove (Object obj);
public virtual void Insert(int index, Object obj);
public virtual void Sort();
Colecţii non-generice .NET
ArrayList
• exemplul anterior utilizând ArrayList: nu mai este necesară definirea
clasei MultimeGrafica, se utilizează direct clasa ArrayList

ArrayList m_gr_array = new ArrayList();


m_gr_array.Add(patrat);
m_gr_array.Add(cerc);
m_gr_array.Add(dreptunghi);

//afisam elementele arraylist-ului


Console.WriteLine("Elementele din ArrayList:");
for (int i = 0; i< m_gr_array.Count; i++)
{
Console.WriteLine(m_gr[i].ToString());
}
//afisare cu foreach
foreach(Punct p in m_gr_array) Console.WriteLine(p.ToString());
Colecţii non-generice .NET
• în general, colecţiile reprezintǎ elemente extrem de puternice pentru
gestionare mulţimilor (colecţiilor) de obiecte
• dezavantajul utilizǎrii colecţiilor non-generice îl reprezintǎ faptul cǎ nu
garanteazǎ tipul obiectelor, elementele fiind declarate de tipul Object =>
din acest motiv, pentru a se asigura faptul cǎ se utilizeazǎ obiecte de un
anumit tip dorit în cadrul aplicaţiei, se realizeazǎ conversii de tip explicite,
fapt care afectează performanţa
• se returneazǎ null dacǎ obiectul nu poate fi convertit în tipul selectat =>
susceptibile de potenţiale excepţii
• în cele mai multe cazuri, se recomandă utilizarea colecțiilor generice,
deoarece acestea funcționează mai rapid decât colecțiile non-generice și, de
asemenea, fiind puternic tipizate, minimizează posibilitatea de a se genera
excepţii, prin realizarea de verificări asupra tipurilor în faza de compilare
(type safe)
Clase generice (Generics)
• utilizarea tipurilor (claselor) parametrizate sau generice în C# conduce la
reducerea duplicării funcţiilor precum şi a claselor cu implementări similare,
dar care diferă prin tipul argumentelor sau a datelor utilizate.
• o definiţie generică furnizează doar formatul general al clasei sau metodei
definite astfel, T, T1,...etc. fiind tipurile generice pe care compilatorul le va
înlocui în momentul apelului, funcţie de tipurile parametrilor actuali
transmişi.
• clasele şi metodele generice combina reutilizarea, siguranţa tipurilor şi
eficienţa într-o maniera nu întotdeauna posibil claselor non-generice
• utilizatorul poate, de asemenea, sa-şi definească propriile clase şi metode
generice
• de exemplu, se poate defini clasa de colecţie MultimeGrafica în variantă
generică, înlocuind tipul elementelor colecţiei cu un tip generic T => clasa
MultimeGraficaGen
Clasa MultimeGrafica generică
internal class MultimeGraficaGen<T> where T:Punct, new()
{ public static int DIM { get; } = 20;
//contor elemente colectie
public int Count { get; set; } = 0;
//structura de date interna care stocheaza
// elementele colectiei de tip generic T
T[] elemente = new T[DIM];
//adauga un element multimii – metoda generica
public void Adauga(T p)
{ elemente[Count++] = p; }
//returneza elementele colectiei ca string
public override string? ToString()
{
string? rez = string.Empty;
for (int i = 0; i < 20; i++)
if (elemente[i] is T) rez += elemente[i].ToString();
return rez;
}
Clasa MultimeGrafica generică
//element de indexare (returneaza elementul de tipul T)
public T this[int index]
{ get
{
if ((index >= 0)
&& (index < DIM))
return elemente[index];
else return default(T);
}
set
{
if ((index >= 0)
&& (index < DIM))
{
elemente[index] = value; Count++;
}
}
}
}
Utilizare clasa MultimeGrafica
generică
Patrat patrat = new Patrat(8, 3, 20);
Cerc cerc = new Cerc(7, 10, 40);
Dreptunghi dreptunghi = new Dreptunghi(2, 7, 30, 50);

//creare obiect colectie cu elemente T de tipul Punct


//am impus constrangerile where T: Punct, new()
MultimeGraficaGen<Punct> m_gr = new MultimeGraficaGen<Punct>();
m_gr.Adauga(patrat);
m_gr.Adauga(cerc);
m_gr.Adauga(dreptunghi);
Console.WriteLine(m_gr.ToString());
Clauza where in definiții generice
• clauza where dintr-o definiție generică specifică constrângerile asupra
tipurilor T care sunt utilizate ca argumente pentru parametrii de tip în
definiţia unui tip generic, metodă, delegate; constrângerile pot specifica
diverse, de exemplu:
– ca tipul T să implementeze o anumită interfaţă, de exemplu
where T: IComparable<T>
– ca tipul T să aibe o anumită clasă de bază where T: Punct
– ca tipul T să fie referință (clasă), de exemplu where T:class
– ca tipul T să dispună de un constructor implicit where new()
– ...
O lista genericǎ implementatǎ de
utilizator
//lista inlantuita generica
internal class ListaGen<T>
{ // clasa generica Nod imbricata pentru definirea unui nod
private class Nod
{
//Next pentru inlantuirea in lista
public ListaGen<T>.Nod Next { get; set; }
// proprietatea Data returneaza un tip T => elementele listei
public T Data { get; set; }
// constructor clasa interna Nod
public Nod(T t)
{
Next = null;
Data = t;
}
} //sfirsit definire clasa Nod
O lista genericǎ implementatǎ de
utilizator
// definire clasa ListaGen
private Nod inceput; // referinta la inceputul listei
// constructor
public ListaGen()
{ inceput = null; }
// functie generica cu parametru de tipul T => insereaza un element T
public void Insereaza(T t)
{
Nod n = new Nod(t);
n.Next = inceput;
inceput = n;
}
O lista genericǎ implementatǎ de
utilizator
//implementarea metodei GetEnumerator() pentru a putea realiza
//iteratii utilizind foreach
public IEnumerator<T> GetEnumerator()
{
//traversare elementele listei inlantuite
Nod current = inceput;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}
}
Utilizare lista genericǎ
implementatǎ de utilizator
ListaGen<int> lista_intregi = new ListaGen<int>();
for (int x = 0; x < 10; x++) { lista_intregi.Insereaza(x); }

// iterare cu foreach
foreach (int i in lista_intregi) { Console.Write(i + " "); }

// lista siruri de caractere


ListaGen<string> lista_siruri = new ListaGen<string>();
for (int x = 0; x < 10; x++)
{ lista_siruri.Insereaza("(" + x.ToString() + ")"); }

// iterare cu foreach
foreach (string i in lista_siruri) { Console.Write(i + " "); }
Colectii generice .NET
• spaţiul de nume System.Collection.Generic defineşte colecţii generice
• colecţiile generice rezolvă problemele specifice cele non-generice, în sensul
că acestea sunt puternic tipizate (type safe). Rezultă următoarele avantaje ale
utilizării structurilor generice ȋn programarea orientată pe obiecte :
– performanţa ridicată prin evitarea eventualelor conversii de tip, cu efect
negativ asupra performanţelor
– siguranţa tipurilor prin verificarea, ȋncă din fază de compilare dacă tipurile
utilizate sunt ȋn conformitate cu tipul sub cu care clasa generică a fost
instanţiată
– reutilizarea codului – pentru mai multe tipuri de date se utilizează aceeaşi
secvenţă de cod
– sunt de preferat a fi utilizate în locul claselor de colecţie
non-generice
– implementează una sau mai multe dintre interfeţele generice de dar
marea majoritate implementează Icollection<T> şi Ienumerable<T>
Colecţii şi tipuri generice .NET
System.Collection.Generic – clase, interfete, etc.
• List<T> - implementează o listă de elemente accesibile prin index şi
furnizează o serie de metode de căutare, sortare şi manipulare a
elementelor listei; implementează interfeţle IList<T>,
ICollection<T>, IEnumerable<T>, IList, ICollection<T>,
IEnumerable<T>
• Queue<T> - reprezintă o colecţie generică cu structură de coadă (FIFO) şi
Implementează interfeţele IEnumerable<T>, ICollection<T>,
IEnumerable<T>
• Stack<T> - reprezintă o colecţie generică cu structură de stivă (LIFO) şi
implementează interfeţele, ICollection<T>, IEnumerable<T>
Colecţii şi tipuri generice .NET
• HashSet<T> - reprezintă o colecţie generică de tip mulţime de valori şi
are la bază interfeţle ISerializable, IDeserializationCallback, ISet<T>,
ICollection<T>, IEnumerable<T>
• LinkedList<T> - reprezintă o listă dublu ȋnlănţuită şi implementează,
ICollection<T>, IEnumerable<T>, ISerializable, IDeserializationCallback
• Dictionary<TKey, TValue> reprezintă o colecţie de perechi
chei/valoare
• interface IComparer<T> defineşte o metodă care este utilizată pentru a
compara două obiecte
Utilizarea colecţiilor generice
Crearea unei liste de şiruri de caractere:
List<string> siruri = new List<string>();
Crearea unei liste de numere întregi :
List<int> intregi = new List<int>();
Crearea unei liste de obiecte definite de utilizator (Produs):
List<Produs> produse = new List<Produs>()
Accesul la elementele colecțiilor
utilizând LINQ
• LINQ (Language Integrated Query) – începînd cu versiunea .NET 3.5
reprezintǎ un set de extensii .NET care furnizeazǎ capabilitǎți de
interogare în cadrul imbajelor C# şi Visual Basic => o sintaxă algebrică
asemănătoare cu SQL
• LINQ extinde sintaxa limbajelor cu operatori de intorogare standard care
permit lucrul cu datele independent de sursa de date => şi accesul către
diferite surse de date realizat printr-o sintaxă unitară (independent de tipul
sursei de date)
– simple colectii – Linq to Objects
– baze de date – Linq to SQL
– XML - Linq to XML
• integrat direct in limbaj, cu verificare tipuri, suport Intellisense, debugger
Accesul la elementele colecțiilor
utilizând LINQ
• LINQ to Objects => admite ca sursǎ de date doar tipuri enumerabile ( care
implementeazǎ IEnumerable<T> )
• rezultatul interogǎrii este stocat într-o variabilǎ interogare a cǎrui tip poate
fi declarat sau nu; în acest ultim caz aceasta se declarǎ folosind var şi
compilatorul va detecta în mod automat tipul acesteia
• iterarea prin rezultatul interogǎrii se realizeazǎ utilizând foreach
• exista mai multe variante de exprimare: utilizând expresii de interogare
sau expresii lambda
• rezultatul este un array simplu care poate fi transformat într-o listă
utilizând ToList() sau într-un şir folosind ToString()
Accesul la elementele colecțiilor
utilizând LINQ
List<Produs> produse = new List<Produs>();
• variabila interogare cu tip specificat:
IEnumerable<Produs> var_interogare_linq =
from prod in produse
where prod.Producator == "Romania"
orderby prod.Nume
select prod;
sau
IEnumerable<Produs> var_interogare_linq =
from prod in produse
where prod.Producator == "Romania"
&& prod.Pret<=200
select prod;
Accesul la elementele colecțiilor
utilizând LINQ
• interogare utilizând variabila de interogare fǎrǎ tip specificat
var interogare = from prod in produse
orderby prod.Nume
group prod by prod.Categorie into gr
select gr;

• iterare prin rezultatul interogǎrii cu foreach:


foreach (var p in interogare)
{
Console.WriteLine();
Console.WriteLine("Categoria "+p.Key + " :");
foreach (Produs prod in p)
{
Console.Write(prod.Nume+" ");
Console.WriteLine(prod.Producator);
}
}
Accesul la elementele colecțiilor
utilizând LINQ

• interogare cu expresii lambda – sintaxă simplificată, expresiile


lambda sunt convertite către metode anonime
IEnumerable<Produs> var_interogare_lambda = produse
.Where(p => p.Producator == "Romania" )
.OrderBy(p => p.Nume)
.Select(p => p);
Operatori standard LINQ
Operatii Operatori LINQ
Restrictii Where
Proiectii Select, SelectMany
Ordonare OrderBy, ThenBy
Grupare GroupBy
Join-uri Join, GroupJoin
Quantificatori Any, All
Partitionare Take, Skip, TakeWhile, SkipWhile
Set-uri Distinct, Union, Intersect, Except
Elemente First, Last, Single, ElementAt
Aggregare Count, Sum, Min, Max, Average
Conversie ToArray, ToList, ToDictionary
Interfeţe - definiţii, generalităţi
• utilizarea interfeţelor în dezvoltarea aplicaţiilor software este benefică şi
reprezintă o tehnică foarte puternică deoarece codul rezultat se caracterizează
prin: cuplare redusă, programare bazată pe componente, întreţinere uşoară,
grad ridicat de reutilizare datorită decuplării implementării faţă de interfaţă
• interfeţele aduc moştenirea multiplă în C# fără însă a implica şi dezavantajele
acesteia; astfel, la nivel de clase C# suportă doar moştenire simplă însă este
posibil ca o clasă să să implementeze mai multe interfeţe
• până la versiune C#8 interfeţele nu era posibil să dispună de niciun fel de
implementare; începând însă cu C#8 se pot crea anumite implementări de
metode în cadrul interfeţelor dar, datorită faptului că acestea reprezintă doar
nişte facilităţi adiţionale introduce în limbaj, implementarea acestora are
anumite particularităţi care o diferenţiază de implementările clasice realizate în
cadrul claselor care implementează interfaţa
• interfaţa nu poate conţine o stare (ex. elemente de tipul câmpuri de date) =>
interfeţele descriu un comportament, ele NU reprezintă capacitate de stocare
pentru date; interfeţele clasice nu conţin, de regulă, nici implementare
Interfeţe - definiţii, generalităţi
• în general, implementarea interfeţelor este responsabilitatea claselor care
implementează interfaţa respectivă => deoarece interfeţele trebuie în general
implementate în cadrul claselor sau structurilor derivate, ele pot fi văzute ca
definind un contract prin care se stipulează obligativitatea unei clase de a
implementa membri respectivi
• înainte de C#8, membrii interfeței erau implicit public; de fapt, utilizarea
explicită a modificatorilor de acces pe un membru al interfeței (inclusiv public)
ar genera o eroare la compilare
• începând cu C#8, membrii interfeței sunt de asemenea implicit public dar sunt
permişi şi alți modificatori de acces, de exemplu private; un membru al
interfeței private nu este accesibil din afara interfeței deci nu există nicio
modalitate de a oferi o implementare pentru un membru al interfeței private în
afara interfeței => sunt cu adevărat utili doar pentru a furniza implementări
explicite şi pentru a fi utilizați eventual de către alţi membri ai interfeței
• interfeţele reprezintă structuri deosebit de utile atunci când se doreşte crearea
de arhitecturi software în cadrul cărora implementarea componentelor să
poată fi realizată cu uşurinţă: atâta timp cât componentele interşanjabile
implementează aceeaşi interfaţă (care nu se schimbă), acestea pot fi
schimbate fără cod suplimentar => arhitectură flexibilă
Structura generalǎ a unei interfeţe
interface INumeInterfata
{
tip_retur NumeProprietate
{
get;
set;
}
tip_retur NumeMetoda();
// declarare element de indexare in cadrul interfetei
object this[int index]
{
get;
set;
}
// declaratia unui eveniment in cadrul interfetei
// Delegat trebuie declarat inainte ca delegat
delegate int Delegat(object obj1, object obj2);
event Delegat NumeEveniment;
……
}
Implementarea interfeţei
class ClasaInterfata : class ClasaInterfata :
INumeInterfata INumeInterfata
{ // implementare implicita a { // implementare explicita a
// membrilor interfetei // membrilor interfetei
public object this[int index] object INumeInterfata.this[int index]
{ // implementare } { // implementare }

public tip_retur NumeProprietate int INumeInterfata.NumeProprietate


{ {
//implementare // implementare
} }
public tip_retur NumeMetoda() int INumeInterfata.NumeMetoda()
{ // implementare } { // implementare }
... ...
} }
Implementarea interfeţei
• implementarea unei interfeţe se poate face implicit sau explicit =>
varianta implicită e de preferat
• diferenţa constă în sintaxa redefinirii membrilor interfeţei în clasă,
varianta explicită prefixând numele membrilor redefiniţi cu numele
interfeţei; de asemenea, membri redefiniţi explicit nu pot fi publici (sunt
private) şi deci nu sunt accesibili prin intermediul obiectelor definite din
clasa respective
// declararea unei instante a interfetei
INumeInterfata ref_interfata = new ClasaInterfata();
// apel membru prin inermediul interfetei
ref_interfata.NumeMetoda();
// declararea unei instante a clasei
ClasaInterfata clasa_interfata = new ClasaInterfata();
// apel membru prin inermediul clasei => inaccesibil in implementarea explicita
//accesibil in implementarea implicita
clasa_interfata.NumeMetoda();
...
Interfeţele şi moştenirea
interface IParinte //metoda de la IParinte
{ public void MetodaInterfataParinte()
void MetodaInterfataParinte(); {
} Console.WriteLine
("Apel la
interface ICopil : IParinte MetodaInterfataParinte()");
{ }
void MetodaInterfataCopil(); //metoda proprie a clasei
} public void AltaMetodaClasa() { }
}
class ClasaInterfata : ICopil
{ //metoda de la ICopil
public void //utilizare – ClasaInterfata redefineste ambele metode
MetodaInterfataCopil() ICopil impl = new
{
Console.WriteLine ClasaInterfata();
("Apel la impl.MetodaInterfataCopil();
MetodaInterfataCopil()"); impl.MetodaInterfataParinte();
}
((Clasa_interfata)impl).AltaMetodaClasa();
Accesul la membri interfeţei/clasei
• o clasǎ care implementeazǎ o interfaţǎ trebuie sǎ implementeze în mod
explicit toţi membri acelei interfeţe
• accesul la membri implementaţi ai interfeţei se va realiza prin intermediul
unei referinţe la interfaţa pe care clasa o implemeneazǎ:
INumeInterfata obj = new ClasaInterfata ();
• secvenţa de mai sus implică polimorfism, aplicat de această dată
interfeţelor; practic clasa care implementează interfaţa este o clasă
“derivată” din interfaţă
• întrucât o interfaţă nu are un constructor, nu se poate crea un obiect pe
baza unei interfeţe decât definind o clasă derivată din aceasta; se spune
“clasa implementează interfaţa”
• prin intermediul referinţei definite se pot accesa doar membri interfeţei;
pentru a accesa eventualii membri proprii ai clasei care implementeazǎ
interfaţa trebuie realizatǎ o conversie explicitǎ a referinţei cǎtre o referinţǎ la
clasa respectivǎ (!)
((Clasa_interfata)impl).AltaMetodaClasa();
Exemplu - interfaţa IDeplaseaza
internal interface IDeplaseaza
{
//definire enumerare cu directiile de deplasare
public enum Directie { Sus, Jos, Stinga, Dreapta };
//definire proprietati pentru care se solicita
//implementare atit pentru get cit si pentru set
int X
{
get; set;
}
int Y
{
get; set;
}
//definire metoda Deplaseaza
void Deplaseaza(Directie directie, int cit);
}
Exemplu – implementarea interfeţei
internal class Punct : IDeplaseaza
{ //proprietatile X si Y redefinite de la interfata
public int X { get; set; }
public int Y { get; set; }
//implementare metoda redefinita de la interfata IDeplaseaza
public void Deplaseaza(IDeplaseaza.Directie directie, int cit)
{ switch (directie)
{
case Directie.Sus: Y -= cit; break;
case Directie.Jos: Y += cit; break;
case Directie.Dreapta: X += cit; break;
case Directie.Stinga: X -= cit; break;
}
}
public virtual void Deseneaza() => Console.WriteLine
("Coordonatele punctului desenat sunt: x={0} , y={1}", this.X, this.Y);
……
}
Exemplu – utilizarea interfeţei
// asignarea unei referinţe la interfata cu o referinta la clasa
IDeplaseaza punct_depla = new Punct();
//acces proprietati redefinite de la interfata
punct_depla.X = 10;
punct_depla.Y = 10;
// acces metoda proprie Deseneaza a clasei Punct prin
//intermediul referinte la interfata necesita conversie explicita
((Punct)punct_depla).Deseneaza();
//apel metoda redefinita interfata
Console.WriteLine("Deplasare in sus 5 pozitii");
punct_depla.Deplaseaza(Directie.Sus, 5);
// acces metoda proprie Deseneaza a clasei Punct => conversie!
((Punct)punct_depla).Deseneaza();
Interfeţele şi moştenirea multiplă
• în C# nu este admisǎ moştenirea multiplǎ la nivel de clase, clasele
putând avea o singurǎ clasǎ de bazǎ => o limitare majoră cu impact
negativ puternic asupra scalabilităţii unei aplicaţii
• moştenirea multiplǎ este însă permisă la nivelul interfeţelor, clasele C#
putând implementa mai multe interfeţe
• atunci când o clasǎ este derivatǎ dintr-o altǎ clasǎ şi implementeazǎ şi
o interfaţǎ (sau mai multe), sintaxa definirii clasei derivate impune ca
întâi sǎ aparǎ numele clasei în listă
Interfeţele şi moştenirea multiplă
internal class Mamifer
{ protected string Specie { get; set; } }
internal interface IInteligenta
{ //declarare metoda interfata
bool Comportare_inteligenta();
}
internal class Om:Mamifer, IInteligenta
{
public Om()
{ Specie = "Oamenii sunt mamifere"; }
//definire metoda interfata
public bool Comportare_inteligenta()
{
Console.WriteLine("{0} si, in general,
au o comportare inteligenta...", Specie);
return true;
}
}
Interfeţele şi moştenirea multiplă
Om un_om = new Om();
un_om.Comportare_inteligenta();
//utilizare is => conversie corecta, in caz contrar exceptie
if (un_om is IInteligenta)
{
// conversie catre tipul interfetei a obiectului un_om
IInteligenta un_om_cu_IQ = (IInteligenta)un_om;
un_om_cu_IQ.Comportare_inteligenta();
}
//utilizare as => conversie corecta, in caz contrar exceptie
IInteligenta un_om_cu_IQ = un_om as IInteligenta;
//daca conversia s-a realizat cu succes
if (null != un_om_cu_IQ)
un_om_cu_IQ.Comportare_inteligenta();
Interfeţe MicroSoft .NET
IComparable<T>
• IComparable<T> (spaţiul de nume System) poate fi utilizată pentru a define
o relaţie de ordonare pe mulţimea obiectelor (instanţelor) unei clase
• Interfaţa definește o singură metodă de comparare generică CompareTo<T>
pe care o clasă o care implementează interfaţa trebuie să o definească în mod
specific; metoda va fi utilizată pentru stabilirea unei relaţii de ordonare a
instanţelor clasei respective (<, >, =) utilă pentru a fi utilizată ulterior în operaţii
de ordonare/sortare
• ca pentru majortatea claselor generice, parametrul T de tip este contravariant
=> se poate utiliza fie tipul concret specificat, fie orice tip care derivate din
acesta
Utilizare IComparable<T>
internal class PunctSortabil:IComparable<PunctSortabil>
{
public PunctSortabil(int x, int y)
{ X = x; Y = y; }
public int X { get; set; }
public int Y { get; set; }
public int CompareTo(PunctSortabil? punct)
{
return this.PatratDistanta() - punct.PatratDistanta();
}
public int PatratDistanta()
{
return (X * X) + (Y * Y);
}
}
• exemplul => reprezentat de o aplicaţie de tip Windows Form App care desenează cu
nuanţe diferite de culoare punctele generate aleator; nuanţa se modifică (i) funcţie de
distanţa (la pătrat) punctului pâna la originea sistemului de coordonate
Utilizare IComparable<T>
private void butDeseneaza_Click(object sender, EventArgs e)
{ List<PunctSortabil> puncte = new List<PunctSortabil>();
//generator numere aleatoare
Random rgen = new Random(); int i = 0;
//creaza un obiect Graphics
Graphics graph = this.CreateGraphics();
//inserararea in tabelul puncte a 100 de puncte generate aleator
for (i = 0; i < 100; i++)
puncte.Add(new PunctSortabil(rgen.Next(200),rgen.Next(200)));
//sortare tablou puncte – foloseste IComparable.CompareTo
puncte.Sort();
//desenare puncte sortate
foreach(PunctSortabil p in puncte)
{ Color color = System.Drawing.Color.FromArgb(200, 50, i++);
System.Drawing.SolidBrush brush =
new System.Drawing.SolidBrush(color);
graph.FillEllipse(brush, p.X, p.Y, 15, 15); brush.Dispose();
}
}
Utilizare IComparable<T>
Interfeţe MicroSoft .NET
IEnumerable şi IEnumerator
• asigurǎ suport pentru a putea realiza enumerǎri printre obiectele unei clase
utilizând foreach
• în cazul în care se doreşte implementarea unei clase de tip colecţie pe care
sǎ se poatǎ realiza enumerǎri cu ajutorul lui foreach, aceastǎ clasǎ trebuie sǎ
fie enumerabilǎ => clasa colecţie respectivǎ trebuie sǎ implementeze
interfaţa IEnumerable sau, de preferat, IEnumerable<T>
• IEnumerator reprezinta iteratorul prin intermediul caruia se realizeaza
enumerarea => o instanţă IEnumerator este utilizată de IEnumerable
pentru a realiza iterarea prin colecţia enumerabilă
• majoritatea claselor de colecţie (generice şi non-generice) predefinite în .NET
Framework pot fi iterate (enumerate) utilizînd foreach: implementeazǎ
IEnumerable (colecţii nongenerice) sau IEnumerable<T>
(colecţii generice)
Definirea une clase enumerabile –
clasa enumerator proprie
• în cazul în care se doreşte implementarea unei clase de tip colecţie definită
de utilizator pe care sǎ se poatǎ realiza enumerǎri cu ajutorul lui foreach,
aceastǎ clasǎ trebuie sǎ fie enumerabilǎ:
– clasa colecţie trebuie sǎ implementeze interfaţa IEnumerable şi, în
consecinţǎ, sǎ implementeze metoda GetEnumerator a interfeţei
IEnumerable
– metoda GetEnumerator returneazǎ o instanţǎ IEnumerator => clasa
va trebui sa defineasca un enumerator propriu, definit de utilizator, care
sǎ implementeze clasa IEnumerator => va fi utilizat doar de metoda
GetEnumerator pentru a realiza enumerarea
• enumerarea unei colecţii eşueazǎ în cazul în care colecţia se modificǎ în
timpul enumerǎrii => exceptie
Definirea une clase enumerabile –
clasa enumerator proprie
• implementarea unui enumerator propriu pentru oclasă de colecţie proprie se
poate realiza prin implementarea interfeţei IEnumerable<T>; definirea unei
clase enumerator propriu este realizată de obicei prin intermediul unei clase
imbricate în cadrul clasei colecţie enumerabilǎ care se defineste
• implementarea interfeţei IEnumerable<T> implică redefinirea a două
metode: metoda IEnumerator<T> GetEnumerator() proprie precum şi
metoda IEnumerable.GetEnumerator() deoarece IEnumerable<T>
extinde de la IEnumerable; astfel, se vor implementa metode atât pentru
enumeratorul generic, cât și pentru enumeratorul non generic
• deoarece aceste două metode returnează un enumerator care iterează prin
colecție de tipul IEnumerator sau IEnumerator<T>, în general trebuie
implementată şi interfaţa IEnumerator<T> (în C# se poate evita acest lucru
utilizând cuvântul cheie yield)
• colecțiile care implementează IEnumerable<T> pot fi enumerate folosind
foreach
Definirea une clase enumerabile –
clasa enumerator proprie
• IEnumerable<T> este interfața de bază pentru colecțiile din spațiul de nume
System.Collections.Generic, cum ar fi List<T>,
Dictionary<TKey,TValue>, Stack<T> și alte colecții generice
• de exemplu: clasa proprie ColectieEnumerabila este o clasǎ colecţie care
vrem sǎ fie enumerabilǎ:
– implementeazǎ IEnumerable<T> => defineste metoda
GetEnumerator generică a interfetei IEnumerable<T> şi cea non-
generică moştenită de la IEnumerator
– clasa Enumerator<T> este o clasǎ definitǎ în interiorul clasei
ColectieEnumerabila (clasǎ internǎ) care implementeazǎ
enumeratorul propriu-zis => implementeazǎ IEnumerator<T> adicǎ
metodele Reset, MoveNext şi proprietatea Current ale interfetei
IEnumerator<T> precum şi Dispose deoarece aceasta la rândul ei
implementează IDisposable
– metoda GetEnumerator<T> returneazǎ o instanţǎ IEnumerator<T>
Exemplu – clasa
ColectieEnumerabila
public class ColectieEnumerabila<T> :
IEnumerable<T> where T:PunctSortabil, new()
{ public ArrayList Colectie_enum { get; set; }
public ColectieEnumerabila(int n)
{ Random rgen = new Random();
Colectie_enum = new ArrayList();
//inserararea in tabel a punctelor generate aleator
for (int i = 0; i < n; i++)
{ T punct = new T();
punct.X = rgen.Next(200); punct.Y = rgen.Next(200);
Colectie_enum.Add(punct);
}
}
public IEnumerator<T> GetEnumerator()
{ return new Enumerator<T>(this); }
IEnumerator IEnumerable.GetEnumerator()
{ return new Enumerator<T>(this); }
Exemplu – clasa
ColectieEnumerabila
//implementare enumerator
class Enumerator<T> : IEnumerator<T> where T:PunctSortabil, new()
{ ColectieEnumerabila<T> colectie;
int curent; int nr_elem;
public Enumerator(ColectieEnumerabila<T> col)
{ colectie = col; curent = -1; nr_elem = col.Colectie_enum.Count; }
public T Current
{ get
{
if (nr_elem != colectie.Colectie_enum.Count)
throw new InvalidOperationException ("Colectia s-a schimbat!);
else
if (curent >= colectie.Colectie_enum.Count)
throw new InvalidOperationException("Valoare curenta incorecta!");
else return (T)colectie.Colectie_enum[curent];
}
}
Exemplu – clasa
ColectieEnumerabila
object IEnumerator.Current
{
get
{
if (nr_elem != colectie.Colectie_enum.Count)
throw new
InvalidOperationException("Colectia s-a schimbat !");
else
if (curent >= colectie.Colectie_enum.Count)
throw new InvalidOperationException("Valoare curenta incorecta!");
else return (T)colectie.Colectie_enum[curent];
}
}
public void Dispose()
{
curent = -1;
}
Exemplu – clasa
ColectieEnumerabila
public bool MoveNext()
{
if (nr_elem == colectie.Colectie_enum.Count)
{ curent++;
if (curent >= colectie.Colectie_enum.Count) return false;
else return true;
}
else
{
throw new InvalidOperationException("Colectia s-a schimbat! ");
}
}
public void Reset()
{ curent = -1; }
}
}
Definirea une clase enumerabile –
fǎrǎ clasǎ enumerator proprie
• definirea unei clase iterator definite de utilizator poate fi evitatǎ dacǎ se
utilizeazǎ cuvântul cheie yield:
• yield semnaleazǎ compilatorului cǎ metoda în care apare reprezintǎ
un bloc de iterare
• compilatorul genereazǎ o clasǎ enumerator care implementeazǎ
comportamentul care este exprimat în blocul metodei
• yield este utilizat împreunǎ cu return pentru a furniza o valoare
obiectului enumerator şi a returna o expresie:
• yield return expresie
• metoda iterator astfel definitǎ se poate utiliza fie pentru iterǎri cu foreach
fie în interogǎri LINQ
• în mod obligatoriu tipul returnat de metoda iterator trebuie sǎ fie
IEnumerable , IEnumerable<T>, IEnumerator sau IEnumerator<T>
• o instrucțiune yield return nu poate aparține unui bloc try-catch
• yield return poate exista doar în secțiunea finally
Definirea une clase enumerabile
fǎrǎ clasǎ enumerator proprie
internal class AltaColectieEnumerabila
{
int[] tablou_numere = { 10, 20, 30, 40, 50 };
public System.Collections.IEnumerable ColectieNumere()
{
for (int i = 0; i < tablou_numere.Length; i++)
yield return tablou_numere[i];
}
}

AltaColectieEnumerabila colectie = new AltaColectieEnumerabila();


foreach (int numar in colectie.ColectieNumere())
Console.Write(numar.ToString() + " ");
Definirea une clase enumerabile –
fǎrǎ clasǎ enumerator proprie
• metoda ColectieNumere utilizeazǎ clasa non-genericǎ IEnumerable =>
returneazǎ IEnumerable()
• in cazul în care se doreşte utilizarea iteratorului generic IEnumerable<T>,
acesta este derivat din IEnumerable => trebuie implementatǎ şi metoda
non-genericǎ GetEnumerator moştenitǎ de la IEnumerable
• in implementarea sa, metoda GetEnumerator trebuie sǎ returneze o
colecție de valori enumerabilǎ utilizând yield return :
public IEnumerator<T> GetEnumerator()
{
// implementare
yield return …ce se returneaza;
}
Definirea une clase enumerabile
fǎrǎ clasǎ enumerator proprie
internal class AltaColectieEnumerabila:IEnumerable<int>
{
int[] tablou_numere = { 10, 20, 30, 40, 50 };

public IEnumerator<int> GetEnumerator()


{
for (int i = 0; i < tablou_numere.Length; i++)
yield return tablou_numere[i];
}
IEnumerator IEnumerable.GetEnumerator()
{
for (int i = 0; i < tablou_numere.Length; i++)
yield return tablou_numere[i];
}
}

AltaColectieEnumerabila colectie = new AltaColectieEnumerabila();


foreach (int numar in colectie) Console.Write(numar.ToString() + " ");
Interfeţe generice .NET
• .NET oferă o serie de interfeţe generice, similare cu cele non-generice
• în mod asemănător claselor non-generice, utilizarea interfeţelor non-generice
necesită conversii de tip explicite, ȋn schimb utilizarea celor generice nu
necesită acest lucru, => siguranţă din punct de vedere al tipurilor utilizate,
eficienţă:
– ICollection<T> defineşte metode prin care se manipulează colecţiile
generice
– IComparer<T> defineşte metoda pe baza căreia se realizează
compararea a două obiecte
– IDictionary(TKey, TValue) reprezintă baza definirii o colecţiiilor
generice de perechi chei/valoare
Interfeţe generice .NET
– IEnumerable<T> şi IEnumerator<T> furnizează un enumerator prin
intermediul căruia se pot realiza iteraţii pe colecţiile generice (foreach)
– IList<T> furnizează acces prin intermediul unui index pentru o colecţie
generică; derivata din ICollection
– ISet<T> interfaţa este nouă ȋn .NET 4 şi defineşte metode pentru operaţii
cu mulţimi care sunt colecții care au elemente unice și operații specifice;
colecțiile HashSet<T> și SortedSet<T> implementează această interfață
– ILookup<TKey, TValue> definește un element de indexare, o
proprietate care returnează dimensiunea și o metodă de căutare cu rezultat
boolean pentru structurile de date care mapează cheile la secvențe de
valori IEnumerable<T>
– ….
Interfeţe generice .NET
– IEnumerable<T> şi IEnumerator<T> furnizează un enumerator prin
intermediul căruia se pot realiza iteraţii pe colecţiile generice (foreach)
– IList<T> furnizează acces prin intermediul unui index pentru o colecţie
generică; derivata din ICollection
– ISet<T> interfaţa este nouă ȋn .NET 4 şi defineşte metode pentru operaţii
cu mulţimi care sunt colecții care au elemente unice și operații specifice;
colecțiile HashSet<T> și SortedSet<T> implementează această interfață
– ILookup<TKey, TValue> definește un element de indexare, o
proprietate care returnează dimensiunea și o metodă de căutare cu rezultat
boolean pentru structurile de date care mapează cheile la secvențe de
valori IEnumerable<T>
– ….
Injecţia de dependinţe
• conceptul de injecţie de dependinţe se referă la un model de proiectare a
unei aplicaţii software în care un obiect sau o funcție primește ca parametri
alte obiecte de care depinde
• reprezintă o formă de inversare a controlului, şi are ca scop reducerea
dependeţei unui obiect de altul sau de o implementare particulară a
acestuia, rezultând structuri mai slab cuplate => mai uşor
modificabile/intershimbabile/mentenabile
• interfeţele pot reprezenta o soluţie pentru definirea parametrilor în acest caz
=> se pot transmite orice obiecte care implementează interfaţa
• exemplu: clasa Punct prin metoda Deseneaza este mult prea dependentă
de clasa Console (afişare la consolă); pentru a reduce această dependenţă
şi pentru a putea realiza, în funcţie de context, afişări atât la consolă dar şi,
de exemplu, într-un fişier, se va utiliza o interfaţă => ILogger
Exemplu – injecţie de dependinţe
internal class Punct
{ … //alte implementari
public ILogger? Logger { get; set; }
//injectia de dependinte in constructor => parametrul Ilogger log
public Punct(int x, int y, ILogger log)
{
this.X = x;
this.Y = y;
Logger = log;
}
//public virtual void Deseneaza() =>
Console.WriteLine(this.ToString());
public virtual void Deseneaza() => Logger.Log(this.ToString());
}
Exemplu – injecţie de dependinţe
public interface Ilogger { void Log(string mesaj); }

public class ConsoleLogger : ILogger


{ //scrie mesajul pe consola
public void Log(string mesaj) => Console.WriteLine(mesaj);
}

public class FileLogger : ILogger


{ //scrie mesajul intr-un fisier
public void Log(string mesaj)
{
string path = @"C:\Users\doina\...\Text.txt";
using (StreamWriter writer = new StreamWriter(path))
{
writer.WriteLine(mesaj);
}
}
Exemplu – injecţie de dependinţe
//scrie mesajul pe consola => se transmite un ConsoleLogger
Punct p1 = new Punct(5, 7, new ConsoleLogger());
p1.Deseneaza();

//scrie mesajul in fisier=> se transmite un FileLogger


Punct p2 = new Punct(5, 7, new FileLogger());
p2.Deseneaza();
Default interface methods
• până la versiunea C#8, orice modificare în structura unei interfeţe
(adăugarea unei noi metode) implica schimbări în toate clasele care
implementau această interfaţă deoarece acestea trebuiau să implementeze
noua metodă declarată în cadrul interfeţei => implică schimbări în lanţ
• datorită acestui fapt, multe dintre bibliotecile .NET sunt construite având la
bază clase abstracte şi nu interfeţe => adăugarea unei noi metode într-o
clasă abstractă nu necesită modificarea claselor derivate decât în măsura în
care metoda era abstractă
• în ideea de a se putea utiliza mai uşor şi interfeţe în locul claselor abstracte,
începând cu versiunea C#8 este permisă implementarea de metode în
cadrul interfeţelor : default interface methods => acestea dispun de
implementare şi se moştenesc ca atare în clasele care implementează
interfaţa care nu este necesar să le redefinească (decât daca se doreste
acest lucru)
Exemplu – ILogger revizuit
public interface ILogger
{
void Log(string mesaj);
//adaugam default interface method => cu implementare
public void Log(Exception ex) => Log(ex.Message);
}

//utilizare: clasa ConsoleLogger nu s-a modificat


//in urma modificarii interfetei
ILogger log = new ConsoleLogger();
//metoda initiala
log.Log("Mesaj catre consola");
//metoda default poate fi de asemenea utilizata
log.Log(new Exception("mesaj Exceptie"));
Moştenirea şi clasele abstracte
• C# oferǎ un mecanism pentru a restricţiona crearea de instanţe din anumite
clase, prin posibilitatea de a defini clase de bazǎ abstracte
• clasele abstracte nu pot fi instanţiate (adicǎ nu se pot crea obiecte din
clasa respectivǎ; se pot, în schimb, crea referinţe cǎtre clase abstracte
• încercările de a instanţia o clasǎ abstractǎ sunt detectate de compilator şi
semnalate ca şi eroare
• o clasă abstractă furnizează un cadru, un schelet pe baza căruia se pot
construi alte clase, prin derivare; declararea unei clase abstracte se face
adǎugînd cuvântul cheie abstract
Moştenirea şi clasele abstracte
• în declaraţia clasei, clasele abstracte pot conţine metode sau proprietǎţi
abstracte, implementate tot prin adǎugarea cuvîntului cheie abstract în
cadrul declaraţiei acestora: declaraţia unui membru abstract nu este urmatǎ
de nici o implementare, cuvîntul cheie abstract impunând ca proprietatea
sau metoda sǎ fie suprascrisă în clasele derivate
• dacǎ cel puţin un membru a unei clase este declarat ca fiind abstract, atunci
toatǎ clasa trebuie declaratǎ ca abstractǎ; dacǎ însǎ se declarǎ o clasǎ
abstractǎ, nu este necesar ca vre-unul dintre membri acesteia sǎ fie
declarat abstract
• pentru a crea o clasǎ instanţiabilǎ derivatǎ dintr-o clasǎ de bazǎ abstractǎ,
trebuie redefiniţi toţi membri ei abstracţi în cadrul clasei derivate
• metodele/proprietăţile declarate abstracte în cadrul unei clase sunt implicit
şi virtuale => suprascrierea acestora în clasele derivate (override) conduce
la un comportament polimorfic
Utilizarea claselor abstracte
public abstract class ContBancar
{
public float Sold { get; set; }
public float Retrageri_totale { get; set; }
public float Depuneri_totale { get; set; }
//proprietate abstracta
public abstract string ID { get; }
virtual public float Depune(float cantitate)
{
Depuneri_totale += cantitate; return Sold += cantitate;
}
virtual public float Retrage(float cantitate)
{
Retrageri_totale -= cantitate; return Sold -= cantitate;
}
public abstract string Tiparire();
}
Utilizarea claselor abstracte
public class ContCurent : ContBancar
{
public ContCurent(string? proprietar)
{
Proprietar = proprietar;
}
public string? Proprietar { get; set; }

//implementare proprietate abstracta


public override string ID { get { return "Cont curent
- titular: " + this.Proprietar; } }

//suprascriere metoda abstracta


public override string Tiparire()
{
return "Cont curent - titular "+this.Proprietar
+" Sold: "+this.Sold;
}
Utilizarea claselor abstracte
//suprascriere metode virtuale
override public float Depune(float cantitate)
{
base.Depune(cantitate); return Sold;
}
override public float Retrage(float cantitate)
{
base.Retrage(cantitate); return Sold;
}
}
Utilizarea claselor abstracte
public class ContEconomii:ContCurent
{
public ContEconomii(string? proprietar, float dobanda)
: base(proprietar)
{
Dobanda = dobanda;
}
public float Dobanda { get; set; }
public float Adauga_Dobinda()
{
Depune(Dobanda * Sold); return Sold;
}
//suprascriere metoda
public override string Tiparire()
{
return base.Tiparire();
}
}
Utilizarea claselor abstracte
ContCurent contb = new ContCurent("Popescu Ion");
contb.Depune(1000);
contb.Retrage(200);
Console.WriteLine(contb.Tiparire());

ContEconomii conte = new ContEconomii("Ionescu Marin", 0.8f);


conte.Depune(1500);
conte.Retrage(300);
conte.Adauga_Dobinda();
Console.WriteLine(conte.Tiparire());
Colecţii abstracte în .NET
• public abstract class CollectionBase :
System.Collections.IList
• clasă de colecţie abstractă utilizată ca bază pentru crearea de
colecţii puternic tipizate (strongly typed)

• public abstract class DictionaryBase :


System.Collections.Idictionary – clasă de colecţie
abstractă utilizată ca bază pentru a crea o colecție puternic tipizată
de perechi cheie/valoare
Exemplu - clasa Carte
public class Carte
{
public Carte(string? titlu, string? autor, string? editura)
{
Titlu = titlu;
Autor = autor;
Editura = editura;
}
public string? Titlu { get; set; }
public string? Autor { get; set; }
public string? Editura { get; set; }
}
Colecţii puternic tipizate
• clasa ColectieCarti, este derivatǎ din clasa de colecţie
DictionaryBase din .NET
• s-a utilizat aceastǎ abordare pentru a putea utiliza metodele puse la
dispoziţie de clasa DictionaryBase dar şi pentru a putea defini astfel o
clasǎ de colecţie puternic tipizatǎ
• colecţiile abstracte sunt puternic tipizate, ceea ce elimină potenţiale erori în
momentul execuţiei
• de asemenea, performanţa este mai bună decît ce a colecţiilor non-tipizate
datorită eliminării conversiilor de tip
• reprezintă o alternativă la utilizarea colecţiilor generice care sunt de
asemenea puternic tipizate; în general, este de preferat utilizarea acestora
din urmă
Exemplu - clasa ColectieCarti
public class ColectieCarti : DictionaryBase
{
public void Adauga(Carte book)
{ //apel metoda Add a clasei Dictionary
this.Dictionary.Add(book.Titlu, book);
}
public void Extrage(string titlu)
{ //apel metoda Remove a clasei Dictionary
this.Dictionary.Remove(titlu);
}
public bool Contine(Carte book)
{ //conversie tipul string în String
String tit = (String)book.Titlu;
//apel metoda Contains a clasei Dictionary
return this.Dictionary.Contains(tit);
}
Exemplu - clasa ColectieCarti
//metoda Contine supraincarcata (overerloading)
public bool Contine(string titlu)
{
String tit = (String)titlu;
return this.Dictionary.Contains(tit);
}
//definire element de indexare
public Carte this[string titlu]
{
get
{
if
(this.Dictionary.Contains(titlu))
return (Carte)(this.Dictionary[titlu]);
else return null;
}
}
}
Clasa DictionaryBase
• furnizează o clasă de bază abstractǎ pentru implementarea colectiilor puternic
tipizate de tip pereche cheie (key) / valoare (value)

public abstract class DictionaryBase :


System.Collections.IDictionary
• proprietăţi:
protected System.Collections.IDictionary Dictionary { get; }
=> obține lista de elemente conținute în instanța DictionaryBase
public int Count { get; }
protected System.Collections.Hashtable InnerHashtable { get; }
=> o tabelă hashing reprezentând însăși instanța DictionaryBase
• metode:
void IDictionary.Add(Object key, Object value);
void IDictionary.Remove(Object key);
bool IDictionary.Contains(Object key);
Exemplu - clasa Biblioteca
public class Biblioteca
{
private ColectieCarti colectie_biblio = new ColectieCarti();
public Biblioteca() { }
public void Intrare(Carte carte)
{
colectie_biblio.Adauga(carte);
}
public Carte Iesire(string? titlu)
{
Carte cartea = colectie_biblio[titlu];
colectie_biblio.Extrage(titlu);
return cartea;
}
Exemplu - clasa Biblioteca
//metoda Contine supraincarcata
public bool Contine(Carte book)
{
return colectie_biblio.Contine(book);
}
public bool Contine(string? titlu)
{
return colectie_biblio.Contine(titlu);
}
}
// utilizare Biblioteca - creare obiecte
Biblioteca librarie = new Biblioteca();
Carte literatura = new Carte("Morometii", "Marin Preda",
"Editura Litera");
Carte tehnica = new Carte("Professional C# and NET",
"Christian Nagel", "John Wiley and Sons");
Carte dictionar = new Carte("Dictionar Roman-Englez",
"Popescu Dorina", "Editura Teora");
Exemplu – utilizare Biblioteca
// adaugare in biblioteca
librarie.Intrare(literatura);
librarie.Intrare(tehnica);
librarie.Intrare(dictionar);

if (librarie.Contine(literatura)) Console.WriteLine("Exista!");
else Console.WriteLine("Nu exista!");
if (librarie.Contine(tehnica.Titlu))
Console.WriteLine("Exista!");
else Console.WriteLine("Nu exista!");
librarie.Iesire("Dictionar Roman-Englez");
//apel Contine cu parametru string
if (librarie.Contine(dictionar.Titlu))
Console.WriteLine("Exista!");
else Console.WriteLine("Nu exista!");
Serializarea
• serializarea este procesul de conversie a stării unui obiect, adică a valorilor
proprietăților acestuia, într-o formă care poate fi stocată şi, eventual
transmisă
• termenul de serializare se foloseşte în limbajele orientate pe obiecte în
legătură cu persistenţa obiectelor (instanţelor); pentru ca un obiect să fie
persistent, el trebuie salvat pe o memorie nevolatilă pentru a putea fi
restaurat ulterior pe baza informaţiilor salvate.
• datele instanţei serializate sunt salvate în ordine, de obicei câmp cu câmp;
daca se doreşte serializarea mai multor instanţe, salvarea datelor acestora
se realizeaza în ordine, una dupa alta
• serializarea se face secvenţial, deci ordinea de salvare trebuie să fie
aceeaşi cu ordinea de citire pentru restaurare; nu se pot accesa înregistrări
individuale, deci serializarea nu se poate folosi pentru stocarea bazelor de
date
• deserializarea reprezinta operaţia inversă serializarii prin care datele
serializate în prealabil sunt inserate într-o instanţă (sau în instanţe) ale
clasei
Moduri de serializare
• se pot utiliza diferite standarde, dintre care cele mai cunoscute standarde
de industrie sunt bazate pe fişierele XML (Extensible Markup Language)
sau pe serializare JSON (JavaScript Object Notation); există, dar este mai
puţin actuală şi varianta de serializare binară (fişier bitmap)
• .NET pune la dispozitie clase specifice pentru operaţiile de
serializare/deserializare:
• XMLSerialization pentru serializarea XML => System.Xml.Serialization
• JsonSerializer pentru serializarea JSON => System.Text.Json
• BinaryFormatter pentru serializarea binarǎ =>
System.Runtime.Serialization.Formatters.Binary;
Serializarea XML
• se utilizează clasa XmlSerializer din spaţui de nume
System.Xml.Serialization
• în XML sunt serializabile doar câmpurile şi proprietǎţile publice
• dacǎ datele instanţelor nu sunt accesibile din câmpurile sau proprietǎţile
publice, ele nu vor fi iniţializate la deserializarea obiectelor => proprietățile
trebuie să aibă modificatori publici (get și set).
• pentru serializarea XML este necesar un constructor public, fǎrǎ parametri
• o clasă trebuie să aibă un constructor fără parametri pentru a fi serializată
XML cu XmlSerializer
• pentru a fi serializată XML, o clasă care implementează IEnumerable
(List<T>) trebuie să implementeze o metodă publică Add cu un parametru
care trebuie să fie coresspunzător (polimorfic) cu tipul returnat de
IEnumerator, respectiv proprietatea Current returnată de la metoda
GetEnumerator
O clasă serializabilă
public class Persoana
{
public string? Nume { get; set; }
public string? Prenume { get; set; }
public int? Varsta { get; set; }
public Persoana(string nume, string prenume, int virsta)
{
Nume = nume; Prenume = prenume; Varsta = virsta;
}
//constructor fǎrǎ parametri pentru serializare
public Persoana() { }
public override string? ToString()
{
return " Nume " + this.Nume + " Prenume "
+ this.Prenume + " Varsta " + this.Varsta;
}
}
O colecţie serializabilă
public class ColectiePersoane
{ public string Nume { get; set; }
public List<Persoana> ElemPersoane { get; set; }
public ColectiePersoane()
{
ElemPersoane= new List<Persoana>();
}
public void Add(Persoana persoana)
{
this.ElemPersoane.Add(persoana);
}
public override string? ToString()
{
string s = string.Empty;
foreach (Persoana p in ElemPersoane) s+=p.ToString()+ " ";
return s;
}
}
O colecţie serializabilă
string fisierXML = "persoane.xml";
ColectiePersoane colectie = new ColectiePersoane();
Persoana pers_01 = new Persoana("Popescu", "Dorina", 40);
Persoana pers_02 = new Persoana("Avramescu", "Claudia", 25);
Persoana pers_03 = new Persoana("Pantea", "Marius", 27);
colectie.Add(pers_01); colectie.Add(pers_02); colectie.Add(pers_03);
System.IO.TextWriter writer = new System.IO.StreamWriter(fisierXML);
//creare obiect xmlSerial cu parametru tipul obiectelor serializate
XmlSerializer xmlSerial = new XmlSerializer(typeof(ColectiePersoane));
//apel metoda de serializare
xmlSerial.Serialize(writer, colectie);
writer.Close();
//deserializare xml
System.IO.TextReader reader = new System.IO.StreamReader(fisierXML);
// metoda Deserialize returneazǎ o instanţǎ System.Object
colectie = (ColectiePersoane)xmlSerial.Deserialize(reader);
Console.WriteLine("Dupa deserializare XML!");
Console.WriteLine(colectie.ToString()); reader.Close();
Rezultatul serializării XML
<?xml version="1.0" encoding="utf-8"?>
<ColectiePersoane xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ElemPersoane>
<Persoana>
<Nume>Popescu</Nume>
<Prenume>Dorina</Prenume>
<Varsta>40</Varsta>
</Persoana>
<Persoana>
<Nume>Avramescu</Nume>
<Prenume>Claudia</Prenume>
<Varsta>25</Varsta>
</Persoana>
<Persoana>
<Nume>Pantea</Nume>
<Prenume>Marius</Prenume>
<Varsta>27</Varsta>
</Persoana>
</ElemPersoane>
</ColectiePersoane>
Serializarea XML
• pentru a serializa o clasǎ care implementeazǎ şi ICollection, cum este
clasa ColectieSerializabila care are ca şi clasǎ de bazǎ
CollectionBase, valorile care urmează să fie serializate sunt preluate
indexat, mai degrabă decât prin apelarea GetEnumerator, astfel trebuie
implementaţi şi urmǎtorii membri:
• metoda Add cu un parametru de acelaşi tip cu obiectul returnat de
proprietatea Current a metodei GetEnumerator
• un element de indexare care sǎ returneze acelaşi tip (polimorfic) cu
metoda Add definitǎ anterior; un astfel de element de indexare a fost
definit în cadrul colecţiei ColectieSerializabila
• aceşti membri permit procesului de serializare sǎ acceseze obiectele
colecţiei prin intermediul elementului de indexare şi sǎ deserializeze
obiectele prin metoda Add
O altă colecţie serializabilă
public class ColectieSerializabila: System.Collections.CollectionBase
{
public ColectieSerializabila() { }
//pentru serializare XML
public void Add(Persoana p)
{
this.InnerList.Add(p);
}
//pentru serializare XML
public Persoana this[int index]
{
get { return (Persoana)(this.InnerList[index]); }
set { this.InnerList[index] = value; }
}
O altă colecţie serializabilă
public void Sterge(Persoana p)
{
this.InnerList.Remove(p);
}
public override string? ToString()
{
string c = string.Empty;
foreach (Persoana p in this.InnerList) c+=p.ToString()+"\n";
return c;
}
}
O altă colecţie serializabilă
ColectieSerializabila persoane = new ColectieSerializabila();
//serializare xml
string fisierXML = "persoane.xml";
Persoana pers_01 = new Persoana("Popescu", "Dorina", 40);
persoane.Add(pers_01);
Persoana pers_02 = new Persoana("Avramescu", "Claudia", 25);
persoane.Add(pers_02);
Persoana pers_03 = new Persoana("Pantea", "Marius", 27);
persoane.Add(pers_03);
//afisare continut persoane inainte de serializare
persoane.ToString();
Console.WriteLine(persoane.ToString());
O altă colecţie serializabilă
//asociere obiect writer cu fisierul
System.IO.TextWriter writer = new System.IO.StreamWriter(fisierXML);
//creare obiect xmlSerial cu parametru tipul obiectelor serializate
XmlSerializer xmlSerial = new
XmlSerializer(typeof(ColectieSerializabila));
//apel metoda de serializare
xmlSerial.Serialize(writer, persoane);
writer.Close();
//deserializare xml
System.IO.TextReader reader = new System.IO.StreamReader(fisierXML);
// metoda Deserialize returneazǎ o instanţǎ System.Object
persoane = (ColectieSerializabila)xmlSerial.Deserialize(reader);
Console.WriteLine("Dupa deserializarea XML");
Console.WriteLine(persoane.ToString());
reader.Close();
O altă colecţie serializabilă
public void Sterge(Persoana p)
{
this.InnerList.Remove(p);
}
public override string? ToString()
{
string c = string.Empty;
foreach (Persoana p in this.InnerList) c+=p.ToString()+"\n";
return c;
}
}
Rezultatul serializării XML
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfPersoana xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Persoana>
<Nume>Popescu</Nume>
<Prenume>Dorina</Prenume>
<Varsta>40</Varsta>
</Persoana>
<Persoana>
<Nume>Avramescu</Nume>
<Prenume>Claudia</Prenume>
<Varsta>25</Varsta>
</Persoana>
<Persoana>
<Nume>Pantea</Nume>
<Prenume>Marius</Prenume>
<Varsta>27</Varsta>
</Persoana>
</ArrayOfPersoana>
Controlul generării fişierelor XML
public class Persoana
{
[XmlElement("NumePersoana")]
public string? Nume { get; set; }
public string? Prenume { get; set; }
[XmlIgnoreAttribute]
public int? Varsta { get; set; }
….
}

• denumirea sub care se crează tag-urile XML poate fi controlată prin


intermediul atributului [XmlElementAttribute] ; atributul indică faptul ca
proprietate publică reprezintă un element XML atunci când obiectul care îl
conţine este serializat sau deserializat utilizând XMLSerializer
• dacă se doreşte ignorara unor anumite proprietăţi în procesul de serializare,
se poate utiliza atributul [XmlIgnoreAttribute]
• alte variante….
Rezultatul serializării XML
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfPersoana xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Persoana>
<NumePersoana>Popescu</NumePersoana>
<Prenume>Dorina</Prenume>
</Persoana>
<Persoana>
<NumePersoana>Avramescu</NumePersoana>
<Prenume>Claudia</Prenume>
</Persoana>
<Persoana>
<NumePersoana>Pantea</NumePersoana>
<Prenume>Marius</Prenume>
</Persoana>
</ArrayOfPersoana>
Limbajul XML
(Extensible Markup Language)
• prima varianta a limbajului XML (XML 1.0) a apǎrut în 1998
• este un limbaj de marcare, asemǎnǎtor HTML-ului dar care diferǎ faţa de
acesta prin urmǎtoarele:
– HTML reprezintǎ doar o tehnologie de prezentare; practic, codul HTML
nu relevǎ absolut nimic despre informaţia asupra cǎreia tag-urile HTML
sunt aplicate.
– numele de tag-uri HTML nu indicǎ nimic referitor la conţinutul la care se
referǎ ci doar la modul în care contintul va fi afişat (aspectul sǎu)
– HTML are un set fix de tag-uri predefinite, care nu poate fi extins de
cǎtre utilizator
Limbajul XML
(Extensible Markup Language)
• documentele XML reprezintǎ de fapt colecţii de date => o ierarhie
imbricatǎ de elemente avînd o singurǎ rǎdǎcinǎ;
• fişierele XML se autodescriu: tag-urile descriu atît structura cît şi tipul
datelor pe care le conţin
• limbajul XML este extensibil în sensul cǎ permite utilizatorului sǎ-şi
defineascǎ propriile tag-uri
• fişierele XML sunt portabile (se bazeazǎ pe Unicode)
• fişierele XML descriu datele sub formǎ de arbori şi/sau grafuri
• documentele XML sunt destinate în primul rînd descrierii şi structurǎrii
datelor
• XML nu trebuie vǎzut ca un înlocuitor al HTML-ului deoarece cele douǎ
limbaje sunt destinate unor scopuri complet diferite:
– XML este destinat descrierii şi structurǎrii datelor şi este orientat pe
conţinutul datelor
– HTML este destinat afişǎrii datelor şi este orientat pe modul cum
acestea sunt vizualizate.
Structura documentelor XML
<?xml version="1.0" • sintaxa este auto-descriptivǎ :
encoding="utf-8"?>
<nota> prima linie conţine declaraţia XML
<catre>Radu</catre> care defineşte de obicei versiunea
<dela>Ioana</dela> XML utilizatǎ
<antet>Atentie</antet> • linia urmǎtoare descrie elementul
<body> rǎdǎcinǎ (root) al documentului
Nu uita tema la matematica!
(<nota>)
</body>
</nota> • restul elementelor sunt elementele
“copii” ale rǎdǎcinii: <catre>,
<radacina> <dela>, <antet> şi <body>.
<copil>
• imbricarea elementelor poate
<subcopil>.....</subcopil>
</copil>
continua pe mai multe niveluri:
</radacina> elementele “copii” pot fi la rândul
lor elemente rǎdǎcinǎ pentru alte
elemente “sub-copii”
Structura documentelor XML
• toate documentele XML trebuie sa aibă tag-uri atît de început cât şi de sfârşit
• elementele XML sunt case-sensitive
• elementele XML trebuie să fie încuibate corespunzător
• toate documentele XML trebuie să aibă un element rădăcină unic (root)
• asemănător cu HTML, tag-urile pot avea şi atribute, care trebuie întotdeuna să
apară între ghilimele
• spaţiile din documentele XML sunt luate în considerare; CR/LF este convertit
în LF
• comentariile în XML sunt similare celor din HTML <!-- -->
Atribute în XML
<librarie>
• elementele XML pot formate <carte categorie="BELETRISTICA">
<titlu limba="en">Gone with the
atât dintr-un conţinut cât şi wind</titlu>
din atribute. <autor>Margaret Mitchell</autor>
• atributele adaugă informaţii <an>2001</an>
suplimentare elementelor <pret>30.00</pret>
XML. </carte>
<carte categorie="COPII">
• valoarea atributelor XML <titlu limba="ro">Pinochio</titlu>
trebuie întotdeauna inclusă <autor>Carlo Collodi</autor>
între ghilimele <an>2003</an>
<pret>25.00</pret>
</carte>
<carte categorie="CALCULATOARE">
<titlu limba="ro">XML</titlu>
<autor>Lee Ann Phillips</autor>
<an>2000</an>
<pret>40.00</pret>
</carte>
</librarie>
Atribute în XML

Element root
<librarie>

Element Atribut
Atribut <carte> “categorie”
“limba”

Element Element Element Element


<titlu> <autor> <an> <pret>
Atribute versus elemente in XML
<persoana sex=”masculin”>
<nume>Popescu</nume>
<prenume>Ion</prenume>
</persoana>

<persoana>
<sex>masculin<sex>
<nume>Popescu</nume>
<prenume>Ion</prenume>
</persoana>

• în XML este de obicei recomandată evitarea atributelor pe cât posibil şi


utilizarea în locul acestora a elementelor XML, în primul rând datorită unor
limitări ale atributelor faţa de elemente:
– atributele nu pot conţine valori multiple, elementele pot
– atributele nu pot conţine structuri, elementele pot
– atributele nu sunt extensibile ulterior
– => pentru memorarea datelor este de preferat să se utilizeze elemente;
atributele se pot utiliza atunci când informaţia conţinută în ele este
relevantă pentru date
Validarea şi corectitudinea
documentelor XML
• există o diferenţă semnificativă vizavi de ceea ce înseamnă un document
XML corect sau valid
– un document XML se consideră a fi corect dacă el se conformează
regulilor de sintaxă XML
– un document XML este valid dacă acesta se conformează regulilor
definite în cadrul unor definiţii impuse pentru structura documentului în
cauză
– pentru a specifica structura și conținutul care sunt pentru un document
XML, se pot utiliza definiţii de tip document (DTD), o schemă Microsoft
XML-Data Reduced (XDR) sau o schemă XML Schema definition
language (XSD).
– schemele XSD sunt modalitatea preferată de a specifica astfel de
definiţii XML în .NET, dar sunt aceptate și variantele DTD și XDR
Validarea şi corectitudinea
documentelor XML
• fişierul XML Nota.xml

<?xml version="1.0" encoding="utf-8" ?>


<nota>
<catre>Radu</catre>
<dela>Ioana</dela>
<antet>Atentie</antet>
<body>Nu uita tema la matematica!</body>
</nota>
Utilizarea schemelor XML
pentru validarea documentelor XML
• fişierul Nota.xsd => generat în Visual Studio pe baza fişierului XML prin
opţiunea XML/Create Schema
="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="nota">
<xs:complexType>
<xs:sequence>
<xs:element name="catre" type="xs:string" />
<xs:element name="dela" type="xs:string" />
<xs:element name="antet" type="xs:string" />
<xs:element name="body" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Structura schemelor XML
• elementul <schema> este elementul rădăcină a oricărei scheme XML,
aastfel încât forma generală a unei scheme XML va fi următoarea :

<xs:schema>
...
</xs:schema>

• o schemă XML poate conţine elemente simple, de forma:


<xs:element name="xxx" type="yyy"/>

• tipurile posibile într-o schemă XML sunt următoarele: xs:string, xs:decimal,


xs:integer , xs:positiveInteger, xs:boolean, xs:date, xs:time:

<xs:element name="nume" type="xs:string"/>


<xs:element name="virsta" type="xs:integer"/>
<xs:element name="datanasterii" type="xs:date"/>
Structura schemelor XML
• un element complex poate conţine atribute, alte elemente şi text
• indicatorul sequence specifică faptul că elementele trebuie să apară în
secvenţa (ordinea) dată
<xs:element name="aviz">
<xs:complexType mixed="true">
<xs:sequence>
<xs:element name="numefirma"
type="xs:string"/>
<xs:element name="numarfactura"
type="xs:positiveInteger"/>
<xs:element name="datafactura"
type="xs:date"/>
</xs:sequence>
</xs:complexType>
</xs:element>

• există diferite variante pentru a valida fişierele xml în conformitate cu un


anumit XML Schema impus, de exemplu
• https://learn.microsoft.com/en-us/dotnet/standard/data/xml/xml-schema-xsd-
validation-with-xmlschemaset
Formatul JSON
• asemănător XML, JSON este un format de text pentru stocarea și
transportul datelor; faţă de XML însă JSON este un format ușor (lightweight)
de schimb de date
• JSON este independent de limbaj: sintaxa JSON este derivată din notația
obiect JavaScript (JavaScript Object Syntax), dar formatul JSON este doar
text
• un obiect JSON conține date sub formă de pereche cheie/valoare unde
cheia trebuie să fie de tip string, iar valoarea poate fi de oricare dintre
următoarele tipuri: string, numeric, Boolean, array, object, null
• notaţia JSON implică o serie de reguli sintactice, printre care:
– cheile și valorile sunt separate prin două puncta şi fiecare intrare
(perechea cheie/valoare) este separată prin virgulă
– colecţii de perechi cheie-valoare se include înte acolade accolade
– colecţii ordonate de perechi cheie-valoare separate prin virgulă se
include între paranteze drepte [...]
Exemplu JSON
{
"nume": " Martinescu Alin ",
"id": "E00245",
"rol": ["Dev", "DBA"],
"varsta": 35,
"adresa": {
"strada": "Republicii 45",
"oras": "Oradea",
"tara": "Romania"
},
}
sau

{"persoane":[
{"nume":"Popescu Ion", "email":"ipopescu@gmail.com"},
{"nume":"Ionescu Marian", "email":"mionescu@gmail.com"},
{"nume":"Avramescu Dan", "email":"davramescu@gmail.com"}
]
}
Echivalent XML
<persoane>
<persoana>
<nume> Popescu Ion</nume>
<email> ipopescu@gmail.com</email>
</persoana>
<persoana>
<nume> Ionescu Marian</nume>
<email> mionescu@gmail.com</email>
</persoana>
<persoana>
<nume> Avramescu Dan</nume>
<email> davramescu@gmail.com</email>
</persoana>
</persoane>
=> alternativa JSON este mult mai concisă decât XML
Serializarea JSON
• spațiul de nume System.Text.Json.Serialization oferă
funcționalităţi pentru serializarea și deserializarea din JavaScript Object
Notation (JSON) => clasa JsonSerializer
• la serializarea JSON, în mod implicit se vor serializa toate proprietățile
publice ale obiectului. dacă se doreşte ignorarea unor proprietăţi
individuale, există şi aici mai multe opțiuni, de exemplu varianta utilizării
atributului [JsonIgnore]
• implicit, deserializarea JSON utilizează constructorul fără parametri pentru
a crea obiectul deserializat, deci un astfel de constructor trebuie să existe;
pot fi setate eventual şi alte opţiuni pentru a se utiliza în procesul de
deserializare şi constructor cu parametri
Exemplu - serializarea JSON a unei liste
List<Persoana> persoane = new List<Persoana>();
Persoana pers_01 = new Persoana("Popescu", "Dorina", 40);
persoane.Add(pers_01);
Persoana pers_02 = new Persoana("Avramescu", "Claudia", 25);
persoane.Add(pers_02);
Persoana pers_03 = new Persoana("Pantea", "Marius", 27);
persoane.Add(pers_03);
string persoaneJSON =
JsonSerializer.Serialize<List<Persoana>>(persoane);
//deserializare JSON => reconstruire obiect List<Persoana>
List<Persoana> DinNoupersoaneJSON =
JsonSerializer.Deserialize<List<Persoana>>(persoaneJSON);
foreach (Persoana p in DinNoupersoaneJSON)
Console.WriteLine(p.ToString());
O altă colecţie serializabilă
public class ColectiePersoane
{
public List<Persoana> ElemPersoane { get; set; }
public ColectiePersoane()
{
ElemPersoane= new List<Persoana>();
}
public void Add(Persoana persoana)
{
this.ElemPersoane.Add(persoana);
}

public override string? ToString()


{ string s = string.Empty;
foreach (Persoana p in ElemPersoane) s+=p.ToString()+ " ";
return s;
}
}
Exemplu - serializarea JSON
ColectiePersoane
ColectiePersoane colectie = new ColectiePersoane();
Persoana pers_01 = new Persoana("Popescu", "Dorina", 40);
Persoana pers_02 = new Persoana("Avramescu", "Claudia", 25);
Persoana pers_03 = new Persoana("Pantea", "Marius", 27);
colectie.Add(pers_01);
colectie.Add(pers_02);
colectie.Add(pers_03);
string persoaneJSON =
JsonSerializer.Serialize<ColectiePersoane>(colectie);
Console.WriteLine(persoaneJSON);
//deserializare JSON => reconstruire obiect ColectiePersoane
ColectiePersoane DinNoucolectie =
JsonSerializer.Deserialize<ColectiePersoane>(persoaneJSON);
Console.WriteLine(DinNoucolectie.ToString());
Controlul serializării JSON
public class Persoana
{ [JsonPropertyName("NumePersoana")]
public string? Nume { get; set; }
public string? Prenume { get; set; }
[JsonIgnore]
public int? Varsta { get; set; }
….
}
• denumirea sub care se crează tag-urile XML poate fi controlată prin
intermediul atributului [JsonPropertyName]; atributul indică faptul ca
proprietate publică reprezintă un element XML atunci când obiectul care îl
conţine este serializat sau deserializat utilizând JsonSerializer
• dacă se doreşte ignorarea unor anumite proprietăţi în procesul de serializare,
se poate utiliza atributul [JsonIgnore]
Delegaţi – definiţii, generalităţi
• reprezintă o altă modalitate de a obţine generalitate şi flexibilitate ȋn cadrul
programelor => delegaţi transmişi ca parametri metodelor => apelul unor
metode diferite în funcţie de context
• se utilizează pentru a transmite metode ca parametri altor metode, ȋn
situaţii ca de exemplu:
– biblioteci de clase generice =>pentru transmiterea unor metode utilizate
de alte metode ȋn cadrul algoritmului acestora ( de exemplu, metoda de
comparare a doua obiecte utilizată de un algoritm de sortare)
– evenimente => pentru a transmite o anumită metodă care se execută ȋn
urma apariţiei unui anumit eveniment
– programare concurentă, pentru pornirea firelor de execuţie => pentru
transmiterea metodei pe care thread-urile o vor executa
Delegaţi – definiţii, generalităţi
• în C++ există aşa numitele funcţii callback => utilizează pointeri la funcţii
pentru a transmite funcţii ca parametri altor funcţii; delegaţii în C# reprezintă
o facilitate similară, mult mai sigură din punctul de vedere al tipurilor
• pentru crearea unui delegat este nevoie de semnătura funcţiilor transmise
acestuia: parametri, numele funcţiei, etc.
• delegaţii sunt de fapt clase: în urma declaraţiei unui delegat, acesta este
derivat din clasele de bază care se referă la delegaţi din biblioteca .NET:
System.MulticastDelegate sau System.Delegate
• dacă tipul returnat în cadrul delegatului este void, atunci în mod automat
acesta devine de tipul System.MulticastDelegate; acest tip suportă
mai multe funcţii prin utilizarea operatorului +=. dacă tipul returnat în cadrul
delegatului este diferit de void, atunci în mod automat acesta devine de tipul
System.Delegate şi nu suportă funcţii multiple
Delegati - invocarea
mai multor metode
• implică utilizarea delegaţilor pentru transmiterea unor metode (funcţii) ca
parametru => referinţe la funcţii
• utilizarea referinţelor la funcţii prin intermediul delegaţilor permite o mai
mare flexibilitate a codului rezultat => apelul unor metode diferite în funcţie
de contextul existent la un anumit moment dat
• problema poate fi generalizată şi soluţionată într-o manieră mai elegantă
dacă se transmite unui algoritm un delegat şi se lasă ca metoda conţinută,
şi pe care delegatul o referă, să poată să fie diferită
• un exemplu în acest sens o reprezintă realizarea unei sortări a unui tablou
de persoane => o dată în funcţie de nume şi o dată în funcţie de vîrsta
acestora; metoda de sortare este generală, şi utilizează un delegat: funcţia
asociată delegatului diferă însă funcţie de tipul sortării dorite
Exemplu - sortare generalizată
public class Persoana
{
//declaratia delegatului
public delegate int Comparator(object obj1, object obj2);
public string? Nume { get; set; }
public string? Prenume { get; set; }
public int? Varsta { get; set; }
public Persoana(string nume, string prenume, int virsta)
{
Nume = nume;
Prenume = prenume;
Varsta = virsta;
}
public override string? ToString()
{
return " Nume " + this.Nume + " Prenume "
+ this.Prenume + " Varsta " + this.Varsta;
}
Exemplu - sortare generalizată
//prima metoda handler a delegatului ComparaNume
public static int ComparaNume(object pers1, object pers2)
{
string? n1 = ((Persoana)pers1).Nume;
string? n2 = ((Persoana)pers2).Nume;
if (String.Compare(n1, n2) > 0) return 1;
else if (String.Compare(n1, n2) < 0) return -1;
else return 0;
}

//a doua metoda handler a delegatului ComparaVirsta


public static int ComparaVirsta(object pers1, object pers2)
{
int? v1 = ((Persoana)pers1).Varsta;
int? v2 = ((Persoana)pers2).Varsta;
if (v1 > v2) return 1;
else if (v1 < v2) return -1;
else return 0;
}
}
Exemplu - sortare generalizată
internal class ClasaCuDelegati
{
List<Persoana> persoane = new List<Persoana>();
public ClasaCuDelegati()
{ persoane.Add(new Persoana("Mateescu", "Ioana", 11));
persoane.Add(new Persoana("Avramescu", "Andreea", 15));
persoane.Add(new Persoana("Stanculescu", "Dan", 24));
persoane.Add(new Persoana("Popescu", "Andrei", 20));
persoane.Add(new Persoana("Gavrilescu", "Stefan", 17));
}
public void TiparireTablouSortat()
{
Console.WriteLine("Persoanele sunt: ");
foreach (Persoana p in persoane)
{ Console.WriteLine("Nume = " + p.Nume
+ "Prenume = " + p.Prenume + " Virsta = " + p.Varsta);
}
}
Exemplu - sortare generalizată
public void Sortare(Comparator compara)
{ //creaza un array pentru sortare
Persoana[] tablou = persoane.ToArray();
Persoana temp;
for (int i = 0;i< tablou.Length; i++)
{
for (int j = i;j < tablou.Length; j++)
{
// utilizarea delegatului compara ca o metoda obişnuită
if (compara(tablou[i],tablou[j]) > 0)
{ temp = tablou[i];
tablou[i] = tablou[j];
tablou[j] = (Persoana)temp;
}
} }
//pune rezultatul sortarii in lista de persoane
this.persoane=tablou.ToList<Persoana>();
}
Exemplu – utilizare
sortare generalizată
ClasaCuDelegati del = new ClasaCuDelegati();
//instantiem delegatul cu metoda handler //Persoana.ComparaNume
Comparator cmpn = new Comparator(Persoana.ComparaNume);
//realizam sortarea elementelor functie de nume
del.Sortare(cmpn);
del.TiparireTablouSortat();
//instantiem delegatul cu metoda handler //Persoana.ComparaVirsta
Comparator cmpv = new Comparator(Persoana.ComparaVirsta);
//realizam sortarea elementelor functie de virsta
del.Sortare(cmpv);
del.TiparireTablouSortat();
Exemplu – utilizare
sortare generalizată
Delegaţi - operaţii matematice
internal class OperatiiMatem //defineste operatii matematice
{
public delegate double DelegatOperatii(double x, double y);

public static double Inmultire(double x, double y)


{
return x * y;
}
public static double Adunare(double x, double y)
{
return x + y;
}
public static void RealizeazaOperatie(DelegatOperatii del,
double x, double y)
{
double rezultat = del(x, y);
Console.WriteLine("Rezultatul operatiei este = " + rezultat);
}
}
Delegaţi - operaţii matematice
//operatia care va fi realizata este transmisa ca parametru
OperatiiMatem.RealizeazaOperatie(OperatiiMatem.Adunare, 2.0, 5.0);
OperatiiMatem.RealizeazaOperatie(OperatiiMatem.Inmultire, 7.0, 3.0);
Delegaţi generici
• NU este ȋntotdeauna necesară definirea unui delegat propriu pentru fiecare
combinaţie de parametri sau tip returnat; .NET pune la dispoziţie ȋn acest sens
doi delegaţi generici:
• delegatul Action<T> este destinat unei referinţe către o metodă care returnează
void; clasa care implementează delegatul există ȋn multiple variante, cu pȃnă la
16 tipuri diferite de parametri
• https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=net-7.0
• delegatul Func<T> este destinat metodelor care au un tip returnat diferit de void;
similar cu Action<T> este definit ȋn multiple variante cu pȃnă la 16 tipuri de
parametri diferiţi
• https://learn.microsoft.com/en-us/dotnet/api/system.func-1?view=net-7.0
Delegaţi - operaţii matematice
• se poate utiliza şi un delegat predefinit în locul celui definit de utilizator
• varianta exemplului precedent în care s-a utilizat delegatul generic Func<T>
datorită faptului că operaţiile matematice returnează double şi nu void este
următoarea:

public static void RealizeazaOperatie(Func<double, double, double> del,


double x, double y)
{
double rezultat = del(x, y);
Console.WriteLine("Rezultatul operatiei este = " + rezultat);
}
Delegaţi - operaţii matematice

//utilizare delegat Func<T>


// operatia de adunare
Func<double, double, double> operatie1 = OperatiiMatem.Adunare;

// operatia de inmultire
Func<double, double, double> operatie2 = OperatiiMatem.Inmultire;

OperatiiMatem.RealizeazaOperatie(operatie1, 2.0, 5.0);


OperatiiMatem.RealizeazaOperatie(operatie2, 7.0, 3.0);
Utilizare delegaţi multicast
• alternativa utilizării delegaţilor multicast: semnătura delegatului trebuie să
returneze void => implică clasei OperatiiMatem din exemplul precedent astfel:

internal class OperatiiMatem


{ //defineste operatii matematice
public delegate double DelegatOperatii(double x, double y);
public static void Inmultire(double x, double y)
{
double rez = x * y;
Console.WriteLine("Rezultatul inmultirii este = " + rez);
}
public static void Adunare(double x, double y)
{
double rez = x + y;
Console.WriteLine("Rezultatul adunarii este = " + rez);
}
}
Utilizare delegaţi multicast
public static void RealizeazaOperatie(Action<double, double> del,
double x, double y)
{ del(x, y); }
} // clasa OperatiiMatem

• ȋn cazul utilizării delegaţilor multicast trebuie avut ȋn vedere faptul că ordinea


ȋn care operaţiile se execută nu este prestabilită => aplicaţiile nu trebuie să se
bazeze pe o anumită ordine particulară

// utilizare delegat multicast => suporta mai multe operatii


pe care le invoca pe rand
// initial => adunarea
Action<double, double> operatii = OperatiiMatem.Adunare;
//apoi adaugam inmultirea
operatii += OperatiiMatem.Inmultire;
//realizeaza ambele operatii, una dupa alta, printr-un apel unic
OperatiiMatem.RealizeazaOperatie(operatii, 2.0, 5.0);
Delegaţi cu metode anonime
• nu este ȋntotdeauna necesară asignarea (şi respectiv, cunoaşterea) metodei
trimise delegatului=> asa numitele metode anonime prin care se specifică doar
tipul parametrilor metodei şi al valorii returnate:
Func<string,string> delegat_necunoscut = delegate(string);
• beneficiul adus de utilizarea metodelor anonime stă faptul că atunci cȃnd se
defineşte un delegat nu trebuie definită şi metoda asociată acestuia => avantaj
in definirea delegaţilor pentru evenimente
• ȋncepȃnd cu versiunea 3.0 a limbajului C# se poate utiliza şi sintaxa aşa
numită expresii lambda pentru a defini şi asocia cod unor delegaţi ȋntr-o
manieră mult mai simplă şi compactă => asocierea metodei
OperatieMatem.Inmultire delegatului Func<T> din exemplul de mai sus
poate fi realizată şi astfel:
Func<double, double, double> lambda = (x,y) => x*y;
Console.WriteLine(lambda(3.0, 5.0));
Evenimente
• oferă un mecanism de publicare şi subscriere bazat pe delegaţi => utilitatea
delegaţilor stă nu numai în aceea că aceştia conţin referinţe la funcţii, dar şi
în aceea că se pot defini şi utiliza numele funcţiilor doar în momentul
execuţiei şi nu în momentul compilării
• aplicabilitatea delegaţilor în modelul evenimentelor din .NET (controale de
tip Button – definit prin clasa Button publică sau dispune de evenimentul
Click) => metoda care tratează evenimentul Click, (handler), denumită ȋn
mod general OnClick va trebui definită cu parametri impuşi de tipul
delegatului evenimentului respectiv
• atunci cȃnd se utilizează evenimente, trebuie declarat un delegat şi apoi un
eveniment care utilizează tipul delegatului respectiv; de asemenea, va trebui
definită o metodă handler care să trateze evenimentul respectiv
• un eveniment este un membru a unei clase care este activat atunci cȃnd
evenimentul pentru care a fost definit se declanşează
• de fiecare dată cînd un eveniment este declanşat, se invocă metoda handler
asociată acestuia
Evenimente
• oricare clasă, fie că este cea care a definit evenimentul sau altă clasă poate
apoi să-şi definească (înregistreze) metode prin care să trateze evenimentul
respectiv => subscriu la evenimentul respectiv
• declararea unui anumit eveniment se face cu cuvîntul cheie event, asociind
de asemenea şi un delegat, pe care evenimentul respectiv este declarat că
îl acceptă
public event Delegat Eveniment;
• tratarea evenimentului: are loc prin intermediul unui delegat, care specifică
semnătura metodei care este inregistată pentru tratarea evenimentului
respectiv; delegatul poate fi unui din delegaţii predefiniţi în .NET sau unul
declarat de utilizator
• delegatul se asignează evenimentului, şi astfel se înregistrează efectiv
metoda care va fi apelată atunci cînd evenimentul se declanşează.
• sintaxa += înregistrează practic un delegat pentru un eveniment:
Eveniment+=new Delegat(OnEveniment);
Exemplu - Evenimente şi delegaţi
internal class Complex
{
//declarare delegat
public delegate void ComplexHandler(string msg);

//declarare eveniment pt. obiecte Complex


//care utilizează tipul delegat
public static event ComplexHandler Schimba;
float real = 0; float imag = 0;
public Complex(float r, float i)
{ real = r; imag = i; }
//metoda afisare
public void Afis()
{
Console.WriteLine("numarul complex este: " + real
+ " + " + imag + "i ");
}
Exemplu - Evenimente şi delegaţi
public void SchimbaR()
{
real = (real / 2);
Console.WriteLine("Declansare eveniment
Schimba valoare partea reala!");
//declansare eveniment
Schimba("Schimbam valoarea partii reale");
}
public void SchimbaI()
{
imag = (imag / 2);
Console.WriteLine("Declansare eveniment
Schimba valoare partea imaginara!");
//declansare eveniment
Schimba("Schimbam valoarea partii imaginare");
}
}
Exemplu - Evenimente şi delegaţi
Complex numar = new Complex(10, 20);
//adaugare handler pentru evenimentul declarat
Complex.Schimba += new Complex.ComplexHandler(OnSchimbaComplex);

//declansare eveniment si apel functia handler a evenimentului


OnSchimbaComplex
numar.Afis();
numar.SchimbaI();
numar.Afis();
numar.SchimbaR();
numar.Afis();
Evenimente şi delegaţi predefiniţi
• utilizarea evenimentelor şi delegaţilor predefiniţi ȋn .NET este foarte simplă şi
din acest motiv este de preferat să se verifice ce există deja definit înainte de a
crea propriile evenimente şi delegaţi
• delegatul EventHandler există în spaţiul de nume System din biblioteca de
clase .NET Framework şi poate fi utillizat cu condiţia ca, la definiţia metodei
handler să se ţină cont de semnătura impusă de acesta, metoda fiind
obligatoriu să fie conformă acestei semnături:
System.EventHandler<TEventArgs> where TEventArgs:EventArgs
• există şi evenimente predefinite => unele clase din biblioteca .NET declară
evenimente (predefinite) – de exemplu evenimentul Click al clasei Button
Evenimente şi delegaţi predefiniţi
• prin convenţie, evenimentele utilizează ȋn mod tipic doi parametri:
– primul este un parametru de tip object şi se referă la transmiţătorul
evenimentului (sender)
– cel de-al doilea parametru oferă informaţii despre evenimentul propriu-zis şi
este diferit funcţie de tipul de eveniment
• .NET 1.0 declară sute de delegaţi pentru fiecare tipuri posibile ! !
• ȋn versiunile ulterioare nu mai este necesar => se utilizează delegaţii generici
EventHandler<T> respectiv EventHandler<TEventArgs> care returnează
void şi acceptă doi parametri
• pentru EventHandler<TEventArgs> primul parametru este de tip object şi
pentru al doilea se defineşte constrȃngerea ca tipul să fie derivat din
EventArgs:
public delegate void EventHandler<TEventArgs>
( Object sender, TEventArgs e)
• evenimentele sunt declarate ca proprietăti ale claselor care publică evenimentul
Exemplu – Utilizare delegaţi predefiniţi
Utilizează delegatul predefinit EventHandler<TEventArgs>

internal class Client //subcriber


{ string nume;
public Client(string n)
{ this.nume = n; }
//defineste metoda handler pentru evenimentul EvenimentProdusNou
public void ProdusNou(object sender, InfoProdusNou e)
{
Console.WriteLine("Clientul " + nume + ": Am aflat ca
a sosit noul produs => "+e.Produs );
}
}
internal class InfoProdusNou:EventArgs
{
public InfoProdusNou(string produs)
{ this.Produs = produs; }
public string Produs { get; set; }
}
Exemplu – Utilizare delegaţi predefiniţi
internal class Publicitate // publisher
{
// publica evenimentul utilizand delegatul predefinit EventHandler
public event EventHandler<InfoProdusNou> EvenimentProdusNou;
public void ProdusNou(string produs)
{
Console.WriteLine("Declasare eveniment: A aparut
urmatorul produs nou: " + produs);
//se verifica daca delegatul InfoProdusNou nu e null => multicast
//se invoca toate handlerele care au subscris la eveniment
if (EvenimentProdusNou != null)
// declansare eveniment
EvenimentProdusNou(this, new InfoProdusNou(produs));
}
}
Exemplu – Utilizare delegaţi predefiniţi
Publicitate pub = new Publicitate();

Client popescu = new Client("Popescu");


// subscrie la evenimentul InfoProdusNou
pub.EvenimentProdusNou += popescu.ProdusNou;
//se declanseaza evenimentul EvenimentProdusNou
pub.ProdusNou("O carte deosebita!");

Client ionescu = new Client("Ionescu");


// subscrie la evenimentul InfoProdusNou
pub.EvenimentProdusNou += ionescu.ProdusNou;
pub.ProdusNou("Un caiet special!");

//renunta la subscriere, nu va mai receptiona evenimentul


pub.EvenimentProdusNou -= ionescu.ProdusNou;
pub.ProdusNou("Un stilou performant!");
Exemplu – Utilizare delegaţi predefiniţi

• clasele care publică (publisher) evenimentul şi cele care subscriu la


eveniment (subscriber) sunt decuplate prin intermediul delegatului =>
cod mai flexibil şi mai robust
• alternativa => utilizare IWeakEventListener => conectare indirectă
transmiţător şi ascultător (WPF)
Modelul evenimentelor
Publicitate (Publisher)
Declarǎ un delegat

Declarǎ un eveniment bazat pe delegat

Declanşeazǎ (fire) evenimentul

Notificǎ aplicațiile care au


subscris la eveniment

Client (Subscriber) – subscrie la eveniment Client (Subscriber) – subscrie la eveniment


Subscrie la eveniment Subscrie la eveniment
declanşeazǎ declanşeazǎ
handler-ul de handler-ul de
evenimente evenimente

Metodǎ handler eveniment Metodǎ handler eveniment


Fire de executie (thread-uri)
• limbajul C# suportă execuţie paralelă prin intermediul multithreading-ului
• în acest sens, un fir de execuţie (thread) reprezintă o cale independentă de
execuţie care se desfăşoară în paralel cu alte căi de execuţie
• intern, multithreading-ul este controlat de un planificator, o funcţie pe care CLR-ul
o delegă către sistemul de operare; la o execuţie pe un singur procesor
planificatorul utilizează un algoritm de planificare bazat pe cuantă de timp fixă
(time-slicing)
• în majoritatea cazurilor un thread nu are nici un control asupra momentului în care
va fi întrerupt datorită time-slicing-ului; în general, cuanta de timp este de ordinul
a zeci de milisecunde, semnificativ mai mare decît timpul necesar pentru
schimbarea contextului (care este, în mod tipic de ordinul a cîteva microsecunde)
• în cazul unui sistem multiprocessor, multithreading-ul este implementat printr-un
amestec de time-slicing şi procesare paralelă
Utilitatea firelor de execuţie
• utilizarea firelor de execuţie (thread-uri) este benefică atunci cînd dorim să
executăm anumite procese (metode, calcule, etc.) consumatoare de timp în
background
• thread-urile = unitǎțile de execuție de bazǎ pentru care sistemul de operare alocǎ
timp de procesare şi resurse.
• în mod deosebit aplicaţiile de tipul rich client applications beneficiază de
avantajele utilizării firelor de execuţie, în special pentru procesarea interacţiunii cu
utilizatorul prin intermediul mouse-ului sau tastaturii; altfel, dacă thread-ul
principal este implicat în diverse alte procesări complexe, în acest timp intrările
provenite de la mouse sau tastatură nu ar putea fi preluate şi aplicaţia ar deveni
non-responsivă
• în general, o aplicaţie C# poate fi constituită din mai multe fire de execuţie fie
explicit (prin crearea şi lansarea în execuţie a mai multor thread-uri) sau utilizînd
facilitatea .NET de a crea thread-uri în mod implicit (de preferat)
• multithreading-ul are însă şi dezavantaje, rezultînd de cele mai multe ori în
programe mai complexe şi necesitînd resurse de sistem suplimentare (pentru
alocarea firelor de execuţie şi schimbările de context)
Crearea firelor de execuţie
• thread-urile se pot crea utilizînd clasa Thread din spaţiul de nume
System.Threading, şi transmiţîndu-i prin intermediul unui delegat
ThreadStart metoda pe care se doreşte să o lanseze în execuţie thread-ul
respectiv, sub forma:

public delegate void ThreadStart();


Thread nume_thread = new Thread(new ThreadStart(nume_metoda));
Exemplu – creare thread
using System.Threading;
using System.Threading.Tasks;
internal class Program
{
private static void Main(string[] args)
{
{
//creare thread
Thread th = new Thread(new ThreadStart(Afisare));
//o varianta prescurtata
Thread th = new Thread(Afisare);
// pornire thread
th.Start();
for (int i = 0; i < 1000; i++) Console.WriteLine(i+"Main");
}
}
static void Afisare()
{
for (int i = 0; i < 1000; i++) Console.WriteLine(i + "Afisare");
}
}
Thread-uri cu parametri
• există posibilitatea de a transmite date (parametri) thread-urilor
• în acest caz nu mai este posibilă utilizarea delegatului ThreadStart deoarece
acesta un acceptă parametri
• se poate utiliza în acest stop un alt delegat, ParameterizedThreadStart,
care acceptă un singur argument de tip object:

public delegate void ParameterizedThreadStart(object obj);


Thread nume_thread = new Thread
(new ParameterizedThreadStart(nume_metoda(param)));
Exemplu – thread cu parametri
using System.Threading.Tasks;
using System.Threading;

internal class Program


{
private static void Main(string[] args)
{
{
Thread.CurrentThread.Name = "MAIN";
Thread th = new Thread
(new ParameterizedThreadStart(Afisare));
th.Name = "TH";
th.Start(true);
Afisare(false);
}
}
Exemplu – thread cu parametri
static void Afisare(object par_maj)
{
bool maj = (bool)par_maj;
for (int i = 0; i < 100; i++)
Console.WriteLine(maj ? "AFISARE CU MAJUSCULE din "
+ Thread.CurrentThread.Name
: "afisare fara majuscule din "
+ Thread.CurrentThread.Name);
}
}

funcţie de valoarea parametrului transmis, se va afişa textul cu majuscule sau nu.


Exemplu – thread cu parametri
Proprietăţile firelor de execuţie
• firelor de execuţie li se poate asigna un nume (MAIN respectiv TH) şi numele
acestora poate fi referit prin intermediul proprietăţii CurrentThread.Name.
• clasa Thread conţine de asemenea şi alte proprietăţi prin intermediul cărora se
pot seta/obţine informaţii legate de thread-ul curent, ca de exemplu: nume
(Name), prioritatea (Priority), starea thread-ului (ThreadState), etc.
• proprietatea IsBackground controlează maniera de execuţie a thread-ului: în
background (true) sau nu (false); trebuie menţionat însă faptul că schimbarea
proprietăţii nu afectează prioritatea şi/sau starea procesului respectiv.
Crearea firelor de execuţie
utilizînd metode anonime
• reprezintă o altă alternativă de creare a unui fir de execuţie
• avantajul metodelor anonime constă în aceea că acestea pot avea mai mulţi
parametri (faţă de template-ul impus de ThreadStart şi
ParameterizedThreadStart care nu acceptă decît metode cu nici un
parametru respectiv cu un singur parametru)
• se pot utiliza diferite variante pentru scrierea metodelor anonime => de preferat
notaţia lambda
Crearea firelor de execuţie
utilizînd metode anonime
internal class Program
{
private static void Main(string[] args)
{
{
//notatie lambda
Thread th = new Thread( () => Afisare(50,
" Parametru text de afisat din thread-ul TH"));
th.Start();
for (int i = 0; i < 50; i++) Console.WriteLine
("Parametru text de afisat din Main");
}
}
static void Afisare(int cit, string text)
{
for (int i = 0; i < cit; i++) Console.WriteLine(text);
}
}
Coordonarea şi sincronizarea
firelor de execuţie
• pentru coordonarea acţiunilor firelor de execuţie pot fi utilizate diferite metode de
blocare a execuţiei acestora, printre care:
• Sleep, care “adoarme” respectiv blochează thread-ul pentru o perioadă definită
de timp, specificată ca parametru
• Join, care aşteaptă pînă cînd un alt thread îşi termină execuţia
• ….altele
Coordonarea şi sincronizarea
firelor de execuţie
internal class Program
{
private static void Main(string[] args)
{
{
Thread th = new Thread(new ThreadStart(Afisare));
th.Start();
//asteapta pina la terminarea thread-ului th
th.Join();
Console.Write("S-a terminat th, suntem in Main");
}
}
static void Afisare()
{
for (int i = 0; i < 100; i++) Console.Write("In thread-ul th!");
}
}
Coordonarea şi sincronizarea
firelor de execuţie
Utilizarea de date în comun
• firele de execuţie pot să utilizeze date în comun; o metodă de a realiza acest lucru
implică utilizarea cîmpurilor de date statice
• accesul la datele utilizate în comun trebuie însă realizat în excludere mutuală,
pentru a asigura consistenţa datelor comune
• C# furnizează diverse modalităţi: lock (lacăt), Mutex, Semaphore; ele sunt
similare, dar sunt totuşi mici deosebiri
• Mutex, spre deosebire de lock funcţinonează pentru procese multiple
• Semaphore reprezintă un lock generalizat, care permite accesul a unui număr de
thread-uri dat de capacitatea sa (parametru); de asemenea, un Semaphore nu are
proprietar, orice thread poate să elibereze resursa, nu doar thread-ul care a
obţinut-o, ca în cazul Mutex-ului sau lock-ului
Exemplu
internal class Program
{ static int comun = 0; // variabila utilizata in comun
static object lacat = new Object(); // pentru lock
static void Main(string[] args)
{ //creaza un nou thread
Thread t = new Thread(Scrie);
// noul thread se lanseaza in executie si executa metoda Scrie
t.Start();
// scrie din Main
for (int i = 0; i < 100; i++)
{ // acces in excludere mutuala
lock (lacat)
{ Thread.Sleep(5); // sa dureze mai mult....
//modificare variabila utilizata in comun!
comun += 1;
Console.WriteLine("In MAIN: " + i.ToString()
+ "variabila comun = " + comun.ToString());
}
} }
Exemplu
static void Scrie()
{
//metoda scrie
for (int i = 0; i < 100; i++)
{
lock (lacat)
{
Thread.Sleep(5);
modificare variabila utilizata in comun!
comun += 5;
Console.WriteLine("In metoda Scrie: " + i.ToString()
+ "variabila comun = " + comun.ToString());
}
}
}
}
Exemplu – rezultatul cu lock
Exemplu – rezultatul fără lock
Un alt exemplu
internal class Program
{
//tablou in care doua thread-uri adauga elemente in mod concurent
static int[] tablou = new int[1000];
static int i = 0;
static object lacat = new object();
static void Main(string[] args)
{
Thread th1 = new Thread(new ThreadStart(Adaugare));
Thread th2 = new Thread(new ThreadStart(Adaugare));
th1.Name = "TH_1";
th2.Name = "TH_2";
th1.Start();
th2.Start();
}
Un alt exemplu
static void Adaugare()
{ Random rand = new Random();
bool gata = false;
while (!gata)
{
lock (lacat)
{
if (i < 1000)
{
Console.Write(Thread.CurrentThread.Name + " : ");
Console.Write("Adauga elementul " + i.ToString() + " = ");
tablou[i] = rand.Next(1000);
Console.WriteLine((tablou[i]).ToString());
i++;
}
else
{
Console.WriteLine("Gata!"); gata = true;
}
}
}
} }
Rezultatul execuţiei – cu lock
Rezultatul execuţiei - fără lock
Execuţia programului
• maniera de execuţie se poate schimba funcţie de granularitatea porţiunii de cod
asupra căruia se aplica lock-ul: acesta este aplicat numai asupra codului
corespunzător unei iteraţii din cadrul funcţiei Adaugare, accesul în excludere
mutuală utilizînd lock nu prezintă un overhead suplimentar semnificativ, lock-ul
însuşi fiind foarte rapid.
Totuşi, pot exista şi dezavantaje:
• scade nivelul concurenţei dacă prea mult cod este prins în cadrul unui lock,
cauzînd blocarea inutilă a celorlalte thread-uri
• apariţia deadlock-ului este posibilă dacă lock-urile sunt utilizate defectuos
• de asemenea, trebuie avut în vedere faptul că, controlul accesului în excludere
mutuală către un obiect utilizînd lock va funcţiona numai dacă toate firele de
execuţie concurente sunt conştiente de acest lucru şi folosesc lock-ul respectiv; cu
cît domeniul de valabilitate al obiectelor respective este mai vast, cu atît este mai
greu de realizat acest lucru
Programarea concurentǎ în .NET
• .NET furnizeazǎ diferite modalitǎți de a realiza procesare concurentǎ bazatǎ pe
utilizarea multiplelor thread-uri => maximizarea performanței şi a responsivitǎții
aplicațiilor
• Crearea explicitǎ de thread-uri nu mai este recomandatǎ: începând cu .NET 4.0 a
apǎrut biblioteca TPL (Task Parallel Library) din cadrul cǎreia cele mai semnificative
clase sunt: System.Threading.Tasks.Parallel şi respectiv
System.Threading.Tasks.Task
• Parallel LINQ (PLINQ), şi noile clase de colecție concurente din spațiul de nume
System.Collections.Concurrent furnizeazǎ concepte şi structuri specifice utilizabile
în programarea concurentǎ
• modelul de programare asincron bazat de metode de tipul async/await şi clasele
System.Threading.Tasks.Task şi System.Threading.Tasks.Task<TResult> din spațiul
de nume System.Threading.Tasks permit crearea simplǎ de operații asincrone
• modelul bazat pe actori (actor model) => pentru aplicații cu foarte multe thread-uri
Excepții în C#
• la apelul unei funcţii în timpul execuţiei unui program, execuţia funcţiei
poate fi: normală, cu erori sau anormală.
• la execuţia normală, funcţia se execută normal şi se revine
• unele funcţii returnează un cod rezultat pentru apelant: un cod rezultat
poate indica succes sau eşec sau un tip de nereuşită
• cazul execuţiei cu eroare poate fi atunci când apelantul face o greşeală la
valoarea argumentele funcţiei sau o apelează pe aceasta într-un context
nepotrivit
• execuţia anormală poate apărea în condiţii care nu sunt sub controlul
programului (adresarea în afara spaţiului de memorie, erori de alocare a
resurselor, nereuşită la căutarea fişierelor
• toate aceste situaţii este bine să fie tratate prin excepţii => în C# erorile
generate la execuţia programului sunt propagate utilizînd mecanismul
excepţiilor
Excepții în C#
• mecanismul tratării excepţiilor permite managerizarea într-o manieră
ordonată a erorilor din timpul execuţiei.
• la apariţia unei erori, programul poate invoca automat o rutină de tratare a
acesteia; dacă nu este prezent niciun handler de excepție pentru o anumită
excepție, programul se oprește cu un mesaj de eroare
• avantajul principal al tratării excepţiilor este faptul că automatizează codul
de tratare a erorilor
• tratarea excepţiilor este binevenită mai ales când eroarea apare la un nivel
îndepărtat, într-un set de rutine încuibate pe multe niveluri
• nu se recomandă însă prinderea unei excepții decât dacă aceasta poate fi
gestionată și dacă în urma gestionării aplicația este lăsată într-o stare
cunoscută, adică atunci când:
– se poate implementa o anumită formă de recuperare, cum ar fi
solicitarea utilizatorului să introducă un nou nume de fișier atunci când
apare o excepţie de tipul FileNotFoundException
– se poate crea și arunca o nouă excepție, mai specifică
Excepții în C#
• excepțiile sunt “aruncate” (trow) de codul în cadrul cǎruia apare eroare (try)
și “prinse” (catch) de codul care poate corecta eroarea
• excepțiile pot fi aruncate de runtime-ul .NET sau de codul dintr-un program;
odată ce o excepție este aruncată, se propagă în stiva de apeluri, de la
nivelul de imbricare la care a apǎrut înspre blocurile exterioare, până când
este găsită o instrucțiune catch care “prinde” excepția
• excepțiile care nu sunt “prinse” de-a lungul propagǎrii sunt gestionate de un
handler general (global) de excepții furnizat de sistem care afișează o
casetă de dialog cu un mesaj general de excepţie
Mecanismul try-catch-throw
• tratarea excepţiilor în C# se bazează pe cuvintele-cheie:
try, catch şi throw
• porţiunea de cod care se doreşte a fi monitorizată se include într-un bloc
try
• la apariţia unei excepţii în blocul try, excepţia este generat/aruncată
(cu trow)
• excepţia este „prinsă” ulterior (cu catch) şi procesată
• excepţiile sunt reprezentate prin clase derivate din clasa de bazǎ
Exception, care identificǎ tipul excepţiei şi conţin detalii referitoare la
excepţie
Excepţii predefinite
• sunt derivate din clasa System.Exception :
System.ApplicationException şi System.SystemException
• clasa System.ApplicationException suportă excepţiile generate de
programele de aplicaţie
• unele excepții sunt aruncate automat de runtime .NET atunci când operațiile
de bază eșuează; clasa System.SystemException este clasă de bază pentru
toate excepţiile de sistem predefinite https://learn.microsoft.com/en-
us/dotnet/csharp/fundamentals/exceptions/compiler-generated-exceptions
– System.IO.IOException, System.IndexOutOfRangeException
– System.ArrayTypeMismatchException, System.NullReferenceException
– System.DivideByZeroException, System.InvalidCastException
– System.OutOfMemoryException, System.StackOverflowException, etc.
• excepţiile pot fi de de tipuri predefinite sau clase definite de utilizator
Generarea unei excepţii
• generarea unei excepţii implicǎ crearea unei instanţe a unei clase
derivate din Exception şi “aruncarea” obiectului generat utilizand
cuvantul cheie throw.

private static void TestThrow()


{
//creare obiect exceptie
System.ApplicationException ex =
new System.ApplicationException
("Mesaj exceptie generate cu throw)");
throw ex;
}
“Prinderea” unei excepţii
• în C#, o excepţie trebuie „prinsă” de un catch imediat următor blocului care
a generat excepţia
• forma generală pentru try şi catch este:
try
{
// bloc try
}
catch(Exceptienoua1 e) {
// bloc catch 1
}
catch(Exceptienoua2 e) {
// bloc catch 2
}
//...
catch(ExceptinouaN e) {
// bloc catch N
}
catch(Exception e) {
// bloc pentru prinderea tuturor celorlalte exceptii
}
Mecanismul try-catch-throw
• dupǎ generarea excepţiei, mediul de execuţie verificǎ dacǎ aceasta este în
interiorul unui bloc try
• dacǎ da, orice bloc catch asociat lui try este verificat pentru a “prinde ”
excepţia
• în mod normal, blocurile catch se specificǎ pe tipuri de excepţii
• corespunzător unui bloc try pot exista mai multe blocuri catch; blocurile
catch sunt examinate în ordinea în care apar
static void TestCatch()
{
try
{
TestThrow();
}
catch (System.ApplicationException ex)
{
System.Console.WriteLine(ex.ToString());
}
}
Mecanismul try-catch-throw
• tipul excepţiei primit ca argument (aruncat) este cel care determină care
dintre blocurile catch asociate unui bloc try va fi folosit
• dacǎ nu se generează nici o excepţie, nu va fi executat nici un bloc catch
• dacă se generează o excepţie pentru care nu este definit un bloc catch
corespunzător, va apărea o eroare de terminare anormală a programului =>
se tratează de handler-ul general de excepţii
• blocul catch nu este apelat, ci i se transferă execuţia, adică nu se mai
revine în blocul try după generarea unei excepţii
• dacă eroarea care a generat excepţia poate fi remediată, execuţia va
continua cu ceea ce urmează după catch; uneori, eroarea nu poate fi
remediată şi un bloc catch va termina programul cu un apel la exit() sau
abort()
Blocurile finally
• pe lîngǎ blocurile catch, o instrucţiune try poate conţine şi un bloc finally;
• practic, la apariţia unei excepţii, înainte de execuţia blocului catch
corespunzǎtor, se verificǎ întîi existenţa blocului finally
• blocurile finally permit programatorului sǎ elimine orice stare ambiguǎ care
ar putea rezulta în urma execuţiei cu eroare a blocului try, sau sǎ elibereze
resurse cum ar fi fișiere, conexiuni la baze de date, etc., fǎrǎ a mai aştepta
colectorul de deşeuri în acest scop
• un bloc finally poate fi utilizat pentru a executa cod independent dacǎ o
excepţie a fost generatǎ sau nu; acest lucru poate fi necesar în condiţiile în
care codul care urmeazǎ o construcţie de tipul try/catch nu se mai executǎ
în condiţiile apariţiei unei excepţii
• utilizarea unei instrucţiuni try fǎra cel puţin un bloc catch sau finally
genereaza o eroare de compilare
Blocurile finally
try
{
// Cod de incercat la executie
}
catch (System.Exception ex)
{
// Cod pentru tratarea exceptiei
}
finally
{
// Cod de executat dupa try (si eventual, posibil catch)
}
Crearea unei excepţii
definite de utilizator
• utilizatorul va trebui sǎ-şi defineascǎ propria clasǎ de excepţii
• clasa excepţie trebuie în mod obligatoriu sǎ fie derivatǎ din
System.ApplicationException.
• în general, clasele de excepţie .NET au mai mulţi constructori şi fiecare
dintre aceştia apeleazǎ de fapt constructorul clasei de bazǎ corespunzǎtor:
un constructor fǎrǎ parametri, un constructor cu un parametru (mesajul
excepţiei) şi un constructor cu doi parametri (mesajul excepţiei şi
proprietatea Inner prin intermediul cǎreia se poate transmite excepţia
respectivǎ), etc.
• clasa excepţie definitǎ de utilizator trebuie sǎ defineascǎ cel puţin aceşti trei
constructori
Exemplu
class ExceptieFormatNumeIncorect:System.ApplicationException
{
public ExceptieFormatNumeIncorect() : base()
{
}
public ExceptieFormatNumeIncorect(string mesaj) : base(mesaj)
{
}
public ExceptieFormatNumeIncorect(string mesaj,
Exception exceptie) : base(mesaj, exceptie)
{
}
}
Exemplu
• programul principal genereazǎ un control de tipul ListBox în cadrul
cǎruia introduce numele de persoane introduse în cîmpul de editare
• dacǎ formatul de introducere nu este corect, se generazǎ o excepţie

class Persoana
{
public string? Nume { get; set; }
public string? Prenume { get; set; }
public override string ToString()
{
return Nume + " " + Prenume;
}
Exemplu
public Persoana(string numeprenume)
{
try
{
string separator = "";
string[] tabel_separat =
numeprenume.Split(separator.ToCharArray());
Nume = tabel_separat[0]; Prenume = tabel_separat[1];
}
catch (Exception ex)
{
throw new ExceptieFormatNumeIncorect
("Format incorect la introducerea
numelui si prenumelui", ex);
}
}
}
Exemplu
private void but_Apasa_Click(object sender, RoutedEventArgs e)
{
try
{ lstBoxPersoane.Items.Add(new Persoana(txt_NumePren.Text));
}
catch (ExceptieFormatNumeIncorect exceptienume)
{
if (exceptienume.InnerException != null)
MessageBox.Show(exceptienume.Message + "\n" +
exceptienume.InnerException.Message);
else MessageBox.Show(exceptienume.Message);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
txt_NumePren.Text = "";
}
Atribute
• atributele sunt elemente (clase care pot fi scrise în C# ) care permit
adăugarea unor informaţii în format declarativ în cadrul programelor
• aceste informaţii sunt utilizate ulterior atît la execuţie cît şi în procesul de
proiectare a aplicaţiei (de exemplu de către uneltele de dezvoltare utilizate)
• atributele reprezintă un mecanism prin intermediul căruia se pot adăuga
metadate în programe (instrucţiuni de compilare, date despre datele,
clasele şi metodele utilizate în cadrul programului, etc.)
• sunt necesare deoarece multe din serviciile care sunt obţinute de pe urma
utilizării atributelor ar fi foarte dificil de obţinut prin scrierea de cod sursa
echivalent
• atunci cînd un program C# este compilat, acesta este convertit într-un
limbaj intermediar MSIL (MicroSoft Intermmediate Language) şi se crează
un fişier numit ansamblu care, în mod normal poate fi un executabil sau o
bibliotecă .dll
• după ce un atribut este asociat cu o entitate de program, atributul poate fi
interogat în timpul rulării utilizând o tehnică numită reflexie
Exemplu definire atribute
• clasa prin intermediul căreia se defineşte atributul ObsoleteAttribute
este definită în felul următor (spaţiul de nume System, în mscorlib.dll):

[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Constructor |
System.AttributeTargets.Delegate |
System.AttributeTargets.Enum |
System.AttributeTargets.Event |
System.AttributeTargets.Field |
System.AttributeTargets.Interface |
System.AttributeTargets.Method |
System.AttributeTargets.Property |
System.AttributeTargets.Struct, Inherited=false)]
public sealed class ObsoleteAttribute : Attribute
Exemplu definire atribute
• se observă utilizarea modificatorului sealed prin intermediul căreia se
marchează faptul că din respectiva clasă nu pot fi derivate alte clase
• un atribut este un obiect care reprezintă date care se doresc asociate cu un
element din cadrul programului
• elementul căruia i se asociază un atribut reprezintă ţinta atributului respectiv
• atributele se aplică de regulă înaintea declaraţiilor de tipuri sau membri ai
unor tipuri, acestea reprezentînd implicit ţinta acestora
• se utilizează paranteze drepte pentru definirea atributelor

[ObsoleteAttribute]

• partea de “attribute” din cadrul numelui e opţională, deci o variantă


echivalentă ar fi:

[Obsolete]
Exemplu utilizare atribute
internal class DemoAtribute
{
[Obsolete]
public void PrimaMetodaDeprecated()
{
Console.WriteLine("Apel PrimaMetodaDeprecated() :");
}
[ObsoleteAttribute]

public void AdouaMetodaDeprecated()


{
Console.WriteLine("Apel AdouaMetodaDeprecated() :");
}
[Obsolete("Aceasta metoda nu mai trebuie utilizata", false)]
public void AtreiaMetodaDeprecated()
{
Console.WriteLine("Apel AtreiaMetodaDeprecated() :");
}
}
Rezultatul utilizării atributelor
• este obţinut în urma compilării programului, şi constă din 3 warning-uri
corespunzătoare celor 3 utilizări ale metodelor respective
• cel de-al treilea atribut avînd şi un parametru, afişează după mesajul
standard şi textul conţinut în parametrul respective

DemoAtribute atribut = new DemoAtribute();


atribut.PrimaMetodaDeprecated();
atribut.AdouaMetodaDeprecated();
atribut.AtreiaMetodaDeprecated();
Rezultatul utilizării atributelor
Categorii de atribute
• atributele pot fi de două categorii: intrinseci, sau predefinite respectiv
definite de utilizator, prin intermediul acestora din urmă fiind posibilă
extinderea limbajului cu sintaxe de atribute customizate
• atributele predefinite sunt furnizate ca parte a Common Language Runtime
(CLR) şi sunt integrate în platforma .NET. Această categorie de atribute este
cea mai utilizată, chiar dacă atributele definite de utilizator pot constitui o
unealtă foarte puternică atunci cînd sunt combinate cu mecanismul de
reflexie – reflection
• există posibilitatea de a adăuga parametri unor atribute, fiind posibilă
utilizarea a două tipuri de parametri: parametri poziţionali şi parametri cu
nume
• parametri cu nume sunt întotdeauna opţionali; parametri poziţionali pot fi
eventual omişi
• atributul [Obsolete] are un parametru poziţional de tip bool care, dacă e
true, forţează o eroare la compilare în loc de un simplu warning.
Categorii de atribute
[Obsolete("Aceasta metoda nu mai trebuie utilizata", true)]
public void AtreiaMetodaDeprecated()
{
Console.WriteLine("Apel AtreiaMetodaDeprecated() :");
}
Categorii de atribute
Domeniul de aplicabilitate
al atributelor
• domeniul de aplicabilitate al atributelor nu se rezumă însă doar la metode
• ţinta (target-ul) atributelor poate fi: assembly (aplicabil în cadrul ansamblului),
module (aplicabil modulului current din ansamblu), method (aplicabil metodei),
field (aplicabil campului), property (aplicabil proprietatii), etc.
• specificarea explicită a ţintei unui atribut poate fi realizată ori de cîte ori pot exista
ambiguităţi, prefixînd atributul cu numele ţintei:
[assembly:CLSCompliant(true)]
• atributul [CLSCompliant] este utilizat pentru a asigura faptul că întregul ansamblu
este în conformitate cu specificaţiile the Common Language Specification (CLS)
• CLS reprezintă un set de standarde care permite comunicarea între diferitele limbaje
furnizate pentru platforma .NET
• declaraţia de mai sus indică faptul că atributul se va aplica întregului ansamblu
• https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/reflection-and-
attributes/
Atribute predefinite
• spaţiul de nume System.Runtime oferă o serie de atribute predefinite
inclusiv atribute pentru ansamble, configurare şi versionare
• prin intermediul atributului AssemblyVersion se poate asocia informatia
despre versiunea programului respectiv:
[assembly: AssemblyVersion(“1.0.2”)]
• din aceeaşi categorie mai fac parte si atributele:
AssemblyCompanyAttribute,
AssemblyConfigurationAttribute,
AssemblyCopyrightAttribute, AssemblyCultureAttribute,
AssemblyDescriptionAttribute,
AssemblyProductAttribute, AssemblyTitleAttribute,
AssemblyTrademarkAttribute.
• atribute specifice sunt utilizate în procesul de serializare, de exemplu
atributul:
System.Reflection.TypeAttributes.Serializable
• este utilizat pentru a indica faptul că obiectele din respectiva clasă sunt
serializabile
[System.Serializable]
Reflexia - Reflection
• mecanismul reflexiei reprezintă procesul prin care un program poate să-şi
citească propriile metadate; se spune că programul se reflectă asupra lui
însuşi, extrăgînd metadatele din ansamblul său şi utilizînd informaţiile fie
pentru a informa utilizatorul sau pentru a-şi modifica propriul comportament
• reflexia furnizează obiecte (în general, de tipul Type) care
încapsulează/descriu ansambluri, module şi tipuri. Reflexia poate fi utilizată
pentru:
– a crea în mod dinamic o instanță a unui tip
– a lega tipul de un obiect existent
– a obține tipul unui obiect existent și pentru a invoca metodele acestuia sau
pentru a accesa câmpurile și proprietățile acestuia
– în plus, dacă se utilizează atribute, reflexia permite atunci cînd este necesar,
accesul la aceste attribute
– crearea de noi tipuri la executie (utilizînd System.Reflection.Emit)
– a realiza legarea dinamica (late binding) şi accesarea metodelor tipurilor create
în momentul execuţiei
Utilizarea reflexiei
• clasele din spaţiul de nume System.Reflection împreună cu System.Type
permit obţinerea de informaţii despre ansamblele încărcate, despre tipurile definite
în cadrul acestora, etc.
• existenţa clasei Type reprezintă cheia spre funcţionalitatea realizată prin
mecanismul de reflexie =>clasa implementează un mare număr de metode şi
proprietăţi (proprietăţile sunt readonly, nu se poate modifica valoarea pentru niciun
tip), care, ȋn esenţă se grupează ȋn următoarele categorii:
– proprietăţi generale despre tip: Name, FullName (nume calificat), NameSpace
(spaţiul de nume din care face parte), BaseType (clasa de baza)
– proprietăţi care indică felul tipului respectiv: IsAbstract, IsClass,
IsEnum, IsValueType, IsPrimitive
– metode prin care se pot obţine detalii despre metodele tipului: GetMethod()
respectiv GetMethods(), care returnează o referinţă respectiv un array de
referinţe la un obiect de tipul System.Reflection.MethodInfo care conţine
detalii despre metoda/metodele respective
– metode similare pentru informaţii despre constructori (GetConstructor),
evenimente (GetEvent), cȃmpuri de date (GetField) şi proprietăţi
(GetProperty)
Utilizarea reflexiei
• un exemplu simplu de utilizare a mecanismului de reflexie îl reprezintă
utilizarea metodei statice GetType(), care este definită în clasa Object şi
moştenită în cadrul tuturor tipurilor:

int i = 30;
System.Type type = i.GetType();
System.Console.WriteLine(type);

• ieşirea secvenţei de mai sus indică tipul intreg, şi anume:

System.Object Item [Int32]


System.Int32

• clasa Type este una dintre cele mai importante în contextul mecanismului
de reflexie
• Common Language Runtime (CLR) crează un obiect Type pentru un anuit
tip, atunci cînd acest lucru este solicitat; ulterior, pot fi utilizate proprietăţile
şi metodele obiectului Type pentru a obţine informaţii asupra tipului
respectiv
Utilizarea reflexiei
using System.Reflection;

Type tip = typeof(System.Collections.ArrayList);


Console.WriteLine("\nReflection.MemberInfo");
// determina tipul si informatii despre membri acestuia
//o alta varianta
Type tip = Type.GetType("System.Collections.ArrayList");
//clasa System.Reflection.MemberInfo obtine informatii despre
atributele unui membru
MemberInfo[] TipTabelMembri = tip.GetMembers();
Console.WriteLine("\nSunt {0} membri in {1}.",
TipTabelMembri.Length, tip.FullName);
//afiseaza toti membri clasei ArrayList (65)
for (int i = 0; i < TipTabelMembri.Length; i++)
Console.WriteLine(TipTabelMembri[i]);
Utilizarea reflexiei

S-ar putea să vă placă și