Sunteți pe pagina 1din 51

Lucrarea de laborator nr.

Platforma .NET - arhitectura


În esenţă, ecosistemul .NET reprezintă o platformă de dezvoltare open-source pentru dezvoltarea de
aplicaţii de diverse tipuri care există din 2002. Furnizeazǎ mai multe limbaje care pot fi integrate pentru
dezvoltarea aplicațiilor utilizând, de regulă, Visual Studio sau Visual Studio Code. Limbajele standard
suportate sunt: Visual Basic, C# , F# şi C++. Tipurile de aplicaţii care pot fi dezvoltate sunt foarte
diverse: biblioteci, aplicaţii desktop (Console, Windows Forms, WPF, UWP), aplicaţii web (ASP.NET),
aplicaţii mobile, jocuri, IoT (Internet of Things), Machine Learning, etc.
Dintr-o perspectivă istorică, MicroSoft .NET a evoluat trecând prin mai multe alternative de
framework-uri: .NET Framework, .NET Core/Xamarin. Astfel, pentru construirea de aplicaţii server-
side existau practic două implementări .NET; ambele implementări împărţind diferite componente
existând însă şi diferenţe seminificative între ele:
• .NET Framework a reprezentat varianta tradiţională de dezvoltare pe platforma .NET, care rulează
doar pe platfoma Windows (versiuni 1.0 - 4.8)
• .NET Core a reprezentat o opţiune mai nouă, cross platform, rulând atât pe Windows cât şi pe
macOS şi Linux (versiuni .NET Core 1.0 - 3.1, .NET 5 şi versiuni ulterioare .NET.)
În noiembrie 2020, a fost lansat .NET 5. Această nouă versiune a framework-ului şi-a propus să fie o
platforma unificată .NET. Astfel, începând cu această versiune nu mai este nevoie de implementări
diferite: .NET 5 a reprezentat în acest sens setul comun de API-uri care rulează pe Windows, Linux,
Mac, iOS și Android. Deci, dacă un proiect vizează .NET 5, codul său va rula pe toate acele platforme.
.NET 5 (fără „core” şi fără „framework”) a preluat tot ce este mai bun din modelele .NET Framework,
.NET Core și Xamarin Development pentru a crea un cadru aşa numit all-in-one care este mai simplu
de utilizat și introduce un spectru larg de capabilități în domeniul serviciilor de dezvoltare de aplicaţii
Astfel, .NET 5+ implementează .NET Standard, aşa încât codul care vizează .NET Standard poate rula
pe .NET 5+. .NET Standard nu este însă doar o altă implementare ci defineşte o specificație formală a
API-urilor .NET pe care mai multe platforme .NET trebuie să le implementeze (respecte). Diferite
versiuni .NET Standard definesc diferite seturi .NET API => o implementare .NET declară ce versiune
.NET Standard acceptă. Astfel, după cum se ilustrează în Figura 1, chiar dacă .NET Core, .NET
Framework sau Xamarin sunt componente diferite, acestea pot utiliza elemente în comun: biblioteca
.NET Standard definind un set de API-uri care sunt utilizate în comun de către toate variantele de
implementare .NET. .NET 5+ este suportat de Visual Studio 2019+.

Figura 1: .NET Standard shared API şi infrastructura comună în .NET (sursa: MicroSoft)
Microsoft .NET 6 fost lansat în noiembrie 2021, în cadrul căruia cea mai mare îmbunătăţire adusă este
ASP.NET Core 6, o actualizare majoră a cadrului Microsoft în ceea ce priveşte construirea de aplicații
web moderne. ASP.NET Core 6 este construit pe .NET Core runtime și combină caracteristicile Web
API și MVC. In prezent, ultima versiune de .NET este .NET 7 care nu aduce modificări majore în
arhitectura .NET, concentrându-se în special pe performanţă.

Figura 2: Ecosistemul .NET (sursa: MicroSoft)

În general, o implementare .NET include în general următoarele (Figura 2):


• unul sau mai multe medii de execuţie (Runtime components): CLR (Common Language Runtime)
este mașina virtuală care rulează framework-ul și care gestionează execuția programelor .NET. Core
CLR este construit pe baza aceluiaşi cod ca .NET Framework CLR dar proiectat să ruleze pe mai
multe platforme. Principalele componente ale CLR sunt: sistemul comun de tipuri (Common Type
System), specificaţiile comune al limbajelor (Common language specification), collectorul de
deşeuri (Garbage collector), compilatoarele pentru diferitele limbaje .NET (Just in Time Compiler),
metadate și ansamble.
• o bibliotecă de clase (Common Base Library) care implementează o versiune de .NET Standard plus
alte API-uri adiţionale specifice. Bibliotecile conţin clase, interfețe, tipuri şi alte componente
reutilizabile.
• diverse framework-uri de dezvoltare specifice: ASP.NET., Windows Forms, WPF (Windows
Presentation Foudation), etc.
• unelte de dezvoltare care sunt folosite în comun (Visual Studio, Visual Studio Code, etc.)

Dezvoltarea pe platforma .NET


Platforma .NET permite dezvoltarea a numeroase tipuri de aplicaţii. În general, dezvoltarea acestora este
bazată pe Visual Studio care este un mediu integrat de dezvoltare
(IDE – Integrated Development Environment) cu facilităţi extrem de complexe atȃt din punctul de
vedere al editării codului cȃt şi de compilare şi depanare, care îl disting faţă de concurenții săi.
Visual Studio include, de asemenea, instrumente de completare a codului (autocomplete, Intellisese),
compilatoare, designer-e grafic (Form designer, WPF designer, etc.) pentru a facilita procesul
dezvoltatorului de a construi o aplicație. O alternativă la Visual Studio este Visual Studio Code care este
în esenţă un editor de cod şi reprezintă o variantă mai lightweight, fiind centrat pe ușurință,
extensibilitate, viteză și flexibilitate.
Deși audiența pentru Visual Studio și Visual Studio Code sunt diferite, se poate spune că sunt foarte
asemănătoare. Astfel, Visual Studio Code este mai limitat în ceea ce oferă, în timp ce IDE-ul Visual
Studio este mult mai complex în ceea ce priveşte facilitătile puse la dispoziţie. Visual Studio Code
reprezintă versiunea simplificată a Visual Studio, care poate fi luată în considerare atunci când nu este
nevoie de un IDE complet.
Visual Studio își propune să ofere o soluție bogată în funcții, robustă și cuprinzătoare pentru construirea
de aplicații, furnizând o serie de facilităţi printre care:
• editoare de text sofisticate cu facilităti de verificare a sintaxei pe măsură ce se editează codul,
subliniind porţiunile care cauzează erori de compilare (aşa numitul design-time debugging) şi
care permite selecţia respectiv completarea automată a numelor metodelor/proprietăţilor ale
unui obiect (facilitate cunoscută sub denumirea de IntelliSense)
• editoare grafice (designer) - Windows Form Designer, Web Form Designer sau XAML
Designer – care simplifică realizarea interfetelor si permit crearea, ȋn spate, a codului sursă
necesar instanţierii obiectelor aferente controalelor ȋn momentul ȋn care acestea sunt plasate prin
intermediul designer-ului grafic
• compilatoare şi unelte de depanare integrate toate pentru limbajele .NET;
• unelte integrate pentru testare
• integrare facilă cu cloud-ul Azure, etc.
Fiecare tip de aplicaţie care poate fi creată ȋn Visual Studio necesită selectarea tipului aferent de proiect
ȋn momentul creării. Un proiect este constituit dintr-un set de fişiere sursă şi resurse care se vor compila
ȋntr-un singur ansamblu. Setul de fişiere care se crează diferă funcţie de tipul proiectului respectiv; de
asemenea vor fi diferite şi referinţele incluse ȋn mod automat ȋn cadrul fiecărui proiect, funcţie de
specificul acestuia. Unul sau mai multe proiecte care compun un pachet software al unei aplicaţii
formează o soluţie.
Visual Studio crează soluţiile ȋntodeauna ȋn cadrul unui fişier cu extensia .sln. Există o multitutine de
tipuri de aplicaţii, unele cu mai multe variante, care pot fi dezvoltate ȋn .NET utilizȃnd Visual Studio,
dintre care cele mai importante sunt următoarele: aplicaţii desktop, aplicaţii web, aplicaţii UWP, jocuri
aplicaţii IoT (Internet of Things), aplicaţii pentru cloud, etc.

Mediul de lucru Visual Studio


Pentru dezvoltarea aplicaţiilor pe platforma .NET utilizând Visual Studio, se poate opta pentru diverse
limbaje de programare, dintre care cele mai cunoscute sunt : C#, F#, C++, Visual Basic, etc. Elementele
de bază ale mediului de lucru sunt Studio:
• bara de meniu
• bara de stare
• bara de unelte
• spaţiul de lucru
Bara de unelte se poate modifica în funcţie de preferinţele fiecărui utilizator. Pentru a realiza acest lucru
se apasă click dreapta (pe bara de unelete) şi se selectează fereastra care se doreşte să fie afişată/ascunsă,
sau Customize pentru a modifica butoanele ce apar pe o anumită fereastră. Spaţiul de lucru Visual
Studio oferă posibilitatea aranjării ferestrelor în grupuri care pot fi ancorate în 5 zone principale: stînga,
dreapta, sus, jos şi mijloc (client). Pentru a realiza acest lucru se poate utiliza drag and drop.
Figura 3: Zone pentru gruparea ferestrelor
Odată ce se selectează fereastra dorită şi începe mutarea acesteia, Visual Studio va indica poziţiile în
care aceasta se poate ancora. Figura 3 prezintă cele 5 zone (cea din mijloc este folosită pentru grupare).
Tipurile de ferestre care pot fi vizualizate se gasesc în meniul View.

Meniul Visual Studio

1. Meniul File
• New – pentru a creea o soluţie nouă sau un proiect nou se poate utiliza meniul File//New – care va
deschide fereastra din care se va putea selecta opţiunea Create a new project. Aceasta va pune la
dispoziţie o fereastră cu toate tipurile de aplicaţii ce pot fi create, selecţia putându-se realiza grupate
după limbajul de programare dorit (C#, Visual Basic,...), platforma pe care rulează (Windows,
Linux,....) şi tipurile de proiecte (Console, Desktop,.... Pentru fiecare tip de proiect care se creaza
există posibilitatea de a selecta o machetă (template) şi astfel Visula Studio va şti ce fişiere trebuie
să genereze la crearea proiectului
• Open – este folosit pentru a deschide o soluţie sau aplicaţie existentă respectiv un fişier existent
(opţiunea File) etc.
• Add – folosit pentru adăugarea de proiecte (noi sau existente) la soluţia curentă
• Close – închide fişierul curent
• Close solution – închide soluţia curentă
• Save – salvează fişierul curent
• Save as – salvează fişierul curent sub un alt nume
• Save all – salvează toate fişierele deschise
• Page setup / Print – folosite pentru tipărire
• Recent Files / Recent Projects and solutions – lista cu ultimele fişiere şi proiecte selectate.
Figura 4 – Tipuri de proiecte în Visual Studio 2022 (template-uri)

2. Meniul View
• Solution explorer – fereastră folosită la gestionarea componentelor unei soluţii sau proiect.
• Server explorer – fereastra cu lista de conexiuni la baza de date precum şi serverele disponibile.
• Class View – fereastra cu ajutorul căreia se pot vizualiza clasele unui proiect
• Code Definition Window – afişează o fereastră cu definiţia pentru codul selectat
• Object Browser – fereastră care arată atît spaţiile de nume (namespace-urile) .NET precum şi cele
din soluţia curentă
• Error List – afişează lista de erori în urma compilării
• Output – afişează informaţiile trimise de către diferite componente ale IDE-ului (Integrated
Development Environment)
• Task List – conţine lista cu task-uri – acestea pot fi User task sau Comments, şi sunt generate în
urma inspecţiei codului sursă prin extragerea comentariilor TODO)
• Toolbox – conţine lista cu componente ce pot fi utilizate (aceasta listă se schimbă în funcţie de
fişierul curent deschis)
• Other Windows – meniu cu ferestre suplimentare
• Toolbars – afişează lista cu barele de unelete ce pot fi folosite
• Full screen – folosit pentru a mări IDE-ul pe tot ecranul
• Properties Window – fereastră cu proprietăţi (pentru toate elementele asociate unui proiect)
Figura 5: Meniul View
Visual Studio oferă posibilitatea grupării componentelor folosite (fişiere, directoare, conexiunile de baze
de date, fişiere sursă, …) utilizând proiecte. Acestea, la rândul lor, pot fi grupate folosind soluţii. Pentru
a gestiona toate componentele unui proiect se poate utiliza fereastra Solution Explorer care poate fi
deschisă selectând Solution Explorer din meniu View.

Figura 6: Ferestra Solution Explorer

În Figura 6 este prezentată o fereastră Solution Explorer care conţine o soluţie cu 1 proiect.
De asemenea, structura de clase a unui proiect precum şi referinţele proiectului sunt vizibile cu ajutorul
ferestrei Class View (Figura 7). O altă fereastră deosebit de utilă în dezvoltarea aplicaţiilor Windows
este fereastra Toolbox (Figura 8).
Figura 7: Fereastra Class View Figura 8: Fereastra Toolbox

3. Meniul Project
• Add Class, Add New Item – deschid fereastra “Add New Item” – pentru clasă, cu template-
ul deja selectat
• Add New DataSource – permite adăugarea unei surse de date proiectului curent
• Add Existing Item – adăugă un obiect existent la proiectul curent
• Exclude From Project – elimină din proiect obiectul selectat ; acest lucru se poate realiza şi
prin meniul de contextual al obiectului respectiv (click dreapta)
• Show All Files – afişează toate fişierele şi directoarele proiectului curent
• Add Project Reference – deschide fereastra Reference Manager şi permite adăugarea unei
noi referinţe către un spaţiu de nume (namespace) în lista de referinţe a proiectului curent
• Set as StartUp Project – setează proiectul selectat ca proiect de start pentru soluţie, în caz că
soluţia conţine mai multe proiecte (mai mult de de 1 proiect)
• Properties – seteaza diferite proprietăţi ale proiectului curent
Figura 9: Adăugarea unei clase

4. Meniul Build
• Build Solution, Rebuild solution – compilează toate proiectele unei soluţii
• Clean Solution – şterge câteva din fişiere rezultate în urma compilării
• Batch Build – pentru proiectele mai mari, se pot compila doar o parte dintre proiecte folosind
meniul Batch Build.

Figura 10: Compilarea proiectelor (Build)

5. Meniul Debug
Windows
Breakpoint – lista cu punctele de oprire folosit pentru a opri execuţia programului la o
anumită linie de cod.
Output – aceeaşi fereastră ca la meniul View (ieşirea programului)
Immediate – fereastră în care se poate introduce expresii pentru a fi evaluate

Figura 11: Meniul Debug

Figura 12: Rularea pas cu pas a codului

• Start Debugging (F5 sau opţiunea Start) – execută aplicaţia în mod depanare (debug)
• Start Without Debugging – execută aplicaţia; acest lucru se poate realiza si utilizand Start de pe
bara de unelte
• Attach to Process – deschide lista de proces de care se poate “agăţa” soluţia curentă.
• Step Into – execuţie pas cu pas: execută următoarea instrucţiune intrând în interiorul codului tuturor
funcţiilor apelate F11
• Step Over – execuţie pas cu pas: execută o funcţie ca o unitate după care trece la instrucţiunea
următoare F10
• New Breakpoint – crează un nou breakpoint; acest lucru se poate realiza si dând click în stânga
numărului liniei pe care se doreşţe să se aplice un breakpoint
6. Meniul Tools
Din acest meniu se pot lansa diferite unelte, se poate personaliza interfaţa mediului, se pot specifica
diferite opţiuni (legate de editor, de tab-urile de editare, de depanare, de compatibilitate, de compilare,
de directoare, de controlul surselor, de spaţiul de lucru, de macro-uri, de sistemul de help, de format,
etc.).

7. Meniul Window
Acest meniu conţine comenzi legate de ferestrele de editare.

8. Alte opţiuni
Pe lângă aceste meniuri, există şi altele specifice fiecărui tip de aplicaţie sau tip de fişier ce se editează.
De exemplu, atunci când edităm un cod sursă dacă apăsăm click-dreapta pe un anumit element selectat
se deschide meniul din Figura 13 care oferă opţiuni de redenumire automată (Rename) sau alte acţiuni
rapide şi refactorizări care pot fi realizate funcţie de context (Quick Actions and Refactorings).

Figura 13: Quick actions and Refactorings - exemplificare

Temă:
1. Studiaţi meniurile Visual Studio. Creaţi soluţii folosind Visual C# cu diferite machete
(template-uri – de exemplu Console application). Analizaţi structura de directoare şi fişiere
generate.

2. Se dau următoarele clase:


• clasa Date_univ: universitatea (string), specializarea (string), an_studiu (int)
• clasa Student: nume (string), adresa (string), an_nastere (int),
date_univ (Date_univ)
Să se realizeze o implementare care sa permită crearea rapidă, prin copiere, a mai multor obiecte
Student aferente studenţilor care sunt la aceeaşi universitate, aceeaşi specializare şi acelaşi an de studiu
(ex. Universitatea din Oradea, Tehnologia Informaţiei, anul 2). Crearea obiectelor se va realiza în
următoarea manieră:
• se va crea iniţial un singur obiect Student în care se va iniţializa doar câmpul
date-univ
• se vor crea apoi restul obiectelor Student (minim 5) prin copierea datelor obiectului Student
creat iniţial
• pentru fiecare obiect Student creat, se vor completa apoi câmpurile nume, anul naşterii şi
adresa în mod specific pentru fiecare student.

Programul va permite la final afisarea tuturor obiectelor astfel create (exemplificare pentru minim 6
studenţi); de asemenea, dacă doi studenţi îşi schimbă ulterior specializarea şi/sau universitatea, să se
realizeze aceasta modificare la nivelul obiectelor deja create si apoi să se afiseze rezutatul pentru a
evidenţia modificările realizate (se vor afişa din nou toate obiectele Student create).
Lucrarea de laborator nr. 2

Clase şi obiecte. Constructori

Clasele reprezintǎ blocurile constructive de bazǎ ale programelor dezvoltate în tehnologie


obiectualǎ. Obiectele cu care programul lucrează sunt concepute pe baza claselor definite în
cadrul acestuia. Obiectele reprezintă entitatea fundamentală în POO; un obiect dintr-o anumită
clasă se spune că reprezintă o instanţă a clasei respective.

O structură completă a unei clase include următoarele elemente:

class nume_clasa
{
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
}

Clasele sunt declarate utilizînd cuvîntul cheie class, urmat de numele clasei şi definiţia
acesteia. Elementele de bază ale claselor le reprezintă cîmpurile de date, proprietăţile şi
metodele.

Cîmpurile de date reprezintă datele propriu-zise ale clasei:


modificator_acces tip_câmp_date nume_câmp_date;

Atunci cînd se crează o instanţă a clasei respective, cîmpurile de date, prin valorile pe care le
conţin, reprezintă starea instanţei în momentul respectiv.

Spre deosebire de cîmpurile de date, proprietǎţile oferǎ doar acces la date, dar nu reprezintǎ
datele propriu-zise. Î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. Proprietǎţile pot fi private sau publice, de tip read/write, read-only sau
write-only. În C#, proprietǎţile nu pot avea parametri. Sintaxa generalǎ a unei proprietăţi de
tipul read/write este urmǎtoarea:
modificator_acces tip_return NumeProprietate
{
get { aici se returneaza o valoare }
set { aici se atribuie o valoare }
}
Definirea unei proprietǎţi de tipul read-only respectiv write-only se poate face conform
urmǎtoarei sintaxe generale:
modificator_acces tip_return NumeProprietateReadOnly
{
get { aici se returneaza o valoare }
}
modificator_acces tip_return NumeProprietateWriteOnly
{
set { aici se atribuie o valoare }
}

Proprietatile auto-implementate furnizeazǎ o manierǎ mai simplǎ si concisǎ de a de a defini


proprietati, atunci când implementarea acestora NU necesitǎ cod adiţional:
modificator_acces tip_return NumeProprietate { get; set; }

Metodele oferă clasei (şi implicit obiectelor din clasa respectivă, pe lîngă stare şi o
funcţionalitate). În esenţă, definirea metodelor este asemănătoare cu cea a unei funcţii:

modificator_acces tip_retur Nume_metoda(lista parametri) { …instructiuni…}

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:

modificator_acces tip_retur Nume_metoda(lista parametri) => instructiune;

Numele metodei trebuie să fie unic; prin intermediul parametrilor se transmit date metodelor
respective. În C# există următorii modificatori de acces valabili pentru membri unei clase,
inclusiv metode:
• private - elementele sunt direct accesibile numai în interiorul clasei; altfel, din exterior
accesul trebuie să se facă numai prin metode definite explicit, şi care nu sunt private
(publice sau protected, dupǎ caz). În mod automat, dacǎ nu se specificǎ nimic în acest
sens, în C# elementele respective (cîmpuri de date sau metode) se considerǎ implicit
private
• protected - reprezintă nivelul doi de acces, mai puţin restrictiv decît private, care
permite în plus faţă de private şi membrilor claselor derivate să aibă acces direct la
membri declaraţi protected. Accesul este posibil indiferent dacă clasele derivate fac
parte sau nu din cadrul aceluiaşi ansamblu.
• public - reprezintă nivelul trei de acces, în mod normal utilizat numai pentru metode
sau proprietǎţi, şi care conferă acestora proprietatea de a fi accesate în mod direct, de
oriunde din afara obiectului.

Metodele non-statice (de instanţă) definite în cadrul unei clase se apelează prin intermediul
obiectelor definite din clasa respectivǎ, prin intermediul notaţiei generale “cu punct”:
nume_obiect.nume_metoda(..parametri...)

Pe lîngă cei trei modificatori de acces definiţi mai sus mai există în C# şi modificatorii internal
şi protected internal, aceştia fiind utilizaţi în aplicaţii mai complexe: internal – elementele
sunt accesibile doar în cadrul ansamblului din care fac parte; 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 ansambl.
Membri unei clase pot de asemenea să fie de două categorii: membri de instanţă (non-statici)
sau membri statici (de clasă). Membri de instanţă aparţin unei instanţe specifice a clasei,
obţinută în momentul creării unui obiect din clasa respectivă. Membri statici aparţin clasei
respective şi nu unei instanţe particulare, putînd fi accesaţi şi utilizaţi chiar dacă nu există nici
o instanţă a clasei. În cazul în care există mai multe instanţe ale unei clase care dispun de un
membru static, acestea împart respectivul membru static. Declararea membrilor statici se
realizează utilizînd cuvîntul cheie static.

Atît cîmpurile de date cît şi proprietǎţile şi metodele pot fi declarate statice: metodele statice
sunt de regulǎ utilizate pentru a prelucra datele statice ale clasei. De fapt, 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. Aceasta categorie
de membri nu au acces la niciuna din datele instanţelor clasei respective (dacǎ existǎ vreuna).

Membri statici se caracterizeazǎ deci prin faptul cǎ aparţin clasei propriu-zise şi nu unui obiect
al unei anumite clase. O consecinţă a acestui fapt este aceea că datele statice pot fi
iniţializate/accesate prin intermediul clasei, chiar dacă nu existǎ încǎ obiecte instanţiate din
clasa respectivă; dacǎ nu sunt publice, se aplicǎ regulile generale de acces. Forma generală
pentru referirea unui membru static este urmǎtoarea:

nume_clasa.cimp_date_membru_static=valoare;
sau
nume_clasa.nume_metoda_statica(..);

Clasele, de regulă mai conţin şi nişte metode speciale numite constructori respectiv
destructori. Motivul principal pentru care este bine să avem constructori este că aceştia
furnizează un mod foarte convenabil de alocare/dealocare a memoriei dinamice. Fiecare clasă
trebuie să aibe un constructor care va fi apelat în momentul în care se crează un obiect din clasa
respectivă. Dacă programatorul nu furnizează în mod explicit cel puţin un constructor (pot
exista mai mulţi) pentru o clasă, compilatorul presupune că acesta există sub cea mai simplă
formă posibilă (constructor implicit).

În multe situaţii constructorii realizează şi iniţializarea datelor membre ale claselor. În C#


constructorii trebuie să aibă acelaşi nume cu clasa însăşi a obiectului. Ei se definesc şi se
utilizează în principiu ca şi funcţiile membre obişnuite; faţă de acestea însă, constructorii sunt
implicit apelaţi atunci cînd se crează obiecte ale clasei respective. Practic, 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.

Constructorii au urmǎtoarele caracteristici:


• constructorii nu returnează nici un fel de valoare, nici măcar de tipul void;
• atunci cînd nici un constructor nu este definit în cadrul unei clase, compilatorul genereazǎ un
constructor implicit, ca metodǎ publicǎ;
• constructorii pot avea multiple definiţii, rezultând ceea ce se numeşte 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 descendenţi ca şi alte metode
Constructorii impliciţi - se pot obţine fie prin declararea de către utilizator al unui constructor
simplu, fără nici un parametru, indiferent de instrucţiuni, fie prin generarea implicită a acestuia
de către compilator, atunci cînd clasa respectivă nu are nici un constructor. În acest ultim caz
corpul acestuia nu va conţine nici o instrucţiune.

Constructorul implicit, fǎrǎ parametri, definit pentru o clasă ar arǎta astfel:


public Nume_clasa() { }

Observaţie: pentru obiectele create sub forma unor structuri de tablou de obiecte trebuie să
existe un constructor fără parametri, în caz contrar semnalîndu-se eroare.

Constructori cu parametri - în cazul acestora, iniţializarea cîmpurilor de date se face pe baza


valorilor primite ca şi parametri de către constructor:
public Nume_clasa(..parametri..)
{
instructiuni
}

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 şi o copie a obiectului primit ca parametru.

public Nume_clasa(obiect_parametru)
{
instructiuni
}

Exercitiu:

Să se creeze o soluţie pentru gestionarea (adăugare, afişare, căutare) unor produse. Pentru
implementarea acesteia creaţi 2 proiecte, unul care se va ocupa cu gestionarea produselor app1,
iar cel de-al doilea va conţine entităţile folosite, entitati.

Creaţi un proiectul consolă app1:

1. Din opţiunile puse la dispoziţie selectaţi Create a new project pentru a deschide fereastra
Create a new project. Această fereastră conţine tipurile de aplicaţii pe care le puteţi
realiza cu Visual Studio, pentru fiecare tip existând o serie de machete.
2. Selectaţi tipul de proiect filtrând opţiunile existente pentru limbajul C# , platforma
Windows respectiv macheta Console App, setaţi numele proiectului app1; locaţia
acestuia; precum şi numele soluţiei POS, după care apasaţi OK.

În urma acestor acţiuni se va genera soluţia POS şi proiectul app1.


Creaţi un nou proiect entitati (librărie de clase) în cadrul soluţiei existente:

3. În fereastra Solution Explorer apăsaţi click dreapta pe soluţia POS, apoi Add // New
Project pentru a deschide fereastra Create a new project.
4. Selectaţi tipul de proiect Visual C# // Windows cu macheta Class Library (.NET),
setaţi numele proiectului entitati, după care apasaţi OK.

În urma acestor acţiuni se va genera proiectul entitati şi clasa Class1.

Creaţi o clasă:

5. În fereastra Solution Explorer apăsaţi click dreapta pe proiectul în care doriţi să creeaţi
clasa, în cazul nostru în proiectul entitati , apoi Add // Class pentru a deschide fereastra
Add New Item.
6. Selectaţi macheta Class , setaţi numele clasei Produs, după care apăsaţi OK.

În urma acestor acţiuni se va genera clasa Produs.

Adăugaţi câmpuri şi proprietăţi la o clasă:

7. Deschideţi fisierul Produs.cs pentru a edita clasa Produs.


8. Scrieţi următorul cod în clasa Produs:

private uint id;// identificator


private String? nume;// numele produsului
private String? codIntern;// codul Intern
private String? producator;// producator

9. Generaţi proprietăţi aferente câmpurilor clasei: click dreapta pe câmpul id, apoi Quick
Actions and Refactoring // Encapsulate field // OK. Rezultatul este generarea
proprietăţii Id/Nume... cu opţiunile get si set; repetaţi acelaşi lucru pentru fiecare
câmp, creînd proprietăţile Nume, CodIntern şi Producator asociate câmpurilor de
date corespunzătoare.
public uint Id { get => id; set => id = value; }
public string? Nume { get => nume; set => nume = value; }
…s.a.m.d

10. Deoarece deocamdată nu vom dori să realizăm alte implementări/validări suplimentare în


cadrul proprietăţilor generate mai sus, putem utiliza proprietăţi auto-implementate: click
dreapta pe Id, Nume, etc. şi selectaţi Quick Actions and Refactorings/Use auto property.
Astfel se vor genera următoarele proprietăţi în versiunea simplificată:

public uint Id { get; set; }


public string? Nume { get; set; }
public string? CodIntern { get; set; }
public string? Producator { get; set; }
Gestionati produsele:

11. În fereastra Solution Explorer apăsaţi click dreapta pe proiectul app1, apoi Add Project
reference, în tab-ul Solution/Projects selectaţi proiectul entităţi.

12. Pentru a putea folosi clasa Produs în clasa Program trebuie să adăugam:
using entitati;

13. Modificaţi declaraţia clasei Produs astfel încât aceasta să fie accesibilă în alte proiect:
public class Produs
14. Codul care citeşte de la tastatură un număr specificat de produse, după care le afişează
este următorul:
using entitati;

Console.Write("Nr. produse:");
uint nrProduse = uint.Parse(Console.ReadLine() ?? string.Empty);
// array de produse
Produs[] produse = new Produs[100];
// citim produsele
for (int cnt = 0; cnt < nrProduse; cnt++)
{
// instantierea unui Produs
Produs prod = new Produs();
Console.WriteLine("Introdu un produs");
Console.Write("Numele:");
prod.Nume = Console.ReadLine();
Console.Write("Codul intern:");
prod.CodIntern = Console.ReadLine();
Console.Write("Producator:");
prod.Producator = Console.ReadLine();
produse[cnt] = prod;
}
// afisam produsele
Console.WriteLine("Produsele sunt:");
for (int cnt = 0; cnt < nrProduse; cnt++)
{
Produs prod = produse[cnt];
Console.WriteLine("Produs: " + prod.Nume + "[" +
prod.CodIntern + "] " + prod.Producator);
}

În funcţie de contextul în care este folosită expresia prod.CodIntern va apela metoda de


set (dacă este folosită ca atribuire), sau get (dacă este folosită ca interogare) a proprietăţii
CodIntern.

15. Apăsaţi F6 sau Build/Rebuild pentru a rula compila programul si apoi F5 pentru a rula
programul (sau Start).
16. Creaţi constructor cu toţi parametri necesari. Acest lucru se realizează prin introducerea
urmatoarelor linii de cod în clasa Produs:
public Produs(uint id, string? nume, string? codIntern,
string? producator)
{
Id = id;
Nume = nume;
CodIntern = codIntern;
Producator = producator;
}

17. Pentru a folosi noul constructor putem folosi următoarea secvenţă (în locul porţiunii de
cod care citeşte produsele):
// instantierea unui Produs
Console.WriteLine("Introdu un produs");
Console.Write("Numele:");
string? nume = Console.ReadLine();
Console.Write("Codul intern:");
string? codIntern = Console.ReadLine();
Console.Write("Producator:");
string? producator = Console.ReadLine();
Produs prod = new Produs((uint)cnt, nume, codIntern, producator);
produse[cnt] = prod;

Cum să vizualizaţi şi să utilizaţi Class Diagram

18. În fereastra Solution Explorer apăsaţi click dreapta pe proiectul entitati, apoi Add New
Item/Class Diagram pentru editorul vizual.
19. Selectaţi elementele din soluţia deschisă în Solution Explorer (Produs) şi vizualizaţi-le în
editorul grafic Class Diagram prin drag-and-drop pe suprafaţa acesuia.
20. Folosind acest editor se pot realiza o mulţime de operaţii printre care: vizualizare detalii
clasă (click dreapta pe clasă Class Details), navigare prin elementele acesteia, adăugare de
noi elemente, etc. Editorul grafic este integrat cu codul aplicaţiei astfel încât modificările
realizate la nivelul acestuia se reflectă în cod şi viceversa.

Figura 2.1: Clasele Produs şi Serviciu

Exerciții

1. Folosind aspectele prezentate în cadrul laboratorului pentru clasa Produs, implementaţi


clasa Serviciu aşa cum este prezentată în Figura 2.1. (Exemplu de serviciu: instalare
windows, reparat calculator..). Scrieţi o secvenţă de program care realizează citirea şi
afişarea mai multor obiecte de tip Produs şi Serviciu din/în fereastra consolă utilizând
câte un tablou pentru a gestiona mulţimea de produse şi respectiv servicii citite (tablou de
obiecte). Utilizaţi pentru instanţierea obiectelor constructorii cu parametri definiţi în cadrul
claselor; pentru afişare utilizaţi proprietăţile definite pentru fiecare tip de obiect.
2. Modificati modul de afisare a obiectelor Produs şi Serviciu prin crearea de metode de
afişare corespunzǎtoare în cadrul fiecǎrei clase şi utilizaţi-le pentru afişarea obiectelor din
clasa respectivǎ. De asemenea, modificaţi modalitatea inserare în cadrul tabloului de obiecte
astfel încât un obiect să fie inserat doar în cazul în care obiectul nu mai existǎ deja în tablou
(presupune o cǎutare şi o comparare între obiecte).

3. Vizualizati, în cadrul editorului, proprietăţile şi metodele obiectelor Produs/Serviciu.


Ce observați? Utilizați tastele speciale F10, F11 pentru a vedeam modul de apelare a
metodelor în cursul execuției. Observati ordinea de apelare şi valorile curente ale anumitor
proprietǎți utilizând ferestrele asociate debugger-ului(Autos, Locals,...).
Lucrarea de laborator nr. 3

Moştenirea şi ierarhia de clase. Polimorfismul şi metodele abstracte/virtuale

Moştenirea reprezintă mecanismul care permite crearea unei ierarhii de obiecte cu descendenţi,
care moşteneşte accesul şi structurile de date ale strămoşilor. Prin intermediul moştenirii se pot
reutiliza şi extinde clasele existente, fără a rescrie codul original.

Mecanismul moştenirii permite ca, avînd o clasă aşa numită de bază, X, pe baza acesteia să
poată fi definită o altă clasă, Y, numită clasǎ derivată, care să moştenească toate elementele
primei clase, adăugându-i totodată altele noi. Astfel, în cadrul clasei Y, partea moştenită de la
X reprezintă totalitatea structurilor de date, metodelor, etc. lui X (Figura 3.1).

X = clasa
de baza

Partea mostenita
de la X
Y = clasa
derivata din X
Partea specifica
lui Y

Figura 3.1: Mecanismul moştenirii

Deoarece clasa derivată moşteneşte elementele clasei de bază, în programarea obiectuală se


admite compatibilitatea între tipul clasei derivate şi tipul clasei de bază, reciproca nefiind însă
adevărată. La nivel de clase, C# suportă numai moştenire simplă: acest lucru implicând faptul
că o clasă poate să fie derivată dintr-o singură clasă de bază. Se mai utilizează în contextul
moştenirii şi terminologia de clasă părinte (bază) şi respectiv copil (derivată).
Atunci când se instanţiază un obiect dintr-o clasă derivată, întotdeauna partea moştenită de la
clasa de bază este instanţiată înaintea clasei derivate: constructorul clasei de bază este apelat
înaintea celui din clasa derivată.

Toate clasele din C# şi .NET Framework sunt derivate dintr-o clasǎ System.Object sau
dintr-o clasǎ derivatǎ din clasa Object. Astfel se poate spune de fapt cǎ toate clasele sunt
derivate din Object.

În contextul moştenirii în general, trebuie amintiţi modificatorii abstract şi sealed care obligă
respectiv se opun procesului de moştenire. Astfel, o clasă declarată abstractă trebuie neapărat
derivată, pentru că ea însăşi nu poate fi instanţiată, C# oferind practic un mecanism pentru a
restricţiona crearea de instanţe din anumite clase, prin posibilitatea de a le defini sub forma de
clase de bazǎ abstracte. Se impune declararea unei clase ca fiind abstractă dacă ea conţine cel
puţin un membru declarat abstract (metodă, proprietate).

In consecinţă, clasele abstracte nu pot fi instanţiate (adicǎ nu se pot crea obiecte din clasa
respectivǎ, care este declaratǎ ca abstractǎ); 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. Practic, 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 în declaraţia clasei.

abstract class nume_clasa


{
…membri non-abstracti ai clasei (daca sunt)
abstract tip_retur nume_metoda_abstracta(…parametri);

}

Clasele abstracte pot conţine metode şi/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 definitǎ în oricare clasǎ derivatǎ pentru ca aceasta să poată deveni
instanţiabilă. 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
membri acesteia sǎ fie declaraţi abstracţi.

De asemenea, o clasă declarată ca sigilată (sealed) nu mai poate fi derivată ulterior, practic ea
trebuie văzută ca o clasă terminală din cadrul ierarhiei. Modificatorul sealed poate fi aplicat
şi metodelor, în acest context el interzicând redefinirea metodei respective în cadrul claselor
derivate.

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 general utilizată în acest sens
este ca aceştia să apeleze în mod implicit sau explicit constructorii claselor de bază. Regula
este următoarea la crearea unui obiect dintr-o clasă derivată: se apelează mai întâi un constructor
ai clasei de bază, apoi se execută acţiuni specifice constructorului clasei proprii (derivate).
Apelul explicit al constructorului clasei de bază din constructorul unei clase derivate se poate
realiza utilizînd base(…):
public nume_constructor_derivat(..parametri..)
:base (..parametri_constructor_baza..)
{
Instructiuni constructor clasa derivata
}

Dacă un se realizează niciun apel la base, se va apela implicit constructorul fără parametri ai
clasei de bază (implicit, dacă un există altul).
De fapt, base poate fi utilizat pentru a apela orice metodă a clasei de bază care este redefinită
într-o clasa derivată, prefixând apelul metodei cu base:

base.nume_metoda(..parametri..);

Moştenirea şi metodele virtuale (sau abstracte) stau la baza obţinerii polimorfismului în


programarea orientată pe obiecte. Metodele abstracte sunt în mod automat (implicit) şi virtuale.
În general, în C#, 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ǎ anulează (suprascrie) metoda din clasa de
bazǎ, ceea ce conduce la un comportament polimorfic. Pe de altǎ parte, utilizarea lui new în loc
de override înseamnǎ cǎ metoda din clasa derivatǎ este nouǎ, aşa cǎ este ascunsǎ dacǎ este
apelatǎ prin intermediul unui pointer la clasa de bazǎ.
Exercitiu:

Să se refactorizeze codul de la laboratorul anterior astfel încât :


• să se creeze o clasa de bază (ProdusAbstract) pentru clasele Produs şi
Serviciu (în proiectul entitati) care să preia elementele comune ale celor două
clase
• codul de citire/afişare a obiectelor de tip Produs sau Serviciu din metoda Main a
clasei Program să fie mutat în metode specifice ale unor clase de tip manager
ProduseMgr şi ServiciiMgr, care se vor ocupa de managementul produselor şi
al serviciilor prin implementarea unor metode corespunzătoare de citire/afişare
• clasele ProduseMgr, ServiciiMgr să fie derivate dintr-o clasă de bază abstractă
numită ProduseMgrAbstract. Ierarhia de clase de tipul manager
(ProduseMgrAbstract,ProduseMgr şi ServiciiMgr) va fi creată în cadrul
proiectului app1 şi va include clase responsabile de managementul obiectelor
Produs respectiv Serviciu, incluzând atât operaţiile aferente gestionării
produselor şi serviciilor cât şi structura de date în care sunt memorate aceste obiecte.
În cadrul acestor clase de tip manager se vor putea include ulterior şi alte operaţii
aplicabile mulţimilor de obiecte de tipul Produs/Serviciu
O astfel de structurare a aplicaţiei permite ca o eventuală extindere/modificare a aplicaţiei să se
poată realiza foarte simplu ulterior, atât prin adăugarea de entitaţi cât şi de manageri.

Crearea ierarhiei de clase entitati

Creaţi clasa abstractă ProdusAbstract în proiectul entitati:

public abstract class ProdusAbstract

Derivaţi clasele Produs si Serviciu din clasa ProdusAbstract modificând


declaraţiile claselor astfel:

public class Produs:ProdusAbstract


public class Serviciu: ProdusAbstract

Mutaţi câmpurile şi proprietăţile comune ale celor 2 clase (Produs şi Serviciu) în clasa
de bază ProdusAbstract. Astfel se va obţine structura din Figura 3.1. Rulaţi programul
pas cu pas pentru a vedea modul în care se realizează apelurile.

Creaţi constructor cu parametri pentru toate câmpurile în cadrul clasei ProdusAbstract:

public ProdusAbstract(uint id, string? nume, string? codIntern)


{
Id = id;
Nume = nume;
CodIntern = codIntern;
}

Folosiţi noul constructor în clasa la crearea constructorului Produs:


public Produs(uint id, string? nume, string? codIntern, string? producator)
:base(id, nume,codIntern)
{
Producator = producator;
}

Figura 3.2: Ierarhia de clase ProdusAbstract, Produs si Serviciu

Faceţi acelaşi lucru şi pentru clasa Serviciu după care rulaţi programul pas cu pas.

Adăugaţi metode polimorfice utilizate pentru afişare în clasele ierarhiei entitati

Varianta 1: bazată pe crearea şi utilizarea de metode abstracte

Creaţi metoda abstractă Descriere în clasa ProdusAbstract, metodă pe care să o


folosiţi pentru afişarea produselor şi serviciilor:

public abstract string Descriere();

Compilând soluţia în acest moment veţi constata că sunt erori de compilare datorate
neimplementării metodei abstracte Descriere în clasele derivate.

Creaţi în clasa Produs metoda Descriere, utilizând override:


public override string Descriere()
{
return "Produsul: " + this.Nume + "[" + this.CodIntern
+ "] " + this.Producator;
}

Faceţi acelaşi lucru şi pentru clasa Serviciu.

Varianta 2: bazată pe crearea şi utilizarea de metode virtuale

Se observă faptul că implementarea celor două metode Descriere (din clasa Produs şi
clasa Serviciu) este foarte asemănătoare deci s-ar putea crea o altă variantă a acestora prin
mutarea părţii comune a acestora în cadrul unei metode din clasa de bază ProdusAbstract.
Realizăm în acest sens o nouă variantă de metode ce urmează a fi utilizate pentru afişare -
AltaDescriere, pe care o facem de data aceasta virtuală. Astfel, în clasa
ProdusAbstract vom avea:

public virtual string AltaDescriere()


{
return this.Nume + "[" + this.CodIntern + "] " ;
}

În clasa Produs vom apela metoda din clasa de bază utilizând base, astfel vom avea:

public override string AltaDescriere()


{
return "Produsul: "+base.AltaDescriere() + this.Producator;
}

Faceţi acelaşi lucru şi pentru clasa Serviciu.


Rulaţi soluţia pas cu pas şi explicaţi diferenţa dintre implementarile bazate pe metode
abstracte şi metode virtuale.

Creaţi clasele manager ProduseMgr şi ServiciiMgr

Creaţi clasa ProduseMgr în proiectul app1. Includeţi în cadrul clasei o structură de tablou în
care vor fi memorate obiectele Produs: adăugaţi declaraţiile pentru tabloul de produse,
precum şi o variabilă CountProduse pentru a contoriza numărul de produse:

private Produs[] produse = new Produs[100];


public uint CountProduse { get; set; } = 0;

Creaţi metodele aferente gestionării produselor de către clasa ProduseMgr; operaţiile de


gestionare vor include deocamdată citirea respectiv afişarea obiectelor Produs;
implementarea acestor operaţii se va realiza mutând codul aferent funcţionalităţii respective din
funcţia Main (versiunea din laboratorul precedent) în metoda corespunzătoare:

//Citeste un singur produs de la tastatura si il adauga in tabloul de produse


public void ReadProdus(){…}
//Citeste un numar de @nr produse de la tastatura si le adauga in tabloul de produse
public void ReadProduse(uint nr){…}
//Afiseaza toate produsele din tabloul de produse
public void WriteProduse(){…}
Utilizaţi aceste metode în funcţia Main pentru a realiza operaţiile corespunzătoare. Creaţi în
programul principal câte un manager de produse respectiv servicii şi utilizaţi metodele
implementate în cadrul acestora pentru a introduce şi afisa diverse produse şi servicii. De
exemplu, pentru produse, trebuie să creaţi întâi un obiect manager de produse:

ProduseMgr mgrProduse = new ProduseMgr();

şi apoi puteţi să apelaţi metodele corespunzătoare obiectului creat (metode de instanţă):

Console.Write("Nr. produse:");
uint nrProduse = uint.Parse(Console.ReadLine() ?? string.Empty);
mgrProduse.ReadProduse(nrProduse); //citire nrProduse – cate dorim sa introducem
mgrProduse.WriteProduse(); //afisare produsele citite

Faceţi acelaşi lucru şi pentru clasa ServiciuMgr (Figura 3.3).

Figura 3.3: Clasele ProduseMgr respectiv ServiciuMgr

Rulaţi aplicaţia pas cu pas pentru a înţelege secvenţa de apeluri. Urmăriţi unde sunt stocate
obiectele Produs şi Serviciu create.

Creaţi ierarhia de clase manager

Ca în cazul ierarhiei de ProdusAbstract/Produs/Serviciu, observăm faptul că şi în


cazul claselor manager create anterior anumite secvenţe de cod sunt similare şi ar putea fi
eventual mutate într-o clasă de bază din care să derivăm aceste clase: derivaţi clasele
ProduseMgr şi ServiciiMgr dintr-o clasă de bază abstractă ProduseAbstractMgr.
Creaţi clasa abstractă ProduseAbstractMgr :

abstract class ProduseAbstractMgr

Derivaţi clasele ProduseMgr si ServiciiMgr din ProduseAbstractMgr modificând


declaraţiile claselor astfel:

internal class ServiciiMgr:ProduseAbstractMgr


internal class ProduseMgr:ProduseAbstractMgr
Introduceţi în cadrul clasei ProduseAbstractMgr un câmp de tip tablou numit
elemente, care va fi utilizat pentru memorarea obiectelor de tip Produs şi/sau Serviciu,
renunţând la tablourile individuale produse şi servicii existente în cadrul claselor
ProduseMgr şi ServiciiMgr. Tabloul elemente se va utiliza atât pentru produsele cât
şi serviciile citite într-o structurǎ definită unitar, de tipul ProdusAbstract:

protected ProdusAbstract[] elemente = new ProdusAbstract[100];


protected int CountElemente { get; set; } = 0;

Explicaţi de ce se foloseşte modificatorul protected. Este corectă definirea tabloului


elemente cu elemente de tipul ProdusAbstract? Există şi ale alternative?

Modificaţi codul metodelor de citire şi afişare din cadrul claselor ProduseMgr respectiv
ServiciuMgr astfel încât citirea şi afişarea să utilizeze tabloul elemente din cadrul clasei
de bază ProduseAbstractMgr. Rulaţi aplicaţia, adăugând două produse şi două servicii, şi
urmăriţi (afişaţi) rezultatul obţinut.

Modificatorul static

Adăugaţi modificatorul static la câmpurile elemente şi CountElemente din clasa de bază


ProduseAbstractMgr:

protected static ProdusAbstract[] elemente = new ProdusAbstract[100];


protected static int CountElemente { get; set; } = 0;

Rulaţi aplicaţia din nou (adăugând două produse şi două servicii). Explicaţii, analizând
rezultatul afişat, care este diferenţa faţă de rularea anterioară (ce implicaţii are adăugarea
modificatorului static).

Temă:

1. Pornind de la ideea de a avea toate produsele şi serviciile stocate în aceeaşi structură de


date comună (în acest caz, acelaşi tablou unic elemente similar cu cel definit anterior),
să se implementeze în cadrul clasei de bază ProduseAbstractMgr o metodă
Write2Console care să afişeze conţinutul întregului tablou elemente şi care să
înlocuiască cele două metode specifice dezvoltate pentru afişarea separată a produselor şi
serviciilor din clasele manager corespunzătoare (unificam cele două metode într-una
singură implementată în cadrul clasei de bază ProduseAbstractMgr). Să se realizeze
un program care să permită utilizatorului să introducă diverse produse şi servicii şi apoi să
vizualizeze elementele introduse folosind metoda implementată. S-ar putea realiza o
implementare care să unifice într-o manieră similară şi metodele de citire? Incercaţi şi
analizaţi această posibilitate.
2. Implementaţi o alta variantă de metodă care poate fi utilizată pentru afişarea produselor şi
serviciilor (o alternativă la AltaDescriere), prin redefinirea metodei ToString()
moştenită de la clasa Object. Analizaţi variantele implementate.
Lucrarea de laborator nr. 4

Supraîncărcarea metodelor şi operatorilor. Compararea obiectelor.

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ă (Nume_metoda) dar cu o semnătură diferită în
clasă (tip_retur respectiv lista de parametri trebuie fi diferită):

modificator_acces tip_retur1 Nume_metoda(lista parametri1){ …instructiuni…}


modificator_acces tip_retur2 Nume_metoda(lista parametri2){ …instructiuni…}

Obligatoriu metodele supraîncărcate trebuie să aibă același nume dar semnături diferite => lista
de argumente trebuie să fie diferită. Selecţia metodei efectiv apelatate se realizeaza funcţie de
argumentele utilizate (parametri) în momentul apelului => metodele sunt diferențiate prin
numărul și tipul parametrilor metodei. În cazul supraîncǎrcǎrii metodelor, comportarea codului
e determinata 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
(compile-time polimorfism). Tipul returnat poate sau nu să fie același, acesta un reprezintă
factor de deferenţiere la selecţia metodei efectiv apelate.

obj1.Nume_metoda(param1, param2);
obj2.Nume_metoda(param1, param2, param3);

Un caz particular de supraîncărcare este reprezentat de supraîncărcarea constructorilor.


Utilizarea metodelor supraîncǎrcate în cadrul claselor simplificǎ interfaţa claselor în situaţii în
care avem operaţii similare dar care necesitǎ parametri diferiţi precum şi implementǎri diferite.

Cu anumite restricţii, este posibilă şi supraîncărcarea operatorilor:


• NU toti operatorii pot fi redefiniţi, deoarece aceasta ar conduce la interacţiuni nedorite cu
limbajul C# însuşi
• 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)
• 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

De exemplu, pentru redefinirea operatorului de testare a egalităţii, un şablon general ar


putea fi următorul:
public static bool operator ==
(tip_param p1, tip_param p2) {…implementare…}

Supraîncărcarea anumitor operatori poate implica obligativitatea sau recomandarea de a


supraîncărca/redefini şi alţi operatori şi/sau metode în cadrul clasei.

Testarea egalităţii obiectelor (compararea) ar putea părea ca un concept simplu la prima vedere,
dar dacă privim mai profund, nu este. În C# obiectele pot fi comparate prin diferite modalităţi
de abordare, astfel:
• utilizând redefinirea operatorului ==
• redefinind metoda Equals(Object) moştenită de la clasa Object
• prin implementarea unor interfeţe specifice care furnnizează capabilităţi de comparare a
obiectelor, de exemplu IComparable, IEquatable<T>, etc.

Exerciții

1. Pornind de la implementarea de la laboratorul precedent a aplicaţiei pentru gestionarea de


produse şi servicii prin intermediul claselor manager, completaţi implementarea realizată
prin crearea de diferite modalitati de comparare a obiectelor Produs şi Serviciu: prin
redefinirea metodei Equals moştenită de la Object şi prin redefinirea operatorului == .
Pentru a realiza o testare simplă a variantelor de comparare realizate, adăugati o nouă
variantă de implementare pentru pentru citirea unui produs/serviciu (de exemplu
ReadUnProdus respectiv ReadUnServiciu) care returnează obiectul produs respectiv
serviciu citit şi utilizaţi-o în Main pentru a citi diferite produse şi pentru a testa
implementările aferente modalitatilor de comparare a obiectelor Produs/Serviciu.

2. Creaţi în clasele manager două metode supraîncarcate pentru căutarea unui anumit obiect
Produs/Serviciu în tabloul de produse şi servicii cu parametri diferiţi (căutare după
obiectul căutat respectiv doar după numele acestuia, de exemplu:
public bool Contine(Produs p);
public bool Contine(string? numep);

Utilizaţi în cadrul implementării acestor metode Contine una dintre modalităţile de


comparare a obiectelor implementate anterior. Pentru varianta funcţiei de căutare cu
parametru nume, cum ar trebui modificată metoda respectivă pentru a returna eventual toate
obiectele care au acelaşi nume, dacă sunt mai multe? Incercaţi o variantă de implementare
în acest sens.

3. Incercaţi o variantă de implementare a comparării obiectelor bazată pe interfeţele


IComparable respectiv IEquatable<T>. Analizaţi diferitele modalităţi de comparare
existente precizând avantajele/dezavantajele (dacă există) a fiecăreia dintre ele.
Lucrarea de laborator nr. 5

Clase generice. Colecţii generice

Clase generice

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.

Utilizatorul poate sǎ-şi defineascǎ propriile clase şi metode generice; de asemenea, utilizatorul
poate, sǎ-şi defineascǎ propriile colecţii generice: implementarea acestora se bazează pe clase
generice. Astfel, o definiţie genericǎ furnizează doar formatul general al clasei sau a 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. Forma generală a unei clase generice
este următoarea:

class nume_clasa < T, T1, ... >


{
//..date, proprietăţi şi metode
//(în cadrul cărora apare tipul T, T1, ...)
}

Se observǎ cǎ parametrul T (sau, eventual, parametri) este utilizat în poziţiile unde în mod
normal ar apǎrea tipuri:
• tipuri ale parametrilor unei metode
• tipuri ale valorilor returnate de metode sau de proprietǎţi
• tipuri ale câmpurilor de date

Definiţia clasei în acest caz furnizează doar formatul general al acesteia în cadrul căruia T,
T1,...etc. reprezintă tipuri pe care compilatorul îl va înlocui în momentul apelului, funcţie de
tipul parametrilor actuali transmişi. Utilizarea unei astfel de clase generice impune instanţierea
clasei respective pentru anumite tipuri concrete, folosind unul din constructorii definiţi în cadrul
acesteia (de exemplu, fără parametri):

nume_clasa<tip_concret> o_instanta_concreta
= new nume_clasa<tip_concret>();

Colecţii generice definite în .NET

În general, avantajele utilizării tipurilor generice ȋn programarea orientată pe obiecte sunt


următoarele:
• 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
Colecţiile generice reprezintă în acest sens un caz particular de clase (tipuri) generice: sunt
puternic tipizate şi prezintă siguranţa tipurilor (type safe)..NET Framework pune la dispoziţie
în cadrul spaţiului de nume System.Collections.Generic o serie de clase de colecţie
generice extem de puternice, şi care sunt de preferat a fi utilizate în locul claselor de colecţie
non-generice (gen ArrayList). Acestea implementează una sau mai multe dintre interfeţele
generice de dar marea majoritate implementează Icollection<T> şi Ienumerable<T>.
Cȃteva dintre cele mai utilizate clase ed colecţie generice sunt următoarele:
• List<T> - implementează o listă puternic tipizată de obiecte 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) care
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>
• HashSet<T> - reprezintă o colecţie generică de tip mulţime de valori care 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

De exemplu, definirea unei liste de şiruri de caractere sau numere întregi se poate realiza foarte
simplu, particularizând tipul T la crearea obiectului List:
List<string> siruri = new List<string>();
List<int> intregi = new List<int>();

Colecţia poate include şi tipuri definite de utilizator:

List<ProdusAbstract> elemente = new List<ProdusAbstract>();

Exerciţiul 1:

Refactorizaţi implementarea aplicaţiei dezvoltată în laboratoarele precedente prin realizarea


următoarelor completări şi modificări:
- se va utiliza colecţia generică List<T> ca variantă de implementare a tabloului
elemente
- se va completa implementarea claselor manager cu metode care citesc dintr-un fişier
XML produsele şi serviciile; se va crea în prealabil fişierul XML în consecinţă
- se vor realiza diferite interogări asupra colecţiei de produse şi servicii utilizând LINQ

Utilizaţi List<T> pentru implementarea tabloului elemente

Se va iniţializa tabloul elemente astfel:

List<ProdusAbstract> elemente = new List<ProdusAbstract>();


De asemenea, se vor realiza adaptările necesare în restul codului aplicaţiei pentru a se adapta la
noua structură de date utilizată.

Inițializaţi colecția pe baza fişierelor XML

Colecțiile de obiecte pot fi create pe baza unor fişiere XML existente. Pentru a putea crea
obiecte mai complexe, completaţi implementarea produselor şi serviciilor cu două câmpuri
suplimentare: pret si categorie.
În continuare creaţi un fişier XML cu informaţii despre produse cu urmǎtoarea structurǎ:

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


<produse>
<Produs>
<Nume>Calculator</Nume>
<CodIntern>157235</CodIntern>
<Producator>USA</Producator>
<Pret>2200</Pret>
<Categorie>Tehnologia Informatiei</Categorie>
</Produs>
…..
</produse>

Iniţializaţi pe baza acestuia o colecția elemente astfel:


public void InitListafromXML()
{
//initializare lista dintr-un fisier XML
XmlDocument doc = new XmlDocument();
//incarca fisierul
doc.Load("...Produse.xml"); //calea spre fisier
//selecteaza nodurile
XmlNodeList lista_noduri = doc.SelectNodes("/produse/Produs");
foreach (XmlNode nod in lista_noduri)
{
//itereaza si selecteaza simpurile fiecarui nod si
//informatia continuta in cadrul proprietatii InnerText
string nume = nod["Nume"].InnerText;
string codIntern = nod["CodIntern"].InnerText;
string producator = nod["Producator"].InnerText;
int pret = int.Parse(nod["Pret"].InnerText);
string categorie = nod["Categorie"].InnerText;

//adauga in lista produse


elemente.Add(new Produs
(produse.Count + 1, nume, codIntern,
producator, pret, categorie));
}
}

Adaptaţi exemplificările de mai sus astfel încât să poată fi utilizate în cadrul aplicaţiei pentru
citirea tabloului de elemente (atât produse şi servicii).

Accesaţi elementele colecției utilizând LINQ

LINQ (Language Integrated Query) – începând cu versiunea .NET 3.5 reprezintǎ un set de
extensii .NET Framework care furnizeazǎ capabilitǎți de interogare în cadrul imbajelor C# şi
Visual Basic, extinzând sintaxa acestora cu operatori de intorogare standard care permit lucrul
cu datele independent de sursa de date (simple colectii – Linq to Objects, baze de date – Linq
to SQL, XML - Linq to XML). Se permite astfel ca accesul către diferite surse de date să poată
fi realizat prin intermediul unei sintaxe unitare. LINQ adreseazǎ interactiunea cu datele în
context obiectual (model obiectual) => interogǎrile beneficiazǎ de verificǎri la compilare şi
facilitǎți de depanare.

In ceea ce priveşte LINQ to Objects, LINQ admite ca sursǎ de date doar tipuri enumerabile
(implementeazǎ IEnumerable<T>); colecţiile reprezintă, în general, sursa de date în acest
caz. 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.

De exemplu, pentru lista de elemente definitǎ astfel:


List<ProdusAbstract> elemente = new List<ProdusAbstract>();

, se pot realiza interogǎri LINQ ca de exemplu:

IEnumerable<ProdusAbstract> interogare_linq =
from elem in elemente
where elem.Categorie == "Tehnologia Informatiei"
orderby elem.Nume
select elem;
sau

IEnumerable<ProdusAbstract> interogare_linq =
from elem in elemente
where elem.Categorie == "Tehnologia Informatiei" && elem.Pret <= 2000
select elem;;

Rezultatul interogării este stocat în variabila enumerabilă interogare_linq.Iterarea prin


rezultatul interogǎrii se realizeazǎ utilizând foreach (se presupune cǎ a fost redefinitǎ pentru
clasa Produs metoda ToString()):

foreach (ProdusAbstract elem in interogare_linq)


Console.WriteLine(prod.ToString());

Pentru interogǎri mai complexe, care returneazǎ grupuri de rezultate, pot fi necesare mai multe
bucle foreach imbricate. De asemenea, este posibilǎ utilizarea variabilei de interogare fǎrǎ
tip specificat, folosind var; în acest caz, compilatorul va detecta în mod automat tipul acesteia:

var interogare_linq =
from elem in elemente
orderby elem.Nume
group elem by elem.Categorie into gr
select gr;
foreach (var gr in interogare_linq)
{
Console.WriteLine("Categoria " + gr.Key + " :");
foreach (ProdusAbstract elem in gr)
{
Console.WriteLine(elem.AltaDescriere());
}
}
Adaptaţi exemplificările de mai sus astfel încât să realizaţi diverse tipuri de interogări asupra
tabloului elemente.

Exerciţiul 2:

Creaţi clasele ListaGen şi Nod

1. Creaţi o clasă cu numele ListaGen. Pentru ca acesta să fie generică clasa trebuie declarată
astfel:

class ListaGen<T>

2. În interiorul acestei clase trebuie să declarăm structura noduilor listei prin intermediul clasei
încuibate (interne)Nod :

private class Nod


{
public ListaGen<T>.Nod Next { get; set; }

public T Data { get; set; }

// constructor clasa interna Nod


public Nod(T t)
{
Next = null;
Data = t;
}
}

3. Adăugăm un constructor pentru clasa ListGen:

public uint Count { get; set; }


private ListaGen<T>.Nod Inceput { get; set; }

// constructor
public ListaGen()
{
Inceput = null;
Count = 0;
}

Adăugaţi funcţionalitate

4. Metodă de adăugare la începutul listei:


public void Add(T t)
{
Nod n = new Nod(t);
n.Next = Inceput;
Inceput = n;
Count++;
}
5. Pentru a putea folosi foreach asupra listei noastre trebuie ca lista sǎ implementeze metoda
non-genericǎ GetEnumerator moştenitǎ de la IEnumerable deoarece
IEnumerator<T> este derivat din IEnumerable:
public IEnumerator<T> GetEnumerator()
{
//traversare elementele listei inlantuite
Nod current = Inceput;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}

Exemplu de utilizare a listei:

6. Pentru a putea folosi lista trebuie să definim un obiect ListaGen particularizând tipul
elementelor din cadrul acesteia, de exemplu, pentru tipul int:
ListaGen<int> listagenerica = new ListaGen<int>();

, sau pentru elemente de tip string:


ListaGen<string> listagenerica = new ListaGen<string>();

, sau elemente de tip ProdusAbstract??!

ListaGen<ProdusAbstract> listagenerica = new ListaGen< ProdusAbstract >();

7. Pentru a adăuga un nou element x in listă putem folosi una din cele 2 metode de inserţie :
listagenerica.Add(x);

Observaţie: x trebuie sǎ fie un obiect care are tipul tipului de date al listei (în cazul de mai
sus int, respectiv string, etc.); ce se întâmplǎ în caz contrar?

8. De exemplu, pentru a afişa lista de elemente cu elemente de tip int, putem folosi :

foreach (int element in listagenerica)


{
Console.Write(element + " ");
}

Observaţie: în exemplu de mai sus element este declarat de tip int. Acesta trebuie să fie
declarat cu acelaşi tip de date pentru care a fost instanțiatǎ lista (tipul elementelor constituente).

Temă:

1. Finalizaţi implementarea aplicaţiei bazată pe colecţia generică List<T> propusă în cadrul


Exerciţiului 1. Citiţi produsele şi serviciile din fişiere XML şi apoi utilizaţi LINQ pentru a
realiza diferite tipuri de căutări/interogări în cadrul listei de produse şi servicii create.

2. Refactorizaţi aplicaţia de la Exerciţiul 1 utilizând o clasă de colecţie generică definită de


utilizator (ListaGen); utilizaţi în acest sens exemplificările prezentate în Exerciţiul 2.
Utilizați apoi lista generică implementată (ListaGen) pentru a realiza operații pentru
diverse tipuri de elemente constituente ; analizaţi în ce masură s-ar putea înlocui List<T>
cu ListaGen<T> în aplicaţia implementată anterior. Ce observaţi ?
Lucrarea de laborator nr. 6

Interfeţe
Interfeţele sunt deosebit de utile în dezvoltarea software 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ţă. În cadrul unei interfeţe
sunt definite metode, proprietǎţi, elemente de indexare şi evenimente pe care o clasǎ care
implementeazǎ interfaţa respectivǎ trebuie sǎ le implementeze. Interfaţele nu pot 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; în schimb, poate conţine proprietăţi, deoarece
acestea sunt implementate prin metode. Definirea unei interfeţe se realizeazǎ utilizînd cuvîntul
cheie interface. Asemenea claselor, o interfaţǎ este membrǎ a unui spaţiu de nume. O interfaţǎ
poate sǎ fie derivată dintr-una una sau mai multe interfeţe de bazǎ.

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.
Interfeţele pot fi implementate implicit sau explicit; varianta implicită este de preferat şi
majoritar utilizată; 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.

Începând cu C#8, membrii interfeței sunt 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; aceştia 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
Î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.

Structura generalǎ a unei interfeţe poate fi urmǎtoarea:

interface INumeInterfata
{
tip_retur NumeProprietate
{ get; set; }
tip_retur NumeMetoda();
object this[int index]
{ get; set; }
delegate int Delegat(object obj1, object obj2);
event Delegat NumeEveniment;
……
}

//clasa care implementeaza interfata – implementare implicită


class ClasaInterfata: INumeInterfata
{
public object this[int index] { // implementare }
public tip_retur NumeProprietate { //implementare }
public tip_retur NumeMetoda() { // implementare }
...
} static void Main()
{
// declararea unei instante a interfetei
INumeInterfata obj = new ClasaInterfata ();
// apel membru interfata
obj.NumeMetoda ();
...
}
}

O clasǎ care implementeazǎ o interfaţǎ trebuie sǎ implementeze în mod explicit toţi membri
acelei interfeţe. Accesul la membri astfel implementaţi nu se va realiza prin intermediul unei
instanţe a clasei ci prin intermediul unei referinţe la interfaţa pe care clasa o implemeneazǎ. În
acest caz, prin intermediul referinţei definite se pot accesa doar membri interfeţei; pentru a
accesa eventualii membri proprii ai clasei care implemeneazǎ interfaţa trebuie realizatǎ o
conversie explicitǎ a referinţei cǎtre o referinţa la clasa respectivǎ.

Exerciţiu:

Pentru a modela situaţia reală în care comerciaţii fac oferte la produse şi/sau servicii „ambalate”
într-un pachet vom extinde implementarea aplicaţiei dezvoltate în laboratoarele precedente prin
construirea unei clase Pachet, clasă care este derivată din ProdusAbstract. Clasa
abstractizează conceptul de pachet, care poate conţine unul sau mai multe produse şi/sau
servicii. Astfel, vom crea o interfaţă IPackageable care să marcheze faptul că un
ProdusAbstract poate sau nu să fie parte a unui pachet.

Creaţi interfaţa IPackageable

1. În fereastra Solution Explorer apăsaţi click dreapta pe proiectul (în care doriţi să
creaţi interfaţa), în cazul nostru în proiectul entitati , apoi Add // New Item pentru a
deschide fereastra Add New Item.

2. Selectaţi macheta Interface , setaţi numele interfeţei IPackageable, după care


apăsaţi OK.

3. Adăugaţi metoda :

bool canAddToPackage(Pachet pachet);

Aceastǎ metodǎ ne spune dacă un ProdusAbstract poate fi vândut (inclus) într-un pachet
sau nu (returnând true respectiv false). Implementarea acestei metode se va face în clasele care
implementează interfaţa IPackageble.

Creaţi clasa Pachet:

4. Creaţi clasa Pachet derivată din ProdusAbstract;clasa abstractizază un pachet


format din elemente de tip Produs respectiv Serviciu
public class Pachet : ProdusAbstract

5. Adăugaţi în cadrul clasei Pachet o listă de elemente numit elem_pachet de tipul


IPackageable; acesta reprezintǎ structura de date în care se vor memora elementele
constituente ale pachetului (obiecte de tip Produs şi/sau Serviciu); se recomandă
a fi utilizatǎ pentru implementarea tabloului o clasǎ predefinitǎ .NET (de exemplu,
List<T>). La implementarea efectivǎ se va alege de modalitatea în care se vor putea
constitui pachetele (dacă se impun sau nu constrângeri în modalitatea de alcǎtuire: de
exemplu : nu mai mult de 3 produse/servicii în total sau combinații de 2 produse cu 1
serviciu, doar produse, etc.).

Implementaţi interfaţa IPackageble în clasele Produs şi Serviciu:

6. Modificaţi declaraţia clasei ProdusAbstract astfel încât aceasta să implementeze


interfaţa:

public class ProdusAbstract : IPackageable

7. Pentru a corecta erorile datorate compilării trebuie să implementăm toate metodele


interfeţei IPackageble, în cazul nostru trebuie să implementăm metoda
canAddToPackage în clasa ProdusAbstract. Existǎ mai multe alternative:
scrierea directǎ a metodei în cadrul clasei (ProdusAbstract) sau alegerea opțiunii
QuickActions and Refactorings/Implement Interface, lucru care va genera o metodă fără
implementare:
public bool canAddToPackage(Pachet pachet)
{
throw new NotImplementedException();
}

8. Deoarece dorim ca metoda să aibe un comportament polimorfic şi sa putem furniza


implementări diferite pe întreaga ierarhie de entitaţi, o vom defini virtuală în cadrul
clasei ProdusAbstract şi o vom redefini în toate clasele derivate din aceasta

9. De exemplu, implementarea metodei în clasa Produs ar putea fi următoarea:


public override bool canAddToPackage(Pachet pachet)
{ return true; }

Faptul că metoda returnează true se traduce prin faptul că orice obiect Produs poate
face parte din orice pachet; acest lucru se poate însǎ modifica ulterior dacǎ se doreşte
impunerea anumitor restricții.

11. Vizualizaţi rezultatul folosind Class diagram (Figura 6.1). In cadrul diagramei, câmpul
elem_pachet al clasei Pachet este un tablou de elemente IPackageable.
Figura 6.1: Implementarea ierarhiei de clase cu interfața IPackageble

Temă:

1. Pornind de la structura aplicaţiei prezentată în Figura 6.1, extindeţi aplicaţia prin


contruirea unei clase manager pentru pachete cu numele PachetMgr, derivată din
ProdusAbstractMgr. În mod similar cu managerii pentru produse/servicii, acesta
va fi responsabil de gestionarea pachetelor. Includeți în cadrul acestuia opțiunea de citire
a unui pachet (metoda ReadPachet – vezi diagrama de clase din Figura 6.2) şi
utilizați-o apoi în Main pentru a citi pachete. Metoda ReadPachet va utiliza metodele
corespunzătoare pentru citirea produselor şi/sau serviciilor din cadrul pachetului
implementate în clasele ProdusMgr şi ServiciuMgr.

2. Adăugaţi în programul principal o secvenţă de cod pentru a permite utilizatorului să


gestioneze pachete. Se citesc, prin intermediul managerilor de produse şi servicii,
produsele şi serviciile aferente fiecǎrui pachet care se creazǎ şi apoi se afişeazǎ
pachetele create. De exemplu, o posibilă secvenţă pentru citirea unor pachete ar putea
fi următoarea:
// creazǎ un manager de pachet
PacheteMgr mgrPachet = new PacheteMgr();

Console.Write("Numar de pachete dorit:");


int nrPack = int.Parse(Console.ReadLine());

//citeste pachetele utilizând managerii de produse şi servicii


for (int i = 0; i < nrPack; i++) mgrPachet.ReadPachet();

//afiseaza pachetele
mgrPachet.Write2Console();

3. Modificaţi implementările funcţiei canAddToPackage astfel încât într-un pachet să


putem avea maxim un produs şi un număr nelimitat de servicii (sau, alte variante de
configurații – de exemplu pentru fiecare pachet, acesta poate fi format dintr-un numar
p de produse şi un numar s de servicii). In ce masurǎ soluția permite adǎugarea de alte
constrângeri în crearea pachetelor?

4. Adăugaţi în cadrul pachetelor posibilitatea de a calcula preţul fiecărui pachet prin


însumarea preţurilor elementelor Produs/Serviciu componente. Calculați prețul
total al fiecǎrui pachet şi afişați pachetele în ordinea crescǎtoare a prețului total. Utilizați
metoda Sort a colecției List<T> pentru sortarea pachetelor.

5. Completaţi (extindeţi) aplicaţia dezvoltată pentru gestionarea produselor, serviciilor şi


pachetelor dezvoltată până în acest moment cu posibilitatea de a realiza diferite tipuri
de filtrări asupra tabloului de elemente construit în cadrul aplicaţiei:
• filtrarea după categorie => toate elementele care aparţin unei categorii
• filtrarea după preţ => toate elementele care au preţul egal/mai mic/mai mare decât
un preţ dat
• …etc.
Implementaţi în acest sens o variantă care să permită extinderea cu uşurinţă a criteriilor de
filtrare (adăugarea unui nou criteriu) precum şi a modalităţii de implementare a filtrelor
(variantă extensibilă). O posibilă arhitectură în acest sens poate fi următoarea:

In cadrul acesteia, se utilizează două interfeţe:


• ICriteriu va fi implementată de toate clasele care definesc un criteriu specific de
filtrare. Fiecare clasă va implementa în mod specific metoda IsIndeplinit
• IFiltrare va fi implementată de clasele care definesc o anumita modalitate de
filtrare; un exemplu de implementare ar putea fi următorul:
public IEnumerable<ProdusAbstract> Filtrare(colectia_de_elemente,criteriul_de_filtrare)
{
//returneaza acele elemente din colectia_de_elemente care satisfac criteriul_de_filtrare
}

Adaptaţi exemplificarea de mai sus în contextul aplicaţiei de la laborator. Analizaţi rolul pe care
implementarea bazată pe interfeţe aduce un plus de generalitate problemei faţă de alte potenţiale
versiuni de implementare particulare. Analizaţi în ce masură s-ar putea introduce în cadrul
arhitecturii de clase de mai sus o filtrare după mai multe criterii (categorie şi preţ, sau alte
variante) şi realizaţi o variantă de implementare.
Lucrarea de laborator nr. 7

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ă între diverse aplicaţii.
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; dacă se doreşte serializarea mai
multor instanţe, salvarea datelor acestora se realizeaza în ordine, una dupa alta, 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.

În procesul de serializare/deserializare, cele mai cunoscute standarde din 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ă (sub formă de 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 unei instanţe a unei colecţii implicǎ la rândul sǎu serializarea tuturor instanţelor
elementelor din cadrul colecţiei. Aceastǎ relaţie de dependenţǎ între clase reprezintǎ graful de
obiecte al clasei respective. Mediul de execuţie .NET parcurge recursiv graful de obiecte pentru
fiecare clasǎ în procesul de serializare şi serializeazǎ toate obiectele acestuia. Este important în
acest sens ca toate clasele componente din cadrul acestui graf sǎ fie corect definite în vederea
serializǎrii.

Faţǎ de serializarea binarǎ, în cadrul serializǎrii XML şi JSON sunt serializabile doar
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 => acestea trebuie să aibă
modificatori publici (get/set). Pentru deserializarea XML respectiv JSON este necesar un
constructor public, fǎrǎ parametri, care este utilizat implicit în procesul de deserializare (re-
creare a obiectului).

Serializarea/deserializarea XML utilizează metodele Serialize/Deserialize a


obiectului XmlSerializer. Serializarea/deserializarea JSON utilizează metodele
Serialize/Deserialize a obiectului JsonSerializer.
Exercitiu:

Folosind noţiunile referitoare la serializarea XML, modificaţi aplicaţia de la laboratorul anterior


astfel încât utilizatorul să poată serializa/deserializa un obiect Serviciu (idem şi pentru
Produs). Adăugaţi capabilităţi de serializare/deserializare managerului
ProdusAbstractMgr, după care modificaţi clasa Program pentru a permite utilizatorului
să salveze/încarce lista de elemente (produse şi servicii) într-un/dintr-un fişier tip XML.

Serializaţi/deserializaţi XML un obiect de tip Serviciu

1. Adăugaţi namespace-urile următoare la clasa Serviciu:

using System.Xml;
using System.Xml.Serialization;

2. Creaţi un constructor fără parametri (dacǎ nu existǎ deja) în clasa Serviciu,


Produs şi ProdusAbstract (aceşti constructori vor fi folosiţi de clasa
XmlSerializer pentru deserializare).

3. Adăugaţi şi metoda pentru serializarea XML , save2XML:

public void save2XML(string fileName)


{
XmlSerializer xs = new XmlSerializer(typeof(Serviciu));
StreamWriter sw = new StreamWriter(fileName + ".xml");
xs.Serialize(sw, this);
sw.Close();
}

o XmlSerializer este clasa care se ocupă de serializarea şi deserializarea


obiectelor. Constructorul folosit primeşte un parametrul de tip Type – în cazul
nostru, ştiind că obiectele sunt de tipul Serviciu, putem folosi :

typeof(Serviciu)

o StreamWrite este clasa care realizează scrierea datelor în fişier

4. Modificaţi clasa Program pentru a testa serializarea unui obiect Serviciu individual

5. Pentru a realiza deserializarea unui serviciu se poate utiliza metoda statică:

public Serviciu? loadFromXML(string fileName)


{
XmlSerializer xs = new XmlSerializer(typeof(Serviciu));
FileStream fs = new FileStream(fileName + ".xml",
FileMode.Open);
XmlReader reader = new XmlTextReader(fs);
//deserializare cu crearea de obiect => constructor fara param
Serviciu? serviciu = (Serviciu?)xs.Deserialize(reader);
fs.Close();
return serviciu;
}
6. Inspectaţi conţinutul fisierul .xml în care aţi serializat obiectul Serviciu şi precum şi
noul obiect Serviciu creat prin deserializare.

Particularizaţi salvarea fisierului XML

7. Salvaţi fişierul .xml în care s-a serializat obiectul Serviciu sub un alt nume.
8. Adăugaţi namespace-urile folosite pentru serializare la clasa ProdusAbstract.
9. Adăugaţi atributul XmlRoot la clasa Serviciu astfel:
[XmlRoot("ServiciuParticularizat")]
public class Serviciu : ProdusAbstract, IPackageble

10. Adăugaţi atribute de serializare pentru fiecare câmp:


[XmlElement("ID")]
public long Id

[XmlElement("Numele")]
public String Nume

[XmlElement("CodulIntern")]
public String CodIntern

11. Inspectaţi fişierul rezultat în urma rulării programului. Comparaţi fişierul obţinut cu cel
generat înainte de a folosi atributele de serializare.

Serializaţi/deserializaţi XML obiectele Produs şi Serviciu din lista de


elemente inclusă în managerul ProduseAbstractMgr

12. Adăugaţi namespace-urile folosite pentru serializare la clasa ProduseAbstractMgr


13. Copiaţi funcţiile save2XML şi loadFromXML în clasa ProduseAbstractMgr
14. Modificaţi funcţiile save2XML şi loadFromXML pentru ca XmlSerializer să ştie
că urmează să serializeze o listă de elemente cu obiecte de tip Serviciu şi Produs:
Type[] prodAbstractTypes = new Type[2];
prodAbstractTypes[0] = typeof(Serviciu);
prodAbstractTypes[1] = typeof(Produs);
XmlSerializer xs = new
XmlSerializer(typeof(List<ProdusAbstract>), prodAbstractTypes);

15. Realizaţi acelaşi tip de modificări şi în funcţia loadFromXML.


16. Utilizaţi funcţiile implementate astfel încât să serializaţi/deserializaţi lista elemente
din inclusă în managerul ProduseAbstractMgr în care aţi introdus obiecte de tip
Produs şi Serviciu
17. Inspectaţi rezutatul serializării/deserializării.
Temă:

1. Creaţi în cadrul aplicaţiei implementate un meniu pentru a permite utilizatorului să


salveze/încarce lista elemente într-un/dintr-un fişier XML specificat, prin
serializare/deserializare, cu tratarea corespunzătoare a excepţiilor (de exemplu,
imposibilitate de citire sau scriere din/în fişier). Afişaţi conţinutul fişierului XML.

2. Utilizați exemplificările precedente pentru a adapta aplicaţia realizată astfel încât aceasta să
serializeze/deserializeze o listă de obiecte Pachet, aşa cum a fost ea dezvoltată în din
cadrul Laboratorului nr. 6 (în cadrul cǎruia au fost utilizatǎ structura elemente de tip
List<T> pentru memorarea elementelor dintr-un pachet şi al pachetelor). Inspectați
fişierul generat în procesul de serializare.

3. Opţional – încercaţi, pe baza exemplificărilor realizate în cadrul cursului, aceleaşi variante


de implementare dar utilizând serializarea JSON.
Lucrarea de laborator nr. 8

Structura aplicaţiilor Windows Presentation Foundation (WPF)

Tehnologia Windows Presentation Foundation permite crearea de aplicaţii de tipul fat client,
caracterizate printr-o interfaţă grafică bogată (rich) care are la bază pe o serie de controale
cărora li se poate asocia un comportament specific ca răspuns la interacţiunea cu utilizatorul,
şi care se găsesc ȋn spaţii de nume precum System.Windows respectiv
System.Windows.Controls. Se bazează pe XAML care este un limbaj de marcare,
declarativ, şi care oferă o modalitate de a separa logica interfeţei UI de funcţionalitatea
aplicaţiei permiţand separarea interfeţei de implementarea din spate. XAML provine de la
eXtensible Markup Language și este un limbaj bazat pe XML care poate fi folosit pentru a
crea și inițializa obiecte .NET. XAML este un limbaj independent care poate fi folosit în
multe domenii diferite, dar este utilizat în principal în crearea interfețelor cu utilizatorul WPF
sau UWP. XAML se mapează la obiectele .NET și poate fi chiar creat cu diverse instrumente,
cel mai popular fiind Blend pentru Visual Studio.

Figura 1: Arhitectura generală WPF

Cadrul WPF este construit pentru a profita de accelerarea hardware a plăcilor grafice moderne
(rezultand performanţă), permițând randarea mai rapidă a interfeței şi făcând-o scalabilă,
independentă de rezoluție și compatibilă cu motorul de randare vectorială; se bazează pe
DirectX pentru redarea elementelor UI, care este foarte performant și este scalabil. In plus,
WPF introduce un nou mecanism de rutare a evenimentelor, în comparație cu WinForms,
numit RoutedEvents, ca modalitate traversare a arborelui vizual de către evenimente.

WPF este bazat pe o arhitectură multistrat (multi-layer), concepută pentru a fi mai sigură
(Figura 1). În partea de sus a stivei sunt plasate serviciile managed de nivel superior scrise în
limbajul C#. În al doilea rând, în stivă se află stratul de integrare media (MIL) – cod
unmanaged. Ultimul strat este reprezentat de API-urile de bază ale sistemului de operare care
ajută la gestionarea procesului aplicației WPF. Ierarhia de clase de bază ale tipurilor WPF este
următoarea:

Figura 2 : Ierarhia de clase de bază WPF

DispatcherObject - aplicația WPF folosește modelul Single-Thread Affinity (STA) și, prin
urmare, fiecare element UI este deținut de un singur thread. Dacă alte thread-uri doresc să
acceseze DispatcherObject, atunci trebuie să apeleze serviciile Invoke sau BeginInvoke
furnizate de Dispatcher cu DispatcherObject asociat. Clasa face parte din spaţiul de nume
System.Threading.

DependencyObject - WPF a introdus un sistem de proprietăți nou și puternic numit


Dependency Property, având caracteristici precum notificarea modificărilor, support pentru
legarea la date (data binding), proprietăți atașate etc. DependencyObject reprezintă obiectul
care este utilizat în acest sens. Clasa face parte din spaţiul de nume System.Windows.

Visual - este o altă clasă care oferă suport pentru randarea în WPF. Toate controalele interfeței
cu utilizatorul precum Button, ListBox derivă din această clasă. Clasa Visual definește toate
proprietățile necesare pentru randare, tăiere, transformare, delimitare, etc. Clasa ajută la
construirea arborelui vizual care conține valorile proprietăților de redare ale elementelor
grafice. Clasa face parte din spaţiul de nume System.Windows.Media.

UIElement - este clasa care adaugă funcţionalităţi de bază pentru layout, input, focus,
evenimente asociate elementelor UI, etc. Clasa oferă, de asemenea, servicii precum legarea
datelor (data binding), posibilitate de a realiza animații, etc. Clasa face parte din spaţiul de
nume System.Windows.
FrameworkElement - clasa extinde funcţionalitatea oferită de UIElement şi suprascrie layout-
ul pentru implementări la nivel de framework. Clasa face parte din spaţiul de nume
System.Windows.

Shapes - este clasă de bază pentru elemente precum Line, Ellipse, Polygon, Path, etc. Clasa
face parte din spaţiul de nume System.Windows.Shapes.

Controls - acest spațiu de nume conține toate elementele care ajută la interacțiunea cu
utilizatorul. Puține controale, precum Textbox, Button, Listbox, Menu, suport pentru font,
culoarea de fundal și aspect etc. sunt prezente în acest spațiu de nume.

ContentControl - este clasă de bază pentru toate controalele care acceptă conținut singular.
Controale de tip Label, Button, Windows etc., toate acceptă numai conținut singular. Clasa
face parte din spaţiul de nume System.Windows.Controls.

ItemsControl - este clasă de bază pentru toate controalele care includ conţinut multiplu, adică
o listă de item-uri, cum ar fi controale de tipul ListBox, TreeView, Menus, Toolbar, etc. Clasa
face parte din spaţiul de nume System.Windows.Controls.

Panel - este clasă de bază pentru toate elementele de tip container, cum ar fi controale de tip
Grid, Canvas, DockPanel, StackPanel, WrapPanel, etc. Clasa face parte din spaţiul de nume
System.Windows.Controls.

Visual Studio generează un schelet de aplicaţie WPF ȋn momentul ȋn care se selectează un


proiect de tipul WPF Application. MainWindow-ul reprezintă elementul central al aplicaţiilor
WPF, reprezentȃnd suprafaţa vizuală prin intermediul căreia se prezintă informaţiile către
utilizator. La crearea unui proiect WPF Application se crează o fereastră goală, care nu face
nimic, dar ȋn spatele căreia s-a generat ȋntrega structură necesară aplicaţiei (Figura 3).

Figura 3 – Crearea unei aplicaţii WPF


Utilizȃnd Solution Explorer se pot observa fişierele generate la crearea aplicaţiei:
MainWindow.xaml respectiv MainWindow.xaml.cs. Cele două fişiere generate,
MainWindow.xaml respectiv MainWindow.xaml.cs nu fac altceva decȃt să separe codul
aferent părţii de logică a aplicaţiei de interfaţa grafică, generată cu ajutorul designer-ului
grafic (design). Ambele fişiere, de fapt, conţin părţi din aceeaşi clasă: clasa MainWindow
derivată din Window, ȋn acest context făcȃndu-se uz de facilitatea de a putea defini clase
parţiale.

Fişierul MainWindow.xaml descrie, utilizand XAML, interfaţa grafică a aplicaţiei. Iniţial,


structura creată reflectă aspectul ferestrei principale a aplicaţiei:

<Window x:Class="WpfAppEx.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppEx"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>

</Grid>
</Window>

Fişierul MainWindow.xaml.cs conţine (parţial) implementarea clasei MainWindow:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfAppEx
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}

Astfel, MainWindow.xaml.cs conţine iniţial doar constructorul clasei care apelează metoda
InitializeComponent, ȋn cadrul căreia se vor iniţializa toate controalele care vor fi
adăugate ȋn cadrul ferestrei MainWindow prin intermediul designer-ului grafic.
Exerciţiu:

Creaţi o aplicaţie Windows Presentation Foundation care să trateze în mod specific


evenimentul Loaded pentru o fereastră definită de utilizator. Aplicaţia va afişa un mesaj prin
care să indice faptul că fereastra a fost încărcată.

Creaţi o aplicaţie Windows Presentation Foundation (WPF) application

1. Selectaţi opţiunea Create a new project şi selectaţi tipul de proiect WPF Application (din
categoria de aplicaţii Desktop) pentru a crea un proiect .NET WPF Application. Setaţi
numele proiectului wpfapp1, locaţia acestuia, după care apasaţi OK.

În urma acestor acţiuni se va genera soluţia wpfapp1 şi proiectul wpfapp1 care va conţine
o fereastră MainWindow. Rulaţi programul şi comentaţi rezultatul.

Inspectaţi codul sursă

2. Deschideţi fereastra MainWindow.xaml.cs şi vizualizaţi codul sursă care s-a creat, aferent
ferestrei MainWindow. Rezultatul este crearea a 2 fişiere :
 MainWindow.xaml.cs => funcţionalitate
 MainWindow.xaml => aspect

3. Daţi click dreapta pe fereastră în designer-ul grafic şi selectaţi opţiunea Properties; daţi un
nume obiectului aferent ferestrei principale (de exemplu, mainWindow) şi schimbaţi titlul
afişat în fereastra MainWindow la Fereastra principală (opţiunea Common/Title).
Observaţi cum schimbările realizate prin modificarea proprietaţilor se reflectă în fişierul
MainWindow.xaml

Creaţi o nouă fereastră

4. În fereastra Solution Explorer apăsaţi click dreapta pe proiectul wapp1 , apoi Add //
Window (WPF) pentru a deschide fereastra Add New Item.

5. Selectaţi macheta Window (WPF), setaţi numele ferestrei MyWindow, după care apăsaţi
Add. Rezultatul este crearea a 2 fişiere :
 MyWindow.xaml.cs
 MyWindow.xaml
Daţi un nume obiectului aferent noii ferestre create (de exemplu myWindow şi modificaţi titlul
ferestrei la Fereastra mea (opţiuni la Properties).

Adăugaţi un buton care afişează noua fereastră

6. Deschideţi fereastra Toolbox folosind meniul View//Toolbox.

7. Din grupul de unelete All WPF Controls, folosind drag/drop adăugaţi pe fereastra
MainWindow un buton; modificaţi proprietăţile obiectului buton şi daţi un nume
obiectului aferent butonului (butMyWindow) şi afişaţi un text pe buton, de exemplu
Apasa! (proprietatea Content).
8. Apasaţi dublu click pe butonul adăugat pe fereastra MainWindow şi modificaţi codul astfel
încât acesta să arate în forma următoare:

private void butMyWindow_Click(object sender, RoutedEventArgs e)


{
MyWindow newWindow = new MyWindow();
newWindow.Show();

9. Rulaţi program şi apăsaţi butonul de mai multe ori. Ce observaţi?

10. Modificaţi apelul Show() în ShowDialog()

11. Rulaţi program şi apăsaţi butonul de mai multe ori. Comentaţi rezultatul.

Modificaţi proprietăţile ferestrei MyWindow la compilare (design time)

12. Selectaţi MyWindow.xaml din fereastra Solution Explorer

13. Deschideţi fereastra Properties (click dreapta)

14. Modificaţi fiecare proprietate pe rând; dupa fiecare modificare rulând aplicaţia

15. Rulaţi programul şi comentaţi rezultatul.

Modificaţi proprietăţile ferestrei MyForm la execuţie (run time)

16. Deschideţi sursa ferestrei MainWindow.xaml.cs pentru a modifica funcţia


butMyWindow_Click

17. Modificaţi titlul ferestrei printr-o atribure folosind obiectul newWindow, de exemplu:
newWindow.Title = "Titlul ferestrei schimbat la executie!";

18. Rulaţi program şi comentaţi rezultatul.

Adăugaţi handler pentru evenimente la compilare (design time)

19. În fereastra Properties a ferestrei MyWindow apăsaţi icoana Events

20. Apasaţi dublu click în dreapta evenimentului Loaded

21. În corpul metodei handler generate adăugaţi:


MessageBox.Show("S-a incarcat fereastra MyWindow!");

22. Inspectaţi modificarile survenite în fişierul MyWindow.xaml.cs . Rulaţi programul.


Adăugaţi handler pentru evenimente la execuţie (run time)

23. Deschideţi sursa ferestrei MainWindow pentru a modifica funcţia butMyWindow_Click

24. Adăugaţi un handler pentru evenimentul Unloaded folosind următoarea secvenţă de cod:
newWindow.Unloaded += new RoutedEventHandler(myWindow_Unloaded);

şi adăugaţi metoda handler astfel:

private void myWindow_Unloaded(object sender, RoutedEventArgs e)


{
MessageBox.Show("Fereastra MyWindow a fost inchisa!");
}

25. Rulaţi programul şi comentaţi rezultatul.

Temă:

1. Studiaţi structura aplicaţiilor WPF precum şi controalele WPF. Folosind cunoştinţele


acumulate adăugaţi şi alte controale pe ferestrele realizate şi încercaţi să realizaţi diverse
operaţii precum modificarea proprietăţilor acestora precum şi adăugarea de handler-e de
evenimente, atat la compilare (design time) cat şi la execuţie (run time).

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