Sunteți pe pagina 1din 0

Dezvoltare aplicatii folosind platforma .

NET - 1/233
Introducere

Scopul acestui curs este de a explica cum se dezvolt aplicaii pentru platforma
.Net. Pentru a nelege dezvoltarea aplicaiilor sub .Net, mai nti este necesar s
nelegem arhitectura cadrului de lucru .Net. In toate exemplele din acest curs se va folosi
limbajul C#.
Cursul se adreseaz programatorilor interesai in a folosi tehnologiile .Net pentru
a construi aplicaii distribuite. Presupun ca cititorul are cunotine despre programarea
orientat pe obiecte, i c este dispus s investeasc destul timp pentru a construi
exemplele din acest curs, pentru a citi atent documentaia din MSDN i alte articole
colaterale i a nu cuta s utilizeze numai tehnica copy/paste n rezolvarea exerciiilor.

Arhitectura .Net

Cadrul de lucru .Net (.Net Framework) este compus din CLR si FCL. Voi explica
modul cum lucreaza CLR (Common Language Runtime) i anumite pri din FCL
(Framework Class Library), biblioteca FCL coninnd foarte multe tipuri. Unul din
scopurile principale cnd s-a dezvoltat aceast nou platform i tehnologie a fost acela
legat de interoperabilitate. Interoperabilitatea ntre limbaje si platforme constituie un
dezavantaj major al dezvoltrii software-ului. Pn la .NET, existau cel puin dou
soluii: Java i COM/DCOM, fiecare cu avantaje i dezavantaje.
Solutia Java presupune un limbaj (Java) i o platform (Java). Mai mult platforma
Java a fost portat pe mai multe sisteme de operare, deci scrii odat i rulezi oriunde.
Dezavantajul major l constituie faptul c trebuie s scrii componentele numai in Java.
Soluia COM definete un standard binar, suportnd limbajele ce au aderat la
acest standard binar. Componentele COM pot comunica ntre ele chiar dac au fost scrise
n limbaje diferite. Dezavantajul e ca aceste componente ruleaz numai sub Windows. Un
alt dezavantaj este acela c scrierea unor astfel de componente este foarte complex.

Voi ncepe cu un exemplu simplu. Ce reprezint urmtorul ir de bii (vezi
Distributed .NET Programming in C# by Tom Barnaby )

0000 0000 0000 0000 0000 0000 0100 1101
Exist mai multe rspunsuri i toate sunt corecte. Dac rspundei 77, este corect. Dac
rspundei M, iari este corect. Dac rspundei 1.079e-403, iari este corect. V-ai
dat seama deja ce se ntmpl. O niruire de bii fr a specifica tipul poate s aib
diverse semnificaii. O aplicaie are nevoie de data (ca valoare) i de tip pentru a
interpreta corect data. Limbajele i platformele difer n implementarea tipurilor de date
i n suportul pe care l ofer pentru interoperabilitate.
Privitor la interoperabiltate, .NET ia ceea ce e mai bun din Java i din COM. In timp ce
COM definete un standard binar, .NET definete un tip standard numit CTS (Common
System Type). Toate limbajle ce lucreaz sub .NET vor mapa aceste tipuri. In acest mod
se pot folosi tipuri definite n diverse limbaje (de exemplu VB .NET).
Dezvoltare aplicatii folosind platforma .NET - 2/233
Intre limbajele de programare exist i alte diferene. Unele limbaje suport motenirea
multipl, altele nu, unele suport suprancrcarea operatorilor, altele nu, unele suport
tipurile fr semn, altele nu (etc.). Toate aceste lucruri creaz probleme n
interoperabilitate i din acest motiv (nu numai) .NET definete Common Language
Specification (CLS), ce conine trsturile minimale pe care un limbaj ce adre la .NET
trebuie s le suporte dac dorete s coopereze cu alte limbaje .NET, altfel spus toate
limbajele ce adre la .NET trebuie s implementeze aceste tipuri din CLS, pentru a
beneficia de interoperabiltate. Bine-neles c pot implementa i tipuri din CTS.
CLS este un subset al CTS.
Exemplu:
public class CursIntroductiv
{
// tipul uint (unsigned integer) nu este definit in CLS
// (is non-CLS compliant).
// Deoarece este declarat private, regulile din CLS nu se aplica.
// Aceasta data membru nu poate fi folosita in exteriorul clasei.

private uint A = 4;

// Este declarat public, dar acest tip nu poate fi folosit
// de catre toate limbajele ce adera la .NET (is non-CLS compliant).
public uint B = 5;

// Tipul long respecta regulile din CLS (is CLS compliant).
public long GetA()
{
return A;
}
}

CLR Common Language Runtime

Putem spune c CLR este o implementare a CTS. CLR este responsabil pentru ncrcarea
i execuia aplicaiilor. Codul ce se execut sub supravegherea CLR se numete cod
managed.
CLR proceseaza un cod intermediar MSIL.

Etapele pentru obinerea unui assembly sunt:
cod sursa -> compilare -> modul managed (PE) fisier executabil portabil executat de
CLR.
CLR folosete spaiile de nume namespace pentru a organiza din punct de vedere
logic tipurile. Calificarea tipurilor se va face folosind namespace-ul unde este definit.

Structura pentru PE:
PE header indic tipul de fiier (GUI, CUI, DLL), are timestamp, informaii
despre codul nativ CPU (dac exist un asemenea cod).
Dezvoltare aplicatii folosind platforma .NET - 3/233
CLR header versiunea necesar de CLR pentru a fi executat, punctul de intrare
WinMain, locul i mrimea metadatei, resurselor, strong name i flag-uri.
Metadata tabele cu metadata: tipurile i membrii definii i tipurile i membrii
referii. Metadata este un superset al vechilor tehnologii: biblioteci de tipuri i
limbaje de descriere a interfeelor. Metadata este folosit pentru:
o serializarea n memorie a cmpurilor unui obiect;
o transmiterea acestor informatii pe alta masina si apoi deserializarea
informatiilor;
o furnizeaza informatii catre Garbage Collector pentru a determina timpul
de viata al obiectelor, si tot de aici GC determina ce campuri apartin altor
obiecte;
o IntelliSense foloseste metadata pentru a implementa auto completion;
o nltur necesitatea existentei fiierelor antet (header) i a bibliotecilor de
tip. CLR extrage informaiile necesare folosind metadata.

IL (Intermediate Language code) cod produs de compilator. Acest cod intermediar
este transpus n cod procesor de catre JIT compiler (Just In Time compiler) i executat.

Assemblies CLR lucreaza cu assemblies. Assemblies pot fi privii ca o grupare logica
de module si/sau resurse, fiind cea mai mica unitate de reutilizare, securitate si
versionare.

Pentru a determina daca .NET Framework este instalat pe o masina, va trebui sa
verificam existenta fisierului MSCorrEE.dll in subdirectorul system32 al directorului
unde este instalat sistemul de operare (%windir%\system32).
Pe o aceeai main pot fi instalate versiuni diferite de .NET
Verificarea versiunii se face citind cheia din regitri

HKLM\Software\Microsoft\.NetFramework\policy

Manifest coninut n PE, bloc de tabele cu metadata. Descriu fiierele ce construiesc un
assembly, tipuri publice exportate, implementate n aceste fiiere, resurse sau fiiere de
date asociate cu assembly.

CLR ofer posibilitatea de a executa aplicaii (managed) multiple ntr-un singur proces
(spaiu de adrese). Fiecare aplicaie managed este numit AppDomain.

Modul cum este ncrcat CLR

Managed EXE contine
PE header
.text section
.idata section
CLR Header
IL
Metadata
Dezvoltare aplicatii folosind platforma .NET - 4/233

Cand compilatorul/linker-ul creaza fisierul executabil, urmatorul cod este creat in
sectiunea .text

JMP _CorExeMain

Pentru ca functia _CorExeMain este importata din MSCorEE.dll (Microsoft
Component Object Runtime Execution Engine), aceasta biblioteca este referita in
sectiunea .idata din header PE.

Cand se invoca un fiser exe (dll), sistemul de operare l trateaza in mod obisnuit. Se
incarca fisierul in memorie si se verifica in sectiunea .idata daca se face referinta la
MSCorrEE.dll. In caz afirmativ se incarca acest fisier (dll) in memorie in spatiul de
adrese al procesului.
Se obtine adresa functiei _CorExeMain, adresa ce va fi plasata in locul lui _CorExeMain
din instructiunea JMP.

Firul primar al procesului isi incepe executia de la aceasta instructiune:
JMP _CorExeMain
_CorExeMain initializeaza CLR si apoi cauta punctul de intrare in program. Codul pentru
aceasta metoda este compilat in cod nativ CPU si apoi executat.

Acelasi algoritm se aplica si pentru DLL, cu deosebirea ca instructiunea de salt arata
astfel:

JMP _CorDllMain

Functia _CorDllMain este de asemenea importata din MSCorEE.dll.

Acesta metoda de incarcare este specifica pentru SO W98, WNT, W2000. Pentru WXP si
WXP .Net Server Family loader-ul a fost modificat.
Se cauta direct in intrarea de index 14 din Header PE si se vede daca este completata
aceasta (IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR din winnt.h).

Executia codului se realizeaza astfel (model foarte simplist):
CLR determina ce functie trebuie executata, si apeleaza JIT-erul. Acesta folosind
metadata si tabelele interne ale CLR-ului determina daca acest cod a mai fost compilat;
daca da, atunci stie de unde sa-l incarce in memorie si va schimba adresa de executie a
functiei (acest lucru este facut de CLR, deci nu mai este necesar un apel la JIT-er); in caz
contrar se aloca un bloc de memorie, se compileaza codul IL si apoi se completeaza
adresa functiei cu adresa blocului de memorie alocat.
Observatie

Daca dorim ca JIT-erul sa nu fie apelat, atunci putem folosi utilitarul NGEN.EXE ce va
genera cod specific procesorului. Cand CLR va fi incarcat pentru aceasta aplicatie, acesta
ve cauta sa vada daca exista cod precompilat, si in caz afirmativ, il va executa.
Dezvoltare aplicatii folosind platforma .NET - 5/233

IL i verificarea

IL este stack-based, asta nseamn c toate instruciunile sale pun operanzii pe stiv i
scot rezultatul din stiv. Nu exist instruciuni de lucru cu regitri.

Instruciunile IL sunt fr tip.
De exemplu IL ofer instruciunea add, ce adun doi operanzi pui pe stiv (nu exist add
pentru ntregi, float, etc.). Cnd add se execut, se determin tipul operanzilor i apoi se
execut instruciunea specific (la nivel de CPU).
Cnd se compileaz IL n cod nativ, CLR execut un proces numit verificare.
Verificarea nseamn examinarea codului IL de nivel nalt i se asigur c orice face este
sigur (safe).
De exemplu, se verifica c nu se citeste dintr-o locaie de memorie fr ca mai nainte s
nu fi existat un proces de scriere n acea locaie.
De asemenea se verific c fiecare metoda este apelat cu numrul corect de parametri, i
c tipurile acestor parametri sunt conform prototipului declarat al funciei.
Dac un anumit cod este nesigur (unsafe) n urma verificrii, atunci se emite
excepia:
System.Security.VerifierException, si metoda nu este executata.

Implicit, C# produce cod safe.

Folosind cuvantul cheie unsafe se poate genera cod care nu este safe din punctul de
vedere al verificarii, dei acesta este un cod sigur.

Pentru a ne asigura c toate metodele produc cod safe, putem folosi utilitarul
PEVerify.exe. Daca acest utilitar detecteaz cod unsafe atunci este un bug n
compilatorul de C#.

.NET Framework Class Library (FCL)

CLR si FCL permit dezvoltarea urmatoarelor tipuri de aplicaii:
XML Web services servicii web;
Web Forms aplicatii bazate pe HTML;
Windows Forms aplicaii windows (GUI), cu interfa grafic;
Windows Console Applications aplicaii tip consol (CUI);
Windows services aplicaii controlate de Windows Service Control Manager folosind
.NET.
Component library componente dezvoltate de utilizator ce pot fi folosite n alte
aplicaii sau componente.

Spatii de nume namespace

Dezvoltare aplicatii folosind platforma .NET - 6/233
Tipurile continute in FCL sunt grupate n spaii de nume (namespace).
Dezvoltatorul i poate crea propriul spaiu de nume pentru a pstra tipurile definite de
acesta.

Exemple de spatii de nume
Spatiu de nume Descrierea continutului
System Toate tipurile de baza folosite de orice aplicatie
System.Collections Tipurile pentru gestionarea colectiilor de obiecte:
stive, cozi, tabele hash, etc.
System.Diagnostics Tipuri ce ajuta la depanarea aplicatiei
System.Drawing Tipuri pentru manipularea graficelor 2-D; folosite in
aplicatiile GUI si pentru crearea imaginilor ce apar in
paginile Web
System.EnterpriseServices Tipuri pentru gestionarea tranzactiilor, activare JIT,
securitate, folosirea codului managed in mod eficient
pe server
System.Globalization Tipuri pentru NLS (National Language Support)
System.IO Tipuri pentru gestionarea fisierelor, directoarelor,
operatii I/O pe stream-uri.
System.Reflection Tipuri pentru inspectarea metadatei, legarea tarzie la
tipuri si membrii acestora
System.Runtime.InteropServices Tipuri ce permit interoperabilitatea (apelare COM,
functii Win32)


Common Type System

Tipurile reprezinta mecanismul prin care codul scris intr-un limbaj de programare poate
comunica cu codul scris in alt limbaj de programare.
Microsoft a creat o specificatie formala, Common Type System, ce descrie modul cum
sunt definite tipurile si cum trebuiesc folosite.
Specificatia CTS spune ca un tip poate contine zero sau mai multi membri.

Pe scurt, un tip poate conine:
field au nume i tip;
method execut operaii asupra obiectului. Au nume, semntur i modificatori.
Semntura specific convenia de apel, numrul parametrilor, tipurile parametrilor i tipul
valorii returnate.
property din punct de vedere al utilizatorului apare ca un field, din punct de vedere al
implementatorului este o metoda (sau doua). Proprietile ne permit s crem fields read-
only, write-only sau read-write.
event permite un mecanism de notificare ntre obiect i alte obiecte interesate.

CTS specific regulile de vizibilitate i de acces pentru membrii tipului.
CTS C#
Dezvoltare aplicatii folosind platforma .NET - 7/233
private private
familly protected
familly and assembly C# nu implementeaz, IL il
implementeaza
assembly internal
familly or assembly protected internal
public public

CTS definete regulile ce guverneaz motenirea, timpul de via al obiectelor, apelul
funciilor virtuale, etc.

CTS suporta mostenirea simpla.

Toate tipurile trebuie sa fie derivate din Object (direct sau indirect).

Metodele din Object sunt:
Metodele publice ale clasei System.Object
Nume metoda Descriere
bool Equals
Compara referintele a doua obiecte in timpul executiei pentru a
determina daca reprezinta acelasi obiect. Daca cele doua variabile se
refera la acelasi obiect se returneaza TRUE. Cu tipul valoare, aceasta
metoda returneaza TRUE daca cele doua obiecte sunt identice si au
aceeasi valoare.
int GetHashCode
Regaseste codul hash specificat pentru un obiect. Functiile hash sunt
folosite cand implementatorul clasei doreste sa puna codul hash al
obiectului intr-o tabela hash din motive de performanta.
Type GetType
Folosita in metodele de reflectie pentru a regasi informatia de tip
pentru un obiect dat.
string ToString
Utilizata implicit pentru a regasi numele obiectului. Aceasta metoda
poate fi suprascrisa in clasa derivata pentru a returna un sir de
caractere ce descrie mai pe inteles obiectul.
void Finalize
Apelata in momentul executiei pentru a permite stergerea obiectului
inainte de a incepe procesul de garbage collection . Aceasta
metoda poate fi sau nu poate fi apelata. Din aceasta cauza nu trebuie
pus cod in aceasta metoda care trebuie sa fie executat.
Object
MemberwiseClone
Reprezinta o copie a obiectului, copie ce contine referinte la alte
obiecte dar nu includ copiile obiectelor referentiate. Daca dorim o
copie completa a obiectlui, care sa contine si obiectele referentiate,
trebuie sa implementam interfata IClonable si sa facem manual
aceasta clonare ori sa-l copiem noi insine in intregime..

La declararea unei clase, tipul System este implicit.
Urmatoarele doua declaratii sunt identice:

class Tip : System{}

Dezvoltare aplicatii folosind platforma .NET - 8/233
class Tip {}

Exemplu de tip.

class Curs
{
// fields
private string shortName;
public string Name;
// methods
public int GetStudents()
{
return 10;
}
// property
public string ShortName
{
get { return shortName;}
set { this.shortName = value; }
}
}

CLS Common Language Specification

Specifica setul minimal de tipuri pe care un compilator ar trebui sa le trateze pentru a
putea genera o aplicatie ce poate fi incarcata si executata de CLR.
Acest set minimal de tipuri trebuie sa fie implementat de orice compilator pentru a putea
expune tipuri create intr-un limbaj si folosite in alt limbaj.
COM-ul asigura interoperabilitate la nivel binar, CLR permite ca obiecte create intr-un
limbaj sa poata fi folosite in alt limbaj. Pentru a asigura aceasta comunicare, trebuie
respectat acest standard impus de CLS.

Integrarea este asigurata de metadata si common execution environment (mediul de
executie comun).

Exemplu

using System;
[assembly:CLSCompliant(true)]
public class App
{
public UInt32 Metoda()
{
return 1;
}
private UInt32 METODA()
{
return 1;
}
}

Dezvoltare aplicatii folosind platforma .NET - 9/233
Aici apare o eroare pentru ca tipul UInt32 nu este in CLS.
De asemenea nu se face distinctie intre Metoda si METODA nu este case senzitiv.

In C putem scrie de exemplu urmatorul cod valid:

#include <stdio.h>
#using <mscorlib.dll>

using namespace System;

void main()
{
printf(printf din biblioteca C);
Console::WriteLine(Console::WriteLine din FCL);
}

Ambele functii vor tipari textul propus:
printf din biblioteca C
Console::WriteLine din FCL

Interoperabilitate cu cod unmanaged

cod managed poate apela o functie unmanaged din DLL;
cod managed paote utiliza componente COM;
cod unmanaged poate utiliza tip managed;
Constructie, impachetare si distribuire (deploying) aplicatie

Metadata este un bloc de date binare ce consta din mai multe tabele.
Dintre aceste tabele enumeram: ModuleDef, TypeDef, FieldDef, MethodDef, EventDef,
ParamDef, PropertyDef, AssemblyRef, ModuleRef, TypeRef.

ModuleDef contine o intrare ce identifica modulul. Intrarea include numele fisierului
modulului si extensia (fara cale (path)) si un ID de versiune al modulului (sub forma
unui GUID creat de compilator).

TypeDef contine o intrare pentru fiecare tip definit in modul. Fiecare intrare include
numele tipului, tipul de baza, modificatori de acces si legaturi la tabela MethodDef,
campuri din tabela FieldDef , proprietati din tabela PropertyDef si evenimente din tabela
EventDef.

FieldDef contine o intrare pentru fiecare camp (data membru) definita in modul.
Fiecare intrare include un nume, flag-uri (private, public, etc.) si tip.

MethodDef contine o intrare pentru fiecare metoda definita in modul. Fiecare intrare
include numele metodei, falg-uri(private, virtual, public, abstract, static, final, etc.),
semnatura si offset-ul in interiorul modulului unde poate fi gasit codul IL. De asemenea
Dezvoltare aplicatii folosind platforma .NET - 10/233
fiecare intrare poate referi o intrare in tabela ParamDef, unde se gasesc mai multe
informatii despre parametrii metodei.

EventDef contine o intrare pentru fiecare eveniment definit in modul. Fiecare
eveniment include numele si flag-uri.

ParamDef contine o intrare pentru fiecare parametru definit in modul. Fiecare intrare
include un nume si flag-uri (in, out, retval, etc.).

PropertyDef contine o intrare pentru fiecare proprietate definita in modul. Fiecare
intrare include un nume, flag-uri, tip.


Tabelele AssemblyRef, ModuleRef, TypeRef contin informatii despre assembly, module
si type referite de catre aplicatie.

Exemplu
using System;
namespace Ex1_CUI
{
/// <summary>
/// Summary description for Curs1.
/// </summary>
class Curs1
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Console.WriteLine("S-a construit tipul Curs1 derivat
din System.Object");
Console.Read();
}
}
}

Am construit tipul Curs1, ce are o metoda Main si foloseste metode din System.
Daca examinam codul IL cu ILDasm vom obtine urmatoarele:

.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
// .z\V.4..
.ver 1:0:5000:0
}
.assembly Ex1_CUI
{
.custom instance void
[mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) =
( 01 00 00 00 00 )
Dezvoltare aplicatii folosind platforma .NET - 11/233
.custom instance void
[mscorlib]System.Reflection.AssemblyKeyFileAttribute::.ctor(string) = (
01 00 00 00 00 )
.custom instance void
[mscorlib]System.Reflection.AssemblyDelaySignAttribute::.ctor(bool) = (
01 00 00 00 00 )
.custom instance void
[mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) =
( 01 00 00 00 00 )
.custom instance void
[mscorlib]System.Reflection.AssemblyKeyNameAttribute::.ctor(string) = (
01 00 00 00 00 )
.custom instance void
[mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = (
01 00 00 00 00 )
.custom instance void
[mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = (
01 00 00 00 00 )
.custom instance void
[mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(strin
g) = ( 01 00 00 00 00 )
.custom instance void
[mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string)
= ( 01 00 00 00 00 )
.custom instance void
[mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = (
01 00 00 00 00 )
// --- The following custom attribute is added automatically, do not
uncomment -------
// .custom instance void
[mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(bool,
//
bool) = ( 01 00 01 01 00 00 )
.hash algorithm 0x00008004
.ver 1:0:2816:28798
}
.module Ex1_CUI.exe
// MVID: {8D95C568-423B-4ACB-B69C-F0672127ECF9}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 4096
.corflags 0x00000001
// Image base: 0x03210000
//
// ============== CLASS STRUCTURE DECLARATION ==================
//
.namespace Ex1_CUI
{
.class private auto ansi beforefieldinit Curs1
extends [mscorlib]System.Object
{
} // end of class Curs1

} // end of namespace Ex1_CUI

// =============== GLOBAL FIELDS AND METHODS ===================
// =============== CLASS MEMBERS DECLARATION
Dezvoltare aplicatii folosind platforma .NET - 12/233
// note that class flags, 'extends' and 'implements' clauses
// are provided here for information only

.namespace Ex1_CUI
{
.class private auto ansi beforefieldinit Curs1
extends [mscorlib]System.Object
{
.method private hidebysig static void
Main(string[] args) cil managed
{
.entrypoint
.custom instance void
[mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 17 (0x11)
.maxstack 1
IL_0000: ldstr "S-a construit tipul Curs1 derivat din
System.Object"
IL_0005: call void
[mscorlib]System.Console::WriteLine(string)
IL_000a: call int32 [mscorlib]System.Console::Read()
IL_000f: pop
IL_0010: ret
} // end of method Curs1::Main

.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 1
IL_0000: ldarg.0
IL_0001: call instance void
[mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Curs1::.ctor

} // end of class Curs1
// =============================================================
} // end of namespace Ex1_CUI

//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file
D:\Exemple\Ex1_CUI\bin\Debug\dump.res

Statistica pentru acest exemplu (furnizata tot de ILDasm) este:
File size : 16384
PE header size : 4096 (496 used) (25.00%)
PE additional info : 2271 (13.86%)
Num.of PE sections : 3
CLR header size : 72 ( 0.44%)
CLR meta-data size : 1232 ( 7.52%)
CLR additional info : 0 ( 0.00%)
CLR method headers : 24 ( 0.15%)
Managed code : 24 ( 0.15%)
Dezvoltare aplicatii folosind platforma .NET - 13/233
Data : 2148 (13.11%)
Unaccounted : 6517 (39.78%)

Num.of PE sections : 3
.text - 4096
.rsrc - 4096
.reloc - 4096

CLR meta-data size : 1232
Module - 1 (10 bytes)
TypeDef - 2 (28 bytes) 0 interfaces, 0
explicit layout
TypeRef - 16 (96 bytes)
MethodDef - 2 (28 bytes) 0 abstract, 0
native, 2 bodies
MemberRef - 17 (102 bytes)
ParamDef - 1 (6 bytes)
CustomAttribute- 12 (72 bytes)
Assembly - 1 (22 bytes)
AssemblyRef - 1 (20 bytes)
Strings - 488 bytes
Blobs - 64 bytes
UserStrings - 108 bytes
Guids - 16 bytes
Uncategorized - 172 bytes

CLR method headers : 24
Num.of method bodies - 2
Num.of fat headers - 2
Num.of tiny headers - 0

Managed code : 24
Ave method size 12

Marimea codului managed este 24 octeti.
Exercitiu. Incercati pe acest exemplu de cod sa folositi toate optiunile furnizate ILDasm.

Combinarea modulelor pentru a forma un assembly

Ceea ce am construit in exemplul anterior este mai mult decat un fisier PE plus metadata,
este un assembly.
Un assembly este o colectie de unul sau mai multe fisiere ce contin definitii de tipuri si
fisiere resurse. Unul din fisierele assembly-ului contine un manifest. Manifestul este o
alta multime de tabele cu metadata ce contin in mod normal fisierele ce formeaza un
assembly. Manifestul descrie de asemenea versiunea assembly-ului, cultura, tipurile
publice exportate, si toate fisierele ce compun assembly.
Dezvoltare aplicatii folosind platforma .NET - 14/233

CLR lucreaza cu assemblies. CLR incarca totdeauna fisierul ce contine tabelele cu
metadata ale manifestului si apoi foloseste manifestul pentru a obtine numele altor fisiere
ce sunt in assembly. Caracteristicile cele mai importante ale aseemblies sunt:
Un assembly defineste tipurile reutilizabile.
Un assembly este marcat cu un numar de versiune.
Un assembly poate avea informatii despre securitate asociate cu el.

Putem configura o aplicatie sa descarce fisiere assembly specificand elementul codeBase
din fisierul de configurare al aplicatiei. Acest element identifica un URL unde pot fi
gasite toate fisierele ce contin assembly. Daca fisierul nu poate fi gasit, CLR arunca
exceptia FileNotFoundException la runtime.
Pentru a construi un assembly, trebuie sa selectati unul din fisierele PE ce va contine
manifestul.
Compilatorul C# produce un assembly cand se specifica unul din urmatoarele switch-uri
in linia de comanda: /t[arget]:exe, /t[arget]:winexe sau /t[arget]:library. Fisierul
rezultat va fi un executabil CUI, sau un executabil GUI sau un DLL.
In plus la aceste switch-uri, compilatorul C# suporta si /t[arget]:module. Acest switch
spune compilatorului C# sa produca un fisier PE care nu contine manifestul. Fisierul
rezultat este totdeauna un DLL, si acest fisier trebuie adaugat la assembly inainte ca
tipurile din el sa fie accesate. Cand se foloseste acest switch, compilatorul C# adauga la
fisierul de iesire extyensia .netmodule.

Exemplu.
Sa presupunem ca avem doua fisiere cu cod sursa: Modul.cs si Curs.cs. Compilam
Modul.cs astfel:

csc /t:module Modul.cs

Rezultatul va fi un fisier Modul.netmodule. Acesta este un fisier standard DLL, dar pe
care CLR nu poate sa-l incarce.

In continuare compilam Curs.cs astfel:

csc /out:IDDType.dll /t:library /addmodule:Module.netmodule Curs.cs

Ce se intampla?
Se compileaza fisierul Curs.cs si rezultatul este IDDType.dll. Pentru ca am specificat
/t:library, se va produce un fisier PE DLL ce contine manifestul (tabele cu metadata ale
manifestului). Switch-ul /addmodule:Module.netmodule spune compilatorului ca acest
fisier Module.netmodule este o parte a aseembly, adica se completeaza tabelele FileDef si
ExportedTypesDef ale manifestului.


Simplificat rezultatul poate fi citit astfel:
Module.netmodule IDDTypes.dll
Dezvoltare aplicatii folosind platforma .NET - 15/233
IL compilat din Module.cs IL compilat din Curs.cs
Metadata
Tipuri, metode, etc. definite de
Module.cs
Tipuri, metode, etc. referentiate de
Module.cs
Metadata
Tipuri, metode, etc. defiite de
Curs.cs
Tipuri, metode, etc. referentiate de
Curs.cs

Manifest (continut in metadata)
o Fisierele asemmbly
(IDDTypes.dll si
Module.netmodule)
o Tipurile publice ale asembly
(IDDTypes.dll si
Module.netmodule)

Orice client ce foloseste tipurile din assembly IDDTypes.dll trebuie construit folosind o
referinta la acesta; trebuie folosit switch-ul /r[eference]:IDDTypes.dll.
Compilatorul are nevoie ca toate fisierele ce construiesc assembly sa fie instalate si
accesibile.

Versiune

Microsoft foloseste o schema de versiune compusa din patru parti: numarul major, minor,
numarul build, numarul reviziei.
Un assembly are trei numere de versiune asociate.
AssemblyFileVersion acest numar are rol informativ. CLR nu prelucreaza acest
numar. In mod normal noi setam acest numar de versiune (ex. Versiunea 1.01) si apoi
incrementam numarul de build de fiecare data cand am facut un nou build al aplicatiei. In
mod normal compilatorul ar trebui sa actualizeze acest numar de build.
AssemblyInformationalVersionAttribute rol informativ. De exemplu aplicatia noastra
poate avea Versiunea 1.5 iar un assembly al acestei aplicatii poate avea versiunea 1.0,
pentru ca l-am adaugat la acest ultim build.
AssemblyVersion memorat in tabela AssemblyDef din manifest. CLR foloseste acest
numar de versiune cand se leaga la assembly strong name. Acest numar identifica in
mod unic un assembly. Cand incepem sa dezvoltam un assembly ar trebui sa setam
numarul major, minor, de buid si de revizie si ar trebui sa nu-l mai schimbam pana nu
trecem la o noua distribuire a aplicatiei.

Visual Studio .NET creaza un fisier AssemblyInfo.cs ce contine printre altele si
versiunea.

Cultura

Dezvoltare aplicatii folosind platforma .NET - 16/233
In general, cand cream un assembly ce contine cod (putem crea si assembly fara cod,
numai cu metadata si manifest), nu-i atribuim o anumita cultura. Un asemenea assembly
se numeste culture neutral.

Observatie
Daca se dezvolta o aplicatie ce tine seama de o anumita cultura, recomandarea este de a
se crea un assembly ce contine codul, si acesta sa fie culture neutral. Acest assembly va
fi folosit de alte assembly ce vor crea si manipula tipuri definite in el. In continuare se
creaza unul sau mai multe assembly ce nu contin cod dar au o anumita cultura specifica.
Acesti assembly sunt numiti satellite assembly.
Nu putem construi un assembly ce referentiaza un satellite assembly.

Assembly partajabili (Shared Assembly)
.NET Framework suporta doua tipuri de assemblies: assemblies cu nume slab (weakly
named assemblies) si assemblies cu nume tari (strongly named assemblies). Din punct de
vedere structural nu exista nici o diferenta intre acestia. Diferenta reala intre weakly
named si strongly named assemblies este aceea ca strongly named assemblies este
semnat cu o pereche de key public/private ce identifica in mod unic producatorul.
Din punct de vedere al instalarii (deployement), weakly named assemblies permit
numai instalare intr-un director privat, in timp ce strongly named assemblies permit si
deployment global (in GAC Global Assembly Cache). Aceasta pereche de chei permite
ca assembly sa fie identificat in mod unic, securizat si versionat si de asemenea permite
plasarea lui oriunde pe HDD clientului sau chiar pe Internet. Acest lucru permite CLR sa
aplice anumite politici known to be safe cand o aplicatie incearca sa se lege la un
strongly named assemblies.
Un strongly named assemblies este identificat de patru atribute: un nume de fisier (fara
extensie), un numar de versiune, cultura si o cheie publica.

Exemplu
IDDTypes, Version=1.0.542.0,Culture=neutral, PublicKeyToken=11aa43ccba210098
IDDTypes, Version=1.0.542.0,Culture=en-US, PublicKeyToken=11aa43ccba210098
IDDTypes, Version=1.0.341.0,Culture=neutral, PublicKeyToken=11aa43ccba210098

Primul string identifica numele assembly, IDDTypes. Compania ce a creat assembly a
creat numarul de versiune 1.0.542.0, cultura este neutral, iar cheia publica asociata cu
assembly este 11aa43ccba210098.

Observatie
Clasa System.Reflection.AssemblyName este o clasa helper ce ne ajuta sa
construim un nume de assembly si de a obtine diverse parti ale acestui nume. Clasa ne
pune la dispozitie mai multe proprietati publice (instanta) CultureInfo, FullName,
KeyPair, Name si Version. De asemenea exista si metode ale instantei: GetPublicKey,
GetPublicKeyToken, SetPublicKey, SetPublicKeyToken.

Dezvoltare aplicatii folosind platforma .NET - 17/233
Crearea unui strongly named assemblies

Pas 1. Obtinirea unei chei folosind utilitarul SN.exe (Strong Name).
Se creaza cheia in fisierul IDD.keys

sn k IDD.keys

Vizualizarea se face astfel:
sn p idd.keys idd.out
sn tp idd.out

Microsoft (R) .NET Framework Strong Name Utility Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

Public key is
002400000480000094000000060200000024000052534131000400000100010005930473ad8c44
a9227590bc687e2b7b202ff2896ff914bccee69316aec7d6dfa6162b7f36207a17fd87f406e64b
2c6cade07c9e1f588c529c2e92670a613795160962a0216f6c8d354f01bfcab29615085a0c8a44
357cae0b086aa3a210308bc7004ff35394766cbe49623621304056392d51752f97665f3117d6ab
f5962bd7

Public key token is 7fba10e871ac4327

Pas 2. Atasarea cheii la assembly
Se aplica atributul System.Reflection.AssemblyKeyFileAttribute la codul sursa.

[assembly:AssemblyKeyFile(IDD.keys)]

Compilatorul este cel ce va semna assembly cu cheia privata si va memora cheia publica
in manifest. Acest lucru se aplica numai pentru assembly ce au manifest.
GAC (the Global Assembly Cache)

Daca assembly este accesat de mai multe aplicatii, acesta trebuie plasat intr-un director
cunoscut de CLR pentru a putea fi incarcat atunci cand se face o referire la el. Acest
director se numeste GAC, care in mod obisnuit este:
C:\Windows\Assembly\GAC

Acest director este structurat pe mai multe directoare. Pentru generarea acestor directoare
se foloseste un algoritm. Nu se vor copia fisierele direct in aceste directoare.
Pentru instalarea acestor assembly se va folosi utilitarul GACUtil.exe. Acest utilitar se
executa din linia de comanda.

Daca nu exista pe computer acest utilitar, atunci se va folosi Windows Installer (MSI)
versiunea 2 sau mai mare. Pentru a determina ce versiune de MSI exista pe computer
rulati in linia de comanada MSIExec.exe.

Cand se instaleaza .NET Framework, se creaza un fisier CSC.rsp folosit de compilatorul
C#. Aici se gasesc referintele la bibliotecile folosite de compilator.
Dezvoltare aplicatii folosind platforma .NET - 18/233

Tipuri

Toate clasele sunt derivate din System.Object.
Exemplu

class Student {}

sau echivalent

class Student: System.Object {}

Toate obiectele vor fi create cu operatorul new. Nu exista un operator delete pentru a
elibera din memorie obiectul creat. Eliberarea din memorie o face componenta Garbage
Collector.

Exemplu:

Student student = new Student();

In acest moment se aloca memorie pentru obiectul nou creat; se fac initializarile necesare
(cerute de tipul respectiv); se asociaza doi membri la acest tip: un pointer la obiect si un
bloc de memorie SyncBlockIndex.
In momemntul executiei CLR stie ce tip are acel obiect, apeland metoda GetType.
Conversia intre tipuri. Operatorii is si as.

Nu este necesara o conversie explicita de la tipul derivat la tipul de baza, in schimb este
necesara aceasta conversie de la tipul de baza la tipul derivat.
upcast = conversie de la clasa derivata la clasa de baza, se poate face totdeauna aceasta
conversie, e o conversie implicita;
downcast = conversie de la clasa de baza la clasa derivata, nu se poate face aceasta
conversie implicit, se foloseste conversia explicita;
Exemple

class Student { }

class OptionaleStudent : Student { }

class ConversieImplicita
{
public static void Main ()
{
// !upcast
Student student = new OptionaleStudent();

Dezvoltare aplicatii folosind platforma .NET - 19/233
// Urmatorul cod nu se compileaza (!downcast)
// OptionaleStudent optStudent = new Student();
}
}
Principiul subsituitiei: putem folosi o clasa derivata in locul clasei de baza daca ierarhia
de clase a fost definita corect.
In general de la clasa de baza la o clasa derivata nu putem face conversii pentru ca nu
exista nici o garantie ca obiectul (instanta a clasei de baza) suporta interfata definita in
clasa derivata.
In cazul unui downcast, trebuie folosit un cast ca in exemplul urmator :
class Student { }

class OptionaleStudent : Student { }

class ConversieExplicita1
{
public static void Main ()
{
// Nu functioneaza downcast eroare la compilare
OptionaleStudent optStudent = (OptionaleStudent)new Student();
}
}

class ConversieExplicita2
{
public static void Main ()
{
// Compilarea este OK!
// Apare o exceptie in momentul executiei!
// CLR determina tipul obiectului in momentul executiei!
// La o conversie invalida se executa System.InvalidCastException
Student s = new Student();
OptionaleStudent optStudent = (OptionaleStudent)s;
}
}

Exemplu

class Student {}
class Facultate : Student{}

class App {
public static void Main()
{
Object obj = new Student(); // nu e necesara conversia explicita
Student student = (Student)obj; // e necesara conversia explicita
pentru ca Student este derivat din Object

Facultate facultate = new Facultate();
Conversie(facultate);
Dezvoltare aplicatii folosind platforma .NET - 20/233

DateTime dt = new dateTime(2007,1,1);

// La acest apel va aparea o exceptie InvalidCastException
// DateTime nu este derivat din Student

Conversie(dt);
}

public void Conversie(Object o)
{
// Nu se stie exact o ce tip de obiect este
Student student = (Student)o;
// Daca nu se poate face conversia la tipul Student
// va aparea o exceptie InvalidCastException
}
}
Operatorul as
O alta metoda de conversie este folosirea cuvintului cheie rezervat as. In acest caz nu mai
apare o exceptie la conversie, dar rezultatul va fi null daca conversia nu poate fi facuta.
class ConversieExplicita3
{
public static void Main ()
{
// Folosirea lui as
OptionaleStudent optStudent = (OptionaleStudent)new Student();
}
}

class ConversieExplicita3
{
public static void Main ()
{
Student s = new Student();
Console.WriteLine("s = {0}",
s == null ? "null" : s.ToString());

OptionaleStudent optStudent = s as OptionaleStudent;
Console.WriteLine("optStudent = {0}",
optStudent == null ?
"null" : optStudent.ToString());
}
}
Rezultatul executiei este:
s = Employee
optStudent = null
In acest caz daca se incearca apelarea unei metode din System.Object pe acest obiect,
CTS va genera o exceptie System.NullReferenceException.
Dezvoltare aplicatii folosind platforma .NET - 21/233
Operatorul is

Operatorul is verifica daca un obiect este compatibil cu un tip dat, iar rezultatul evaluarii
este un Boolean: true sau false. Operatorul is nu va arunca niciodata execeptii.

Exemplu

System.Object o = new System.Object();
System.Boolean b1 = (o is System.Object); // b1 are valoarea true.
System.Boolean b2 = (o is Student); // b2 are valoarea false.

Operatorul is este folosit in mod obisnuit astfel:

if (o is Student)
{
Student student = (Student)o;
...
}
Conversia numerica
Cand se executa operatii aritmetice pentru tipuri ce nu au preconstruite operatiile (nu sunt
definiti operatorii aritmetici) compilatorul modifica implicit expresia catre un tip implicit
apropiat pentru care aceste operatii sunt definite. De exemplu short este transformat in
int.
public class NumConvertApp
{
public static void Main(string[] args)
{
short s1 = 1;
short s2 = 2;
short suma = (s1 + s2); // Eroare
// compilatorul face conversia lui s1 si s2 catre int
// valoarea returnata este un int
// nu se face conversia automata la short
Console.WriteLine("suma = {0}", suma);
}
}
Rezolvare:
//short suma = (s1 + s2); // Eroare
short suma = (short)(s1 + s2);

In acest caz codul se compileaza si chiar se executa, dar programatorul
este responsabil pentru aceasta conversie.
Cu codul de mai sus si aceasta modificare rezultatul operatiei este 3
(corect!).

De exemplu daca

s1 = 22222 ;
Dezvoltare aplicatii folosind platforma .NET - 22/233
s2 = 22222 ;

atunci rezultatul este

-21092 (incorect!)

pentru ca valoarea maxima pentru short este 32767.

Pentru a preintimpina acest neajuns, putem scrie urmatorul cod:

checked
{
short suma = (short)(s3 + s4); // OverflowException
Console.WriteLine("suma = {0}", suma);
}

In acest caz in timpul executiei apare eroarea:
Unhandled exception: System.OverflowException: Arthmetic operation resulted in an
overflow at DefaultNameSpace.MainClass.Main[...]... Main.cs line ...
C# suporta:
Conversie implicita : conversie executata cu succes fara pierdere de informatie. Nu
exista conversie implicita din date numerice in date de tip caracter.
//char x = 65; // Eroare: nici o conversie
Console.WriteLine("(char)(byte)321 = {0}", (char)(byte)c);
Rezultatul este:
(char)(byte)321 = A
Conversie explicita: (ca in C/C++) se indica la ce tip se face conversia; exista
posibilitatea pierderii de informatie (rezultate incorecte).
Context controlat (checked) si context necontrolat (unchecked)
Instructiunile C# pot fi executate intr-un bloc controlat (checked) sau bloc necontrolat
(unchecked) care este implicit. Exista de asemenea optiunea la compilare /checked,
/checked+ sau /checked- (unchecked) ce are ca efect verificarea sau nu a tuturor
depasirilor in cadrul operatiilor aritmetice. Urmatoarele operatii sunt afectate de acest
switch: ++, --, -(unary), +, -, *, / si conversia numerica explicita intre tipurile integrale.
In blocurile controlate depasirile aritmetice provoaca o exceptie.
Exceptiile pot fi controlate si cu
try {...
}
catch (Exception e)
Dezvoltare aplicatii folosind platforma .NET - 23/233
{
Console.WriteLine( Exceptie : {0} , e.Message) ;
}
Cuvintele cheie checked si unchecked pot fi folosite si in cadrul conversiei explicite ca
mai jos:
short h = 321;
byte i = (byte)h; // Conversie controlata daca /checked
i = unchecked((byte)h); // Totdeauna unchecked
i = checked((byte)h); // Todeauna checked
Operatori definiti
Operatorii aritmetici de baza din C# sunt:
= atribuirea
+, +(unary)
-, -(unary)
* inmultire, / impartire, % restul impartirii
?: operator ce implica trei operanzi
Precedenta operatorilor
C# Precedenta operatorilor
Categoria operatorului Operatori
Primary
(x), x.y, f(x), a[x], x++, x- -, new, typeof, sizeof,
checked, unchecked
Unary +, -, !, ~, ++x, - -x, (T)x
Multiplicative *, /, %
Additive +, -
Shift <<, >>
Relational <, >, <=, >=, is
Equality ==
Logical AND &
Logical XOR ^
Logical OR
Conditional AND &&
Conditional OR
Conditional ?:
Assignment =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, =

Toti operatorii binari (exceptie atribuirea) sunt asociati la stanga, deci expresia este
evaluata de la stanga la dreapta.
Dezvoltare aplicatii folosind platforma .NET - 24/233
Operatorul de atribuire si operatorul conditional sunt asociati la dreapta, deci expresia
este evaluata de la dreapta spre stanga.
Evitarea confuziei asocierilor se face folosind ().
Operatori C#

(x), x.y, f(x), a[x], x++, xaceeasi ca in C/C++.
new = folosit pentru a instantia obiecte din definitiile claselor.
typeof
Reflection reprezinta abilitatea de a regasi informatia de tip in momentul executiei.
Aceasta informatie cuprinde:
numele tipurilor;
numele claselor;
membrii.
Clasa System.Type este folosita pentru a regasi aceste aceste informatii. Aceasta clasa
este radacina tuturor operatorilor de reflexie, si poate fi obtinuta folosind operatorul
typeof.
using System;
using System.Reflection;

public class Student
{
public int nID;
public void Studii()
{
}
}

public class TipAplicatie
{
static void Main(string[] args)
{
Type t = typeof(Student);
string className = t.ToString();

Console.WriteLine("\nInformatii despre clasa {0}",
className);

Console.WriteLine("\n{0} metode", className);
Console.WriteLine("-----------------------------");
MethodInfo[] methods = t.GetMethods();
foreach (MethodInfo method in methods)
{
Console.WriteLine(method.ToString());
Dezvoltare aplicatii folosind platforma .NET - 25/233
}

Console.WriteLine("\nToti {0} membrii clasei", className);
Console.WriteLine("-----------------------------");
MemberInfo[] allMembers = t.GetMembers();
foreach (MemberInfo member in allMembers)
{
Console.WriteLine(member.ToString());
}
}
}

Rezultatul este:

Informatii despre clasa Student

Student metode
-----------------------------
Int32 GetHashCode()
Boolean Equals(System.Object)
System.String ToString()
Void Studii()
System.Type GetType()

Toti membrii clasei Student
-----------------------------
Int32 nID
Int32 GetHashCode()
Boolean Equals(System.Object)
System.String ToString()
Void Studii()
System.Type GetType()
Void .ctor()

sizeof = returneaza marimea in octeti a tipului specificat.

Poate fi folosit numai cu tipul valoare. Nu poate fi folosit pe tipul clasa.
Operatorul poate fi utilizat numai in metode sau cod marcat ca nesigur . O metoda se
declara ca fiind nesigura folosind cuvantul cheie unsafe in declaratia metodei. Metoda
in care va fi apelata aceasta functie trebuie declarata de asemenea unsafe. La compilare se
va folosi switch-ul /unsafe.

sizeof folosit pentru struct nu returneaza valoarea corecta a marimii structurii, deoarece
compilatorul poate aloca structura la adrese multiplu de 4.

Operatori matematici nu e nimic deosebit fata de celelalte limbaje de programare.

Consultati acest exemplu (preluat din Inside C#, Tom Archer ...)

class MathOpsApp
{
static void Main(string[] args)
{
// The System.Random class is part of the .NET
Dezvoltare aplicatii folosind platforma .NET - 26/233
// Framework class library. Its default constructor
// seeds the Next method using the current date/time.
Random rand = new Random();
int a, b, c;

a = rand.Next() % 100; // Limit max to 99.
b = rand.Next() % 100; // Limit max to 99.

Console.WriteLine("a={0} b={1}", a, b);

c = a * b;
Console.WriteLine("a * b = {0}", c);

// Note the following code uses integers. Therefore,
// if a is less than b, the result will always
// be 0. To get a more accurate result, you would
// need to use variables of type double or float.
c = a / b;
Console.WriteLine("a / b = {0}", c);

c = a + b;
Console.WriteLine("a + b = {0}", c);

c = a - b;
Console.WriteLine("a - b = {0}", c);

c = a % b;
Console.WriteLine("a % b = {0}", c);
}
}

Operatori unari: plus (+) si minus (-), (T)x folosit in conversia explicita, va fi discutata
la supraincarcarea operatorilor. Pentru a evita ambiguitatile folositi ().

Operatori de atribuire compusi
Sintaxa:
x op= y

echivalenta cu

x = x op y

op poate fi +, -, /, *, %.

Exemplu

...
int nIndex = 1;
...
nIndex += 5;

este echivalent cu

Dezvoltare aplicatii folosind platforma .NET - 27/233
nIndex = nIndex + 5;

Atentie la ordinea operatiilor

int x;
x = 5;
x = x * 3 + 4;

va produce rezultatul 19, in timp ce

x = 5;
x *= 3 + 4;
va produce rezultatul 35. In ultimul exemplu ordinea operatiilor este x = x * (3 + 4);.
Operatori de incrementare si decrementare

Exista doua variante: operatori de incrementare / decrementare postfixati si pre-fixati.
Lucreaza exact ca in C.
Tipuri primitive. Tipuri valoare. Tipuri referinta.

Anumite tipuri de data folosite foarte des utilizeaza o sintaxa simplificata pentru a fi
declarate. Pentru a aloca un intreg putem folosi sintaxa:

System.Int32 nIndex = new System.Int32();

dar acelasi lucru poate declarat si astfel:

int nIndex = 0;

Codul IL generat de compilator este acelasi in ambele cazuri.
Orice tip de data pe care compilatorul il suporta in mod direct se numeste tip primitiv (tip
preconstruit). Aceste tipuri primitive mapeaza direct tipurile din biblioteca .NET
Framework (FCL Framework Class Library). Dupa cum se vede si din exemplul de mai
sus, int mapeaza direct pe System.Int32.
Pentru tipurile primitive compilatorul atribuie o valoare implicita, in caz ca nu s-a
specificat nimic la construirea tipului.
In urmatorul exemplu toate declaratiile produc acelasi cod IL:

int nIndex = 0;
int nIndex = new int();
System.Int32 nIndex = new System.Int32();
System.Int32 nIndex = 0;

Tipurile primitive sunt:

Nume tip CTS C# Alias Descriere
System.Object Object Base class for all CTS types
Dezvoltare aplicatii folosind platforma .NET - 28/233

Nume tip CTS C# Alias Descriere
System.String String String, an array of characters
System.Sbyte Sbyte Signed 8-bit byte
System.Byte Byte Unsigned 8-bit byte
System.Int16 Short Signed 16-bit value
System.UInt16 Ushort Unsigned 16-bit value
System.Int32 Int Signed 32-bit value
System.UInt32 Uint Unsigned 32-bit value
System.Int64 Long Signed 64-bit value
System.UInt64 Ulong Unsigned 64-bit value
System.Char Char 16-bit Unicode character
System.Single Float IEEE 32-bit float
System.Double Double IEEE 64-bit float
System.Boolean Bool Boolean value (true/false)
System.Decimal Decimal
128-bit data type that is exact to 28 or 29 digits
mainly used for financial applications in which a great
degree of accuracy is required

Tipul System.Decimal este un tip special din urmatoarele motive:
In timp ce C# il considera un tip primitiv, CLR face o tratare speciala. CLR nu are
instructiuni IL care sa stie cum sa manipuleze o valoare Decimal. Compilatorul va genera
cod pentru apelul metodelor din System.Decimal. Asta inseamna in final o performanta
redusa in lucrul cu tipurile Decimal. Operatorii checked si unchecked nu au efect asupra
acestui tip. Operatiile cu valori Decimal vor arunca totdeauna execeptii daca acestea nu
pot fi ecxecutate (OverflowException).

Tipul referinta

Tipurile referinta sunt alocate din managed heap, si operatorul new din C# returneaza
adresa de memorie a obiectului. Cand se lucreaza cu aceste tipuri trebuie sa avem in
vedere urmatoarele aspecte:
Memoria trebuie alocata din managed heap.
Fiecarui obiect alocat in heap i se ataseaza cativa membri in plus care trebuie
initializati.
Alocarea unui obiect in heap poate forta o actiune pentru garbage collector.

Tipuri valoare : variabila contine valoarea.

Nu pot fi null (o variabila de tip value are intotdeauna o valoare). In mod normal se aloca
pe stiva, si deci nu vor fi eliberate de garbage collector.

Folosirea unei asemenea variabile ca argument al unei functii are ca efect pasarea valorii.
Modificarea valorii in cadrul functiei este locala.
Dezvoltare aplicatii folosind platforma .NET - 29/233
Exemplu:
System.Int32 nVarsta = 22;
Se aloca 32 biti pentru nVarsta si valoarea este 22.
De fiecare data cand declaram o variabila de un anumit tip, sistemul aloca numarul de
octeti asociati tipului respectiv si putem lucra in mod direct cu memoria alocata.
Documentatia pentru .NET Framework indica care tipuri sunt tipuri referinta si care sunt
tipuri valoare. Tipul referinta este asociat claselor, iar tipul valoare este asociat
structurilor sau enumerarilor.
Tipurile valoare trebuie sa fie derivate din System.ValueType.
Exemplu
// Tip referinta ( class)
class TipReferinta { public int nIndex ; }
// Tip valoare (struct)
struct TipValoare { public Int32 nIndex ; }

static void Metoda()
{
TipReferinta tr1 = new TipReferinta() ; // alocat in heap
TipValoare tv1 = new TipValoare() ; // alocat pe stiva
tr.nIndex = 10 ; // modificat in heap ; va fi eliberat de Garbage
Collector.
tv.nIndex = 10 ; // modificat pe stiva

TipReferinta tr2 = tr1 ; // se copie numai referinta (pointer)
TipValoare tv2 = tv1 ; // se aloca pe stiva si se copie membrii
tr1.nIndex = 6 ; // se modifica tr1.Nindex si tr2.nIndex
tv1.nIndex = 6 ; // se modifica numai tv1.nIndex. tv2.nIndex nu
}
Observatie : operatorul new se foloseste pentru ambele tipuri. Folosirea operatorului nu
inseamna ca se va crea obiectul in memoria heap. Folosirea operatorului new are ca efect
initializarea membrilor instantei.
Exemplu
TipValoare tv = new TipValoare() ;
Int32 n = tv.nIndex ; // n are valoarea zero
TipValoare ts ;
int n = ts.nIndex ; // eroare la compilare. error CS0170 : Use of
possibly unassigned field nIndex

Cateva reguli de care ar trebui sa tinem seama cand construim un tip valoare (toate
acestea trebuie sa fie adevarate) :
Tipul se comporta ca un tip primitiv (preconstruit).
Dezvoltare aplicatii folosind platforma .NET - 30/233
Tipul nu are nevoie sa mosteneasca alt tip.
Nu e nevoie sa derivam alte tipuri din acesta.
Instantele tipului nu sunt folosite in mod frecvent ca parametri ai metodei.
Implicit, parametrii sunt pasati prin valoare, ceea ce are ca efect copierea valorii,
si deci reducerea performantei.
Instantele tipului nu sunt frecvent returnate din metode. Valoarea returnata va
trebui copiata intr-o locatie de memorie alocata de apelant, si deci reducerea
performantei.
Instantele tipului nu sunt utilizate frecvent in colectii cum ar fi ArrayList,
HashTable, etc. Clasele ce gestioneaza o multime generica de obiecte necesita tip
referinta, deci aceste obiecte vor fi boxed .

Diferente intre tipul valoare si tipul referinta
Obiectele tip valoare au doua reprezentari : unboxed si boxed. Tipul referinta este
numai boxed.
Tipurile valoare sunt derivate din System.ValueType. Cand definim un tip
valoare, ar trebui sa suprascriem metodele Equals si GetHashCode si sa furnizam
o implementare explicita.
Pentru tipul valoare, metodele nu pot fi abstracte, toate sunt implicit sealed (nu
pot fi suprascrise).
Variabilele de tip referinta contin adresa de memorie a obiectului in heap. Implicit
cand este creata o variabila de tip referinta, aceasta este initializata cu null.
In procesul de atribuire, pentru tipul valoare se copie valoarea in noua locatie,
pentru tipul referinta se copie numai adresa de memorie unde a fost creat obiectul.
Din cauza ca tipul valoare se aloca pe stiva si nu in heap, memoria este eliberata
cand instanta tipului nu mai este activa. Aceasta inseamna ca o instanta a tipului
valoare nu primeste o notificare (prin metoda Finalize) cand memoria este
eliberata.
Intr-un mediu de executie multifir (multithreading) blocarea unui tip valoare se
face cu ajutorul unui tip referinta (vezi lock).
Tipul valoare : boxind si unboxing
Boxing = conversia de la tip valoare la tip referinta.
Unboxing = conversia de la tip referinta la tip valoare.

Conversia din tip valoare la tip referinta

Se creaza o copie noua a obiectului ce va fi supus conversiei.

Exemplu

int nVarsta = 22; // tip valoare
object refVarsta = nVarsta; // nVarsta este convertit la refVarsta
// nu e nevoie de o conversie explicita

Ce se intampla ?
Dezvoltare aplicatii folosind platforma .NET - 31/233
Se aloca memorie in heap (pentru a pastra valoarea cat si pentru tabela metodelor
virtuale).
Se copie valoarea.
Adresa obiectului nou alocat este plasata pe stiva, si aceasta puncteaza catre un tip
referinta.

Conversia din tip Referinta la tip valoare

Nu se creaza o noua copie a obiectului supus conversiei.

In acest caz conversia poate fi facuta la orice tip. Se aplica reguli stricte definite in CTS.

Exemplu:

int nVarsta = 22; // tip valoare.
object refVarsta = nVarsta; // (boxed) refVarsta puncteaza la nVarsta
int nAlt = (int)refVarsta; // (Unboxed) inapoi la int.
// Daca conversia esueaza se arunca exceptia InvalidCastException.
// este nevoie de o conversie explicita


Exemplu (vezi Conversie_CUI)
class Boxing
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
int n1 = 32;
object o1 = n1;
int n2 = (int)o1;
Console.WriteLine("Initializare. n1 = {0}, o1 = {1},
n2 = {2}", n1, o1.ToString(), n2);
n1 = 35;
Console.WriteLine("Dupa modificare. n1 = {0}, o1 =
{1}, n2 = {2}", n1, o1.ToString(), n2);
Console.Read();
}
}

Codul generat arata astfel:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() =
( 01 00 00 00 )
// Code size 84 (0x54)
.maxstack 4
.locals init ([0] int32 n1,
[1] object o1,
[2] int32 n2)
Dezvoltare aplicatii folosind platforma .NET - 32/233
IL_0000: ldc.i4.s 32
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: box [mscorlib]System.Int32
IL_0009: stloc.1
IL_000a: ldloc.1
IL_000b: unbox [mscorlib]System.Int32
IL_0010: ldind.i4
IL_0011: stloc.2
IL_0012: ldstr "Initializare. n1 = {0}, o1 = {1}, n2 = {2}"
IL_0017: ldloc.0
IL_0018: box [mscorlib]System.Int32
IL_001d: ldloc.1
IL_001e: callvirt instance string
[mscorlib]System.Object::ToString()
IL_0023: ldloc.2
IL_0024: box [mscorlib]System.Int32
IL_0029: call void [mscorlib]System.Console::WriteLine(string,
object,
object,
object)
IL_002e: ldc.i4.s 35
IL_0030: stloc.0
IL_0031: ldstr " Dupa modificare. n1 = {0}, o1 = {1}, n2 = {2}"
IL_0036: ldloc.0
IL_0037: box [mscorlib]System.Int32
IL_003c: ldloc.1
IL_003d: callvirt instance string
[mscorlib]System.Object::ToString()
IL_0042: ldloc.2
IL_0043: box [mscorlib]System.Int32
IL_0048: call void [mscorlib]System.Console::WriteLine(string,
object,
object,
object)
IL_004d: call int32 [mscorlib]System.Console::Read()
IL_0052: pop
IL_0053: ret
} // end of method Boxing::Main
Observati ca se aplica boxing in metoda Console.WriteLine. De ce ?
Daca cautam prototipul pentru aceasta metoda vom gasi ( sau ne uitam in codul IL)
public static void WriteLine(string format,
params object[] arg);

Parametrul al doilea este un vector de obiecte de tip referinta.
Operatii pe obiecte. Obiecte egale. Obiecte identice.

Tipul System.Object are o metoda virtuala Equals ce returneaza true daca doua obiecte au
aceeasi valoare . Pentru tipurile ce nu suprascriu explicit metoda Equals, se va folosi
implementarea data de System.Object. Aceasta metoda este folosita de foarte multe clase.
Dezvoltare aplicatii folosind platforma .NET - 33/233
De exemplu metoda IndexOf din System.Array, metoda Contains din
System.Collections.ArrayList apeleaza intern Equals.
Metoda Equals din Object este implementata astfel :

class Object {
public virtual Boolean Equals (Object obj)
{
// Daca ambele referinte puncteaza la acelasi obiect,
// acestea trebuie sa fie egale
if (this == obj) return (true) ;

// Se presupune ca obiectele nu sunt egale
return false ;
}

Cand implementam metoda Equals, aceasta trebuie sa satisfaca urmatoarele proprietati :
reflexivitate ;
simetrie ;
tranzitivitate ;
consistenta daca valorile ce se compara nu se modifica trebuie sa furnizeze
acelasi rezultat ori de cate ori este apelata.


Exista trei pattern-uri pentru a implementa metoda Equals.

Implementarea metodei Equals pentru un tip referinta a carui clasa de baza nu
suprascrie metoda Equals din Object

class RefType : BaseType
{
TipReferinta refobj ; // tip referinta
TipValoare valobj ; // tip valoare

public override Boolean Equals(Object obj)
{
// Daca obj este null atunci obiectele nu sunt egale
// this nu poate fi null
if (obj == null) return false ;

// Daca obiectele sunt de tipuri diferite, atunci nu sunt
egale
if (this.GetType() != obj.GetType()) return false ;

// Aceasta conversie nu poate sa arunce exceptii
// pentru ca stim sigur ca obj este de tipul RefType
RefType reft = (RefType)obj ;

// Pentru a compara campurile referinta apelam
// metoda statica Equals din Object
if ( !Object.Equals(refobj, reft.refobj)) return false ;

// Pentru a compara campurile valoare, apelam
if( !valobj.Equals(reft.valobj)) return false ;
Dezvoltare aplicatii folosind platforma .NET - 34/233

return true ; // Obiectele sunt egale
}

// Optional supraincarcam operatorii == si !=
public static Boolean operator ==(RefType r1, RefTyep r2)
{
if (r1 == null) return false ;
return r1.Equals(r2) ;
}

public static Boolean operator != (RefType r1, RefType r2)
{
return !(r1 == r2) ;
}
}

Dezvoltare aplicatii folosind platforma .NET - 35/233
Implementarea metodei Equals pentru un tip referinta cand una sau mai multe din
clasele de baza suprascriu metoda Equals din Object

class RefType : BaseType
{
TipReferinta refobj ; // tip referinta
TipValoare valobj ; // tip valoare

public override Boolean Equals(Object obj)
{
// Lasam clasa de baza sa-si compare campurile
if ( !base.Equals(obj)) return false ;

// In continuare este acelasi cod ca mai sus

// Daca obj este null atunci obiectele nu sunt egale
// this nu poate fi null
if (obj == null) return false ;

// Daca obiectele sunt de tipuri diferite, atunci nu sunt
egale
if (this.GetType() != obj.GetType()) return false ;

// Aceasta conversie nu poate sa arunce exceptii
// pentru ca stim sigur ca obj este de tipul RefType
RefType reft = (RefType)obj ;

// Pentru a compara campurile referinta apelam
// metoda statica Equals din Object
if ( !Object.Equals(refobj, reft.refobj)) return false ;

// Pentru a compara campurile valoare, apelam
if( !valobj.Equals(reft.valobj)) return false ;

return true ; // Obiectele sunt egale
}

// Optional supraincarcam operatorii == si !=
public static Boolean operator ==(RefType r1, RefTyep r2)
{
if (r1 == null) return false ;
return r1.Equals(r2) ;
}

public static Boolean operator != (RefType r1, RefType r2)
{
return !(r1 == r2) ;
}
}

Implementarea metodei Equals pentru tipurile valoare

struct ValType
{
TipReferinta refobj ;
Dezvoltare aplicatii folosind platforma .NET - 36/233
TipValoare valobj ;
public override Boolean Equals(Object obj)
{
// Daca obj nu e tipul nostru,
// atunci obiectele nu pot fi egale
if ( !(obj is ValType)) return false ;

return this.Equals((ValType) obj) ;
}

// Implementam o versiune a metodei Equals
// pentru tipul nostru ValType
public Boolean Equals(ValType obj)
{
// Comparam campuri referinta
if ( !Object.Equals(this.refobj, obj.refobj))
return false ;

// Comparam campuri valoare
if ( !this.valobj.Equals(obj.valobj)) return false ;

return true ; // obiectele sunt egale
}

// Optional
public static Boolean operator ==(ValType v1, ValType v2)
{
return (v1.Equals(v2)) ;
}

public static Boolean operator != (ValType v1, valType v2)
{
return !(v1 == v2) ;
}
}

Observatie

Cand se suprascrie metoda Equals va trebui sa suprascriem si metoda GetHashCode.
Metoda GetHashCode returneaza un Int32 si nu are parametri. Pe baza acestui Int32
obiectele pot fi plasate intr-o colectie.
Cand adaugam o pereche cheie/valoare la un obiect Hashtable se calculeaza mai intai
codul hash pentru obiectul cheie. Acest cod hash indica in ce buchet se va memora
perechea cheie/valoare. Cand obiectul Hashtable cauta dupa o anumita cheie, obtine mai
intai codul hash si apoi determina buchetul unde se gaseste obiectul. Identificarea
obiectului se face apoi apeland metoda Equals pentru fiecare obiect din buchet.

Exemplu pentru implementarea metodei GetHashCode

class Curs
{
Int32 nID ;
public override Int32 GetHashCode()
{
Dezvoltare aplicatii folosind platforma .NET - 37/233
return nID % 1001 ;
}
}

Implementarea metodei GetHashCode din System.ValueType foloseste reflection si
returneaza codul hash pentru primul camp al instantei definit in acest tip.

calss ValueType
{
public override Int32 GetHashCode()
{
FieldInfo[] fields = this.GetType().GetFields(
BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic) ;
if (fields.Length > 0)
{
// Returneaza cod hash pentru primul camp nenul
for (int i = 0 ; i < fields.Length ; i++)
{
Object obj = fields[i].GetValue(this) ;
if (obj != null) return obj.GetHashCode() ;
}
}
// Daca nu exista nici un camp cu valori
// metoda nedocumentata
return GetMethodTablePtrAsInt(this) ;
}
}


Doua obiecte sunt identice daca pointeaza la aceeasi zona de memorie.
Pentru a testa daca doua obiecte sunt identice, vom apela metoda Object.ReferenceEquals
care este implementata astfel in Object :

public static Boolean ReferenceEquals(Object o1, Object o2)
{
return (o1 == o2) ;
}
Clase si structuri
O clasa poate fi definita ca incapsularea datelor si metodelor ce lucreaza pe aceste date.
Sintaxa pentru definirea claselor este urmatoarea:

[attributes] [modifiers] class <className> [: baseClassName]
{
[corp clasa]
}[;]

Exemplu:

class Student
{
Dezvoltare aplicatii folosind platforma .NET - 38/233
private long IdStudent;
}

Contine o singura data membru, IdStudent, ce are modificatorul de acces private.

Membrii clasei

In continuare prezentam lista tipurilor ce o putem defini ca membri pentru o clasa C#.

Campuri (Fields)
Un camp este o data membru ce contine o instanta a tipului valoare sau o referinta pentru
tipul referinta. CLR suporta campuri statice si campuri nestatice (instanta). Pentru
campurile de tip valoare memoria se aloca cand tipul este incarcat in AppDomain. Pentru
campurile de instanta, memoria se aloca atunci cand se construieste o instanta a tipului.

CLR suporta campuri readonly si read/write. Un camp readonly trebuie initializat in
constructor; nici o alta metoda nu poate schimba valoarea acestuia.

Pentru un camp se pot aplica mai multi modificatori: static, readonly si const.

class Camp
{
public readonly string sir = "Un sir"; // camp al instantei
public Int32 nID = 10; // camp al instantei R/W
public static int nIndex = 0; // camp static R/W
public Camp(){ sir = "123"; }
// Daca decomentam acest cod, atunci codul nu se mai compileaza
//public Change(){ sir = "321";}
}
Codul este OK si se compileaza.

Accesul la aceste campuri se face astfel:

Camp c = new Camp();
int n = c.nID; // avem nevoie de instanta
int ni = Camp.nIndex; // prefixat cu numele clasei (nu avem nevoie de
instanta)

Metode
O metoda contine codul ce actioneaza pe datele obiectului.

Proprietati
Proprietatile mai sunt cunoscute si sub numele de smart fields pentru ca ele sunt
metode, iar de client sunt vazute drept campuri.

Constante
O constanta este un camp cu o valoare ce nu poate fi schimbata.

Indexeri
Dezvoltare aplicatii folosind platforma .NET - 39/233
Un indexer este referit ca smart array; permite sa lucram cu clase ce reprezinta in mod
logic un tablou de date.
Pentru a indexa datele clasei, proiectantul clasei are doua posibilitati: fie permite accesul
direct la datele interne ale clasei (date reprezentate sub forma de tablou), fie furnizeaza
metode ce au ca parametri un index ce defineste elementul din tablou ce va fi folosit.

Evenimente
Un eveniment are ca efect executia unui cod. SO Windows se bazeaza pe evenimente. In
general evenimentele genereaza mesaje.

Operatori
In C# putem supraincarca operatorii matematici.

Modificatori de acces pentru campuri

Modificator de acces Descriere
public
Membrul este accesibil din afara definitiei clasei si a ierarhiei
claselor derivate.
protected
Membrul nu este vizibil in afara clasei si poate fi accesat
numai de clasele derivate.
private
Membrul nu este vizibil in afara clasei si nici in clasele
derivate.
internal
Membrul este vizibil numai in interiorul unitatii curente de
compilare, numai din fisierele din acelasi assembly. Acest
modificator creaza un hibrid intre public si protected, totul
depinzind in ultima instanta de locul unde se afla codul. O
utilizare comuna a acestui modificator este in dezvoltarea
bazata pe componente pentru ca permite unui grup de
componente sa coopereze intr-un mod privat.

protected internal *
Accesul este limitat la assembly-ul curent sau la tipurile
derivate din clasa continuta.

Implicit modificatorul de acces este private.

Membru in Accesibilitate
implicita
Permite modificarea accesibilitatii
enum public -
class private public
protected
internal
private
protected internal
interface public -
struct private public
internal
Dezvoltare aplicatii folosind platforma .NET - 40/233
private

In C# trebuie sa indicam pentru fiecare data membru modificatorul de acces.
In C++ putem scrie:

class Cpp
{
public:
int a;
int b;
int c;
protected:
int d;
int e;
};

iar in C# acelasi lucru se descrie astfel:

class CSharp
{
public int a;
public int b;
public int c;
protected int d;
protected int e;
}

Exemple:

Modificator de acces protected

class A
{
protected int x = 123;
}

class B : A
{
void F()
{
A a = new A();
B b = new B();
a.x = 10; // Eroare
b.x = 10; // OK
}
}

Modificator de acces: internal

Exemplul (vezi MSDN) urmator contine doua fisiere, Assembly1.cs si Assembly2.cs. In
primul fisier se declara o clasa de baza, BaseClass ca fiind internal, in cel de-al doilea
fisier se incearca sa se acceseze membrul din clasa de baza.
Fiser Assembly1.cs:
Dezvoltare aplicatii folosind platforma .NET - 41/233
// Assembly1.cs
// compile with: /target:library
internal class BaseClass
{
public static int IntM = 0;
}
Fisier Assembly2.cs
// Assembly2.cs
// compile with: /reference:Assembly1.dll
// CS0122 expected
class TestAccess
{
public static void Main()
{
BaseClass myBase = new BaseClass();
// eroare, BaseClass nu este vizibila in afara assembly
}
}

Domeniul de accesibilitate
Domeniul de accesibiltate a unui membru specifica unde, in sectiunile programului, un
membru poate fi referit.

Urmatorul exemplu contine tipul T1, si doua clase imbricate M1 si M2.

// cs_Accessibility_Domain.cs
using System;
namespace MyNameSpace
{
public class T1
{
public static int myPublicInt;
internal static int myInternalInt;
private static int myPrivateInt = 0;

public class M1
{
public static int myPublicInt;
internal static int myInternalInt;
private static int myPrivateInt = 0;
}

private class M2
{
public static int myPublicInt = 0;
internal static int myInternalInt = 0;
private static int myPrivateInt = 0;
}
}

public class MainClass
{
public static int Main()
{
// Acces la campurile lui T1:
Dezvoltare aplicatii folosind platforma .NET - 42/233
T1.myPublicInt = 1; // Accesul este nelimitat
T1.myInternalInt = 2; // Accesibil numai in assembly curent
// T1.myPrivateInt = 3; // Eroare: inaccesibil in afara lui T1

// Acces la campurile din M1:
T1.M1.myPublicInt = 1; // Acces nelimitat
T1.M1.myInternalInt = 2; // Accesibil numai in assembly curent
//T1.M1.myPrivateInt = 3; //Eroare: inaccesibil in afara lui M1

// Acces la campurile lui M2:
// T1.M2.myPublicInt = 1; // Eroare: inaccesibil in afara lui T1
// T1.M2.myInternalInt = 2; // Eroare: inaccesibil in afara lui T1
// T1.M2.myPrivateInt = 3; // Eroare: inaccesibil in afara lui M2

return 0;
}
}
}

Memento
Modificatorii de clasa
Fiecare instructiune executabila trebuie plasata in interiorul unei clase sau structuri.
Clasele definesc tipul referinta ce sta la baza construirii blocurilor de instructiuni din C#.

O clasa poate fi:

abstract: O instanta a clasei nu poate fi creata. Aceasta serveste ca o clasa de baza.
sealed: clasa nu poate fi folosita in procesul de derivare, altfel spus nu poate fi clasa de
baza pentru o alta clasa. O clasa nu poate fi abstracta si sealed.
internal: clasa este accesibila numai din alte clase din cadrul aceluiasi assembly. Acesta
constituie accesul implicit pentru tipuri ne-cuibarite. Daca nu se specifica nici un
modificator, implicit este internal.
new: folosit numai cu clase imbricate. "new" indica ca clasa ascunde un membru
mostenit cu acelasi nume.
private: O clasa incuibarita ce poate fi accesata numai in interiorul clasei unde este
definita.
public: Instantele acestei clase sunt disponibile oricarei clase ce doreste sa o acceseze.

Constructori

O clasa defineste date membru, metode si tipuri incuibarite (nested types). Un ctor este o
metoda speciala folosita in mod normal pentru a initializa datele membru ale clasei, si are
acelasi nume ca si clasa. Nu returneaza valori. Pot fi definiti mai multi ctori in cadrul unei
clase. Daca nu s-a definit nici un ctor, compilatorul C# furnizeaza un ctor implicit fara
parametri. Orice camp pe care constructorul nu il initializeaza explicit, este initializat cu
zero sau valoarea null.
Fiecare ctor trebuie sa aiba o semnatura diferita si poate avea modificatori de acces
diferiti.

Dezvoltare aplicatii folosind platforma .NET - 43/233
In C# obiectele se creaza numai folosind cuvintul cheie new.

C# introduce un nou tip de constructor numit constructor static.
Acest lucru se datoreaza faptului ca .NET a implementat un mecanism de gestiune a
memoriei cunoscut sub numele de garbage collection.

using System;
class CtorApp
{
CtorApp()
{
Console.WriteLine("[CtorApp] " + "S-a apelat ctor din
CtorApp");
}

public static void Main()
{
Console.WriteLine("\n[Main] Instantiem un obiect CtorApp ");
CtorApp app = new CtorApp();
}
}

Obiectele sunt instantiate folosind operatorul new ce are urmatoarea sintaxa:

<class> <object> = new <class>(constructor arguments)

In C++ putem crea obiectele pe stiva sau in heap.
In C# instantierea obiectelor este diferita. Locul unde va fi creat obiectul depinde de tipul
ce va fi instantiat (tip valoare creat pe stiva, tip referinta creat in heap).
Operatorul new creaza o noua instanta a clasei, dar nu determina locul unde va fi creat
obiectul.

De exemplu, o declaratie de tipul:

MyClass myClass;

semnifica in C# ca myClass este o variabila de tipul MyClass, dar nu instantiaza obiectul
(ctor-ul nu este apelat). Instantierea se face cu operatorul new, ca in mai jos :

myClass = new MyClass();

Declararea unei variabile de tipul MyClass se foloseste atunci cind dorim sa reutilizam
clasa MyClass in interiorul altei clase. Imbricarea de clase se numeste containment
sau agregare.

Membri statici si membri ai instantei

Putem defini un membru al unei clase ca fiind static sau membru al instantei (instance
member). Implicit fiecare membru al unei clase este instance member ceea ce
inseamna ca o copie a acestui membru este facuta pentru fiecare instanta a clasei.
Dezvoltare aplicatii folosind platforma .NET - 44/233
Pentru un membru static exista o singura copie pentru fiecare instanta a clasei.
Un membru static este creat cand aplicatia ce contine clasa este incarcata si exista atita
timp cat exista aplicatia.
Putem accesa un membru static inainte ca clasa sa fie instantiata.
De exemplu metoda Main, ce constituie punctul de intrare in aplicatie, trebuie declarata
ca fiind statica. De asemenea daca dorim sa monitorizam cate instante ale unui obiect
sunt create pe durata de existenta a aplicatiei putem folosi o data membru statica.
Un membru static trebuie sa aiba o valoare valida si poate fi initializat in momentul
declararii.

using System;
class InstCount
{
public InstCount()
{
instanceCount++;
}
static public int instanceCount;
// instanceCount = 0;
}
class AppClass
{
public static void PrintInstanceCount()
{
Console.WriteLine("[PrintInstanceCount] Now there {0} " +
"{1} instance{2} of the InstCount class",
InstCount.instanceCount == 1 ? "is" : "are",
InstCount.instanceCount,
InstCount.instanceCount == 1 ? "" : "s");
}
public static void Main()
{
PrintInstanceCount();

InstCount ic;
for (int i = 0; i < 2; i++)
{
ic = new InstCount();
Console.WriteLine("[Main] Instantiated a " +
"{0} object...", ic.GetType());
PrintInstanceCount();
}
}
}
Initializarile constructorilor

Toti ctorii obiectelor C# (exceptie ctor System.Object radacina) includ o invocare a
ctorilor clasei de baza inainte de invocarea ctorului obiectului curent.
Aceste initializari ne permit sa specificam ce clasa si ce ctor dorim sa apelam.
Exista doua forme :

Dezvoltare aplicatii folosind platforma .NET - 45/233
base()

ce permite sa apelam ctor clasei de baza (legat de numarul de parametri si tipul acestora).

this()

permite clasei curente sa apeleze un alt constructor.

Exemplu

using System;
class Baza
{
public Baza()
{
Console.WriteLine("[ClasadeBaza.Baza] " +
"Constructor apelat");
}
}
class Derivata: Baza
{
public Derivata()
{
Console.WriteLine("[Clasaderivata.Derivata] " +
"Constructor apelat");
}
}
class Initializare
{
public static void Main()
{
Console.WriteLine("[Main] Instantierea unui obiect Derivata " +
"obiect Derivata");
Derivata d = new Derivata();
}
}

Acelasi lucru ca mai sus, dar in clasa derivata la ctor se apeleaza base(...):
Efectul este acelasi.

using System;
class Baza
{
public Baza()
{
Console.WriteLine("[ClasadeBaza] " +
"Constructor apelat");
}
}
class Derivata : Baza
{
public Derivata()
: base()
{
Dezvoltare aplicatii folosind platforma .NET - 46/233
Console.WriteLine("[ClasaDerivata.Derivata] " +
"Constructor apelat");
}
}
class Initializare
{
public static void Main()
{
Console.WriteLine("[Main] Instantierea unui obiect Derivata " +
"obiect Derivata ");
Derivata d = new Derivata();
}
}

using System;
class Baza
{
public Baza()
{
Console.WriteLine("[ClasadeBaza.Baza()] " +
"Apelat ctor fara parametri");
}
public Baza(int n)
{
Console.WriteLine("[ClasadeBaza.Baza(int n)] " +
"n = {0}", n);
}
}
class Derivata: Baza
{
public Derivata(int n)
{
Console.WriteLine("[ClasaDerivata.Derivata] " +
"n = {0}", n);
}
}
class Initializare
{
public static void Main()
{
Console.WriteLine("[Main] Instantierea unui obiect " +
"Derivata ...");
Derivata d = new Derivata(10);

}
}

using System;

class Baza
{
public Baza()
{
Console.WriteLine("[Baza()] " +
"Apelat ctor fara parametri");
}
Dezvoltare aplicatii folosind platforma .NET - 47/233
public Baza(int n)
{
Console.WriteLine("[Baza(int n)] " +
"n = {0}", n);
}
}

class Derivata: Baza
{
public Derivata(int n) : base(n)
{
Console.WriteLine("[Derivata] " +
"n = {0}", n);
}
}

class Initializare
{
public static void Main()
{
Console.WriteLine("[Main] Instantierea unui obiect " +
"Derivata...");
Derivata d = new Derivata(10);

}
}

Dezvoltare aplicatii folosind platforma .NET - 48/233

Specificarea informatiei la run-time in lista de initializare a constructorului

Probleme legate de membrii statici si membrii instanta in initializarea constructorilor.
In C# numai membrii statici pot fi utilizati, in lista de initializare, cand se apeleaza
constructorii din clasa de baza. Mai exact, daca ctor din clasa derivata nu are parametri
iar ctor din clasa de baza are parametri atunci in lista de initializare trebuie specificati
membri statici.

Vezi urmatorul exemplu:

class Baza
{
protected Baza(int i) { }
};

class Derivata: Baza
{
int i;

// ERROR: The compiler states that the following will not
// compile because an object reference is needed for
// nonstatic fields. However, if you qualify the i parameter
// with the this value--base(this.i)--you will still receive
// an error, this time indicating that the this keyword cannot
// be used in this context (constructor initializer).

Derivata() : base(i) { }
};

O alta incercare de rezolvare a problemei ar fi:

class Baza
{
public Baza(string fileName)
{
}
}

enum TableId
{
Curs,
Laborator,
Seminar
};

class Derivata: Baza
{
public Derivata(TableId tableId)
{
}
}

Dezvoltare aplicatii folosind platforma .NET - 49/233
Eroarea este de acelasi tip, nu exista ctor fara parametri.

Rezolvarea consta in a folosi o metoda statica in lista de initializare a ctorului.
Vezi codul:

using System;
class Baza
{
public Baza(string fileName)
{
Console.WriteLine("[Baza." +
" nume fisier = {0}", fileName);

}
}

enum TableId
{
Curs,
Laborator,
Seminar
};

class Derivata: Baza
{

public Derivata(TableId tableId) : base(GetFileName(tableId))
{
Console.WriteLine( [Derivata.] id tabela = {0} ,
tableId.ToString()) ;
}

static string GetFileName(TableId tableId)
{
string fileName;

switch(tableId)
{
case TableId.Curs:
fileName = "curs.txt";
break;

case TableId.Laborator:
fileName = "laborator.txt";
break;

case TableId.Seminar:
fileName = "seminar.txt";
break;

default:
throw new ArgumentException(
"[GetFileName] Nu se poate determina numele
tabelei");
}
Dezvoltare aplicatii folosind platforma .NET - 50/233

return fileName;
}

}


class Initializare
{
public static void Main()
{
Console.WriteLine("[Main] Instantiere Derivata");
Derivata d = new Derivata (TableId.Curs);
}
}

Metode

Metodele sunt totdeauna definite in interiorul clasei sau structurii.
Metodele pot fi:
instance (apelata ca o instanta a tipului in interiorul caruia metoda a fost definita)
sau
static, unde metoda este asociata cu tipul insusi.
Metodele pot fi declarate ca virtual, abstract sau sealed.
Metodele pot fi supraincarcate, suprascrise si ascunse (overloaded, overridden si
hidden).

Modificatorii de acces pentru metode

Sunt folositi ca parti in sintaxa de declarare a metodelor si pot fi:

internal
private
protected
protected internal (Microsoft spune ca este nivel de acces)
public

Implicit se considera acces private.

public indica ca o metoda este accesibila atit in interiorul cat si in exteriorul clasei in care
a fost definita.
internal inseamna ca metoda este accesibila numai tipurilor definite in acelasi assembly.
protected metoda este accesibila in tipul in care a fost definita si in tipurile derivate din
acest tip. Acesta este folosit pentru a da accesul claselor derivate la metodele din clasa de
baza.
protected internal metoda este accesibila tipurilor definite in acelasi assembly sau
tipurilor dintr-un assembly derivat.
private metodele sunt accesibile numai in calsa unde au fost definite.
Dezvoltare aplicatii folosind platforma .NET - 51/233
metodele sealed sunt metode care suprascriu o metoda virtuala mostenita avand aceeasi
signatura. Cand o metoda este sealed, aceasta nu poate fi suprascrisa intr-o clasa
derivata.



Modificator
de acces
Restrictii
public
Nici o restrictie. Atributele si metodele marcate cu public sunt vizibile oricarei
metode din orice clasa.
private
Atributele si metodele din clasa A care sunt marcate ca private sunt accesibile
numai metodelor din clasa A.
protected
Atributele si metodele din clasa A care sunt marcate ca protected sunt accesibile
metodelor din clasa A precum si metodelor din clasele derivate din clasa A.
internal
Atributele si metodele din clasa A care sunt marcate ca internal sunt accesibile
metodelor din orice clasa a assembly A.
protected
internal
Atributele si metodele din clasa A care sunt marcate ca protected internal sunt
accesibile metodelor clasei A, metodelor claselor derivate din A si de asemenea
oricarei clase din assembly A. Acesta este in mod efectiv protejat.

Accesul membrilor

Accesibilitate declarata: public, protected, internal, protected internal, private.

Namespaces sunt implicit public. Nu sunt permisi modificatori de acces pentru namespace.

Tipurile declarate in unitatile de compilare sau namespaces pot avea accesibilitatea public sau
internal (implicit internal).
Membrii clasei pot avea accesibilitatea: public, protected, internal, protected internal,
private.
Membrii interfetei au implicita accesibilitatea public. Nu este permisa modificarea accesibilitatii.

Parametrii transmisi prin valoare si referinta
Valoarea parametrilor transmisi prin valoare ramine neschimbata in programul apelant.
Modificarile valorilor parametrilor transmisi prin referinta sunt vazute in programul
apelant.
Vedeti urmatoarele exemple:

class SomeClass
{
Dezvoltare aplicatii folosind platforma .NET - 52/233
public int ChangeInt(int val)
{
return val*2;
}
}

class ValRefTest
{
static void Main(string[] args)
{
SomeClass sc = new SomeClass();
int val1 = 3;
int val2 = sc.ChangeInt(val1);
Console.WriteLine("val1 = {0}, val2 = {1}",
val1, val2);
}
}
Iesirea va fi:
val1 = 3, val2 = 6
Cand folosim parametri tip referinta se transmite o copie a referintei (o alta referinta la
acceasi data).
Vezi urmatorul cod:
class AnotherClass
{
public int ID;
}
class SomeClass
{
public AnotherClass ChangeObject(AnotherClass ref1)
{
ref1.ID = ref1.ID*2;
return ref1;
}
}
class ValRefTest
{
static void Main(string[] args)
{
AnotherClass ref1 = new AnotherClass();
ref1.ID = 3;
AnotherClass ref2 = sc.ChangeObject(ref1);
Console.WriteLine("ref1.ID = {0}, ref2.ID = {1}",
ref1.ID, ref2.ID);
}
}
Iesirea va fi:
ref1.ID = 6, ref2.ID = 6
Dezvoltare aplicatii folosind platforma .NET - 53/233
In ambele cazuri se transmite o copie a parametrilor din programul apelant catre
programul apelat. In primul caz se transmite o copie a valorii, iar in al doilea caz se
transmite o copie a referintei.
ref ca parametrii ai metodei
Cuvantul cheie ref spune compilatorului C# ca argumentele pasate puncteaza la aceeasi
memorie ca si variabilele din codul apelant. Daca metoda apelata modifica valorile,
modificarile sunt vazute in codul apelant.
Restrictie:
Cand folosim ref, trebuie sa initializam argumentele inainte de a le folosi ca argumente
ale functiei.
Exemplu:

class Color
{
public Color()
{
this.red = 0;
this.green = 127;
this.blue = 255;
}

protected int red;
protected int green;
protected int blue;

public void GetRGB(
ref int red, ref int green, ref int blue)
{
red = this.red;
green = this.green;
blue = this.blue;
}
}

class Class1
{
static void Main(string[] args)
{
Color c = new Color();
int red = 0;
int green = 0;
int blue = 0;
c.GetRGB(ref red, ref green, ref blue);
Console.WriteLine("R={0}, G={1}, B={2}",
red, green, blue);
}
}
Dezvoltare aplicatii folosind platforma .NET - 54/233
Iesirea va fi:
R=0, G=127, B=255

out ca parametrii ai metodei
Are acelasi efect ca si ref, dar diferenta principala este ca nu e necesara initializarea
parametrilor inainte de apel.
Exemplu:

class Color
{
public Color()
{
this.red = 0;
this.green = 127;
this.blue = 255;
}

protected int red;
protected int green;
protected int blue;

public void GetRGB(out int red, out int green,
out int blue)
{
red = this.red;
green = this.green;
blue = this.blue;
}
}

class Class1
{
static void Main(string[] args)
{
Color c = new Color();
int red;
int green;
int blue;
c.GetRGB(out red, out green, out blue);
Console.WriteLine("R={0}, G={1}, B={2}",
red, green, blue);
}
}
Parametrii prefixati cu out trebuiesc modificati in metoda apelata, iar parametrii
prefixati cu ref pot fi modificati.
Dezvoltare aplicatii folosind platforma .NET - 55/233
Supraincarcarea metodelor
Aceleasi nume de metoda dar cu parametri diferiti. Nu are importanta daca returneaza
tipuri diferite si nu au aceeasi modificatori de acces.
Exemplu:

class Log
{
public Log(string fileName)
{
// Open fileName and seek to end.
}

public void WriteEntry(string entry)
{
Console.WriteLine(entry);
}

public void WriteEntry(int resourceId)
{
Console.WriteLine(
"Retrieve string using resource id and write to log");
}
}

class Class1
{
static void Main(string[] args)
{
Log log = new Log("My File");
log.WriteEntry("Entry one");
log.WriteEntry(42);
}
}
Urmatoarele metode sunt considerate supraincarcari corecte:
public void WriteEntry(string entry, int resourceId)
{
Console.WriteLine(entry + " " + resourceId);
}

public void WriteEntry(int resourceId, string entry)
{
Console.WriteLine(resourceId + " " + entry);
}
In timp ce modificatorii ref si out sunt suficienti pentru a face diferenta de la o versiune la
alta a unei metode, compilatorul considera ref si out echivalenti. Din acest motiv
metodele urmatoare sunt corecte, dar nu luate impreuna (ori una, ori alta):
public void WriteEntry(ref string entry)
{
Dezvoltare aplicatii folosind platforma .NET - 56/233
Console.WriteLine(entry);
}

public void WriteEntry(out string entry)
{
entry = "Ok";
Console.WriteLine(entry);
}
Supraincarcarea constructorilor
Daca scriem un constructor cu parametri, compilatorul nu mai genereaza constructorul
implicit fara parametri.
class File
{
}

class CommaDelimitedFile
{
public CommaDelimitedFile(String fileName)
{
Console.WriteLine("Constructed with a file name");
}

public CommaDelimitedFile(File file)
{
Console.WriteLine("Constructed with a file object");
}
}

class Class1
{
static void Main(string[] args)
{
File file = new File();
CommaDelimitedFile file2 =
new CommaDelimitedFile(file);
CommaDelimitedFile file3 =
new CommaDelimitedFile("Some file name");
}
}
Nu putem declara CommaDelimitedFile astfel:
CommaDelimitedFile file4 =
new CommaDelimitedFile();
Daca suprimam constructorii definiti, atunci compilatorul va genera ctor implicit fara
parametri si urmatorul cod va fi corect:
class File
{
}
Dezvoltare aplicatii folosind platforma .NET - 57/233

class CommaDelimitedFile
{
/* public CommaDelimitedFile(String fileName)
{
Console.WriteLine("Constructed with a file name");
}

public CommaDelimitedFile(File file)
{
Console.WriteLine("Constructed with a file object");
}
*/
}

class Class1
{
static void Main(string[] args)
{
File file = new File();
// CommaDelimitedFile file2 =
// new CommaDelimitedFile(file);
// CommaDelimitedFile file3 =
// new CommaDelimitedFile("Some file name");

CommaDelimitedFile file4 =
new CommaDelimitedFile();
}
}
Problema de mai sus (se apeleaza ctor fara parametri dintr-un ctor cu parametri) se
rezolva folosind cuvintul cheie this.
Vezi urmatorul cod:

public CommaDelimitedFile(String fileName) : this()
si expresia this() rezolva un apel la:
public CommaDelimitedFile()
{}
care trebuie sa fie disponibil.
Putem folosi o lista de initializare a constructorului pentru a apela explicit un constructor
din alt constructor:
public CommaDelimitedFile() : this("Default string")
{}
O metoda este considerata supraincarcata si in cazul cind exista versiuni diferite ale
acesteia numai in clasa de baza.
Dezvoltare aplicatii folosind platforma .NET - 58/233
Exemplu:

class Log
{
public Log(string fileName) {}
public Log() {}

public void WriteEntry(string entry)
{
Console.WriteLine(entry);
}
}

class LogEx : Log
{
public LogEx(string fileName) {}

public void WriteEntry(int resourceId)
{
Console.WriteLine(
"Retrieve resource id and write to log");
}
}

class Class1
{
static void Main(string[] args)
{
LogEx log = new LogEx("My File");
log.WriteEntry("Entry one");
log.WriteEntry(42);
}
}
Iesirea va fi:
Entry one
Retrieve resource id and write to log
Dezvoltare aplicatii folosind platforma .NET - 59/233

Mostenire. Metode virtuale
Suprascrierea metodelor
Sa analizam urmatorul exemplu, atentia fiind indreptata catre functia Calcul.
class Student
{
public void Calcul()
{
Console.WriteLine("Student.Calcul()");
}
}
Acum derivam o clasa din Student si suprascriem metoda Calcul pentru a realiza altceva
decit in clasa de baza.
class Boboc: Student
{
new public void Calcul()
{
Console.WriteLine("Boboc.Calcul()");
}
}

class Test
{
static void Main(string[] args)
{
Student s = new Student();
s.Calcul();

Boboc b = new Boboc();
b.Calcul();
}
}
Rezultatul este:
Student.Calcul()
Boboc.Calcul()
Ce se intimpla daca omitem cuvintul new?
Acest cod se compileaza si rezultatul este identic. In acest caz este implicit.
Cuvintul cheie new este cerut pentru metoda Boboc.Calcul() pentru ca aceasta ascunde
membrul mostenit Student.Calcul().
Dezvoltare aplicatii folosind platforma .NET - 60/233
Cele doua metode din clasa de baza si din cea derivata nu sunt in nici un fel de relatie.
Faptul ca au aceeasi semnatura este o simpla coincidenta.
Polimorphism
Metodele suprascrise cu new lucreaza bine daca avem o referinta la un obiect derivat.
Ce se intimpla daca avem o referinta in sus in ierarhie (upcast reference) la o clasa de
baza si dorim ca compilatorul sa apeleze metoda din clasa derivata?
Aceasta problema este rezolvata de polimorfism, ce permite sa definim o metoda de mai
multe ori in cadrul ierarhiei de clase astfel incit la runtime sa se apeleze versiunea corecta
pentru obiectul specificat.
Presupunem ca vrem sa populam un vector cu obiecte din clasa de baza si din clasa
derivata. Dar cum un vector contine elemente de acelasi tip, vom fi obligati sa folosim
obiecte din clasa de baza. Cind vom itera elementele acestui vector dorim sa fie apelata
metoda corecta pentru fiecare obiect, chiar daca obiectul este din clasa derivata si este
salvat ca upcast la clasa de baza, in cadrul acestui vector.
Consideram exemplul urmator in care am mai adaugat o clasa, Absolvent. Clasa aplicatiei
principale (contine Main) un tablou cu elemente de tipul Student si inca doua metode:
Load incarca un obiect Student in vector, si Scan ce itereaza elementele vectorului,
apelind pentru fiecare, metoda Calcul.
class Student
{
public string name;
public Student (string name)
{
this.name = name;
}
public void Calcul()
{
Console.WriteLine("Student.Calcul() Name ={0} ", name);
}
}

class Boboc: Student
{
public Boboc(string name) : base(name){}
new public void Calcul()
{
Console.WriteLine("Boboc.Calcul() Name = {0}", name);
}
}


class Absolvent: Student
{
public Absolvent(string name) : base(name)
Dezvoltare aplicatii folosind platforma .NET - 61/233
{
}

public new void Calcul ()
{
Console.WriteLine(
"Absolvent.Calcul() Name = {0}",
name);
}
}

class TestNotPolymorphic
{
protected Student[] studenti;

public void Load ()
{
// Simulare incarcare.
studenti = new Student[2];
studenti[0] = new Absolvent(
"Absolvent");
studenti[1] = new Boboc(
"Boboc");
}

public void Scan()
{
for (int i = 0; i < Studenti.GetLength(0); i++)
{
studenti[i].Calcul();
}
}

static void Main(string[] args)
{
TestNotPolymorphic t = new TestNotPolymorphic();
t.Load();
t.Scan();
}
}
Rezultatul este:
Student.Calcul Name = Absolvent
Student.Calcul Name = Boboc
Nu e ceea ce am vrut. In fiecare caz s-a apelat metoda din clasa de baza. In fapt s-a folosit
early binding legarea timpurie, realizata de compilator.
Noi avem nevoie de adresa corecta a functiei in timpul executiei, altfel spus late
binding.
Pentru a realiza acest lucru vom folosi cuvintele cheie virtual si override pentru metoda
in cauza.
Dezvoltare aplicatii folosind platforma .NET - 62/233
virtual se foloseste in clasa de baza.
override se foloseste in clasa derivata.
Exemplu:
class Student
{
public string name;
public Student (string name)
{
this.name = name;
}
public virtual void Calcul()
{
Console.WriteLine("Student.Calcul() Name ={0} ", name);
}
}

class Boboc: Student
{
public Boboc(string name) : base(name){}
public override void Calcul()
{
Console.WriteLine("Boboc.Calcul() Name = {0}", name);
}
}


class Absolvent: Student
{
public Absolvent(string name) : base(name)
{
}

public override void Calcul ()
{
Console.WriteLine(
"Absolvent.Calcul() Name = {0}",
name);
}
}

class TestPolymorphic
{
protected Student[] studenti;

public void Load ()
{
// Simulare incarcare.
studenti = new Student[2];
studenti[0] = new Absolvent(
"Absolvent");
studenti[1] = new Boboc(
"Boboc");
}
Dezvoltare aplicatii folosind platforma .NET - 63/233

public void Scan()
{
for (int i = 0; i < Studenti.GetLength(0); i++)
{
studenti[i].Calcul();
}
}

static void Main(string[] args)
{
TestPolymorphic t = new TestPolymorphic();
t.Load();
t.Scan();
}
}

Rezultatul este:
Absolvent.Calcul Name = Absolvent
Boboc.Calcul Name = Boboc
Functia suprascrisa trebuie sa aiba acelasi nivel de acces (protected, public, samd)
ca si functia virtuala pe care o suprascrie.
Un membru virtual nu poate fi declarat privat.
Metode new si virtual
Putem combina new si virtual cand declaram o functie. Pentru ca este new ea ascunde
orice functie mostenita cu aceeasi semnatura. Pentru ca este virtuala ea poate fi
suprascrisa in clasele derivate.
Exemplu

class Student
{
public string name;
public Student (string name)
{
this.name = name;
}
public virtual void Calcul()
{
Console.WriteLine("Student.Calcul() Name ={0} ", name);
}
}

class Boboc: Student
{
public Boboc(string name) : base(name){}
public new override void Calcul()
Dezvoltare aplicatii folosind platforma .NET - 64/233
{
Console.WriteLine("Boboc.Calcul() Name = {0}", name);
}
}


class Absolvent: Boboc
{
public Absolvent(string name) : base(name)
{
}

public override void Calcul ()
{
Console.WriteLine(
"Absolvent.Calcul() Name = {0}",
name);
}
}

class TestPolymorphic
{
protected Student[] studenti;

public void Load ()
{
// Simulare incarcare.
studenti = new Student[2];
studenti[0] = new Absolvent(
"Absolvent");
employees[1] = new Boboc(
"Boboc");
}

public void Scan()
{
for (int i = 0; i < Studenti.GetLength(0); i++)
{
studenti[i].Calcul();
}
}

static void Main(string[] args)
{
TestPolymorphic t = new TestPolymorphic();
t.Load();
t.Scan();
}
}


Urmatoareledouadeclaratiisuntidentice:

public new virtual void Calcul()
//public virtual void Calcul()
Dezvoltare aplicatii folosind platforma .NET - 65/233
Metode statice

Metoda statica nu este specifica fiecarei instante a clasei.
Nu e nevoie ca sa existe creat obiectul pentru a fi apelata.
Exemplu:

class StaticMethod
{
public static void Calcule()
{
Console.WriteLine("Asteptati! Se calculeaza ...");
}
}

class App
{
static void Main(string[] args)
{
StaticMethod.Calcule();
}
}
Definirea unei metode ca fiind statica se face cu ajutorul cuvantului cheie static.
Sintaxa de apel este:
Class.Method(..);
Urmatorul exemplu este eronat:
static void Main(string[] args)
{
StaticMethod.Calcule();
StaticMethod stm = new StaticMethod();
stm.Calcule(); // Eroare
}

Accesul la membrii clasei

Ce membrii din cadrul clasei putem accesa din cadrul unei metode statice?
Pot fi accesati membrii statici, nu cei de instanta.
O metoda nestatica poate accesa membri statici cit si membri ai instantei.
Cuvintul cheie this se foloseste numai cu membrii nonstatici.

Dezvoltare aplicatii folosind platforma .NET - 66/233
Constructori statici
Un constructor static este manipulat intr-un mod special: putem avea un singur ctor static
si fara parametri si normal nu poate apela membri ai instantei, inclusiv pointerul this.
Un ctor static este executat inaintea primei instante ce se vrea creata pentru acea clasa.
Modificatorii de acces nu sunt permisi pentru ctor static.

class SomeClass
{
public static int x;
public int y;

// static public SomeClass() // Access modifiers illegal
// static SomeClass(int i, int j) // Parameters illegal
static SomeClass()
{
x = 1;
// this.y = 2; // this illegal in static method
Console.WriteLine("SomeClass initialized");
}
}

class Test
{
static void Main(string[] args)
{
SomeClass sc = new SomeClass();
}
}
Iesirea este:
SomeClass initialized
Contrar regulilor de supraincarcare, putem furniza un ctor nonstatic cu aceeasi semnatura
ca si un ctor static. Ctor static va fi apelat primul.
public SomeClass()
{
Console.WriteLine("Non-static ctor");
}
Rezultatul va fi:
SomeClass initialized
Non-static ctor
Ctor static este executat inaintea oricarui membru static, fie data sau functie.
class SomeClass
{
Dezvoltare aplicatii folosind platforma .NET - 67/233
public static int x;

static SomeClass()
{
x = 1;
Console.WriteLine("SomeClass initialized");
}

public SomeClass()
{
Console.WriteLine("Non-static ctor");
}

public static void Metoda()
{
Console.WriteLine("Metoda");
}
}

class Test
{
static void Main(string[] args)
{
// Oricare din aceste trei linii de cod
// va invoca ctor static
SomeClass sc = new SomeClass();
SomeClass.Metoda();
Console.WriteLine(SomeClass.x);
}
}
Dezvoltare aplicatii folosind platforma .NET - 68/233
Proprietati
Proprietatile nu permit accesul direct la membri. Sunt furnizati doi accesori (set, get) ce
lucreaza direct cu data membru. Scopul este de a pastra date cit mai corecte in obiectul
instantiat.
Definirea si folosirea proprietatilor
O propritate in C# consta din declararea unui camp si a unui accesor folosit pentru a-i
modifica valoarea. Accesorii sunt referiti ca metode setter si getter.
[attributes] [modifers] <type> <property-name>{
[ set { <accessor-body> } ]
[ get { <accessor-body >} ]
}
Observatii
Nu e nevoie sa definim ambii accesori.
get face proprietatea read
set face proprietatea write
proprietatea nu poate fi utilizata ca parametru intr-o metoda.
modificatorul static poate fi utilizat pentru proprietate.

// Example Address class using C# properties
class Address
{
protected string city;
protected string zipCode; // data membru

public string ZipCode // proprietate
{
get { return zipCode; }
set
{
// Validate value against some data store.
zipCode = value; // value cuvant rezervat
// Update city based on validated zipCode.
}
}
}

In client

Address addr = new Address();
addr.ZipCode = "30338";
string zip = addr.ZipCode;

Mostenirea proprietatilor
Dezvoltare aplicatii folosind platforma .NET - 69/233
Modificatorii de acces trebuiesc specificati la nivel de proprietate.
Suprascrierea proprietatilor mostenite
Trebuie specificata proprietatea ca fiind virtual in clasa de baza pentru a o suprascrie, iar
in clasa derivata override.
using System;

class CSVFile
{
public CSVFile(string fileName)
{
this.fileName = fileName;
}

protected string fileName;
public virtual string FileName
{
get { return fileName; }
}
}

class CustomerTable : CSVFile
{
public const string FILENAME = "customers.txt";

public CustomerTable() : base(FILENAME) {}

public override string FileName
{
get { return "Customers table"; }
}
}

class OverrideProperties
{
public static void Main()
{
Console.WriteLine("\nInstantiating a CSV object for " +
"the Customer file...");
CSVFile customerFile =
new CSVFile(CustomerTable.FILENAME);
Console.WriteLine("Customer file name = {0}",
customerFile.FileName);

Console.WriteLine("\nInstantiating a Customer " +
"Table object...");
CustomerTable customerTable = new CustomerTable();
Console.WriteLine("Customer file name = {0}",
customerTable.FileName);

Console.ReadLine();
}
}
Dezvoltare aplicatii folosind platforma .NET - 70/233
Intr-o clasa abstracta o proprietate abstracta arata ca in exemplul de mai jos:
abstract class AbstractBaseClass
{
public abstract double AbstractProperty
{
get;
set;
}
}
In exemplul urmator este descrisa suprascrierea proprietatilor mostenite.
using System;
using System.Collections;

abstract class Employee
{
protected Employee(int employeeId, int hoursWorked)
{
this.employeeId = employeeId;
HoursWorked = hoursWorked;
}

protected int employeeId;
public int EmployeeId
{
get { return employeeId; }
}

protected int HoursWorked;

protected double hourlyCost = -1; // dummy init value
public abstract double HourlyCost
{
get;
}
}

class ContractEmployee : Employee
{
public ContractEmployee(int employeeId, double hourlyWage,
int hoursWorked)
: base(employeeId, hoursWorked)
{
HourlyWage = hourlyWage;
}

protected double HourlyWage;
public override double HourlyCost
{
get
{ return HourlyWage; }
}
}

Dezvoltare aplicatii folosind platforma .NET - 71/233
class SalariedEmployee : Employee
{
public SalariedEmployee(int employeeId, double salary,
int hoursWorked)
: base(employeeId, hoursWorked)
{
Salary = salary;
}

protected double Salary;
public override double HourlyCost
{
get
{
return (Salary / 52) / HoursWorked;
}
}
}

class OverrideProperties
{
public static ArrayList employees = new ArrayList();

public static void PrintEmployeesHourlyCostToCompany()
{
foreach (Employee employee in employees)
{
Console.WriteLine("{0} employee (id={1}) costs {2}" +
" per hour",
employee,
employee.EmployeeId,
employee.HourlyCost);
}
}

public static void Main()
{
ContractEmployee c = new ContractEmployee(1, 50, 40);
employees.Add(c);

SalariedEmployee s =
new SalariedEmployee(2, 100000, 65);
employees.Add(s);

PrintEmployeesHourlyCostToCompany();

Console.ReadLine();
}
}


Dezvoltare aplicatii folosind platforma .NET - 72/233
Atribute. Interfete.

Atribute
Ideea de baza: autodescrierea componentelor. Astfel compilatorul si CLR pot citi aceste
informatii. De asemenea dezvoltatorul componentei are toate informatiile despre
componenta intr-un singur loc.
Atributele ne furnizeaza informatiile asociate cu tipul definit in C#. Important e ca
aceasta informatie poate fi definita de dezvoltatorul tipului si nu este statica, legata de
limbaj.
Aceasta asociere de informatie urmeaza aceleasi principii folosite in dezvoltarea XML.
Putem crea un atribut bazat pe orice informatie dorim. Exista un mecanism standard
pentru definirea atributelor si pentru interogarile membrului sau tipului la run time.

Toate atributele, preconstruite sau definite de utilizator, sunt derivate direct sau indirect
din System.Attribute.
Atributele mostenesc o anumita comportare implicita:
atributul poate fi asociat cu orice element tinta;
poate fi sau nu poate fi mostenit de un element derivat;
instante multiple pot fi permise sau nu pe acelasi element tinta.
Aceste comportari sunt specificate in AttributeUsageAttribute.

Un atribut este o adnotare ce poate fi plasata pe un element al codului sursa si este folosit
pentru a memora in momentul compilarii, informatii specifice aplicatiei. Aceasta
informatie este memorata in metadata si poate fi accesata fie in timpul executiei
aplicatiei, prin procesul cunoscut sub numele de reflection sau cand un alt utilitar citeste
metadata. Atributele pot schimba comportarea aplicatiei in momentul executiei.
Exemplu
Urmatorul exemplu creaza si atribuie atribute definite de utilizator unei anumite clase.
Atributul contine numele programatorului si versiunea clasei.

using System;

[AttributeUsage(AttributeTargets.Class|
AttributeTargets.Struct,
AllowMultiple=true)]
public class Autor : Attribute
{
string nume;
public double versiune;

public Autor(string nume)
{
this.nume = nume;
versiune = 1.0;
}

public string getNume()
{
return nume;
Dezvoltare aplicatii folosind platforma .NET - 73/233
}
}

[Autor("Un autor")]
class A
{
/*...*/
}

class B // nu are atributul Autor
{
/*...*/
}

[Autor("Un autor"),
Autor("Alt autor", versiune=1.1)]
class C
{
/*...*/
}

class Test
{
public static void Main()
{
PrintAutorInfo(typeof(A));
PrintAutorInfo(typeof(B));
PrintAutorInfo(typeof(C));
}
public static void PrintAutorInfo(Type type)
{
Console.WriteLine("Informatii despre autor pentru {0}",
type);
Attribute[] att = Attribute.GetCustomAttributes(type);
foreach(Attribute a in att)
{
if (a is Autor)
{
Autor autor = (Autor)a;
Console.WriteLine(" {0}, versiune {1:f}",
autor.getNume(),
autor.versiune);
}
}
Console.WriteLine();
}
}

Iesirea este:
Informatii despre autor pentru A
Un autor, versiune 1.00
Informatii despre autor pentru B
Informatii despre autor pentru C
Un autor, versiune 1.00
Alt autor, versiune 1.10
Dezvoltare aplicatii folosind platforma .NET - 74/233

Definirea atributelor

Un atribut este in momentul de fata o clasa derivata din System.Attribute.

Sintaxa folosita pentru a atasa un atribut la un tip sau membru este asemanatoare cu cea
folosita la instantierea unei clase.

Interogarea atributelor

Pentru a interoga un tip sau un membru despre atributele atasate acestuia, trebuie sa
utilizam reflection.

Atribute la nivel de clasa

Exemplu : Presupunem ca dorim sa definim un atribut ce va defini calculatorul pe care
va fi creat un obiect. Daca nu folosim atribute, atunci trebuie sa salvam aceasta
informatie intr-o constanta sau un fisier de resurse al aplicatiei.
Cu ajutorul atributelor, adnotam clasa cu calculatorul pe care vom crea obiectele astfel :

public enum Computers
{
THOR,
FENRIR,
TROIA
}

public class AtributCursAttribute : Attribute
{
public AtributCursAttribute(Computers Server)
{
this.server = Server;
}

protected Computers server;
public string Server
{
get
{
return Computers.GetName(
typeof(Computers), this.server);
/*
Daca folosim:
return RemoteServers.GetName(typeof(Computers), 1 );
rezultatul este FENRIR
Vezi Enum in .NET FCL (Framework Class Library).
*/
}
}
}

Dezvoltare aplicatii folosind platforma .NET - 75/233
[AtributCurs(Computers.TROIA)]
class Curs{}

Pentru a determina serverul pe care se creaza obiectul putem folosi urmatorul cod
(explicatiile sunt comentate in cod):

class Test
{
[STAThread]
static void Main(string[] args)
{
//
// Obtinem tipul obiectului asociat cu Curs
//
Type type = typeof(Curs);
//
// Dupa ce am obtinut obiectul, putem incepe interogarea lui
//
/*
GetCustomAttributes returneaza un vector ale carui elemente sunt
atributele obiectului. Parametrul true specifica daca cautarea
se face si pentru membrii mosteniti. Din cauza ca nu avem
mostenire in acest caz, putem folosi true/false cu acelasi
efect.
*/
foreach (Attribute attr in
type.GetCustomAttributes(true))
{
/*
Se incearca conversia atributului la
AtributCursAttribute.
Daca conversia nu reuseste atunci
remoteAttr = null;
*/
AtributCursAttribute remoteAttr =
attr as AtributCursAttribute;
if (null != remoteAttr)
{
Console.WriteLine(
"Obiect creat pe {0}.",
remoteAttr.Server);
}
}
}
}

Rezultatul este:

Obiect creat pe TROIA.
Pentru a defini mai multe atribute la o clasa vezi si exemplul din pagina web.

Dezvoltare aplicatii folosind platforma .NET - 76/233
Atribute la nivel de metoda

Codul pentru interogarea atributelor unei metode este diferit de cel folosit la interogarea
atributelor unei clase.

Explicam acest lucru pe baza unui exemplu.

namespace AtributeMetode
{
public class AtributTranzactie : Attribute
{
public AtributTranzactie()
{
Console.WriteLine(Ctor AtributTranzactie);
}
}

class Atx
{
[AtributTranzactie]
public void Txt1(){}

public void NonTxt(){}

[AtributTranzactie]
public void Txt2(){}
}

class Test
{
[STAThread]
static void Main(string[] args)
{
// Clasa se califica cu numele spatiului
// pentru ca am definit un namespace
Type type = Type.GetType("AtributeMetode.Atx");
foreach (MethodInfo method in type.GetMethods())
{
foreach (Attribute attr in
method.GetCustomAttributes(true))
{
if (attr is AtributTranzactie)
{
Console.WriteLine(
"Metoda
{0} are atributul AtributTranzactie.",
method.Name);
}
}
}
}
}
}

Iesirea este:
Dezvoltare aplicatii folosind platforma .NET - 77/233
AtributTranzactie ctor
Metoda Txt1 are atributul AtributTranzactie.
AtributTranzactie ctor
Metoda Txt2 are atributul AtributTranzactie.

Observatie:
Cand atasam un atribut cu un constructor fara parametri, nu mai este nevoie de paranteze.

Cateva explicatii:
Type.GetMethods = regaseste un vector ce are elemente obiecte de tip
MethodInfo.
Fiecare element contine informatii despre o metoda din clasa Atx.
MethodInfo.GetCustomAttributes = regaseste toate atributele create de
utilizator pentru o metoda.
Pentru a testa daca metoda are atributul AtributTranzactie folosim operatorul is.

Exemplu 2
Se ataseaza atributul Serializable (atribut preconstruit) in vederea serializarii datelor unei
clase. Vezi codul ce urmeaza (ex. din internet).

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Soap;
[Serializable]
public class User
{
public string userID;
public string password;
public string email;
public string city;
public void Save(string fileName)
{
FileStream s=new FileStream(fileName,FileMode.Create);
SoapFormatter sf=new SoapFormatter();
sf.Serialize(s,this);
}
static void Main(string[] args)
{
User u=new User();
u.userID="firstName";
u.password="Zxfd12Qs";
u.email="asdf@qwer.com";
u.city="TheCity";
u.Save("user.txt");
}
}

Atribute pentru campuri

Dezvoltare aplicatii folosind platforma .NET - 78/233
Vom explica atributele pentru campuri pe baza unui exemplu, in care incercam sa
validam lungimea unor siruri de caractere (user, password, email).
user si password : cu lungimea intre 4 si 8 caractere ;
email : cu lungimea intre 4 si 60 caractere ;
Dorim validarea acestor campuri inainte de serializare. In caz ca cel putin un camp este
invalid se abandoneaza operatia de serializare.
Definim un atribut ValidLength (clasa este ValidLengthAttribute), ce accepta doi
parametri pozitionali, lungimea minima si lungimea maxima, si un parametru optional ce
va contine mesajul afisat utilizatorului.
Daca nu se specifica o valoare pentru parametrul optional se va afisa un mesaj implicit.

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Soap;
using System.Reflection;
using System.Collections;

[Serializable]
public class User
{
[ValidLength(4,8,Message=
"UserID should be between 4 and 8 characters long")]
public string userID;

[ValidLength(4,8,Message=
"Password should be between 4 and 8 characters long")]
public string password;

[ValidLength(4,60)]
public string email;

public string city;
public void Save(string fileName)
{
FileStream s=new FileStream(fileName,FileMode.Create);
SoapFormatter sf=new SoapFormatter();
sf.Serialize(s,this);
}
static void Main(string[] args)
{
User u=new User();
u.userID="first";
u.password="Zxfd12Qs";
u.email=".com";
u.city="";
Validator v=new Validator();
if(!v.IsValid(u))
{
foreach(string message in v.Messages) Console.WriteLine(message); }
else {u.Save("user.txt");} }
}
Pentru a valida obiectul, apelam metoda IsValid a obiectului Validator.
Dezvoltare aplicatii folosind platforma .NET - 79/233
Clasa Validator poate fi acum utilizata pentru a valida un obiect al oricarei clase apeland
metosa IsValid.

Clasa pentru definirea atributului are urmatoarea descriere:

[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field)]
public class ValidLengthAttribute : Attribute
{
private int _min;
private int _max;
private string _message;
public ValidLengthAttribute(int min,int max)
{
_min=min;
_max=max;
}
public string Message
{
get {return(_message);}
set {_message=value;}
}
public string Min
{
get{return _min.ToString();}
}
public string Max
{
get{return _max.ToString();}
}
public bool IsValid(string theValue)
{
int length=theValue.Length;
if(length >= _min && length <= _max) return true;
return false;
}
}

iar clasa Validator arata astfel:

public class Validator
{
public ArrayList Messages=new ArrayList();
public bool IsValid(object anObject)
{
bool isValid=true;
FieldInfo[] fields =
anObject.GetType().GetFields(BindingFlags.Public|BindingFlags.Instance);
foreach (FieldInfo field in fields)
if(!isValidField(field,anObject)) isValid=false;
return isValid;
}
private bool isValidField(FieldInfo aField,object anObject)
{
object[] attributes=
aField.GetCustomAttributes(typeof(ValidLengthAttribute),true);
if(attributes.GetLength(0) ==0) return true;
return isValidField(aField,anObject,
Dezvoltare aplicatii folosind platforma .NET - 80/233
(ValidLengthAttribute)attributes[0]);
}
private bool isValidField(FieldInfo aField,
object anObject,ValidLengthAttribute anAttr)
{
string theValue=(string)aField.GetValue(anObject);
if (anAttr.IsValid(theValue))
return true;
addMessages(aField,anAttr);
return false;
}
private void addMessages(FieldInfo aField,ValidLengthAttribute anAttr)
{
if(anAttr.Message !=null)
{
Messages.Add(anAttr.Message); return;
}
Messages.Add("Invalid range for "+ aField.Name+
". Valid range is between " + anAttr.Min +" and " + anAttr.Max);
}

Clasa Validator foloseste clasele reflection pentru a valida obiectul pasat ca parametru
metodei IsValid.
1. Se extrag toate campurile publice din obiect folosind metoda
GetType().GetFields(BindingFlags.Public|BindingFlags.Instance).
Pentru fiecare camp, se extrage atributul definit de utilizator de tip ValidLengthAttribute
folosind metoda GetCustomAttributes(typeof(ValidLengthAttribute),true).
Daca nu se gaseste atributul nostru pentru un camp, metoda presupune campul ca fiind
valid. Daca se gaseste atributul nostru pentru acel camp, se apeleaza metoda IsValid din
ValidLengthAttribute pentru a valida valoarea campului.

Parametrii pozitionali si parametri cu nume

In exemplele anterioare, am examinat atasarea atributelor via constructorul lor.
Vom examina cateva probleme legate de constructorul atributelor.
In exemplul pentru atributele campurilor am folosit un atribut cu numele FieldAttribute.

Constructorul acestuia arata astfel:

public FieldAttribute(Informatica an, String nume)

Folosind signatura constructorului, am atasat atributul la camp astfel:

[FieldAttribute(Informatica.AN_4, "Popescu")]
public int nCuAtribut;

Putem folosi valori implicite folosind parametri pozitionali si parametri cu nume.

Dezvoltare aplicatii folosind platforma .NET - 81/233
Parametrii pozitionali sunt parametrii din constructorul atributului.
Acestia trebuiesc specificati de fiecare data cand folosim atributul.

Parametrii cu nume nu sunt definiti in constructorul atributului, acestia sunt campuri
nestatice si proprietati (am folosit in primul exemplu din acest curs).

Parametrii cu nume permit clientului sa seteze atributul campurilor si proprietatilor cand
atributul este instantiat, fara a avea nevoie sa cream un constructor pentru fiecare
combinatie posibila de campuri si proprietati.

Fiecare ctor public poate defini un sir de parametri pozitionali.
Sa vedem etapele pentru a folosi parametri cu nume.

Utilizatorul poate referentia ca parametru cu nume orice camp care nu este readonly,
static sau const sau orice proprietate ce include un accesor set setter care nu este
statica.

Pentru a face FieldAttribute.Nume un parametru cu nume, il vom sterge din ctor pentru
ca el exista deja ca o proprietate read/write:

// public FieldAttribute(Informatica an, String nume)
public FieldAttribut(string nume)
{
// this.an = an;
this.nume = nume;
Console.WriteLine("Ctor FieldAttribute");
}

Utilizatorul poate atasa atributul in doua moduri:
[FieldAttribute("Popescu")]
public int nCuAtribut;

sau
[FieldAttribute(Popescu,An = Informatica.AN_4)]
public int nCuAtribut;

iar rezultatul va fi acelasi.

O problema apare aici. Care este valoarea implicita pentru FieldAttribute.An?
In cazul de fata An este un enum si deci de tip int, ca atare compilatorul va initializa cu 0.
Dar AN_1 este 0, deci am putea crede ca l-am initializat noi. Trucul de programare
consta in a scrie enum astfel:
public enum Informatica
{
AN_1 = 1,
AN_2,
AN_3,
AN_4,
POSTUNIVERSITARE }
Dezvoltare aplicatii folosind platforma .NET - 82/233
astfel ca putem identifica daca An a fost initializat de compilator sau de utilizator.
In ctor va trebui sa adaugam cod pentru a seta An pe o valoare corecta scopurilor noastre.
Acest cod poate fi:

public FieldAttribue(String nume)
{
if (this.An == 0)
this.An = Informatica.AN_4;
this.nume = nume;
}

Vezi exemplul complet in pagina.

Greseli cu parametrii cu nume
Cand folosim parametri cu nume, trebuie sa specificam parametri pozitionali mai intai, si
apoi parametri cu nume in orice ordine dorim.
Compilatorul incearca sa rezolve mai intai parametrii cu nume si apoi ce ramine, incearca
sa gaseasca un ctor cu signatura potrivita.
Parametrii cu nume pot fi orice camp sau proprietate accesibila publica incluzind o
metoda setter care nu este statica sau constanta.
Tipurile parametrului attribut

Tipurile parametrilor pozitionali si ai celor cu nume pentru o clasa atribut sunt limitate la
tipurile parametrului atributului, si care poate fi:
bool, byte, char, double, float, int, long, short, string
System.Type
object
tipul enum cu conditia ca oriunde se gaseste definit sa fie accesibil in mod public.
un tablou unidimensional ce are ca tipuri de elemente unul din cele de mai sus.
Atributul AttributeUsage

AttributeUsage defineste modul cum atributele sunt folosite.
Sintaxa este:

[AttributeUsage(
validon,
AllowMultiple = allowmultiple,
Inherited = inherited )]

Observam un parametru pozitional si doi cu nume.

Parametrul validon

Parametrul validon este de tipul AttributeTarget, ne permite sa specificam tipurile pe care
atributul poate fi atasat.
Dezvoltare aplicatii folosind platforma .NET - 83/233

public enum AttributeTargets
{
Assembly = 0x0001,
Module = 0x0002,
Class = 0x0004,
Struct = 0x0008,
Enum = 0x0010,
Constructor = 0x0020,
Method = 0x0040,
Property = 0x0080,
Field = 0x0100,
Event = 0x0200,
Interface = 0x0400,
Parameter = 0x0800,
Delegate = 0x1000,
All = Assembly Module Class Struct Enum Constructor
Method Property Field Event Interface Parameter
Delegate,

ClassMembers = Class Struct Enum Constructor Method
Property Field Event Delegate Interface,
}

Implicit este AttributeTargets.All.
Membrii din enumerare pot fi folositi cu operatorul OR (|) ca in exemplul:

[AttributeUsage(AttributeTargets.Field AttributeTargets.Property)]

Folosim acest parametru cand vrem sa controlam exact modul de utilizare al unui atribut.
In exemplele anterioare am folosit atribute pentru clase, metode, campuri.
Pentru a fi siguri ca atributele pe care le-am scris sunt folosite pentru tipurile pentru care
au fost proiectate, putem indica in definitia atributului pentru ce este proiectat:

[AttributeUsage(AttributeTargets.Class)]
public class AtributCursAttribute : Attribute{}

[AttributeUsage(AttributeTargets.Method)]
public class AtributTranzactie : Attribute{}

[AttributeUsage(AttributeTargets.Field)]
public class FieldAttribute : Attribute{}

[AtributCurs(Computers.TROIA)]
class D
{
[AtributTranzactie]
public void Foo() {}

[FieldAttribute("Bar", An = Informatica.AN_4)]
public int Bar;
}

Parametrul AllowMultiple
Dezvoltare aplicatii folosind platforma .NET - 84/233

Ne permite sa definim un atribut ce poate fi folosit o singura data pentru un camp sau de
mai multe ori. Implicit toate atributele sunt folosite ca single, deci nu putem declara de
doua ori acelasi atribut pentru un camp. Vezi exemplul:

public class SomethingAttribute : Attribute
{ public SomethingAttribute(String str){}
}

// Error: "Duplicate single-use Something attribute"
[Something("abc")]
[Something("def")]
class MyClass{}

Parametru AllowMultiple rezolva aceasta problema.
AllowMultiple = true atribut multiplicat;
= false atribut single.
Vezi si exemplul:

[AttributeUsage(AttributeTargets.All, AllowMultiple=true)]
public class SomethingAttribute : Attribute
{
public SomethingAttribute(String str){}
}

[Something("abc")]
[Something("def")]
class MyClass{}

Parametrul Inherited

Acest parametru ne spune daca atributul poate fi mostenit sau nu. Valoarea implicita este
false. Parametrii AllowMultiple si Inherited trebuiesc considerati impreuna. Vezi tabelul
de mai jos.
Inherited AllowMultiple Result
True false Atributul derivat suprascrie atributul de baza.
True true
Atributele derivate si cele din clasa de baza sunt
combinate.

De exemplu:

using System;
using System.Reflection;

namespace AttribInheritance
{
[AttributeUsage(
AttributeTargets.All,
// AllowMultiple=true,
AllowMultiple=false,
Inherited=true)]
Dezvoltare aplicatii folosind platforma .NET - 85/233
public class SomethingAttribute : Attribute
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}

public SomethingAttribute(string str)
{
this.name = str;
}
}

[Something("abc")]
class MyClass{}

[Something("def")]
class Another : MyClass{}

class Test
{
[STAThread]
static void Main(string[] args)
{
Type type =
Type.GetType("AttribInheritance.Another");
foreach (Attribute attr in
type.GetCustomAttributes(true))
// type.GetCustomAttributes(false))
{
SomethingAttribute sa =
attr as SomethingAttribute;
if (null != sa)
{
Console.WriteLine(
"Custom Attribute: {0}",
sa.Name);
}
}
}
}
}

Cu AllowMultiple = false, rezultatul este:

Custom Attribute: def

iar cu AllowMultiple = true, rezultatul devine:

Custom Attribute: def
Custom Attribute: abc

Identificatorii atributului
Dezvoltare aplicatii folosind platforma .NET - 86/233

Trebuie sa precizam la ce se aplica un atribut: la o metoda sau la valoarea returnata de
metoda.
Din acest punct de vedere urmatorul cod este ambiguu.

class SomeClass
{
[HRESULT]
public long Foo() { return 0; }
}

In COM, HRESULT reprezinta valoarea returnata de metodele din interfata (mai putin
AddRef si Release, acestea returneaza ULONG).
Putem defini atributul HRESULT astfel:
public class HRESULTAttribute : Attribute
{
public HRESULTAttribute(){}
}

Alte ambiguitati sunt date de urmatoarele situatii:

1. Metode vs. tip returnat;
2. Evenimente vs. camp vs. proprietate ;
3. Delegate vs. tip returnat;
4. Proprietate vs. accessor vs. valoare returnata din getter vs. valoarea
parametrului unei metode set;

Pentru a fixa lucrurile, putem folosi identificatorul atributului, listat mai jos:

assembly
module
type
method
property
event
field
param
return

Sintaxa este:

identificator:atribut

Atentie la :
Vezi si exemplul de mai jos.

class SomeClass
{
[HRESULT]
Dezvoltare aplicatii folosind platforma .NET - 87/233
public long Foo() { return 0; }
[return: HRESULT]
public long Bar() { return 0; }
}

Alte exemple:

class SomeClass
{
[method: HRESULT]
public long Foo() { return 0; }

[return: HRESULT]
public long Bar() { return 0; }

[property: HRESULT]
public long Goo { get { return 12345; } }
}

Vom folosi reflection pentru a determina identificatorul atributului, ca in exemplul
urmator:

static void Main(string[] args)
{
Type type =
Type.GetType("AttribIdentifiers.SomeClass");
foreach (MethodInfo m in type.GetMethods())
{
foreach (Attribute a in
m.GetCustomAttributes(true))
{
if (a is HRESULTAttribute)
{
Console.WriteLine(
"method: {0}, "
+ "CustomAttributes: {1}",
m.Name, a);
}
}

ICustomAttributeProvider icap =
m.ReturnTypeCustomAttributes;
foreach (Attribute a in
icap.GetCustomAttributes(true))
{
Console.WriteLine("method: {0}, "
+ "ReturnTypeCustomAttribs: {1}",
m.Name, a);
}
}
foreach (MemberInfo m in type.GetProperties())
{
foreach (Attribute a in
m.GetCustomAttributes(true))
{
Console.WriteLine("property: {0}, "
Dezvoltare aplicatii folosind platforma .NET - 88/233
+ "CustomAttributes: {1}",
m.Name, a);
}
}
}

Rezultatul este:

method: Foo, CustomAttributes: AttribIdentifiers.HRESULTAttribute
method: Bar, ReturnTypeCustomAttribs:
AttribIdentifiers.HRESULTAttribute
property: Goo, CustomAttributes: AttribIdentifiers.HRESULTAttribute

Atribute predefinite

Cadrul de lucru .NET ofera clase de atribute predefinite, dintre care mai importante sunt:

Predefined .NET
Attribute
Valid Targets Description
AttributeUsage Class Specifies the valid usage of another attribute class.
CLSCompliant All
Indicates whether a program element is compliant
with the Common Language Specification (CLS).
Conditional Method
Indicates that the compiler can ignore any calls to
this method if the associated string is defined.
DllImport Method
Specifies the DLL location that contains the
implementation of an external method.
MTAThread Method (Main)
Indicates that the default threading model for an
application is multithreaded apartment (MTA).
NonSerialized Field
Applies to fields of a class flagged as Serializable;
specifies that these fields wont be serialized.
Obsolete
All except
Assembly, Module,
Parameter, and
Return
Marks an element obsoletein other words, it
informs the user that the element will be removed
in future versions of the product.
ParamArray Parameter
Allows a single parameter to be implicitly treated
as a params (array) parameter.
Serializable
Class, struct, enum,
delegate
Specifies that all public and private fields of this
type can be serialized.
STAThread Method (Main)
Indicates that the default threading model for an
application is STA.
StructLayout Class, struct
Specifies the nature of the data layout of a class or
struct, such as Auto, Explicit, or Sequential.
ThreadStatic Field (static)
Implements thread-local storage (TLS)in other
words, the given static field isnt shared across
multiple threads and each thread has its own copy
Dezvoltare aplicatii folosind platforma .NET - 89/233
Predefined .NET
Attribute
Valid Targets Description
of the static field.

Atributul Conditional

Ideea: Apelul unei metode poate fi ignorat daca nu este definita o anumita valoare.
Exemplu:

[Conditional("DEBUG")]
public void SomeDebugFunc()
{ Console.WriteLine("SomeDebugFunc"); }

iar cod complet:

using System;
using System.Diagnostics;

namespace CondAttrib
{
class Thing
{
private string name;
public Thing(string name)
{
this.name = name;
SomeDebugFunc();
SomeFunc();
}
public void SomeFunc()
{ Console.WriteLine("SomeFunc"); }

[Conditional("DEBUG")]
public void SomeDebugFunc()
{ Console.WriteLine("SomeDebugFunc"); }
}

public class Class1
{
[STAThread]
static void Main(string[] args)
{
Thing t = new Thing("T1");
}
}
}

Iesirea este:

SomeDebugFunc
SomeFunc

Dezvoltare aplicatii folosind platforma .NET - 90/233
Pentru a vedea efectele acestui cod trebuiesc modificate proprietatile proiectului.
Compilatorul decide daca functia este compilata sau nu.

Putem combina atributul Conditional cu directivele de preprocesare ca in exemplul:
#if DEBUG
SomeDebugFunc();
#else
SomeFunc();
#endif

Conditional este un atribut cu AllowMultiple = true.

Atributele DllImport si StructLayout

Codul C# poate apela functii in cod nativ ce se gaseste in DLL. Aceasta caracteristica a
runtime-ului se numeste platform invoke.

Exemplu

Vom apela functia Win32 API MessageBoxA ce se gaseste in user32.dll.
Codul este:

public class Test
{
[DllImport ("user32.dll")]
public static extern int MessageBoxA (
int h, string m, string c, int type);

[STAThread]
public static void Main(string[] args)
{
MessageBoxA(0, "Hello World", "nativeDLL", 0);
}
}

Runtime .NET transmite parametrii din codul C# manged catre codul nativ din DLL.
Sintaxa completa pentru DllImport este:

[DllImport("user32", EntryPoint="MessageBoxA",
SetLastError=true,
CharSet=CharSet.Ansi, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern int MessageBoxA (
int h, string m, string c, int type);

Pentru a transmite structuri din codul managed catre codul unmanaged si invers trebuie sa
folosim atributul StructLayout, ce are ca efect construierea structurii exact ca in declaratia
ei initiala (adica secvential). Este obligatoriu acest lucru.

Dezvoltare aplicatii folosind platforma .NET - 91/233
Exemplu pentru apelul functiei GetLocalTime().

[StructLayout(LayoutKind.Sequential)]
public class SystemTime {
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}

In continuare putem apela functia astfel:

public static void Main(string[] args)
{
SystemTime st = new SystemTime();
GetLocalTime(st);
string s = String.Format("date: {0}-{1}-{2}",
st.wMonth, st.wDay, st.wYear);
string t = String.Format("time: {0}:{1}:{2}",
st.wHour, st.wMinute, st.wSecond);
string u = s + ", " + t;
MessageBoxA(0, u, "Now", 0);
}

Dezvoltare aplicatii folosind platforma .NET - 92/233
Interfete

Interfetele ne dau posibilitatea de a defini o multime de metode si proprietati pe care
clasa selectata le poate implementa. Din punct de vedere conceptual interfetele sunt
contracte intre doua parti de cod separate.
Se poate lucra in felul urmator:
se defineste o interfata (contine metode abstracte); interfata defineste o anumita
comportare ;
se defineste o clasa ce implementeaza aceasta interfata, se spune ca clasa este
derivata din interfata ;
clientii vor utiliza clasa ce a implementat interfata.

Utilizarea interfetei

In C#, o interfata este un concept de prima clasa, adica o trasatura preconstruita a
limbajului, ce declara un tip referinta ce include numai declaratiile metodelor.

Declararea interfetelor

Interfetele pot contine metode, proprietati, indexeri si evenimente, si nici unul nu este
implementat de interfata insasi. Interfetele sunt definte ca o clasa abstracta.

Exemple

interface IValidate
{
bool Validate();
}

Observatii
Nu trebuie sa declaram ca metodele sunt pur virtuale (=0).

interface IExempluInterface
{
// Exemplu de declarare a unei proprietati
int testProperty { get; }

// Exemplu de declarare a unui eveniment
event testEvent Changed;

// Exemplu de declarare a unui indexer
string this[int index] { get; set; }
}


Implementarea interfetelor

Dezvoltare aplicatii folosind platforma .NET - 93/233
Fiecare clasa ce implementeaza o interfata trebuie sa defineasca fiecare membru al
interfetei.
Sa examinam urmatorul exemplu.

using System;
public class Control
{
public Control(string data) { Data = data; }

protected string data;
public string Data
{
get { return data; }
set { data = value; }
}
}

interface IValidate
{
bool Validate();
}

// implementeaza interfata IValidate
class SSN : Control, IValidate {
const char DELIMITER = '-';
public SSN(string val) : base(val) {}

public bool Validate()
{
Console.WriteLine("[SSN.Validate] : Validating '{0}'",
data);
return (11 == data.Length) && (CorrectFormat());
}

protected bool CorrectFormat()
{
bool correctFormat = true;

for (int i = 0; (correctFormat && i < data.Length); i++)
{
correctFormat =
((IsDelimiterPosition(i)
&& data[i] == DELIMITER)
(IsNumberPosition(i)
&& char.IsNumber(data[i])));
}
return correctFormat;
}

protected bool IsDelimiterPosition(int i)
{ return (i == 3 i == 6); }

protected bool IsNumberPosition(int i)
{ return (i != 3 && i != 6); }
}
Dezvoltare aplicatii folosind platforma .NET - 94/233

// Test pentru tipul construit
// Folosit din linia de comanda

class InterfacesApp
{
public static void Main(string[] args)
{
string data = "";
if (0 < args.GetLength(0))
data = args[0];
SSN ssn = new SSN(data);

// ...
// Pentru a valida controlul, scriem urmatorul cod
// o conversie a obiectului...

IValidate val = (IValidate)ssn;

Console.WriteLine("[Main] Apel SSN.Validate");
//
// apelam metoda din interfata
//
bool success = val.Validate();

Console.WriteLine("[Main] The validation of " +
"SSN '{0}' was {1}successful",
ssn.Data,
(true == success ? "" : "NOT "));
}
}

Operatorii is si as

Operatorul is test existenta interfata

Pentru a testa un obiect daca implementeaza o anumita interfata folosim operatorul is.
Operatorul is ne permite sa controlam in momentul executiei daca un tip este compatibil
cu alt tip. Sintaxa este:

expresie is tip

Vezi exemplul ce urmeaza.

using System;
using System.Collections;
public class Control
{
public Control(string data) { Data = data; }
protected string data;
public string Data
{
get { return data; }
Dezvoltare aplicatii folosind platforma .NET - 95/233
set { data = value; }
}
}
interface IValidate
{
bool Validate();
}

// Clasa va implementa interfata IValidate

class SSN : Control , IValidate
{
const char DELIMITER = '-';
public SSN(string val) : base(val) {}
public bool Validate()
{
Console.WriteLine("[SSN.Validate] : Validating '{0}'",
data);
return (11 == data.Length) && (CorrectFormat());
}

protected bool CorrectFormat()
{
bool correctFormat = true;
for (int i = 0; (correctFormat && i < data.Length); i++)
{
correctFormat =
((IsDelimiterPosition(i)
&& data[i] == DELIMITER)
(IsNumberPosition(i)
&& char.IsNumber(data[i])));
}

return correctFormat;
}

protected bool IsDelimiterPosition(int i)
{ return (i == 3 i == 6); }
protected bool IsNumberPosition(int i)
{ return (i != 3 && i != 6); }
}

// Aceasta clasa nu este derivata din IValidate
class Address : Control
{
public Address(string data) : base(data) {}
}

// Test operator is

class IsOperatorApp
{
public static void Main()
{
// 1. Vom pune in acest ArrayList obiecte instantiate din SSN
// si Address.
Dezvoltare aplicatii folosind platforma .NET - 96/233
// 2. Vom itera acest vector si pentru obiectele instantiate
// din SSN vom apela metoda Validate.
ArrayList controls = new ArrayList();

Console.WriteLine("Adauga un obiect SSN in tabloul controls ");
SSN ssn = new SSN("555-55-5555");
controls.Add(ssn);

Console.WriteLine("Adauga un obiect Address in tabloul
controls);
Address addr = new Address("Strada Sperantei");
controls.Add(addr);

Console.WriteLine("\nParcurgerea elementelor tabloului ...);
foreach (Control control in controls)
{
if (control is IValidate)
{
Console.WriteLine("\n\t{0} implementeaza IValidate",
control);
Console.Write("\t");
bool b = ((IValidate)control).Validate();
Console.WriteLine("\tValidare {0}",
(b == true ? "succeded" : "failed"));
}
else
{
Console.WriteLine("\t{0} nu implementeaza " +
"IValidate", control);
}
}
}
}

Operatorul as

Operatorul as face conversia intre tipurile compatibile si are urmatoarea sintaxa:
obiect = expresie as tip

unde expresie este un tip referinta. In urma acestei conversii trebuie scris cod de
verificare a conversiei, adica daca obiect are o valoare valida.

Exemplu

using System;
using System.Collections;

public class Control
{
public Control(string data) { Data = data; }
protected string data;
public string Data
{
get { return data; }
Dezvoltare aplicatii folosind platforma .NET - 97/233
set { data = value; }
}
}

interface IValidate
{
bool Validate();
}

class SSN : Control , IValidate
{
const char DELIMITER = '-';
public SSN(string val) : base(val) {}
public bool Validate()
{
Console.WriteLine("[SSN.Validate] : Validare '{0}'",
data);
return (11 == data.Length) && (CorrectFormat());
}

protected bool CorrectFormat()
{
bool correctFormat = true;
for (int i = 0; (correctFormat && i < data.Length); i++)
{
correctFormat =
((IsDelimiterPosition(i)
&& data[i] == DELIMITER)
(IsNumberPosition(i)
&& char.IsNumber(data[i])));
}

return correctFormat;
}

protected bool IsDelimiterPosition(int i)
{ return (i == 3 i == 6); }

protected bool IsNumberPosition(int i)
{ return (i != 3 && i != 6); }
}

class Address : Control
{
public Address(string data) : base(data) {}
}

class AsOperatorApp
{
public static void Main()
{
ArrayList controls = new ArrayList();

Console.WriteLine("Adauga un obiect SSN in vectorul control");
SSN ssn = new SSN("555-55-5555");
controls.Add(ssn);
Dezvoltare aplicatii folosind platforma .NET - 98/233

Console.WriteLine("Adauga un obiect Address in vectorul
controls");
Address addr = new Address("Strada Sperantei");
controls.Add(addr);

Console.WriteLine("\nIterare elemente din vector...");
IValidate iValidate;
foreach (Control control in controls)
{
iValidate = control as IValidate;
if (null != iValidate)
{
Console.WriteLine("\n\t{0} implementeaza IValidate",
control);
Console.Write("\t");
bool b = iValidate.Validate();
Console.WriteLine("\tValidare {0}",
(b == true ? "succeded" : "failed"));
}
else
{
Console.WriteLine("\n\t{0} nu " +
"implementeaza IValidate", control);
}
}
}
}

Ascunderea numelor din interfete. Rezolvarea coliziunilor de nume.

O metoda de apel al unei interfete este:

ClasaEx clx = new ClasaEx();
IInterfataEx iex = (IInterfataEx)clx;
iex.Metoda(); // metoda din interfata

Observatie :
Putem apela si prin sintaxa :

clx.Metoda() ;

Avem posibilitatea de a ascunde membrii publici ai unei interfete, in special a celor ce nu
ne sunt de folos. Pentru a ascunde un membru al interfetei este nevoie sa stergem
modificatorul de acces public si sa calificam numele membrului cu numele interfetei :

using System;
public interface IDataBound
{
void Bind();
}

public class EditBox : IDataBound
Dezvoltare aplicatii folosind platforma .NET - 99/233
{
// IDataBound implementation
void IDataBound.Bind()
{
Console.WriteLine("Binding to data store...");
}
}

class NameHiding2App
{
public static void Main()
{
Console.WriteLine();
EditBox edit = new EditBox();
Console.WriteLine("Calling EditBox.Bind()...");
// ERROR: This line won't compile because
// the Bind method no longer exists in the
// EditBox class's namespace.
edit.Bind();
Console.WriteLine();
IDataBound bound = (IDataBound)edit;
Console.WriteLine("Calling (IDataBound)" +
"EditBox.Bind()...");
// This is OK because the object was cast to
// IDataBound first.
bound.Bind();
}
}

Cand ascundem un membru, nu putem folosi modificatori de acces.

Coliziuni de nume

Consideram urmatorul exemplu in care avem doua interfete ce contin o metoda cu acelasi
nume, SaveData, dar cu functionalitati diferite in cadrul fiecarei interfete.

using System;
interface ISerializable
{
void SaveData();
}

interface IDataStore
{
void SaveData();
}

class Test : ISerializable, IDataStore
{
public void SaveData()
{
Console.WriteLine("Test.SaveData called");
}
}
Dezvoltare aplicatii folosind platforma .NET - 100/233

class NameCollisions1App
{
public static void Main()
{
Test test = new Test();
Console.WriteLine("Calling Test.SaveData()");
test.SaveData(); // aici apare ambiguitatea
}
}

Urmatorul cod este si mai ambiguu:

using System;
interface ISerializable
{
void SaveData();
}

interface IDataStore
{
void SaveData();
}

class Test : ISerializable, IDataStore
{
public void SaveData()
{
Console.WriteLine("Test.SaveData called");
}
}

class NameCollisions2App
{
public static void Main()
{
Test test = new Test();
Console.WriteLine("Testing to see if Test " +
"implements ISerializable...");
Console.WriteLine("ISerializable is {0}implemented\n",
test is ISerializable ? "" : "NOT ");
Console.WriteLine("Testing to see if Test " +
"implements IDataStore...");
Console.WriteLine("IDataStore is {0}implemented\n",
test is IDataStore ? "" : "NOT ");
}
}

Clasa Test apare ca implementind ambele interfete.
Codul se executa (atentie si la versiunea de C#).
Aici trebuie sa fim atenti si la mesajele de averisment emise de compilator :

NameCollisions2.cs(29,21): warning CS0183: The given
expression is always of the provided ('ISerializable') type
NameCollisions2.cs(33,21): warning CS0183: The given
Dezvoltare aplicatii folosind platforma .NET - 101/233
expression is always of the provided ('IDataStore') type
Rezolvare problema

using System;

interface ISerializable
{
void SaveData();
}
interface IDataStore
{
void SaveData();
}
class Test : ISerializable, IDataStore
{
void IDataStore.SaveData()
{
Console.WriteLine("[Test.SaveData] IDataStore " +
"implementation called");
}
void ISerializable.SaveData()
{
Console.WriteLine("[Test.SaveData] ISerializable " +
"implementation called");
}
}
class NameCollisions3App
{
public static void Main()
{
Test test = new Test();

Console.WriteLine("[Main] " +
"Testing to see if Test implements " +
"ISerializable...");
Console.WriteLine("[Main] " +
"ISerializable is {0}implemented",
test is ISerializable ? "" : "NOT ");
((ISerializable)test).SaveData();

Console.WriteLine();

Console.WriteLine("[Main] " +
Testing to see if Test implements " +
"IDataStore...");
Console.WriteLine("[Main] " +
"IDataStore is {0}implemented",
test is IDataStore ? "" : "NOT ");
((IDataStore)test).SaveData();
}
}

Dezvoltare aplicatii folosind platforma .NET - 102/233
Interfete si mostenirea

Cateva scenarii posibile
Clasa de baza ce implementeaza interfata contine o metoda ce are nume
identic cu al unei metode din interfata
Exemplificare

using System;
public class Control
{
public void Serialize()
{
Console.WriteLine("Control.Serialize called");
}
}
public interface IDataBound
{
// EditBox never implements this, but it still compiles!!!
void Serialize();
}
public class EditBox : Control, IDataBound
{
}
class InterfaceInh1App
{
public static void Main()
{
EditBox edit = new EditBox();
edit.Serialize();
// Care metoda Serialize se executa?
}
}

Codul se compileaza desi nu am scris cod pentru Serialize din IDataBound. Daca codul
executa corect ceea ce am vrut noi este o alta problema.
In fapt se apelaeaza Serialize din clasa de baza.
Folosirea operatorului is nu rezolva problema neimplementarii unei metode din interfata.
Practic is spune ca o anumita clasa suporta o anumita interfata, dar nu ca o si
implementeaza.

Vezi exemplu:

using System;
public class Control
{
public void Serialize()
{ Console.WriteLine("Control.Serialize called");
}
}
public interface IDataBound
{ void Serialize();
}
Dezvoltare aplicatii folosind platforma .NET - 103/233
public class EditBox : Control, IDataBound{}
class InterfaceInh2App
{
public static void Main()
{
EditBox edit = new EditBox();
IDataBound bound = edit as IDataBound;
if (bound != null)
{
Console.WriteLine("IDataBound is supported...");
bound.Serialize();
}
else
{
Console.WriteLine("IDataBound is NOT supported...");
}
}
}
2. Clasa derivata din clasa ce implementeaza o interfata are o metoda cu acelasi
nume ca cea din clasa de baza, nume ce corespunde unei metode din interfata
.
Sa analizam urmatorul exemplu:

using System;
interface ITest
{ void Foo();
}
// Base implements ITest.
class Base : ITest
{ public void Foo()
{ Console.WriteLine("Base.Foo (ITest implementation)");
}
}
class MyDerived : Base
{
public new void Foo()
{ Console.WriteLine("MyDerived.Foo");
}
}
public class InterfaceInh3App
{
public static void Main()
{ Console.WriteLine("InterfaceInh3App.Main : " +
"Instantiating a MyDerived class");
MyDerived myDerived = new MyDerived();
Console.WriteLine();

Console.WriteLine("InterfaceInh3App.Main : " +
"Calling MyDerived.Foo - Which method will be " +
"called?");
myDerived.Foo();
Console.WriteLine();

Dezvoltare aplicatii folosind platforma .NET - 104/233
Console.WriteLine("InterfaceInh3App.Main : " +
"Calling MyDerived.Foo - Casting to ITest " +
"interface...");
((ITest)myDerived).Foo();
}
}

new si casting-ul din ultimile linii de cod rezolva problema in mod corect.
Se recomanda folosirea cast-ului in utilizarea metodelor din interfete.

Dezvoltare aplicatii folosind platforma .NET - 105/233
Delegate

Delegate suporta apelul metodelor statice si al metodelor instantei.
Metode callback sunt folosite in tratarea notificarilor, exceptiilor, schimbarea starii
ferestrelor, selectie articole de meniu, operatii asincrone, etc.

Declarare, creare si folosire delegate
Etape
Se defineste tipul delegate:

1. public delegate void nume_delegate(lista_parametri);
2. Definire metoda ce are un parametru de tip nume_delegate
public void Metoda_delegate(nume_delegate
fct_callback)
3. Apel metoda ce are parametrii dati la etapa 1. (lista_parametri)
if (fct_callback != null)
fct_callback(lista_parametri)

Pentru a apela functia callback, Functie_callback, va trebui sa scriem o asemenea functie
in clasa noastra si apoi apelam Metoda_delegate furnizandu-i ca parametru functia
noastra.
Metoda_delegate(new obiect.Functie_callback);
Se disting doua situatii: metode callback statice si metode callback de instanta.
Le vom trata separat. Vom explica aceste probleme pe baza exemplului de mai jos.

using System;
using System.Windows.Forms;
using System.IO;
class Set
{
private Object[] items;
public Set(Int32 numItems)
{
items = new Object[numItems];
for (Int32 i = 0; i < numItems; i++)
items[i] = i;
}
// Definim tipul FeedBack. (delegate)
// ATENTIUNE! Tipul se defineste in cadrul clasei Set
// In fapt este un prototip de functie (metoda),
// in realitate se genereaza o clasa.

public delegate void Feedback(Object value,
Int32 item,
Int32 numItems);
//
// Definim metoda ce are un parametru de tip FeedBack
//
public void ProcessItems(Feedback feedback)
{
Dezvoltare aplicatii folosind platforma .NET - 106/233
for (Int32 item = 0; item < items.Length; item++)
{
if (feedback != null)
{
// Daca s-a specificat o functie callback,
// atunci o apelam.
feedback(items[item], item + 1, items.Length);
}
}
}
}

class App
{
static void Main()
{
StaticCallbacks();
InstanceCallbacks();
}
static void StaticCallbacks()
{
// Creem un tablou cu 5 elemente de tip Set
Set setOfItems = new Set(5);
// Procesam articolele fara a furniza o metoda callback
setOfItems.ProcessItems(null);
Console.WriteLine();
// Procesam articolele si furnizam metoda
// callback FeedbackToConsole
setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToConsole));
Console.WriteLine();
// Procesam articolele fara a furniza o alta metoda callback
setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToMsgBox));
Console.WriteLine();
// Procesam articolele, si folosim doua metode callback:
// FeedbackToConsole si FeedbackToMsgBox.
// Pentru fiecare articol se apeleaza ambele metode.
// Se creaza o lista de metode callback.

Set.Feedback fb = null;
fb += new Set.Feedback(App.FeedbackToConsole);
fb += new Set.Feedback(App.FeedbackToMsgBox);
setOfItems.ProcessItems(fb);
Console.WriteLine();
}
// este private
static void FeedbackToConsole(
Object value, Int32 item, Int32 numItems)
{
Console.WriteLine("Processing item {0} of {1}: {2}.",
item, numItems, value);
}
// este private
static void FeedbackToMsgBox( Object value, Int32 item,
Int32 numItems)
{
MessageBox.Show(String.Format("Processing item {0} of {1}: {2}.",
item, numItems, value));
Dezvoltare aplicatii folosind platforma .NET - 107/233
}

static void InstanceCallbacks()
{
// Creaza o multime cu 5 elemente.
Set setOfItems = new Set(5);
// Proceseaza articolele, si apeleaza
// metoda callback FeedbackToFile.
App appobj = new App();
setOfItems.ProcessItems(new Set.Feedback(appobj.FeedbackToFile));
Console.WriteLine();
}
// este private
void FeedbackToFile( Object value, Int32 item, Int32 numItems)
{
StreamWriter sw = new StreamWriter("Status", true);
sw.WriteLine("Processing item {0} of {1}: {2}.", item, numItems,
value);
sw.Close(); }
}

Explicatii.
Clasa Set constructorul creaza un tablou de obiecte, tablou cu dimensiune specificata.
Intializeaza fiecare element al tabloului cu un numar intreg.
Clasa Set defineste ca public delegate: Feedback.
Tot public se defineste si metoda ProcessItem ce are ca parametru o referinta la un
obiect delegate Feedback. Aceasta metoda scaneaza tabloul de elemente si pentru fiecare
element este apelata metoda callback.

Delegate si metode statice callback

Atentia ne va fi indreptata asupra metodei StaticCallbacks definita mai sus.
FeedbackToConsole este o metoda privata, si este apelata de ProccesItem din clasa
Set. Nu apare nici o problema legata de securitate.
Obiectul delegate este un wrapper pentru metoda, iar metoda va fi apelata indirect via
acest wrapper.

Urmatorul cod:

// Procesam articolele, si folosim doua metode callback:
// FeedbackToConsole si FeedbackToMsgBox.
// Pentru fiecare articol se apeleaza ambele metode.
// Se creaza o lista de metode callback.

//
// Se defineste o variabila fb de tip Set.Feedback
// Atentiune! Nu se apeleaza constructorul.
//
Set.Feedback fb = null;
//
// Se construieste o lista inlantuita de delegate
// Operatorul += face acest lucru. Adauga elemente la
// lista inlantuita.
Dezvoltare aplicatii folosind platforma .NET - 108/233
// Primul element din lista
//
fb += new Set.Feedback(App.FeedbackToConsole);
//
// Al doilea element din lista
//
fb += new Set.Feedback(App.FeedbackToMsgBox);
//
// Apel callback din lista inlantuita
//
setOfItems.ProcessItems(fb);
Console.WriteLine();

creaza o lista de metode callback.
Vezi comentariile pentru explicatii.

Folosire delegate pentru apelul metodelor de instanta

Codul ce il vom examina se gaseste in metoda InstanceCallbacks.

static void InstanceCallbacks()
{
// Creaza o multime cu 5 elemente
Set setOfItems = new Set(5);
// Proceseaza articolele si apeleaza metoda FeedbackToFile.
App appobj = new App();
setOfItems.ProcessItems(new Set.Feedback(appobj.FeedbackToFile));
Console.WriteLine();
}

Cand un nou obiect delegate Feedback este creat, se paseaza constructorului acestuia
appobj.FeedbackToFile. Efectul este ca delegate va construi o referinta la metoda
FeedbackToFile, care este o metoda a instantei (nu este o metoda statica). Cand aceasta
metoda a instantei este apelata, obiectul appobj se refera la obiectul pe care se va lucra
(pointer this). Pentru metodele instantei, delegate are nevoie sa stie instanta obiectului ce
contine metoda.

Delegates in detaliu

Din punct de vedere al clientului: definim delegate, construim instanta si apelam metoda
(callback). Din punctul de vedere al compilatorului si al CLR-ului lucrurile sunt mai
complicate.

Vom examina codul:

public delegate void Feedback( Object value,
Int32 item,
Int32 numItems);

La intilnirea acestei linii de cod compilatorul va defini o clasa ce arata cam asa
(documentatie Microsoft):
Dezvoltare aplicatii folosind platforma .NET - 109/233

public class Feedback : System.MulticastDelegate
{
// Constructor
public Feedback(Object target, Int32 methodPtr);
// Method with same prototype as specified by the source code
public void virtual Invoke( Object value,
Int32 item,
Int32 numItems);
// Methods allowing the callback to be called asynchronously
public virtual IAsyncResult BeginInvoke(
Object value,
Int32 item,
Int32 numItems,
AsyncCallback callback,
Object object);
public virtual void EndInvoke(IAsyncResult result);
}

Vom discuta constructorul si metoda Invoke. Pentru a va convinge ca asa stau lucrurile
folositi ILDasm.exe.

Toate tipurile delegate sunt derivate din System.MulticastDelegate.

Clasa Feedback este derivata din System.MulticastDelegate, si numai compilatorul poate
face acest lucru cand intilneste cuvantul cheie delegate.
Modificatorul de acces al clasei este acelasi cu modificatorul de acces folosit la
declararea delegate.
Avem de a face cu o clasa definita in interiorul altei clase.

Campuri principale din System.MulticastDelegate
Camp Tip Descriere
_target System.Object Se refera la obiectul pe care se va lucra cand
metoda callback va fi apelata. Acest camp
este folosit pentru metode callback ale
instantei.
_methodPtr System.Int32 Un intreg folosit intern de CLR pentru a
identifica metoda callback apelata.
_prev System.MulticastDelegate Se refera la un alt obiect delegate. In mod
normal are valoarea null. Este folosit pentru
a crea o lista inlantuita de obiecte
MulticastDelegate.

Constructorii pentru delegate au doi parametri: o referinta la un obiect si un intreg ce
identifica metoda callback.

Secventa de cod ce transmite metoda callback nu foloseste forma ctor cu doi parametri :

Set setOfItems = new Set(5);
// Proceseaza articolele si apeleaza metoda FeedbackToFile.
Dezvoltare aplicatii folosind platforma .NET - 110/233

App appobj = new App();
setOfItems.ProcessItems(new Set.Feedback(appobj.FeedbackToFile));

In acest caz compilatorul adauga cod pentru identificarea metodei, mai precis pentru a
construi al doilea parametru al constructorului.
Pentru metodele instantei _target retine referinta la obiect, iar pentru metodele callback
statice, _target = null.

MulticastDelegate defineste doua proprietati: Target si Method.

Proprietatea Target returneaza o referinta la obiectul pe care se va lucra daca metoda
callback apelata este o metoda a instantei. Daca metoda este statica Target returneaza
null.
Proprietatea Method returneaza un obiect System.Reflection.MethodInfo, obiect ce
identifica metoda callback. Putem folosi aceasta informatie in mai multe moduri. De
exemplu, putem verifica daca un obiect delegate se refera la metoda instanta a clasei de
un anume tip :

Boolean DelegateRefersToInstanceMethodOfType(
MulticastDelegate d, Type type)
{
return((d.Target != null) && d.Target.GetType() == type);
}

Putem verifica daca metoda callback are un anumit nume:

Boolean DelegateRefersToMethodOfName( MulticastDelegate d,
String methodName)
{
return(d.Method.Name == methodName);
}

Invocarea metodelor callback

Reluam ProcessItem din clasa Set.

public void ProcessItems(Feedback feedback)
{
for (Int32 item = 0; item < items.Length; item++)
{
if (feedback != null)
{
// Daca s-a specificat o functie callback,
// atunci o apelam.
feedback(items[item], item, items.Length);
}
}
}

Dupa cum se observa apelam o metoda cu numele feedback, desi nu am definit asa ceva.
Dezvoltare aplicatii folosind platforma .NET - 111/233
Din declaratie feedback este o variabila ce se refera la un obiect delegate.
Compilatorul va genera cod pentru a apela metoda Invoke a obiectului delegate.
Pentru linia de cod :
feedback(items[item], item, items.Length);
compilatorul va genera ceva de genul:

feedback.Invoke(items[item], item, items.Length);

Metoda Invoke nu poate fi apelata direct din codul sursa, compilator C#.
In Visual Basic trebuie apelata explicit metoda Invoke.
Metoda Invoke foloseste campurile _target si _methodPtr pentru a apela metoda corecta
pe obiectul specificat.
Metoda Invoke are aceeasi signatura ca si delegate.

Comparare delegate pentru egalitate

Clasa de baza Delegate suprascrie metoda Equals din System.Object.
Metoda Equals compara doua obiecte delegate pentru a vedea daca campurile _target si
_methodPtr se refera la acelasi obiect si aceeasi metoda, altfel spus doua obiecte delegate
sunt egale daca fac refrinta la acelasi obiect si la aceeasi metoda.

Exemplu

Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToConsole);
//
// Chiar daca fb1 si fb2 se refera la doua obiecte diferite,
// intern ele refera acelasi obiect si aceeasi metoda.
//
Console.WriteLine(fb1.Equals(fb2)); // Afiseaza "True"

In plus tipurile Delegate si MulticastDelegate supraincarca operatorii == si != (trebuie
supraincarcati impreuna), ca in exemplul urmator:

Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToConsole);
Console.WriteLine(fb1 == fb2); // Afiseaza "True"

Putem folosi acesti operatori in locul metodei Equals.

Lanturi (liste) de delegate

Pe acelasi obiect apelam mai multe metode callback. Campul _prev din
MulticastDelegate este folosit pentru a mentine lista inlantuita de delegate.
Clasa Delegate defineste urmatoarele metode pe care le putem folosi in manipularea listei
inlantuite de obiecte delegate.

class System.Delegate
{
Dezvoltare aplicatii folosind platforma .NET - 112/233
// Combines the chains represented by head and tail;
// head is returned.
// NOTE: head will be the last delegate called.

public static Delegate Combine(Delegate tail, Delegate head);

// Creates a chain represented by the array of delegates.
// NOTE: entry 0 is the head
// and will be the last delegate called.

public static Delegate Combine(Delegate[] delegateArray);

// Removes a delegate matching values
// Target/Method from the chain.
// The new head is returned and will be the last delegate called.

public static Delegate Remove(Delegate source, Delegate value);
}

Cand se construieste un obiect delegate, campul _prev = null. Pentru a construi lista
inlantuita de delegate folosim metodele Combine, ca mai jos :

Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fbChain = (Feedback) Delegate.Combine(fb1, fb2);
App appobj = new App();
Feedback fb3 = new Feedback(appobj.FeedbackToStream);
fbChain = (Feedback) Delegate.Combine(fbChain, fb3);

Metoda Combine, varianta a doua:

Feedback[] fbArray = new Feedback[3];
fbArray[0] = new Feedback(FeedbackToConsole);
fbArray[1] = new Feedback(FeedbackToMsgBox);
App appobj = new App();
fbArray[2] = new Feedback(appobj.FeedbackToStream);
Feedback fbChain = Delegate.Combine(fbArray);

Tipul delegate Feedback returneaza void.
Daca acest tip ar returna (de ex. Int32) prototipul este :

public delegate Int32 Feedback( Object value,
Int32 item,
Int32 numItems);
iar metoda interna Invoke va arata astfel:

class Feedback : MulticastDelegate
{
public Int32 virtual Invoke(Object value,
Int32 item,
Int32 numItems)
{
if (_prev != null) _prev.Invoke(value, item, numItems);
return _target.methodPtr(value, item, numItems);
Dezvoltare aplicatii folosind platforma .NET - 113/233
}
}

Daca delegate returneaza o valoare, si avem o lista de delegate, numai delegate din capul
listei va returna valoarea.

Stergerea unui delegate dintr-o lista

Metoda folosita este Remove.
Vezi exemplul urmator:

Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fbChain = (Feedback) Delegate.Combine(fb1, fb2);
// fbChain se refera la o lista cu doi delegate.
// Se apeleaza cele doua metode callback.
if (fbChain != null) fbChain(null, 0, 10);
fbChain = (Feedback) Delegate.Remove(
fbChain,
new Feedback(FeedbackToMsgBox));
// s-a sters o metoda din lista.
// Se va apela singura metoda ce a mai ramas.

if (fbChain != null) fbChain(null, 0, 10);

Se sterge cate o singura metoda.
Operatorul += este sinonim cu Combine, iar -= este sinonim cu Remove.

Feedback fb = new Feedback(FeedbackToConsole);
App appobj = new App();
fb += new Feedback(appobj.FeedbackToStream);
...
fb -= new Feedback(FeedbackToConsole);

Mai mult control asupra metodei Invoke

Neajunsuri: se poate prelua numai o singura valoare returnata, cea returnata de ultima
metoda callback apelata. Ce se intimpla daca o metoda callback arunca o exceptie sau ia
mult timp procesor pentru executie?
Pentru situatiile cand algoritmul de executie al metodelor callback (algoritm serial) nu
este suficient de bun, putem folosi metoda de instanta GetInvocationList din
MulticastDelegate. Aceasta metoda o putem folosi pentru a apela fiecare delegate in
mod explicit dintr-o lista de delegates.

public class MulticastDelegate
{
// Creates a delegate array; each item is a clone from the chain.
// NOTE: entry 0 is the tail, which would normally be called first.

public virtual Delegate[] GetInvocationList();
}
Dezvoltare aplicatii folosind platforma .NET - 114/233

Metoda GetInvocationList opereaza cu referinte la delegate si returneaza un tablou
de referinte la delegates. Se creaza o clona pentru fiecare obiect delegate din lista.
Fiecare clona are campul _prev = null.

Exemplu (J. Richter):

using System;
using System.Text;
// Define a Light component.
class Light
{
// This method returns the lights status.
public String SwitchPosition()
{
return "The light is off";
}
}

// Define a Fan component.
class Fan
{
// This method returns the fans status.
public String Speed()
{
throw new Exception("The fan broke due to overheating");
}
}

// Define a Speaker component.

class Speaker
{
// This method returns the speakers status.
public String Volume()
{
return "The volume is loud";
}
}

class App
{
// Definition of delegate that allows querying a components status
delegate String GetStatus();
static void Main()
{

// Declare an empty delegate chain.
GetStatus getStatus = null;

// Construct the three components, and add
// their status methods
// to the delegate chain.

getStatus += new GetStatus(new Light().SwitchPosition);
Dezvoltare aplicatii folosind platforma .NET - 115/233
getStatus += new GetStatus(new Fan().Speed);
getStatus += new GetStatus(new Speaker().Volume);

// Show consolidated status report reflecting
// the condition of the three components.

Console.WriteLine(GetComponentStatusReport(getStatus));
}

// Method that queries several components
// and returns a status report
static String GetComponentStatusReport(GetStatus status)
{

// If the chain is empty, theres nothing to do.

if (status == null) return null;

// Use this to build the status report.

StringBuilder report = new StringBuilder();
// Get an array where each element
// is a delegate from the chain.

Delegate[] arrayOfDelegates = status.GetInvocationList();

// Iterate over each delegate in the array.
foreach (GetStatus getStatus in arrayOfDelegates)
{
try
{
// Get a components status string,
// and append it to the report.

report.AppendFormat("{0}{1}{1}",
getStatus(), Environment.NewLine);
}
catch (Exception e)
{
// Generate an error entry in the report
// for this component.

Object component = getStatus.Target;

report.AppendFormat(
"Failed to get status from
{1}{2}{0} Error: {3}{0}{0}",
Environment.NewLine,
((component == null) ? "" :
component.GetType() + "."),
getStatus.Method.Name, e.Message);
}
}

// Return the consolidated report to the caller.

return report.ToString();
Dezvoltare aplicatii folosind platforma .NET - 116/233
}
}

Rezultatul este:

The light is off
Failed to get status from Fan.Speed
Error: The fan broke due to overheating
The volume is loud

Delegate si Reflection

In metodele callback discutate pina acum, informatia necesara (parametrii) erau stiuti in
momentul compilarii.
Tratam problema completarii acestor parametri in momentul executiei plus problema
apelarii unei anumite metode callback. Este exact ceea ce intimpla in programarea
Windows. Un eveniment genereaza un mesaj, daca exista un handler pentru mesaj acesta
va fi tratat, altfel are o tratare implicita( !).

Metodele din clasa delegate ce realizeaza acest lucru sunt:

public class Delegate
{
// Construct a delType delegate wrapping
// the specified methodInfo.
public static Delegate CreateDelegate(
Type delType,
MethodInfo mi);

// Construct a delType delegate wrapping a types static method.

public static Delegate CreateDelegate(
Type delType,
Type type,
String methodName);

// Construct a delType delegate wrapping
// an objects instance method.

public static Delegate CreateDelegate(
Type delegateType,
Object obj,
String methodName);

// Construct a delType delegate wrapping
// an objects instance method.

public static Delegate CreateDelegate(
Type delegateType,
Object obj,
String methodName,
Boolean ignoreCase);

public Object DynamicInvoke(Object[] args);
Dezvoltare aplicatii folosind platforma .NET - 117/233
}

Toate metodele CreateDelegate construiesc un nou obiect de tip Delegate, identificat
de primul parametru, delType.
Ceilalti parametri determina metoda callback pe care obiectul derivat din Delegate o va
implementa.
Metoda DynamicInvoke ne permite sa invocam o metoda callback a obiectului delegate,
pasindu-i parametrii la run time.
DynamicInvoke face un control al parametrilor (acelasi numar, acelasi tip, aceeasi
pozitie) pentru metoda callback invocata. In caz de esec se arunca o exceptie.
DynamicInvoke returneaza obiectul returnat de metoda callback.

Exemplu:

using System;
using System.Reflection;
using System.IO;

// Here are some different delegate definitions.
delegate Object TwoInt32s(Int32 n1, Int32 n2);

delegate Object OneString(String s1);

class App
{
static void Main(String[] args)
{
if (args.Length < 2)
{
String fileName = Path.GetFileNameWithoutExtension(
Assembly.GetEntryAssembly().CodeBase);
Console.WriteLine("Usage:");
Console.WriteLine("{0} delType methodName [Param1] [Param2]",
fileName);
Console.WriteLine(" where delType must be TwoInt32s or
OneString");
Console.WriteLine(" if delType is TwoInt32s, " +
"methodName must be Add or Subtract");
Console.WriteLine(" if delType is OneString, " +
"methodName must be NumChars or Reverse");
Console.WriteLine();
Console.WriteLine("Examples:");
Console.WriteLine(" {0} TwoInt32s Add 123 321", fileName);
Console.WriteLine(" {0} TwoInt32s Subtract 123 321", fileName);
Console.WriteLine(" {0} OneString NumChars \"Hello there\"",
fileName);
Console.WriteLine(" {0} OneString Reverse \"Hello there\"",
fileName);
return;
}

Type delType = Type.GetType(args[0]);
if (delType == null)
{
Dezvoltare aplicatii folosind platforma .NET - 118/233
Console.WriteLine("Invalid delType argument: " + args[0]);
return;
}

Delegate d;
try
{
d = Delegate.CreateDelegate(delType, typeof(App), args[1]);
}
catch (ArgumentException)
{
Console.WriteLine("Invalid methodName argument: " +
args[1]);
return;
}

Object[] callbackArgs = new Object[args.Length - 2];

if (d.GetType() == typeof(TwoInt32s))
{
try
{
for (Int32 a = 2; a < args.Length; a++)
callbackArgs[a - 2] = Int32.Parse(args[a]);
}
catch (FormatException)
{
Console.WriteLine("Parameters must be integers.");
return;
}
}

if (d.GetType() == typeof(OneString))
{
Array.Copy(args, 2, callbackArgs, 0, callbackArgs.Length);
}

try
{
Object result = d.DynamicInvoke(callbackArgs);
Console.WriteLine("Result = " + result);
}
catch (TargetParameterCountException)
{
Console.WriteLine("Incorrect number of parameters +
specified.");
}
}

// This is the callback method that takes two Int32 parameters.

static Object Add(Int32 n1, Int32 n2) {
return n1 + n2;
}

static Object Subtract(Int32 n1, Int32 n2) {
return n1 - n2;
Dezvoltare aplicatii folosind platforma .NET - 119/233
}

// This is the callback method that takes one String parameter.
static Object NumChars(String s1) {
return s1.Length;
}

static Object Reverse(String s1) {
Char[] chars = s1.ToCharArray();
Array.Reverse(chars);
return new String(chars);
}
}
Dezvoltare aplicatii folosind platforma .NET - 120/233
Definirea evenimentelor cu MulticastDelegates
Aplicatiile Windows au nevoie sa proceseze evenimente in mod asincron. Evenimentele
genereaza mesaje, iar mesajele au atasate functii ce le trateaza. Modelul delegate
multicast are la baza paternul Publish/Subscribe, in care o clasa publica un eveniment pe
care-l poate genera, si un alt numar de clase subscriu pentru acest eveniment. Cand
evenimentul a fost generat, runtime-ul are grija sa notifice producerea evenimentului
fiecarei clase ce asteapta acel eveniment.

Metoda apelata ca rezultat al producerii unui eveniment este definita de un delegate.
Cand folosim delegate in acest mod, trebuie sa respectam urmatoarele reguli:
1. Delegate trebuie definit ca avand doi parametri.
2. Argumentele reprezinta totdeauna doua obiecte: obiectul ce a generat
evenimentul, si un obiect eveniment.
3. Al doilea parametru trebuie sa fie derivat din clasa EventArgs.
Exemplificam pe baza urmatorului exemplu. Vrem sa avem posibilitatea monitorizarii
corecte a schimbarilor de stocuri dintr-o magazie de marfa. Modificarea stocului poate fi
facuta prin intermediul urmatoarelor actiuni: aprovizionare, vinzare, inventar faptic.
O idee de a implementa acest lucru este de a construi o clasa ce va fi folosita totdeauna
pentru actualizarea stocului. Aceasta clasa ar trebui sa publice un eveniment ce va fi
generat ori de cite ori stocul se modifica conform actiunilor descrise mai sus.
Atunci orice clasa ce are nevoie sa fie actualizata ar trebui sa subscrie pentru acest
eveniment.
Codul ar putea fi (Tom Archer: Inside C# Second Edition):

using System;

class InventoryChangeEventArgs : EventArgs
{
//
// Schimbare stoc din inventar
//
public InventoryChangeEventArgs(string sku, int change)
{
this.sku = sku;
this.change = change;
}

string sku;
public string Sku
{
get { return sku; }
}

int change;
public int Change
{
get { return change; }
}
};

Dezvoltare aplicatii folosind platforma .NET - 121/233
// Publisher
class InventoryManager
{
public delegate void InventoryChangeEventHandler(
object source,
InventoryChangeEventArgs e);

public event InventoryChangeEventHandler OnInventoryChangeHandler;

public void UpdateInventory(string sku, int change)
{
if (0 == change)
return; // No update on null change.

// Code to update database would go here.

InventoryChangeEventArgs e =
new InventoryChangeEventArgs(sku, change);

if (OnInventoryChangeHandler != null)
{
Console.WriteLine("[InventoryManager" +
".UpdateInventory] Raising event to " +
"all subscribers...\n");
OnInventoryChangeHandler(this, e);
}
}
};

// Subscriber
class InventoryWatcher
{
InventoryManager inventoryManager;

public InventoryWatcher(InventoryManager inventoryManager)
{
Console.WriteLine("[InventoryWatcher" +
".InventoryWatcher] Subscribing to " +
"InventoryChange event\n");
this.inventoryManager = inventoryManager;

inventoryManager.OnInventoryChangeHandler += new
InventoryManager.InventoryChangeEventHandler(OnInventoryChange);
}
// Metoda apelata la eveniment
void OnInventoryChange(object source,
InventoryChangeEventArgs e)
{
int change = e.Change;
Console.WriteLine("[InventoryManager.OnInventoryChange]" +
"\n\tPart '{0}' was {1} by {2} units\n",
e.Sku,
change > 0 ? "increased" : "decreased",
Math.Abs(e.Change));
}
}

Dezvoltare aplicatii folosind platforma .NET - 122/233
// test tipuri definite
class DelegateEvents
{
public static void Main()
{
InventoryManager inventoryManager =
new InventoryManager();

Console.WriteLine("[DelegateEvents.Main] " +
"Instantiating subscriber object\n");
InventoryWatcher inventoryWatch =
new InventoryWatcher(inventoryManager);

inventoryManager.UpdateInventory("111 006 116", -2);
inventoryManager.UpdateInventory("111 005 383", 5);

Console.ReadLine();
}
};

Sa examinam acest cod.

public delegate void InventoryChangeEventHandler
(object source, InventoryChangeEventArgs e);

Am definit un delegate, deci o signatura pentru o metoda. Atentie la numarul de
parametri si tipul acestora.

public event InventoryChangeEventHandler OnInventoryChangeHandler;

Aici apare cuvatul cheie event, ce defineste un membru de tip event, cu care specificam
delegate si metoda(ele) ve va (vor) fi apelata (e) cand evenimentul este generat.
In exemplul de mai sus UpdateInventory va fi apelata ori de cite ori se genereaza
evenimentul ce anunta modificarea stocului. Aceasta metoda creaza un obiect de tipul
InventoryChangeEventArgs. Acest obiect este pasat tuturor celor ce au subscris pentru
acest eveniment, si este utilizat sa descrie evenimentul.
In urmatorul cod:

if (OnInventoryChangeHandler != null)
{
Console.WriteLine("[InventoryManager" +
".UpdateInventory] Raising event to " +
"all subscribers...\n");
OnInventoryChangeHandler(this, e);
}

instructiunea if verifica daca pentru acest eveniment exista obiecte ce asteapta producerea
lui.

Dezvoltare aplicatii folosind platforma .NET - 123/233
Sa examinam codul din cei ce s-au inscris pentru eveniment. In situatia de fata clasa
InventoryWatcher.
Ceea ce are nevoie sa faca aceasta clasa este:
sa instantieze un delegate de tip InventoryManager.InventoryChangeEventHandler si
sa adauge acest delegate la evenimentul
InventoryManager.OnInventoryChangeHandler.

inventoryManager.OnInventoryChangeHandler += new
InventoryManager.InventoryChangeEventHandler(
OnInventoryChange);

Singurul argument utilizat indica numele metodei ce va apelata cand se va produce
evenimentul. Urmatorul lucru ce trebuie facut este implementarea functiei ce trateaza
evenimentul. In acest caz functia este InventoryWatcher.OnInventoryChange, ce afiseaza
un mesaj. In final, codul ce ruleaza aceasta aplicatie, instantiaza clasele
InventoryManager si InventoryWatcher, si de fiecare data cand metoda
InventoryManager.UpdateInventory este apelata, se genereaza automat un eveniment ce
are ca efect apelul metodei InventoryWatcher.OnInventoryChanged.


Dezvoltare aplicatii folosind platforma .NET - 124/233
Evenimente

Membrul event al unei clase permite acesteia de a notifica alte obiecte ca ceva s-a
intimplat.

Un tip ce defineste un membru event are urmatoarele capabilitati:

capabilitatea obiectelor de a-si inregistra interesul lor pentru un evenimet;
capabilitatea de a renunta la un eveniment (stergerea inregistrarii);
capabilitatea obiectului ce defineste evenimentul de a mentine o lista a obiectelor
inregistrate pentru eveniment si de a le notifica acestora producerea
evenimentului.

Modelul eveniment al CLR-ului este bazat pe delegates.

Consideram urmatorul exemplu. Dorim sa proiectam o aplicatie pentru e-mail.
Cand soseste un mesaj, utilizatorul doreste ca acesta sa fie redirectionat (forward) catre
un fax sau un pager.

Construim urmatoarele tipuri:
MailManager, ce primeste mesajele e-mail. MailManager va expune un eveniment numit
MailMsg.
Alte tipuri (Fax, Pager) trebuie sa-si manifeste interesul fata de acest eveniment.
Cand se primeste un e-mail, MailManager va produce evenimentul, distribuind mesajul
catre clasele ce s-au inregistrat pentru acest mesaj.

Fiecare obiect proceseaza mesajul dupa cum doreste.

Proiectarea unui tip ce expune un eveniment

Pattern-ul recomandat de Microsoft pentru definirea unui eveniment este urmatorul :
Etapa 0 : Definire tip ce va contine event. (class MailManager).
Etapa 1 : Definirea unui tip imbricat, derivat din EventArgs, tip ce va contine informatia
ce va fi pasata obiectelor interesate de acest eveniment (public class
MailMsgEventArgs : EventArgs).
Etapa 2 : Definim tipul delegate ce ne va da prototipul metodei callback pe care apelatii
vor trebui sa o implementeze (
public delegate void MailMsgEventHandler(
Object sender,
MailMsgEventArgs args);
Etapa 3: Declararea datei membru event
public event MailMsgEventHandler MailMsg;

Etapa 4: Definirea metodei responsabila cu notificarea clientilor despre aparitia
evenimentului
protected virtual void OnMailMsg(MailMsgEventArgs e)
Dezvoltare aplicatii folosind platforma .NET - 125/233

Etapa 5: Definirea metodei ce identifica daca evenimentul a aparut sau nu:
public void SimulateArrivingMsg(
String from,
String to,
String subject,
String body)
Exemplu:

class MailManager
{
// The MailMsgEventArgs type is defined within the MailManager
// type.
public class MailMsgEventArgs : EventArgs
{
// 1. Type defining information passed to receivers of the event
public MailMsgEventArgs(
String from, String to, String subject, String body)
{
this.from = from;
this.to = to;
this.subject = subject;
this.body = body;
}

public readonly String from, to, subject, body;
}

// 2. Delegate type defining the prototype of the callback
// method that receivers must implement

public delegate void MailMsgEventHandler(
Object sender, MailMsgEventArgs args);

// 3. The event itself

public event MailMsgEventHandler MailMsg;

// 4. Protected, virtual method responsible for notifying
// registered objects of the event

protected virtual void OnMailMsg(MailMsgEventArgs e)
{
// Has any objects registered interest with the event?
if (MailMsg != null)
{
// Yesnotify all the objects in the delegate linked list.
MailMsg(this, e);
}
}

// 5. Method that translates the input into the desired event.
// This method is called when a new e-mail message arrives.

public void SimulateArrivingMsg(String from, String to,
String subject, String body)
Dezvoltare aplicatii folosind platforma .NET - 126/233
{
// Construct an object to hold the information you want
// to pass to the receivers of the notification.
MailMsgEventArgs e =
new MailMsgEventArgs(from, to, subject, body);

// Call the virtual method notifying the object
// that the event occurred.
//If no derived type overrides this method, the object
// will notify all the registered listeners.

OnMailMsg(e);
}
}

Codul critic se gaseste in clasa MailManager.
Dezvoltatorul trebuie sa defineasca urmatoarele articole:

1. Sa defineasca un tip ce va mentine informatii aditionale ce ar trebui trimise celor ce
asteapata notificarea evenimentului. Tipurile ce mentin informatia despre un eveniment
sunt derivate din System.EventArgs, si numele tipului ar trebui sa se termine cu
EventArgs. In exemplul nostru, tipul MailMsgEventArgs contine campuri ce idetifica cine
a trimis mesajul (from), cine primeste mesajul (to), subiectul mesajului (subject) si corpul
mesajului (body).

EventArgs este definita in FCL astfel:

[Serializable]
public class EventArgs
{
public static readonly EventArgs Empty = new EventArgs();
public EventArgs() { }
}

Acesta serveste ca un tip de baza din care alte tipuri pot deriva.
Exista si evenimente care nu au informatii aditionale de transmis (clic pe un buton).

Cand definim un eveniment ce nu are informatii aditionale de transmis putem folosi
EventArgs.Empty in loc de a construi un nou obiect EventArgs.

2. Definim un tip delegate, specificand prototipul metodei ce va fi apelata cand se
genereaza evenimentul. Prin conventie numele delegate se termina cu EventHandler.
Prototipul trebuie sa returneze void si sa aiba doi parametri. Primul parametru este un
Object ce se refera la la obiectul ce a trimis notificarea, iar al doilea parametru este un tip
derivat din EventArgs ce contine informatii aditionale despre notificare.

Prototipul pentru EventHandler este urmatorul:

public delegate void EventHandler(Object sender, EventArgs e);

Dezvoltare aplicatii folosind platforma .NET - 127/233
3. Definim un eveniment, MailMsg, de tip MailMsgEventHandler, deci metoda callback
trebuie sa aiba acest prototip.

4. Definim o metoda virtuala, protected, metoda responsabila pentru notificarea
obiectelor inregistrate sa primeasca acest eveniment. Metoda OnMailMsg este apelata
cand un nou mesaj soseste. Aceasta metoda primeste un obiect MailMsgEventArgs
initializat ce contine informatii aditionale despre eveniment. Aceasta metoda verifica
daca exista cineva interesat de acest eveniment pentru a da drumul evenimentului.
Un tip ce foloseste MailManager ca tip de baza poate suprascrie metoda OnMailMsg.

5. Definim o metoda ce transforma intrarea in evenimentul dorit.
In exemplul nostru metoda SimulatingArrivingMsg este apelata pentru a indica ca un
mesaj nou a sosit in MailManager.

Sa examinam indeaproape ce inseamna sa definim un event MailMsg.

public event MailMsgEventHandler MailMsg;

Compilatorul C# transforma aceasta linie de cod in:

// 1. A PRIVATE delegate field that is initialized to null
// Referinta la inceputul listei de delegates

private MailMsgEventHandler MailMsg = null;

// 2. A PUBLIC add_* method
// Allows objects to register interest in the event.

[MethodImplAttribute(MethodImplOptions.Synchronized)]
public void add_MailMsg(MailMsgEventHandler handler)
{
MailMsg = (MailMsgEventHandler)Delegate.Combine(
MailMsg, handler);
}
// 3. A PUBLIC remove_* method
// Allows objects to unregister interest in the event.
[MethodImplAttribute(MethodImplOptions.Synchronized)]
public void remove_MailMsg(MailMsgEventHandler handler)
{
MailMsg = (MailMsgEventHandler)
Delegate.Remove(MailMsg, handler);
}

Cand un obiect este interesat de eveniment, acest camp referentiaza o instanta a delegate
MailMsgEventHandler.
Fiecare instanta delegate MailMsgEventHandler are un pointer la un alt delegate
MailMsgEventHandler sau la null pentru a marca sfarsitul listei.

Proiectarea unui tip ce asteapta pentru un eveniment

Dezvoltare aplicatii folosind platforma .NET - 128/233
Exemplu

class Fax
{
// Pass the MailManager object to the constructor.
public Fax(MailManager mm)
{
// Construct an instance of the MailMsgEventHandler
// delegate that refers to the FaxMsg callback method.
// Register the callback with MailManagers
// MailMsg event.
mm.MailMsg+= new MailManager.MailMsgEventHandler(FaxMsg);
}
// This is the method that MailManager will call to
// notify the Fax object that a new e-mail message has arrived.

private void FaxMsg(
Object sender, MailManager.MailMsgEventArgs e)
{
// The sender identifies the MailManager in case
// you want to communicate back to it.
// The e identifies the additional event information
// that the MailManager wants to provide.
// Normally, the code here would fax the e-mail message. //
// This test implementation displays the information
// on the console.

Console.WriteLine("Faxing mail message:");
Console.WriteLine(
" To: {0}\n From: {1}\n Subject: {2}\n Body: {3}\n",
e.from, e.to, e.subject, e.body);
}

public void Unregister(MailManager mm)
{
// Construct an instance of the MailMsgEventHandler
// delegate that refers to the FaxMsg callback method.
MailManager.MailMsgEventHandler callback =
new MailManager.MailMsgEventHandler(FaxMsg);
// Unregister with MailManagers MailMsg event.
mm.MailMsg -= callback;
}
}

Cand aplicatia de e-mail se initializeaza, va construi mai intai un obiect MailManager si
va salva referinta la acest obiect intr-o variabila. In continuare se construieste un obiect
de tip Fax, pasindu-i referinta la obiectul MailManager. In ctor Fax se construieste un nou
obiect delegate MailManager.MailMsgEventHandler care este un wrapper pentru metoda
FaxMsg din Fax ce are acelasi prototip ca MailMsgEventHandler (altfel codul nu se
compileaza). In continuare obiectul Fax se inregistreaza pentru evenimentul MailMsg din
mailManager :

mm.MailMsg += new MailManager.MailMsgEventHandler(FaxMsg);

Dezvoltare aplicatii folosind platforma .NET - 129/233
Compilatorul C# va transforma operatorul += in urmatoarea linie de cod pentru a adauga
interesul obiectului pentru eveniment :

mm.add_MailMsg(new MailManager.MailMsgEventHandler(FaxMsg));

Aceasta metoda o putem apela si explicit.

Cand MailManager produce evenimentul, obiectul Fax va apela metoda FaxMsg ce
primeste o referinta la un obiect MailManager si o referinta la MailMsgEventArgs.
Referinta la MailManager poate fi folosita pentru a accesa campuri sau metode din
MailManager. Din obiectul MailMsgEventArgs, metoda FaxMsg are acces la toate
caracteristicile mesajului.

Dezvoltare aplicatii folosind platforma .NET - 130/233
Exemplu cu evenimente (help Microsoft)

using System;

// FireEventArgs: a custom event inherited from EventArgs.

public class FireEventArgs: EventArgs {
public FireEventArgs(string room, int ferocity) {
this.room = room;
this.ferocity = ferocity;
}

// The fire event will have two pieces of information--
// 1) Where the fire is, and 2) how "ferocious" it is.

public string room;
public int ferocity;

} //end of class FireEventArgs


// Class with a function that creates the eventargs and initiates the
event
public class FireAlarm {

// Events are handled with delegates, so we must establish a
FireEventHandler
// as a delegate:

public delegate void FireEventHandler(object sender, FireEventArgs
fe);

// Now, create a public event "FireEvent" whose type is our
FireEventHandler delegate.

public event FireEventHandler FireEvent;

// This will be the starting point of our event-- it will create
FireEventArgs,
// and then raise the event, passing FireEventArgs.

public void ActivateFireAlarm(string room, int ferocity) {

FireEventArgs fireArgs = new FireEventArgs(room, ferocity);

// Now, raise the event by invoking the delegate. Pass in
// the object that initated the event (this) as well as
FireEventArgs.
// The call must match the signature of FireEventHandler.

FireEvent(this, fireArgs);
}
} // end of class FireAlarm


// Class which handles the event
Dezvoltare aplicatii folosind platforma .NET - 131/233

class FireHandlerClass {

// Create a FireAlarm to handle and raise the fire events.

public FireHandlerClass(FireAlarm fireAlarm) {

// Add a delegate containing the ExtinguishFire function to the class'
// event so that when FireAlarm is raised, it will subsequently execute
// ExtinguishFire.

fireAlarm.FireEvent += new
FireAlarm.FireEventHandler(ExtinguishFire);
}

// This is the function to be executed when a fire event is raised.

void ExtinguishFire(object sender, FireEventArgs fe) {
Console.WriteLine("ExtinguishFire function was called by {0}.",
sender.ToString());

// Now, act in response to the event.

if (fe.ferocity < 2)
Console.WriteLine("This fire in the {0} is no problem. I'm
going to pour some water on it.", fe.room);
else if (fe.ferocity < 5)
Console.WriteLine("I'm using FireExtinguisher to put out
the fire in the {0}.", fe.room);
else
Console.WriteLine("The fire in the {0} is out of control.
I'm calling the fire department!", fe.room);
}
} //end of class FireHandlerClass

public class FireEventTest {
public static void Main () {
// Create an instance of the class that will be firing an event.
FireAlarm myFireAlarm = new FireAlarm();
// Create an instance of the class that will be handling the event.
// Note that it receives the class that will fire the event
// as a parameter.

FireHandlerClass myFireHandler = new
FireHandlerClass(myFireAlarm);

//use our class to raise a few events and watch them get handled
myFireAlarm.ActivateFireAlarm("Kitchen", 3);
myFireAlarm.ActivateFireAlarm("Study", 1);
myFireAlarm.ActivateFireAlarm("Porch", 5);

return;

} //end of main

} // end of FireEventTest
Dezvoltare aplicatii folosind platforma .NET - 132/233
Exceptii

O linie de cod poate sa nu se execute corect din diverse motive: depasire aritmetica,
depasire stiva, memorie insuficienta, indici in afara intervalului, etc.
O aplicatie ce si-ar propune sa verifice toate (sau aproape toate) posibilitatile ce pot
aparea in executarea unei linii de cod si-ar pierde din claritate, ar fi foarte greu de
intretinut, iar mare parte din timp procesor s-ar consuma cu aceste verificari.
Realizarea unei astfel de aplicatii este aproape imposibila.
Folosind exceptiile nu este nevoie sa scriem cod pentru a detecta toate aceste posibile
caderi. In plus codul se executa mai rapid.
Trebuie spus de la inceput ca exceptiile genereaza alte probleme care trebuiesc rezolvate.

Dintre avantajele introducerii exceptiilor amintim :
abilitatea de a mentine cod clar si de a intelege logica aplicatiei mai usor;
posibilitatea de a detecta si localiza bug-uri in cod;
Cand apare o cadere, CLR scaneaza stiva firului cautind codul ce ar putea trata
exceptia. Daca nu exista un asemenea cod, primim o notificare unhandled exception.
Folosirea incorecta a tratarii execeptiilor poate crea neplaceri mai mari decat nefolosirea
lor.

Evolutia manipularii exceptiilor

Win32 API si COM nu folosesc exceptiile pentru a notifica utilizatorul despre problema
aparuta. Multe functii returneaza FALSE sau NULL sau o valoare de tip HRESULT
pentru a indica ca ceva nu s-a executat corect. De altfel nu s-a standardizat tratarea
erorilor in Win32 API. La inceput C si C++ nu suportau exceptiile.
Metoda veche de raportare a erorilor se limita la un numar pe 32 de biti. In marea
majoritate a cazurilor mesajele nu sunt clare si nu ofera suficienta informatie pentru a
detecta ce anume s-a intimplat.

Din acest punct de vedere mecanismul de manipulare al exceptiilor ofera mai multe
avantaje:
Eexceptia include un sir de caractere ce o descrie.
Sirul de caractere contine o cale catre apelul ce a declansat anomalia. Informatiile
se gasesc pe stiva.

Exceptiile nu pot fi ignorate.

Daca o metoda arunca o exceptie, aceasta inseamna ca nu lucreaza asa cum ar trebui.
Daca aplicatia nu trateaza exceptia, CLR termina aplicatia.

Toate metodele definite de tipurile din cadrul de lucru .NET arunca exceptii pentru a
indica ca anumite presupuneri au fost incalcate. Nu se returneaza nici o valoare de stare.


Dezvoltare aplicatii folosind platforma .NET - 133/233


Mecanismul manipularii exceptiilor

Mecanismul de manipulare al exceptiilor cadrului de lucru .NET este construit folosind
Structured Exception Handling (SEH). Urmatorul cod C# arata o folosire standard a
mecanismului de tratare al exceptiilor.

void SomeMethod()
{
try
{
// aici scriem codul ce vrem sa-l protejam (supus verificarii)
}
catch (InvalidCastException)
{
// Aici este codul ce se va executa daca apare exceptia
// InvalidCastException sau orice exceptie a unui
// tip derivat din InvalidCastException.
}
catch (NullReferenceException)
{
// Aici este codul ce se va executa daca apare exceptia
// NullReferenceException sau orice exceptie a unui
// tip derivat din NullReferenceException.
}
catch (Exception e)
{
// In interiorul acestui bloc scriem codul care se executa
// pentru o exceptie CLS-compliant (conforma cu CLS).
// In mod normal se rearunca exceptia.

throw;
}
catch
{
// Aici este codul ce se va executa pentru orice tip de exceptie
// In mod normal se rearunca exceptia.
throw;
}
finally
{
// Acest cod este garantat ca se executa totdeauna, dar
// binenteles exista cateva exceptii de la
// aceasta presupunere.
// Codul de aici se executa indiferent daca a aparut
// sau nu o exceptie.
// Este asa numitul cod de curatare,
// mai precis de terminare
// a unor operatii incepute in bocul try.
}

//
// In continuare este codul ce se executa cand nu
// a aparut nici o exceptie.
Dezvoltare aplicatii folosind platforma .NET - 134/233

}

In mod normal in cod avem un bloc try, unul sau mai multe blocuri catch si optional
un bloc finally.

Exemplu:

using System;//includes System.Exception
class ExceptionApp
{
public TestMethod( int i, int j)
{
try
{
...
// Functia apelata poate arunca o exceptie.
ExceptionMethod(); //*1
...
int k=0;
// Operatiile pot arunca exceptii
// Daca j = 0 atunci avem impartire la zero
k = i/j; //*2
...
//Constient, se arunca o exceptie, ce tine de logica aplicatiei
if( k == 0)
throw new Exception(); //*3
}
catch (Exception e) //*4 se prind toate exceptiile
{
}
Console.Writeline("Cod dupa exceptie"); // *5
}
}

Blocul try

In general un bloc try contine cod ce are nevoie de operatii de terminare corecta
(curatare) sau operatii de tratare a exceptiilor. Codul pentru terminare corecta (de ex.
inchiderea unui fisier) trebuie plasat in blocul finally. Codul de tratare al exceptiilor
trebuie plasat in unul sau mai multe blocuri catch. Fiecare bloc catch identifica un anumit
tip de exceptie. Putem crea cate un bloc catch pentru orice tip de eveniment pentru care
avem posibilitatea de a corecta exceptia aparuta sau a obtine mai multe informatii despre
aceasta. Aceste informatii pot ajuta pentru depistarea unui bug sau neindeplinirea unor
conditii de sistem (de ex. adresa IP).

Un bloc try trebuie sa fie asociat cu cel putin un bloc catch sau finally.

Blocul catch
Dezvoltare aplicatii folosind platforma .NET - 135/233

Blocul catch contine cod ce se executa cand apare o exceptie. Daca codul din blocul try
nu cauzeaza o exceptie, atunci codul din blocul catch nu se executa. Daca exista blocul
finally atunci codul se executa indiferent daca s-a aruncat sau nu o exceptie. Daca nu a
aparut o exceptie atunci executia continua cu codul de dupa blocul finally.

Expresia dintre paranteze de la blocul catch se numeste filtru exceptie.
Exceptia filtru este un tip ce reprezinta o situatie exceptionala pe care dezvoltatorul a
anticipat-o si poate sa o trateze intr-un anume fel (poate iesi din aceasta situatie). In C#,
tipul din filtru trebuie sa fie System.Exception sau un tip derivat din
System.Exception. Blocurile catch sunt tratate in mod secvential, de sus in jos, deci
blocurile catch pentru tratarea exceptiilor specifice trebuie plasate cat mai aproape de
blocul try.

Daca o exceptie este aruncata de codul din blocul try (sau orice metoda apelata din cadrul
acestuia), CLR cauta blocurile catch al caror filtru se potriveste cu exceptia aruncata.
Daca nu exista un asemenea bloc catch, atunci CLR cauta in stiva un bloc catch al carui
filtru se potriveste. Daca cautarea s-a terminat si nu s-a gasit un asemenea bloc atunci
CLR opreste aplicatia si afiseaza mesajul Unhandled exception.
CLR pastreaza o arborescenta a tuturor metodelor apelate din blocul try.
Cand s-a localizat un bloc catch ce trateaza excepia (filtrul se potriveste), CLR executa
codul din toate blocurile finally, plecand de la acela al blocului try ce a generat exceptia
si oprindu-se la acela al blocului try ce contine blocul catch ce trateaza exceptia.
Codul din ultimul bloc finally se va executa dupa ce s-a executat codul din blocul catch.

In C#, un filtru catch poate specifica o variabila de tip exceptie. Aceasta variabila se
refera la un obiect derivat din System.Exception ce a aruncat aceasta exceptie. Codul
din blocul catch poate referi aceasta variabila pentru a obtine informatii suplimentare
despre exceptie.
La sfarsitul ultimului bloc catch avem urmatoarele posibilitati:
1. rearuncarea aceleasi exceptii;
2. aruncarea unei alte exceptii;
3. sa lasam ca firul sa termine cautarea aici.

In primele doua situatii, CLR va cauta in continuare pe stiva un bloc catch ce poate trata
exceptia. In ultima situatie se va executa codul din blocul finally si apoi se continua in
secventa cu codul ce urmeaza acestui bloc. Daca nu exista un bloc finally, se continua
executia cu codul ce urmeaza ultimului bloc catch.

Blocul finally

Un bloc finally contine cod ce este garantat ( ?!) ca se va executa.

Exemplu:

void ReadData(String pathname)
{
Dezvoltare aplicatii folosind platforma .NET - 136/233
FileStream fs = null;
try
{
fs = new FileStream(pathname, FileMode.Open);
// Procesare date din fisier
}
catch (OverflowException)
{
// Inside this catch block is where you put code that recovers
// from an OverflowException (or any exception type derived
// from OverflowException).
}
finally
{
// Make sure that the file gets closed.
if (fs != null) fs.Close();
}
}

Indiferent de cum se executa codul din blocul try, codul din blocul finally se va executa.
Blocul finally trebuie sa apara dupa toate blocurile catch asociate cu blocul try.

Nu se arunca exceptii in blocul finally. Exista riscul este de a construi o aplicatie ce nu
se mai termina.

Ce este o exceptie?

Termenul eroare indica ca programatorul a facut ceva gresit.
Exceptiile nu indica neaparat o eroare in cod.
Exceptiile nu conduc neaparat la oprirea executiei unei aplicatii.

O exceptie poate fi definita ca fiind violarea unei presupuneri implicite asupra unei
interfete (J. Richter).

De exemplu construirea unui tip nu poate cuprinde toate situatiile in care acesta poate fi
folosit. Cazurile care nu sunt implementate dar se incearca a fi folosite vor arunca
anumite exceptii in ideea de a nu folosi tipul respectiv altfel decat a fost proiectat.

Trebuie sa facem distinctie intre exceptiile aruncate de aplicatie si cele aruncate de CLR.
Comportarile aplicatiei sunt diferite functie de cine a aruncat exceptia.

CLR arunca exceptii, de ex. System.StackOverflowException, sau
System.OutOfMemoryException sau System.ExecutionEngineException.

In aceste situatii este posibil ca codul din blocul finally sa nu se poata executa.

OutOfMemoryException = este aruncata aceasta exceptie cand construim un nou obiect
si nu exista memorie suficienta. In acest caz procesul se termina imediat, codul din blocul
finally se executa. Daca exceptia este aruncata de CLR atunci procesul se termina imediat
Dezvoltare aplicatii folosind platforma .NET - 137/233
si aplicatia nu mai este in stare sa prinda aceasta exceptie si blocul finally nu se mai
executa.

StackOverflowException = CLR arunca aceasta exceptie cand un fir a utilizat tot
spatiul pentru stiva. Aplicatia poate prinde aceasta exceptie dar codul din blocul finally
nu se va executa pentru ca si asta are nevoie de spatiu pe stiva.
Fiecare bloc catch ce prinde aceasta exceptie trebuie sa o arunce in continuare (rethrow)
pentru a da sansa CLR-ului sa termine procesul.
Daca exceptia apare in CLR, aplicatia nu poate prinde aceasta exceptie si blocul finally
nu se executa. In acest caz CLR termina procesul sau lanseaza un debugger.

ExecutionEngineException = CLR arunca aceasta exceptie cand detecteaza ca
structurile sale de date interne sunt corupte sau are anumite bug-uri. CLR-ul termina
procesul sau va lansa un debugger pentru proces. Nu se executa nici un bloc catch si nici
finally.

Clasa System.Exception

Modelul de tratare al exceptiilor este bazat pe reprezentarea exceptiilor ca obiecte si
separarea codului aplicatiei de codul de tratatre al exceptiilor. Un bloc catch ce trateaza
System.Exception trebuie sa fie ultimul bloc pentru try.

Ordinea blocurilor catch ce trateaza o exceptie va fi urmatoarea : intai exceptia pentru
tipul derivat si apoi exceptia pentru clasa de baza (blocurile catch se trateaza secvential,
iar cautarea se opreste dupa primul bloc catch ce trateaza exceptia).

Exemplu (cod incorect al doilea bloc catch nu va fi executat niciodata pentru ca
System.Web.HttpException este derivat din System.Exception).

try
{
// Code which can cause a web exception or arithmetic exception.
}
catch (System.Exception ex)
{
MessageBox.Show ( "An exception occurred." );
}
catch (System.Web.HttpException ex)
{
MessageBox.Show ( "A web exception occurred." );
}

Acelasi exemplu, dar de aceasta data corect:

try
{
// Code which can cause a web exception or arithmetic exception.
}
catch (System.Web.HttpException ex)
{
MessageBox.Show ( "A web exception occurred." );
}
Dezvoltare aplicatii folosind platforma .NET - 138/233
catch(System.Exception ex)
{
MessageBox.Show ( "An exception occurred." );
}

In clasa Exception exista doua categorii de exceptii:
clase exceptii predefinite de CLR, derivate din System.Exception.
Clase exceptii definite de utilizator la nivel de aplicatie si derivate din
Application.Exception.

CLR permite o instanta de orice tip sa fie aruncata pentru o exceptie.

Tipurile de exceptii ce sunt derivate din System.Exception se numesc CLS-compliant
(conforme cu CLS).

Tipul System.Exception este descris in continuare.

Message - Read-only String Indica de ce a fost aruncata exceptia.
Source - Read/write String Contine numele assembly ce a generat exceptia.
StackTrace - Read-only String Contine numele si signatura metodei apelate ce a dus la
aruncarea exceptiei.
TargetSite - Read-only MethodBase Contine metoda ce a generat exceptia.
HelpLink - Read-only String Contine un URL la documentatia exceptiei.
InnerException - Read-only Exception Indica exceptia anterioara daca exceptia
curenta a fost generata in timp ce manipula o alta exceptie.
Metoda GetBaseException returneaza originea exceptiei.
HResult - Read/write Int32 Proprietate protected, folosita cand exista cod managed si
unmanaged COM.

ApplicationException este aruncata de un program al utilizatorului si nu de CLR. In
mod normal exceptiile create de utilizator ar trebui derivate din acest tip.
ApplicationException diferentiaza exceptiile definite de utilizator fata de cele definite
de sistem.
ApplicationException nu furnizeaza informatii despre cauza exceptiei.
In mod normal nu se arunca o asemenea exceptie de catre aplicatie, dar in cazul cand se
face acest lucru se va pasa constructorului clasei un mesaj ce va descrie cauza aparitiei
exceptiei.

Clase exceptii des utilizate

System.ArithmeticException exceptii ce apar in timpul operatiilor aritmetice, cum
ar fi System.DivideByZeroException and System.OverflowException.

System.ArrayTypeMismatchException - ArrayTypeMismatchException este aruncata
cand un obiect incompatibil se incearca a fi memorat intr-un Array.

System.DivideByZeroException incercare de impartire prin zero.
Dezvoltare aplicatii folosind platforma .NET - 139/233
System.IndexOutOfRangeException - IndexOutOfRangeException este aruncata cand
se incearca sa se acceseze un tablou folosind un index mai mic ca zero sau in afara
intervalului tabloului.
System.InvalidCastException aruncata cand o conversie implicita de la tipul de
baza sau interfata la tipul derivat nu se executa corect in momentul executiei.
System.NullReferenceException aruncata cand se foloseste o referinta null la un
obiect pentru a-l accesa.
System.OutOfMemoryException - OutOfMemoryException este aruncata cand operatia
'new' (crearea unui nou obiect) nu se executa cu succes pentru ca nu exista suficienta
memorie.
System.OverflowException - OverflowException este aruncata cand apare depasire la
executia unei operatii aritmetice.
System.StackOverflowException - StackOverflowException este aruncata cand cand
stiva de executie este prea mica pentru a executa toate apelurile initiate (probabil bucla
infinita).
ArgumentException exceptie aruncata cand un argument furnizat unei metode este
invalid.

CLR arunca exceptii pentru tipurile derivate din SystemException.
O parte dintre aceste exceptii nu semnaleaza situatii fatale, o alta parte da.

Metodele definite de tipurile din FCL sunt presupuse ca arunca exceptii derivate din
System.Exception.

Exemplu:

using System;
public class MyClass {}
public class ArgExceptionExample {
public static void Main() {
MyClass my = new MyClass();
string s = "sometext";
try {
int i = s.CompareTo(my);
}
catch (Exception e) {
Console.WriteLine("Error: {0}",e.ToString());
}
}
}

Iesirea este:

Error: System.ArgumentException: Object must be of type String.
at System.String.CompareTo(Object value)
at ArgExceptionExample.Main()

Definirea claselor de exceptii

Dezvoltare aplicatii folosind platforma .NET - 140/233
Prin conventie numele unui tip exceptie ar trebui sa se termine cu Exception.
Metodele ar trebui sa-si verifice argumentele inainte de a le folosi.
Clasele din FCL ce pot fi folosite pentru acest lucru sunt:
ArgumentNullException,
ArgumentOutOfRangeException
DuplicateWaitObjectException.

Nu se va scrie cod ce va arunca o exceptie de tipul Exception, ApplicationException, sau
SystemException.

Exemplu:

class SomeType
{
public void SomeMethod(Object o)
{
if (!((o is ICloneable) && (o is IComparable)))
throw new LipsaInterfataException(...);
// urmeaza cod
}
}

Aici avem tipul LipsaInterfataException. Cum il definim?
Singura regula de care trebuie sa tinem seama este cea referitoare la efectele colaterale.
Daca derivam aceasta exceptie din ArgumentException, atunci codul existent ce
capteaza aceasta exceptie va capta si noua exceptie.
Cand definim acest tip trebuie sa vedem cu cine se aseamana dintre exceptiile existente,
asemanare in sensul metodei de tratare a exceptiei.
Daca derivam tipul din Exception nu exista prea multe informatii despre exceptia
aruncata.
Cel mai indicat este sa ne gandim la efectele colaterale decat la cele directe cand derivam
un tip exceptie.

Clasa de baza Exception defineste trei constructori:
1. unul implicit ce seteaza toate campurile si proprietatile la valori implicite;
2. un ctor cu un parametru de tip String ce creaza o instanta a tipului si seteaza un
mesaj specific.
3. un ctor cu doi parametri: un String si o instanta a unui tip derivat din Exception ce
seteaza un mesaj si o exceptie interna.

Daca adaugam campuri la un tip exceptie acestia trebuiesc initializati in constructor.
Exemplu
Aruncarea si tratarea unei exceptii ce face referinta la inner Exception.
using System;
public class MyAppException:ApplicationException
{
public MyAppException (String message) : base (message) {}
public MyAppException (String message, Exception inner) :
base(message,inner) {}
Dezvoltare aplicatii folosind platforma .NET - 141/233
}

public class ExceptExample
{
public void ThrowInner ()
{
throw new MyAppException("ExceptExample inner exception");
}
public void CatchInner()
{
try
{
this.ThrowInner();
}
catch (Exception e)
{
throw new MyAppException("Error caused by trying
ThrowInner.",e);
}
}
}

public class Test
{
public static void Main()
{
ExceptExample testInstance = new ExceptExample();

try
{
testInstance.CatchInner();
}
catch(Exception e)
{
Console.WriteLine ("In Main catch block. Caught: {0}",
e.Message);
Console.WriteLine ("Inner Exception is {0}",e.InnerException);
}
}
}

Iesirea este:
In Main catch block. Caught: Error caused by trying ThrowInner.
Inner Exception is MyAppException: ExceptExample inner exception
at ExceptExample.ThrowInner()
at ExceptExample.CatchInner()


Exemplu (2) de definire a unei exceptii

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;
// Allow instances of DiskFullException to be serialized.
[Serializable]
Dezvoltare aplicatii folosind platforma .NET - 142/233
sealed class DiskFullException : Exception, ISerializable
{
// The three public constructors
public DiskFullException()
: base()
{ // Call base constructor. }

public DiskFullException(String message)
: base(message)
{ // Call base constructor. }

public DiskFullException(String message, Exception
innerException)
: base(message, innerException)
{ // Call base constructor. }

// Define a private field.
private String diskpath;

// Define a read-only property that returns the field.
public String DiskPath { get { return diskpath; } }

// Override the public Message property so that the
// field is included in the message.
public override String Message
{
get
{
String msg = base.Message;
if (diskpath != null)
msg += Environment.NewLine + "Disk Path: " + diskpath;
return msg;
}
}

// Because at least one field is defined, define the
// special deserialization constructor. Because this
// class is sealed, this constructor is private. If this
// class is not sealed, this constructor should be protected.
private DiskFullException(SerializationInfo info,
StreamingContext context)
: base(info, context)
{ // Let the base deserialize its fields.
// Deserialize each field.
diskpath = info.GetString("DiskPath");
}

// Because at least one field is defined,
// define the serialization method.

void ISerializable.GetObjectData(SerializationInfo info,
StreamingContext context)
{
// Serialize each field.
info.AddValue("DiskPath", diskpath);
// Let the base type serialize its fields.
base.GetObjectData(info, context);
Dezvoltare aplicatii folosind platforma .NET - 143/233
}

// Define additional constructors that set the field.

public DiskFullException(String message, String diskpath)
: this(message) { // Call another constructor.
this.diskpath = diskpath;
}
public DiskFullException(String message, String diskpath,
Exception innerException)
: this(message, innerException) { // Call another constructor.
this.diskpath = diskpath;
}
}

// The following code tests the serialization of the exception.
class App {
static void Main() {
// Construct a DiskFullException object, and serialize it.
DiskFullException e =
new DiskFullException("The disk volume is full", @"C:\");
FileStream fs = new FileStream(@"Test", FileMode.Create);
IFormatter f = new SoapFormatter();
f.Serialize(fs, e);
fs.Close();
// Deserialize the DiskFullException object, and check its fields.
fs = new FileStream(@"Test", FileMode.Open);
e = (DiskFullException) f.Deserialize(fs);
fs.Close();
Console.WriteLine("Type: {1}{0}DiskPath: {2}{0}Message: {3}",
Environment.NewLine, e.GetType(), e.DiskPath, e.Message);
}
}


In codul de mai sus daca operatia de serializare sau deserializare arunca o exceptie, atunci
fisierul poate ramane intr-o stare incorecta.

Un cod corect de serializare (fisierul nu ramine intr-o stare incorecta, se aduce la starea
de dinanintea aparitiei exceptiei):

public void SerializeObjectGraph(FileStream fs,
IFormatter formatter, Object rootObj) {
// Save the current position of the file.
Int64 beforeSerialization = fs.Position;
try {
// Attempt to serialize the object graph to the file.
formatter.Serialize(fs, rootObj);
}
catch { // Catch all CLS and non-CLS exceptions.
// If ANYTHING goes wrong, reset the file back to a good state.
fs.Position = beforeSerialization;
// Truncate the file.
fs.SetLength(fs.Position);
// NOTE: The preceding code isnt in a finally block because
Dezvoltare aplicatii folosind platforma .NET - 144/233
// the stream should be reset only when serialization fails.
// Let the caller(s) know what happened by
// rethrowing the SAME exception.
throw;
}
}

Exemplu (3) de definire a unei exceptii :

using System;
using System.IO;

public class CustomException : Exception
{
object extraInfo;

public CustomException(string message, object extra) : base(message)
{
extraInfo = extra;
}

public override string ToString()
{
return "CustomException: " +
extraInfo.ToString() + "" +
base.Message + "" +
base.StackTrace;
}
}

class CustomExceptionGenerator
{
static void Main()
{
try
{
SomeMethod();
}
catch (CustomException ce)
{
Console.WriteLine(ce.ToString());
}

Console.ReadLine();
}

public static void SomeMethod()
{
// Daca aici adaugam urmatorul cod:
//
// int i, j,k ;
// i = 10 ;
// j = 0 ;
// k = i/j ;
//
// atunci apare Unhandled Exception
Dezvoltare aplicatii folosind platforma .NET - 145/233
// si apoi exceptia creata.
//

throw new CustomException("Something went wrong", "ExtraInfo");
}
}

Iesirea este :

CustomException: ExtraInfo
Something went wrong
at CustomExceptionGenerator.SomeMethod() in c:\Documents and Settings\info\My
Documents\SharpDevelop Projects\p5_exception1\Main.cs:line 49
at CustomExceptionGenerator.Main() in c:\Documents and Settings\info\My Docum
ents\SharpDevelop Projects\p5_exception1\Main.cs:line 36

Ascunderea detaliilor de implementare

Exemplu (din exemple Microsoft si reluat in foarte multe articole pe Internet) :
Acest exemplu arata cum ar trebui sa renuntam la validarea parametrilor metodelor si
folosirea exceptiilor in cadrul codului. Oricum decizia finala sta in mina programatorului.

public Int32 SomeMethod(Int32 x)
{
try
{
return 100 / x;
}
catch (DivideByZeroException e)
{
throw new ArgumentOutOfRangeException("x", x,
"x cant be 0", e);
}
}

Exceptii netratate = echivalent cu lipsa blocului catch.


Dezvoltare aplicatii folosind platforma .NET - 146/233
Gestiunea automata a memoriei. Garbage Collector.

Etapele necesare pentru a accesa o resursa:
1. Creare resursa (operatorul new).
2. Initializare memorie pentru a seta starea initiala a obiectului inainte de a o utiliza
(operatiile se realizeaza in constructor).
3. Utilizare resursa prin accesarea membrilor tipului respectiv.
4. Eliberare resursa (eliberarea zonei de memorie ocupata de resursa).

Managementul automat al memoriei incearca sa rezolve problemele legate de accesarea
obiectelor inexistente (bug de programare) precum si mentinerea memoriei heap intr-o
stare consistenta (pierderi de memorie la nivel de aplicatie).
Programtorul este degrevat de sarcina gestionarii memoriei; momentul cand va trebui sa
elibereze memoria ocupata de un anumit obiect. Garbage collector nu are informatii
suficiente despre resursa ce reprezinta un anumit tip in memorie.
Pentru ca o resursa sa se poate elibera corect din memorie, dezvoltatorul trebuie sa scrie
cod in metodele Finalize, Dispose si Close.

Tipurile preconstruite, cum ar fi Int32, Point, Rectangle, String, ArrayList si
SerializationInfo, reprezinta resurse ce nu au nevoie de cod special pentru a fi eliberate
din memorie.

Tipurile ce reprezinta (sau wrap) resurse negestionate (unmanaged) cum ar fi fisiere,
socket-uri, mutexuri, bitmap, etc., au nevoie de executia unui cod cand obiectul este
distrus.

Alocare memorie si initializare resurse.

Cerinta principala a CLR-ului este ca toate resursele sa fie alocate intr-o memorie heap
numita managead heap. Din aceasta memorie managed heap, dezvoltatorul nu va elibera
obiectele, acestea vor fi dealocate automat cand aplicatia nu mai are nevoie de ele.
Cand stie managed heap ca un obiect nu mai este necesar in aplicatie si trebuie eliberat?
Exista mai multi algoritmi pentru a realiza acest lucru, fiecare lucrand bine intr-un anumit
mediu. Cand un proces este initializat, CLR rezerva o zona contigua de memorie (adrese)
care initial nu contine nici un obiect. Aceasta zona (heap) este gestionata cu ajutorul uni
pointer, numit in cazul de fata, NextObjPtr. Acest pointer indica adresa urmatorului bloc
liber in heap, deci unde se va aloca urmatorul obiect nou creat. Initial, NextObjPtr
contine adresa de inceput a zonei rezervate pentru heap.

Operatorul new este folosit pentru alocarea unui obiect in heap, iar in IL (limbaj
intermediar) ii corespunde instructiunea newobj.
CLR-ul executa urmatorele lucruri cand se creaza un nou obiect:
1. Calculeaza numarul de octeti necesari pentru noul tip si toate tipurile sale de
baza ;
Dezvoltare aplicatii folosind platforma .NET - 147/233
2. fiecare obiect mentine doua campuri speciale (32 biti lungime): un camp ce
mentine un pointer la o tabela de pointeri la metode si un SyncBlockIndex. Pe un
sistem pe 32 biti se mai adauga in plus 8 octeti pentru fiecare obiect, iar pe un
sistem 64-biti se adauga 16 octeti pentru fiecare obiect.
3. Verifica daca exista memorie suficienta in heap. Daca exista memorie, obiectul
este alocat la adresa data de NextObjPtr.
4. Se apeleaza constructorul obiectului (valoarea lui this este egala cu valoarea lui
NextObjPtr) si se obtine adresa unde este memorat obiectul (pointerul this).
5. Se modifica valoarea lui NextObjPtr, astfel incat acesta sa puncteze la urmatoarea
zona de memorie, libera.



Dupa cum se observa din figura, intre obiecte nu exista spatiu de memorie nefolosit. Una
din caracteristicile managed heap este si aceasta de a memora obiectele unul dupa
altul fara zone libere de memorie intre obiecte (zona contigua de obiecte). Din punctul de
vedere al memoriei gestionate (managed heap) aceasta presupune ca spatiul de adrese
de memorare este infinit. Din cauza ca acest lucru nu este posibil, s-a implementat un
mecanism ce tine sub control aceasta presupunere : garbage collector.
In principiu acest mecanism asigura intretinerea zonei heap astfel incat obiectele ce nu
mai sunt necesare aplicatiei sunt eliminate si memoria este compactata.
Ideea generala pentru GC : este apelat in momentul cand se cere alocarea unui nou obiect
in heap managed si nu mai exista loc. In realitate mecanismul este foarte sofisticat si
supus mereu schimbarii. Acesta poate fi implementat ca un fir de executie ce se executa
in background, fir ce are o prioritate scazuta. Prioritatea firului poate creste in momentele
critice, cand este absolut necesara compactarea memoriei. Obiectele din heap au asociate
anumite atribute folosite de algoritmul implementat in GC. De exemplu obiectele pot
impartite in clase (generatii) pentru a face diferenta intre obiectele vechi si cele noi.
De asemenea obiectele pastreaza o stare ce poate indica faptul ca acestea nu mai sunt
necesare (ar fi trebuit sa se execute destructorul) dar ele inca persista in aceasta zona.

Algoritmul Garbage Collection

GC verifica daca exista in heap obiecte ce nu mai sunt utilizate de aplicatie, iar in caz
afirmativ obiectele sunt eliberate si memoria compactata. In cazul cand nu mai exista
memorie heap disponibila se lanseaza exceptia OutOfMemoryException.

Fiecare aplicatie are o multime de radacini (puncte de intrare) in heap. Un punct de
intrare contine un pointer la un tip referinta. Acest pointer poate referi un obiect din heap
sau poate fi null. Toate variabilele globale si locale, variabilele statice, de tip referinta
Dezvoltare aplicatii folosind platforma .NET - 148/233
sunt considerate puncte de intrare (radacini) in heap, de asemenea variabilele parametru
de tip referinta de pe stiva unui fir sunt considerate puncte de intrare in heap.
In interiorul unei metode, un registru CPU ce se refera la un obiect de tip referinta, este
considerat punct de intrare. Compilatorul JIT creaza tabele interne in momentul cand
compileaza o metoda (cod IL), iar punctele de intrare in aceste tabele contin adrese sau
registri CPU ce contin puncte de intrare in heap. Folosind aceste tabele si alte informatii
pentru obiectele din heap, GC poate determina daca o anumita zona din heap contine un
obiect valid sau nu. De asemenea GC are acces la stiva firului si o poate examina pentru a
determina daca exista metode ce fac referire la obiecte din heap.
GC presupune ca toate obiectele din heap sunt invalide (adica trebuies eliminate).
Se construieste un graf al tuturor punctelor de intrare in heap pentru obiectele valide.
Ceea ce nu se gaseste in acest graf se considera ca sunt obiecte ce trebuiesc eliminate. In
continuare GC parcurge liniar memoria heap si va elimina obiectele ce nu sunt in graf
(algoritmul este foarte simplificat) compactind in acelasi timp memoria heap.
Compactarea memoriei heap de catre GC are ca efect modificarea tabelei ce contine
puncte de intrare in heap, in caz contrar am avea referinte la obiecte ce nu mai sunt la
adresa corecta (daca un obiect contine un pointer la alt obiect se va modifica si valoarea
acestui pointer). In final NextObjPtr va puncta la prima zona de meorie libera (memorie
organizata in mod liniar). Din punctul de vedere al programatorului, exista anumite
avantaje. Acesta nu mai trebuie sa scrie cod pentru a gestiona timpul de viata al
obiectului.

Urmatorul cod arata cum sunt alocate si gestionate obiectele in heap.

class App {
static void Main()
{
// Obiect ArrayList creat in heap, a este radacina.
ArrayList a = new ArrayList();
// Creaza 10000 de obiecte in heap.
for (Int32 x = 0; x < 10000; x++)
{
a.Add(new Object()); // Obiect creat in heap.
}
// Right now, a is a root (on the threads stack). So a is
// reachable and the 10000 objects it refers to are reachable.
Console.WriteLine(a.Length);
// After a.Length returns, a isnt referred to in the code
// and is no longer a root.
Console.WriteLine("End of method");
}
}

CLR foloseste metadata pentru a determina membrii unui obiect ce se refera la alte
obiecte.

Finalizare

Orice tip ce incapsuleaza (wrap) o resursa negestionata (unmanaged), cum ar fi fisier,
obiect nucleu mutex, socket, etc., trebuie sa suporte finalizarea, adica sa ofere resursei
Dezvoltare aplicatii folosind platforma .NET - 149/233
eliberarea corecta cand aceasta va fi supusa garbage collection (colectata pentru
eliminare).

Pentru aceasta tipul implementeaza o metoda numita Finalize.

Cand GC determina ca un obiect trebuie eliminat din memorie, acesta apeleaza metoda
Finalize a obiectului, daca aceasta exista.

In metoda Finalize vom apela metoda corespunzatoare pentru eliberarea resursei,
pentru fisiere sau obiecte nucleu aceasta metoda poate avea numele CloseHandle.
Pentru tipurile ce folosesc resurse unmanaged este obligatoriu de a defini aceasta
metoda Finalize, in caz contrar resursa nu este eliberata corect din memorie, si vom
avea pierderi de memorie ce vor fi rezolvate de catre SO la terminarea procesului
(vezi ceva asemanator la obiecte nucleu).

Urmatorul exemplu arata cum ar trebui scris codul din metoda Finalize.

public sealed class OSHandle
{
// This field holds the Win32 handle of the unmanaged resource.
private IntPtr handle;

// This constructor initializes the handle.
public OSHandle(IntPtr handle)
{
this.handle = handle;
}
// When garbage collected, the Finalize method, which
// will close the unmanaged resources handle, is called.

protected override void Finalize()
{
try
{
CloseHandle(handle);
}
catch()
{
Console.WriteLine(Nu putem elibera resursa!!!);
}
finally
{
base.Finalize();
}
}

// Public method returns the value of the wrapped handle

public IntPtr ToHandle()
{
return handle;
}

Dezvoltare aplicatii folosind platforma .NET - 150/233
// Public implicit cast operator returns the value of
// the wrapped handle

public static implicit operator IntPtr(OSHandle osHandle)
{
return osHandle.ToHandle();
}

// Private method called to free the unmanaged resource

[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
}

Acest cod nu se compileaza.
Explicatii :
Cand obiectul OSHandle este supus procesului de curatare (obiectul in acest moment nu
mai este valid pentru aplicatie, dar pentru GC este inca valid), GC va apela metoda
Finalize a acestui obiect.
In blocul finally se apeleaza metoda de baza Finalize, adica cea din System.Object,
aceasta asigurand ca acest cod se va executa chiar daca a aparut o exceptie (nu in CLR !).

Pentru a inlatura eventualele codari eronate in metoda Finalize (netratarea exceptiilor si
neapelarea metodei System.Object.Finalize) s-a definit o sintaxa speciala pentru aceasta
metoda.

Sa urmarim codul urmator care este aproape identic cu cel anterior.

public sealed class OSHandle
{
// This field holds the Win32 handle of the unmanaged resource.
private IntPtr handle;
// This constructor initializes the handle.
public OSHandle(IntPtr handle)
{
this.handle = handle;
}
// When garbage collected, the destructor (Finalize) method,
// which will close the unmanaged resources handle, is called.

~OSHandle()
{
CloseHandle(handle);
}

// Public method returns the value of the wrapped handle

public IntPtr ToHandle()
{
return handle;
}

// Public implicit cast operator returns
Dezvoltare aplicatii folosind platforma .NET - 151/233
// the value of the wrapped handle

public static implicit operator IntPtr(OSHandle osHandle)
{
return osHandle.ToHandle();
}

// Private method called to free the unmanaged resource

[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
}

Observatii
Aici apare o confuzie in utilizarea acestui destructor pentr OSHandle.
Daca se examineaza codul IL, atunci acesta apare ca cel descris in primul exemplu.

Pentru a crea o instanta a obiectului OSHandle, vom apela o functie Win32 ce returneaza
un handle la o resursa unmanaged, de ex. CreateFile, CreateMutex, CreateSemaphore,
CreateEvent, etc., apoi folosim operatorul new din C# pentru a construi o instanta pentru
OSHandle, pasind ca parametru in ctor acest handle. Cand acest obiect va fi tratat de GC,
GC va vedea ca exista o metoda Finalize implementata si o va apela, ceea ce va
permite eliberarea resursei nucleu (vezi si gestionarea timpului de viata al unui obiect
nucleu, se va apela CloseHandle()).
CLR nu suporta distrugerea determinista a unui obiect (nu putem scrie cod pentru a
distruge obiectul). Finalize va fi apelata numai de GC, iar cand GC se executa nu stim
si nu putem programa acest lucru.
Nu se recomanda folosirea metodei Finalize cand se dezvolta un tip. Timpul procesor
pentru GC va creste caci trebuie sa apeleze Finalize pe obiectele pe care le va distruge si
pe toate obiectele la care face referire un alt obiect ce are metoda Finalize implementata.
De exemplu, pentru un tablou cu 10000 de obiecte pe fiecare obiect se va apela metoda
Finalize cand acesta va fi in atentia GC.
Pentru resursele unmanaged este obligatorie folosirea metodei Finalize. Intern se
construieste o lista cu obiectele ce implementeaza metoda Finalize. GC va trebui sa
consulte si aceata lista.
Nu trebuie scris cod in Finalize ce acceseaza obiecte interne ale obiectului curent,
membrii pentru asemenea obiecte.
De exemplu avem un obiect, numit Extern, ce contine un pointer la un alt obiect, numit
Intern. Deoarece GC nu apeleaza intr-o ordine determinista metoda Finalize, este posibil
sa se apeleze Finalize pe obiectul Intern in timp ce obiectul extern este in viata . Nu
avem nici un control asupra ordinii executiei metodelor Finalize.
In acest moment pointerul din obiectul Extern la obiectul Intern este invalid.
Codul din Finalize trebuie sa se execute foarte rapid (asemanator ca la sectiuni critice).

Eliberarea acestor obiecte se face cu metoda Dispose().

1. Nu trebuie scris cod pentru sincronizare in cadrul acestei metode.
2. In Finalize trebuiesc tratate exceptiile.
Dezvoltare aplicatii folosind platforma .NET - 152/233
3. In Finalize nu trebuie sa facem referire la obiecte managed sau metode statice
managed, este posibil ca aceste obiecte sa nu mai existe sau metodele statice sa
faca referire la obiecte ce nu mai exista.

Urmatorul exemplu emite un sunet cand GC executa o colectie.

public sealed class GCBeep
{
~GCBeep()
{
// Were being finalized, beep.
MessageBeep(-1);
// If the AppDomain isnt unloading, create a new object
// that will get finalized at the next collection.
if (!AppDomain.CurrentDomain.IsFinalizingForUnload())
new GCBeep();
}

[System.Runtime.InteropServices.DllImport("User32.dll")]
private extern static Boolean MessageBeep(Int32 uType);
}

class App
{
static void Main()
{
// Constructing a single GCBeep object causes a beep to
// occur every time a garbage collection starts.
new GCBeep();
// Construct a lot of 100-byte objects.
for (Int32 x = 0; x < 10000; x++)
{
Console.WriteLine(x);
Byte[] b = new Byte[100];
}
}
}


Metoda Finalize va fi apelata chiar daca ctor instantei tipului arunca o exceptie. Deci
metoda Finalize nu trebuie sa presupuna ca obiectul este intr-o stare buna,
consistenta.

Exemplu

class TempFile
{
String filename = null;
public FileStream fs;
public TempFile(String filename)
{
// The following line might throw an exception.

fs = new FileStream(filename, FileMode.Create);
Dezvoltare aplicatii folosind platforma .NET - 153/233

// Save the name of this file.
this.filename = filename;
}
~TempFile()
{
// The right thing to do here is to test filename
// against null because you cant be sure that
// filename was initialized in the constructor.

if (filename != null)
File.Delete(filename);
}
}

Acelasi exemplu tratat altfel.

class TempFile
{
String filename;
public FileStream fs;
public TempFile(String filename)
{
try
{
// The following line might throw an exception.
fs = new FileStream(filename, FileMode.Create);
// Save the name of this file.
this.filename = filename;
}
catch
{
// If anything goes wrong, tell the garbage collector
// not to call the Finalize method.

GC.SuppressFinalize(this);

// Let the caller know something failed.

throw;
}
}

~TempFile()
{
// No if statement is necessary now because this code
// executes only if the constructor ran successfully.
File.Delete(filename);
}

}

Cauzele ce produc apelul metodei Finalize

Dezvoltare aplicatii folosind platforma .NET - 154/233
1. Generatia 0 este plina. Acest eveniment este calea cea mai obisnuita de a apela
metoda Finalize, pentru ca acesta apare cand aplicatia se executa si are nevoie sa
aloce noi obiecte.
2. Codul apeleaza explicit metoda statica Collect din System.GC. Codul poate cere
in mod explicit CLR-ului sa execute o colectie. MS nu recomanda apelul acestei
metode.
3. CLR descarca un AppDomain. Cand se intimpla acest lucru, CLR considera ca
nu exista intrari in heap in cadrul AppDomain si apeleaza Finalize pentru
obiectele ce au fost create in AppDomain.
4. CLR se inchide (shuting down). Cand un proces se termina OK, se incearca
terminarea corecta a CLR-ului. In acest moment CLR considera ca nu exista
puncte de intrare in heap in cadrul procesului si apeleaza metoda Finalize pentru
obiectele din heap managed.

CLR foloseste un fir dedicat pentru a apela metodele Finalize. Pentru primele trei
evenimente metoda Finalize intra intr-o bucla infinita, firul este blocat si nu se mai
apeleaza nici o metoda Finalize. Este un dezastru.

Pentru evenimentul 4, fiecare metoda Finalize consuma aproximativ 2 secunde pentru
executie. Daca nu se intimpla asa, CLR omoara procesul si nu se mai apeleaza alte
metode Finalize. De asemenea, daca CLR consuma mai mult de 40 secunde pentru a
apela o metoda Finalize a unui obiect, CLR omoara procesul.
Valorile scrie mai sus pot fi schimbate de MS in cadrul procesului de imbunatatire al
algoritmului pentru GC.

Codul din Finalize poate construi alte obiecte; daca se intimpla asta in timp ce CLR se
termina (shutdown) CLR va continua sa colecteze obiecte si sa apeleze metodele lor
Finalize pina cand nu mai exista nici un obiect sau au trecut 40 de secunde.

Reluam exemplul GCBeep.

Daca un obiect GCBeep este pe cale sa se finalizeze din cauza evenimentului 1 sau 2, un
nou obiect GCBeep este construit. Este corect pentru ca aplicatia continua sa ruleze,
presupunind ca vor fi mai multe colectii in viitor.
Daca un obiect GCBeep este pe cale sa se finalizeze din cauza evenimentului 3 sau 4, un
nou obiect nu va putea fi construit pentru ca acest obiect ar trebui sa fie creat in timp ce
se descarca AppDomain sau CLR a fost oprit. Daca aceste noi obiecte au fost create,
CLR ar trebui sa poata apela Finalize pentru aceste obiecte.
Pentru a preveni constructia acestor noi obiecte GCBeep, metoda Finalize din GCBeep
apeleaza metoda AppDomain.IsFinalizingForUnload. Aceasta metoda returneaza
TRUE daca metoda Finalize a obiectului este apelata pentru ca AppDomain se descarca
din memorie. Solutia de mai sus este buna numai pentru AppDomain.
Pentru CLR MS a adaugat proprietatea read-only HasShutdownStarted la clasa
System.Environment.

Dezvoltare aplicatii folosind platforma .NET - 155/233
Metoda Finalize din GCBeep ar trebui sa citeasca aceasta proprietate si daca este TRUE
sa nu mai permita crearea de noi obiecte GCBeep. Proprietatea este inaccesibila, pentru
ca a fost implementata eronat, a fost implementata ca o proprietate ce tine de instanta
clasei, iar clasa Environment are un ctor privat si deci nu putem construi obiecte din
aceasta clasa.
Pina la modificarea bibliotecii FCL nu exista o cale de a sti daca CLR este pe cale de
terminare sau nu.

Finalizare probleme interne

Sa ne reamintim ca obiectele ce au implementata metoda Finalize sunt pastrate intr-o lista
separata, lista de finalizare, gestionata de GC. Obiectele ce nu implementeaza propria
metoda Finalize nu sunt trecute in lista de finalizare, chiar daca sunt derivate din
System.Object ce are metoda Finalize.

Sunt doua actiuni importante ce trebuiesc facute : alocare memorie pentru obiectul creat,
inscrierea obiectului in aceasta lista. In fapt se considera actiuni elementare care fiecare la
rindul ei este critica. Putem distinge astfel de operatii: alocare memorie, completare lista
finalizare, construire obiect (apelare constructor).
Ordinea acestor operatii (conform documentatiei MS) este : alocare memorie, completare
lista finalizare, apelare constructor.
Daca obiectul nu este construit din diverse motive, atunci sistemul trebuie sa fie capabil
sa faca rollback pe operatiile anterioare. Cel mai important este eliminarea obiectului din
lista de finalizare. In rest ramane treaba GC sa rezolve problema cu memoria locata si
nefolosita.


Sa consideram exemplul din figura de mai sus.

Cand se executa GC, obiectele B, E, G, H, I, si J nu mai constituie puncte valide de
intrare in heap (radacini) si deci vor fi supuse procesului de colectare si eliminare.
Pentru fiecare obiect se cauta daca exista in lista de finalizare, iar in caz afirmativ este
eliminata intrarea din aceasta lista si trecuta intr-o alta lista F-reachable si asta inseamna
ca pentru obiectele de aici se va apela metoda Finalize.

Dezvoltare aplicatii folosind platforma .NET - 156/233
Dupa colectie managed heap arata astfel :




Un fir dedicat din CLR, va apela metodele Finalize.
Un fir dedicat este folosit pentru a preveni eventualele probleme legate de sincronizare.
Cand coada F este vida, acest fir este in asteptare. El va fi lansat numai pe evenimente ce
spun ca sunt obiecte in coada F.
Obiectele ce au intrari in coada F nu sunt supuse procesului de eliminare, ele constituie
inca radacini valide pentru heap managed.

Memoria heap managed dupa etapa a doua a colectarii si eliminarii arata astfel:


Paternul Dispose: Fortarea unui obiect sa se stearga

Metoda Finalize se foloseste in special pentru resurse unmanaged.
Metoda nu poate fi apelata direct.
Tipurile ce ofera posibilitatea de a fi in mod determinist dispose sau inchise
implementeaza ceea ce se numeste dispose pattern.
Dispose pattern defineste conventiile la care un dezvoltator ar trebui sa adere in
momentul cand defineste un tip ce doreste sa poata fi sters in mod implicit.
Pentru un tip ce implementeaza dispose pattern, utilizatorul va putea elimina obiectul
exact atunci cand nu mai are nevoie de el.
Un tip poate contine atat Finalize cat si Dispose.
Exemplu : clasa System.IO.BinaryWriter intra in aceasta categorie.
Dezvoltare aplicatii folosind platforma .NET - 157/233

Exemplul urmator descrie o versiune mai buna pentru tipul OSHandle.

using System;
// Implementing the IDisposable interface signals users of
// this class that it offers the dispose pattern.

public sealed class OSHandle : IDisposable
{
// This field holds the Win32 handle of the unmanaged resource.
private IntPtr handle;

// This constructor initializes the handle.

public OSHandle(IntPtr handle)
{
this.handle = handle;
}

// When garbage collected, this Finalize method, which
// will close the unmanaged resources handle, is called.
~OSHandle()
{
Dispose(false);
}
// This public method can be called to deterministically
// close the unmanaged resources handle.

public void Dispose()
{
// Because the object is explicitly cleaned up, stop the
// garbage collector from calling the Finalize method.
GC.SuppressFinalize(this);

// Call the method that actually does the cleanup.
Dispose(true);
}

// This public method can be called instead of Dispose.
public void Close()
{
Dispose();
}

// The common method that does the actual cleanup.
// Finalize, Dispose, and Close call this method.
// Because this class is sealed, this method is private.
// If this class werent sealed, this method
// wouldnt be protected.
private void Dispose(Boolean disposing)
{
// Synchronize threads calling Dispose/Close
// simultaneously.
lock (this)
{
if (disposing)
Dezvoltare aplicatii folosind platforma .NET - 158/233
{
// The object is being explicitly disposed of/closed, not
// finalized. It is therefore safe for code in this if
// statement to access fields that reference other
// objects because the Finalize method of
// these other objects
// hasnt yet been called.

// For the OSHandle class, there is nothing to do in here.
}
// The object is being disposed of/closed or finalized.
if (IsValid)
{
// If the handle is valid, close the unmanaged resource.
// NOTE: Replace CloseHandle with whatever function is
// necessary to close/free your unmanaged resource.

CloseHandle(handle);

// Set the handle field to some sentinel value.
// This precaution prevents the possibility
// of calling CloseHandle twice.

handle = InvalidHandle;
}
}
}

// Public property to return the value of an invalid handle.
// NOTE: Make this property return an invalid value for
// whatever unmanaged resource youre using.

public IntPtr InvalidHandle
{
get { return IntPtr.Zero; }
}

// Public method to return the value of the wrapped handle
public IntPtr ToHandle() { return handle; }

// Public implicit cast operator returns the value of the wrapped handle
public static implicit operator IntPtr(OSHandle osHandle)
{
return osHandle.ToHandle();
}

// Public properties to return whether the wrapped handle is valid.
public Boolean IsValid
{
get { return (handle != InvalidHandle); }
}

public Boolean IsInvalid
{
get { return !IsValid; }
}

// Private method called to free the unmanaged resource.
Dezvoltare aplicatii folosind platforma .NET - 159/233
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
}

Cam mult cod, dar acest cod se repeta pentru fiecare tip ce foloseste o resursa
unmanaged.

Explicatii

OSHandle implementeaza interfata System.IDisposable.
In FCL aceasta este definita astfel:

public interface IDisposable
{
void Dispose();
}

Pentru ca un tip sa adere la dispose pattern trebuie sa implementeze aceasta
interfata.
Asta inseamna ca tipul ofera o metoda publica, fara parametri, Dispose, ce poate fi
apelata pentru a elibera resursa wrapped de catre obiect. Memoria din heap managed
pentru obiect va fi eliberata de GC.

OSHandle implementeaza o metoda publica Close, ce apeleaza Dispose. Patternul
dispose nu obliga folosirea acestei metode.

Metodele fara parametri, Dispose si Close trebuie sa fie publice si nevirtuale.
Metoda Dispose este la baza virtuala fiind dintr-o interfata. In tipul nostru se defineste ca
fiind public si sealed (nu poate fi folosita in derivare). Compilatorul face implicit asta
pentru noi.
Metoda Dispose fara parametri va apela metoda Dispose cu un parametru de tip bool,
iar codul pentru curatare se va gasi in aceasta ultima metoda. In cadrul metodei Dispose
are loc o sincronizare.

Cand se apeleaza metoda Finalize a unui obiect, valoarea parametrului metodei
Dispose este false.
Asta inseamna ca metoda Dispose nu ar trebui sa execute cod ce face referire la alte
obiecte managed. Daca clasa OSHandle nu ar fi fost declara sealed, metoda
Dispose(bool) ar fi trebuit sa fie implementata ca protected virtual si nu ca private
nonvirtual. Orice clasa ce deriva din OSHandle ar trebui sa implementeze metoda
Dispose(boolean) nu si metodele fara parametri Dispose si Close si nu trebuie sa
implementeze Finalize.

Exemplu de clasa derivata din OSHandle

class SomeType : OSHandle
{
// This field holds the Win32 handle of the unmanaged resource.
Dezvoltare aplicatii folosind platforma .NET - 160/233
private IntPtr handle;
protected override void Dispose(Boolean disposing)
{
// Synchronize threads calling Dispose/Close simultaneously.
lock (this)
{
try
{
if (disposing)
{
// The object is being explicitly disposed of/closed, not
// finalized. It is therefore safe for code in this if
// statement to access fields that reference other
// objects because the Finalize method of these other
// objects hasnt been called.
// For this class, there is nothing to do in here.
}
// The object is being disposed of/closed or finalized.
if (IsValid)
{
// If the handle is valid, close the unmanaged resource.
// NOTE: Replace CloseHandle with whatever function is
// necessary to close/free your unmanaged resource.
CloseHandle(handle);

// Set the handle field to some sentinel value. This
precaution
// prevents the possibility of calling CloseHandle twice.

handle = InvalidHandle;
}
}
finally
{
// Let the base class do its cleanup.
base.Dispose(disposing);
}
}
}
}


Folosirea unui tip ce implementeaza patternul Dispose

Vom explica acest lucru pe baza unui exemplu, folosind clasa System.IO.FileStream.
Aceasta clasa ofera posibilitatea de a lucra cu fisiere.
Dezvoltare aplicatii folosind platforma .NET - 161/233
Intern aceasta clasa pastreaza un handle, cu acces private, la fisierul cu care lucreaza,
handle returnat de functia CreateFile.
Vom crea un fisier, in care se scrie ceva si apoi se sterge.

using System;
using System.IO;
class App
{
static void Main()
{
// Create the bytes to write to the temporary file.
Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 };

// Create the temporary file.
FileStream fs = new FileStream("Temp.dat",
FileMode.Create);

// Write the bytes to the temporary file.
fs.Write(bytesToWrite, 0, bytesToWrite.Length);

// Delete the temporary file.
File.Delete("Temp.dat"); // Throws an IOException
}
}

Dupa executie obtinem urmatoarea exceptie:

Unhandled Exception: System.IO.IOException: The process cannot access the file "
Temp.dat" because it is being used by another process.
at System.IO.__Error.WinIOError(Int32 errorCode, String str)
at System.IO.File.Delete(String path)
at App.Main() in c:\Documents and Settings\info\My Documents\SharpDevelop Pro
jects\p6_gc3\Main.cs:line 25
Press any key to continue . . .

Problema apare in metoda Delete.
Exista si sanse ca acest cod sa functioneze. Daca alt fir cauzeaza un GC dupa apelul lui
Write si inainte de Delete, obiectul FileStrem va apela metoda Finalize si fisierul va fi
inchis.
Implementarea patternului Dispose in aceasta clasa inlatura acest bug.

Codul in noua varianta poate fi:

using System;
using System.IO;
class App
{
static void Main()
{
// Create the bytes to write to the temporary file.
Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 };

Dezvoltare aplicatii folosind platforma .NET - 162/233
// Create the temporary file.
FileStream fs = new FileStream("Temp.dat",
FileMode.Create);

// Write the bytes to the temporary file.
fs.Write(bytesToWrite, 0, bytesToWrite.Length);

// Explicitly close the file when done writing to it.
((IDisposable) fs).Dispose();

// Delete the temporary file.
File.Delete("Temp.dat"); // This always works now.
}
}

Diferenta este in apelul:
((IDisposable) fs).Dispose();
care inchide fisierul.

Acelasi exemplu, dar inchiderea fisierului se face cu o metoda din clasa FileStream.

using System;
using System.IO;
class App
{
static void Main()
{
// Create the bytes to write to the temporary file.
Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 };

// Create the temporary file.
FileStream fs = new FileStream("Temp.dat",
FileMode.Create);

// Write the bytes to the temporary file.
fs.Write(bytesToWrite, 0, bytesToWrite.Length);

// Explicitly close the file when done writing to it.
fs.Close();

// Delete the temporary file.
File.Delete("Temp.dat"); // This always works now.
}
}

Important
Cand definim propriul tip ce implementeaza patternul Dispose, trebuie sa scriem cod in
toate metodele noastre pentru a lansa execeptia System.ObjectDisposedException daca
obiectul a fost eliminat in mod explicit.
Metodele Dispose si Close nu trebuie sa arunce niciodata aceasta exceptie.

Folosirea instructiunii using

Dezvoltare aplicatii folosind platforma .NET - 163/233
Exemplu

using System;
using System.IO;
class App
{
static void Main()
{
// Create the bytes to write to the temporary file.
Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 };
// Create the temporary file.

using (FileStream fs =
new FileStream("Temp.dat", FileMode.Create))
{
// Write the bytes to the temporary file.
fs.Write(bytesToWrite, 0, bytesToWrite.Length);
}

// Delete the temporary file.
File.Delete("Temp.dat"); // This always works now.
}
}

In using se initializeaza obiectul si se salveaza o referinta la acesta intr-o variabila. Cu
aceasta referinta apelam metode din obiect.
La intilnirea instructiunii using, compilatorul construieste blocurile try si finally, iar in
blocul finally se face cast la IDisposable pentru a apela metoda Dispose.
Instructiunea using lucreaza numai cu tipuri ce implementeaza interfata
IDisposable.

Generatii

Un GC ce lucreaza cu generatii de obiecte face urmatoarele presupuneri:
1. Un obiect este cu atit mai nou cu cit timpul sau de viata este mai mic.
2. Un obiect este cu atit mai vechi cu cit timpul sau de viata este mai mare.
3. Colectarea unei portiuni din heap este mai rapida decat parcurgerea intregului.

La initializare managed heap nu contine obiecte. Obiectele adaugate se spun ca sunt din
generatia 0. La initializare, CLR stabileste marimea zonei pentru generatia 0 (pp 256
KB).

Dezvoltare aplicatii folosind platforma .NET - 164/233


Dupa un timp obiectele C si E nu mai sunt radacini.
Cand marimea acestei zonei satbilita de CLR este depasita se lanseaza procesul de
colectare.
Obiectele ce au fost tratate odata de GC trec in generatia urmatoare. Fiecarei generatii i se
stabileste o marime in heap. De la generatie la generatie aceste marimi pot sa difere (cf
doc. MS). Noile obiecte ce se creaza pleaca cu generatia 0.


La o noua colectare generatia 1 va deveni 2, 0 va deveni 1.
Se presupune ca obiectele cu generatia mare nu au nevoie permanenta de colectare.
Oricum cand exista legaturi intre obiecte din generatii diferite, se supun procesului GC si
generatiile vechi.

Dezvoltare aplicatii folosind platforma .NET - 165/233
Fire in .NET

Spatiul de nume System.Threading
System.Threading pune la dispozitie clase si interfete ce permit programarea multifir. Pe
langa clasele de sincronizare a firelor si a datelor ( (Mutex, Monitor, Interlocked,
AutoResetEvent, etc.), se gaseste si clasa ThreadPool ce permite reutilizarea firelor, si
clasa Timer ce executa metode callback pe fire din pool thread.
Clasa Thread
Creaza si controleaza un fir, seteaza prioritatea si obtine starea acestuia.
Un proces poate crea unul sau mai multe fire ce executa o anumita parte de cod din
cadrul procesului.
Metoda ThreadStart specifica codul ce va fi executat de un fir.
Un fir poate fi in una din urmatoarele stari :

Actiune ThreadState
Un fir este creat in interiorul CLR. Nestartat.
Un fir apeleaza Start Running
Firul incepe executia. Running
Firul apeleaza Sleep WaitSleepJoin
Firul apeleaza Wait pe un alt obiect. WaitSleepJoin
Firul apeleaza Join pe alt fir. WaitSleepJoin
Alt fir apeleaza Interrupt Running
Alt fir apeleaza Suspend SuspendRequested
Firul raspunde la o cerere Suspend. Suspended
Alt fir apeleaza Resume Running
Alt fir apeleaza Abort AbortRequested
Firul raspunde la o cerere Abort. Stopped
Un fir s-a terminat. Stopped

Corespunzator acestor stari exista membri in clasa.

Nume membru Descriere
Aborted Firul este in starea Stopped.
AbortRequested Pe fir a fost invocata metoda Thread.Abort dar firul nu a primit
inca System.Threading.ThreadAbortException ce va incerca
sa-l termine.
Background Firul se executa in background.
Running Firul a fost startat si nu este blocat.
Stopped Firul a fost oprit.
StopRequested Firului i se cere sa se termine.
Suspended Firul a fost suspendat.
SuspendRequested Firului i se cere sa se suspende.
Unstarted Metoda Thread.Start nu a fost invocata pe fir.
WaitSleepJoin Firul este blocat ca rezultat al unui apel la Wait, Sleep, sau
Dezvoltare aplicatii folosind platforma .NET - 166/233
Join.
Constructor Thread
[C#]
public Thread(ThreadStart start);
Parametrii
start Un delegate ThreadStart ce face referinta la metoda ce va fi invocata cand firul isi
incepe executia.
Executia firului se face la apelul metodei Start.
Exemplu
[C#]
using System;
using System.Threading;

class Test
{
static void Main()
{
// In acest exemplu functia pentru fir este statica
Thread newThread = new Thread(new ThreadStart(Work.DoWork));
newThread.Start();
}
}
class Work
{
Work() {}
public static void DoWork() {}
}
Acelasi exemplu (modificat)
using System;
using System.Threading;

class Test
{
static void Main()
{
Thread newThread = new Thread(new ThreadStart(Work.DoWork));
newThread.Start();
Console.WriteLine("Fir primar dupa lansarea firului secundar");
for (int i = 0; i<10;i++)
{
Thread.Sleep(100);
Console.WriteLine("FP [i = {0}]",i);
}
}
}

class Work
{
Work() {}
public static void DoWork()
{
for (int i = 0; i < 10; i++)
Dezvoltare aplicatii folosind platforma .NET - 167/233
{
Thread.Sleep(100);
Console.WriteLine("Fir secundar [i = {0}]", i);
}
Console.ReadLine();
}
}
Rezultatul este
Fir primar dupa lansarea firului secundar
Fir secundar [i = 0]
FP [i = 0]
Fir secundar [i = 1]
FP [i = 1]
Fir secundar [i = 2]
FP [i = 2]
Fir secundar [i = 3]
FP [i = 3]
Fir secundar [i = 4]
FP [i = 4]
FP [i = 5]
Fir secundar [i = 5]
FP [i = 6]
Fir secundar [i = 6]
FP [i = 7]
Fir secundar [i = 7]
FP [i = 8]
Fir secundar [i = 8]
Fir secundar [i = 9]
FP [i = 9]
Metoda Thread.Join - Prototipuri

[C#] public void Join();
Blocheaza firul apelant pina cand un alt fir nu se termina sau s-a scurs un anumit timp.
[C#] public bool Join(int);
[C#] public bool Join(TimeSpan);
Metoda Thread.Start
Sistemul de operare va schimba starea instantei curente la ThreadState.Running.
[C#]
public void Start();
Cand un fir este in starea Running, sistemul de operare il poate programa pentru timp
procesor. Dupa ce firul s-a terminat, nu mai poate fi lansat inca o data cu un apel la
metoda Start.

using System;
using System.Threading;

Dezvoltare aplicatii folosind platforma .NET - 168/233
public class ThreadWork
{
public static void DoWork()
{
for(int i = 0; i<3;i++)
{
Console.WriteLine("Working thread...");
Thread.Sleep(100);
}
}
}
class ThreadTest
{
public static void Main()
{
ThreadStart myThreadDelegate =
new ThreadStart(ThreadWork.DoWork);
Thread myThread = new Thread(myThreadDelegate);
myThread.Start();
for(int i = 0; i<3; i++)
{
Console.WriteLine("In main.");
Thread.Sleep(100);
}
}
}

Exemplu cu Sleep si Join

using System;
using System.Threading;

// Simple threading scenario: Start a static method running
// on a second thread.
public class ThreadExample {
// The ThreadProc method is called when the thread starts.
// It loops ten times, writing to the console and yielding
// the rest of its time slice each time, and then ends.
public static void ThreadProc() {
for (int i = 0; i < 10; i++) {
Console.WriteLine("ThreadProc: {0}", i);
// Yield the rest of the time slice.
Thread.Sleep(0);
}
}

public static void Main() {
Console.WriteLine("Main thread: Start a second thread.");
// The constructor for the Thread class requires a ThreadStart
// delegate that represents the method to be executed on the
// thread. C# simplifies the creation of this delegate.

Thread t = new Thread(new ThreadStart(ThreadProc));

// Start ThreadProc. On a uniprocessor, the thread does not get
// any processor time until the main thread yields. Uncomment
Dezvoltare aplicatii folosind platforma .NET - 169/233
// the Thread.Sleep that follows t.Start() to see the difference.
t.Start();
//Thread.Sleep(0);

for (int i = 0; i < 4; i++) {
Console.WriteLine("Main thread: Do some work.");
Thread.Sleep(0);
}

Console.WriteLine("Main thread: Call Join(), to wait until
ThreadProc ends.");
t.Join();
Console.WriteLine("Main thread: ThreadProc.Join has returned.
Press Enter to end program.");
Console.ReadLine();
}
}
Clasa ThreadPool

Furnizeaza un set de fire ce pot fi utilizate pentru a executa functii ordinare callback,
procese asincrone I/O, asteptari pentru alte fire, procese timer. Acest set de fire este
utilizat de sistem.

System.Object
System.Threading.ThreadPool
[C#]
public sealed class ThreadPool
Pentru a trata o cerere in acest fel (work items) apelam metoda QueueUserWorkItem.
Metoda se executa cand un fir devine disponibil.
Sintaxa acestei metode este:

[C#] public static bool QueueUserWorkItem(WaitCallback);

Parametrul este de tip WaitCallback. Metoda ia ca parametru o referinta la o metoda sau
delegate ce va fi executat. Metoda contine un parametru in plus fata de prima varianta. In
acest parametru de tip obiect se paseaza informatii aditionale pentru executia functiei
callback.

[C#] public static bool QueueUserWorkItem(WaitCallback, object);

Observatie: Nu exista nici o metoda de a anula (opri executia) unui asemenea articol dupa
ce a fost pus in coada de lucru (.NET 1.1). Timerele si operatiile de asteptare (wait
operations) folosesc thread pool. Functiile lor callback sunt puse in coada pentru thread
pool. Thread pool este creat prima data cand se creaza o instanta a clasei ThreadPool.
Limita implicita este de 25 fire pentru fiecare procesor disponibil, limita care poate fi
schimbata folosind CorSetMaxThreads, definita in mscoree.h. Fiecare fir foloseste
marimea stivei implicita si ruleaza la prioritatea implicita. Fiecare proces poate avea
numai un singur thread pool.
Dezvoltare aplicatii folosind platforma .NET - 170/233

[C#]
using System;
using System.Threading;
public class Example {
public static void Main() {
// Queue the task.
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
Console.WriteLine("Main thread does some work, then sleeps.");
// If you comment out the Sleep, the main thread exits before
// the thread pool task runs. The thread pool uses background
// threads, which do not keep the application running. (This
// is a simple example of a race condition.)
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
// This thread procedure performs the task.
static void ThreadProc(Object stateInfo) {
// No state object was passed to QueueUserWorkItem, so
// stateInfo is null.
Console.WriteLine("Hello from the thread pool.");
}
}
Metoda ThreadPool.RegisterWaitForSingleObject
Inregistreaza un delegate ce asteapta pentru un WaitHandle.
Inregistreaza un delegate ce asteapta pentru un WaitHandle, folosind un intreg pe 32 biti
pentru time-out.

[C#]
public static RegisteredWaitHandle
RegisterWaitForSingleObject(WaitHandle, WaitOrTimerCallback, object, int,
bool);

Inregistreaza un delegate ce asteapta pentru un WaitHandle, folosind un intreg pe 64 biti
pentru time-out.

[C#] public static RegisteredWaitHandle
RegisterWaitForSingleObject(WaitHandle, WaitOrTimerCallback, object,
long, bool);

Inregistreaza un delegate ce asteapta pentru un WaitHandle, folosind o valoare de tip
TimeSpan pentru time-out.

[C#] public static RegisteredWaitHandle
RegisterWaitForSingleObject(WaitHandle waitObject, WaitOrTimerCallback
callBack, object state, TimeSpan milisecondsTimeOutInterval, bool
executeOnlyOnce);
Parametrii

waitObject = WaitHandle pentru inregistrare.
callBack = delegate WaitOrTimerCallback ce trebuie apelat cand waitObject este
semnalat.
Dezvoltare aplicatii folosind platforma .NET - 171/233
state = obiectul pasat catre delegate.
millisecondsTimeOutInterval = time-out in milisecunde.
executeOnlyOnce = true: firul nu va mai astepta pe WaitHandle dupa ce delegate a fost
apelat; false: contrar.
using System;
using System.Threading;

// TaskInfo contains data that will be passed to the callback
// method.
public class TaskInfo
{
// RegisteredWaitHandle este o clasa ce va fi descrisa mai jos
public RegisteredWaitHandle Handle = null;
public string OtherInfo = "default";
}

public class Example {
public static void Main(string[] args)
{
// The main thread uses AutoResetEvent to signal the
// registered wait handle, which executes the callback
// method.

AutoResetEvent ev = new AutoResetEvent(false);

TaskInfo ti = new TaskInfo();
ti.OtherInfo = "First task";

// The TaskInfo for the task includes the registered wait
// handle returned by RegisterWaitForSingleObject. This
// allows the wait to be terminated when the object has
// been signaled once (see WaitProc).

ti.Handle = ThreadPool.RegisterWaitForSingleObject(
ev,
new WaitOrTimerCallback(WaitProc),
ti,
1000,
false
);

// The main thread waits three seconds, to demonstrate the
// time-outs on the queued thread, and then signals.
Thread.Sleep(3100);
Console.WriteLine("Main thread signals.");
ev.Set();
// The main thread sleeps, which should give the callback
// method time to execute. If you comment out this line, the
// program usually ends before the ThreadPool thread can
// execute.
Thread.Sleep(1000);
// If you start a thread yourself, you can wait for it to end
// by calling Thread.Join.
//This option is not available with thread pool threads.
}

Dezvoltare aplicatii folosind platforma .NET - 172/233
// The callback method executes when the registered wait times out,
// or when the WaitHandle (in this case AutoResetEvent) is signaled.
// WaitProc unregisters the WaitHandle the first time the event is
// signaled.

public static void WaitProc(object state, bool timedOut)
{
// The state object must be cast to the correct type, because the
// signature of the WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo) state;
string cause = "TIMED OUT";
if (!timedOut)
{
cause = "SIGNALED";
// If the callback method executes because the WaitHandle is
// signaled, stop future execution of the callback method
// by unregistering the WaitHandle.
if (ti.Handle != null)
ti.Handle.Unregister(null);
}
Console.WriteLine("WaitProc( {0} ) executes on thread {1};
cause = {2}.",
ti.OtherInfo,
Thread.CurrentThread.GetHashCode().ToString(),
cause
);
}
}

Clasa RegisteredWaitHandle

Reprezinta un handle la WaitTimer, ce a fost inregistrat cand s-a apelat metoda
RegisterWaitForSingleObject. Clasa nu poate fi mostenita.
Dezvoltare aplicatii folosind platforma .NET - 173/233

BackgroundWorker (.NET 2.0)

Clasa BackgroundWorker permite de a executa operatii pe un fir separat, dedicat. Acest
mod de lucru este recomandat pentru operatii mari consumatoare de timp (download-uri,
operatii asupra bazelor de date executii proceduri stocate, operatii ce fac ca interfata cu
utilizatorul sa fie blocata).
Pentru a executa in background operatii mari consumatoare de timp, se creaza un
BackgroundWorker si se asculta pe evenimente ce raporteaza progresul operatiei sau
terminarea acesteia. Un asemenea fir se poate crea din Windows Forms Designer sau in
mod dinamic din aplicatie. Pentru a seta o operatie in background, se va adauga un
handler pentru evenimentul
DoWork
in care se va specifica codul ce trebuie executat in
background. Startul operatiei se face prin apelul metodei
RunWorkerAsync
.
Pentru a primi notificari despre progresul aplicatiei se va trata evenimentul
ProgressChanged
, iar terminarea aplicatiei se va face prin tratarea evenimentului
RunWorkerCompleted
.

Daca operatia din background are nevoie de un parametru, va trebui sa apelam
RunWorkerAsync
cu acel parametru. Valoarea parametrului o putem extrage din
proprietatea
DoWorkEventArgs.Argument
.
Clasa AutoResetEvent
Folosita in sincronizarea firelor.
Notifica un fir in asteptare ca a aparut un anumit eveniment. Clasa nu poate fi mostenita.
AutoResetEvent permite firelor sa comunice intre ele prin semnalizare. Aceasta
comunicatie este legata de accesul la o resursa la care firele au nevoie de acces exclusiv.
Un fir asteapta un semnal apeland WaitOne pe AutoResetEvent. Daca AutoResetEvent
este in starea nesemnalat, firul se blocheaza, asteapta ca firul ce controleaza acea resursa
sa se termine, firul din urma apeland metoda Set. Cand obiectul devine semnalat, firul ce
era in asteptare pe acel obiect va fi activat, si obiectul devine din nou nesemnalat. Daca
nici un fir nu asteapta pe un obiect semnalat, acest obiect va ramane in starea semnalat.
Starea initiala a unui AutoResetEvent poate fi controlata printr-o valoare pasata ctor, true
= stare initiala semnalata, false = stare initiala nesemnalata.
AutoResetEvent poate fi folosit cu metodele statice WaitAll si WaitAny.
Metoda WaitHandle.WaitAll
Asteapta ca toate elementele din tabloul de handle specificati sa primeasca un semnal.
[C#] public static bool WaitAll(WaitHandle[]);
Asteapta ca toate elementele din tabloul specificat sa primeasca un semnal, folosind in
plus o valoare Int32 pentru a masura un interval de timp, si daca sincronizarea sa se
termine inainte de asteptare.
[C#] public static bool WaitAll(WaitHandle[], int, bool);
Acelasi lucru ca mai sus numai ca se foloseste a valoare TimeSpan.
[C#] public static bool WaitAll(WaitHandle[], TimeSpan, bool);

Dezvoltare aplicatii folosind platforma .NET - 174/233
Observatie
WaitAny are aceeasi functionalitate ca si WaitAll, diferenta fiind ca se asteapta ca cel
putin un element din tablou sa devina semnalat.
Exemplu:
using System;
using System.IO;
using System.Security.Permissions;
using System.Threading;

// Request permission to create and write files to C:\TestTest.
[assembly: FileIOPermissionAttribute(SecurityAction.RequestMinimum,
All = "C:\\TestTest")]

class Test
{
static void Main()
{
const int numberOfFiles = 5;
string dirName = "C:\\TestTest";
string fileName;

byte[] byteArray;
Random randomGenerator = new Random();

ManualResetEvent[] manualEvents =
new ManualResetEvent[numberOfFiles];

// Informatie ce o pasez firului

State stateInfo;

if(!Directory.Exists(dirName))
{
Directory.CreateDirectory(dirName);
}

// Queue the work items that create and write to the files.
for(int i = 0; i < numberOfFiles; i++)
{
fileName = string.Concat(
dirName, "\\Test", i.ToString(), ".dat");

// Create random data to write to the file.
byteArray = new byte[1000000];
randomGenerator.NextBytes(byteArray);
manualEvents[i] = new ManualResetEvent(false);
stateInfo =
new State(fileName, byteArray, manualEvents[i]);
ThreadPool.QueueUserWorkItem(
new WaitCallback(Writer.WriteToFile), stateInfo);
}

Dezvoltare aplicatii folosind platforma .NET - 175/233
// Since ThreadPool threads are background threads,
// wait for the work items to signal before exiting.
if(WaitHandle.WaitAll(
manualEvents, new TimeSpan(0, 0, 5), false))
{
Console.WriteLine("Files written - main exiting.");
}
else
{
// The wait operation times out.
Console.WriteLine("Error writing files - main exiting.");
}
}
}

// Maintain state to pass to WriteToFile.
class State
{
public string fileName;
public byte[] byteArray;
public ManualResetEvent manualEvent;
public State(string fileName, byte[] byteArray,
ManualResetEvent manualEvent)
{
this.fileName = fileName;
this.byteArray = byteArray;
this.manualEvent = manualEvent;
}
}

class Writer
{
static int workItemCount = 0;
Writer() {}
//
// Executata de fir (avem thread pool = fire generate de sistem).
//
public static void WriteToFile(object state)
{
int workItemNumber = workItemCount;
Interlocked.Increment(ref workItemCount);
Console.WriteLine("Starting work item {0}.",
workItemNumber.ToString());
//
// Preluare informatii pentru fir
// Este obligator cast
//
State stateInfo = (State)state;
FileStream fileWriter = null;

// Create and write to the file.
try
{
Dezvoltare aplicatii folosind platforma .NET - 176/233
fileWriter = new FileStream(
stateInfo.fileName, FileMode.Create);
fileWriter.Write(stateInfo.byteArray,
0, stateInfo.byteArray.Length);
}
finally
{
if(fileWriter != null)
{
fileWriter.Close();
}

// Signal Main that the work item has finished.
Console.WriteLine("Ending work item {0}.",
workItemNumber.ToString());
stateInfo.manualEvent.Set();
}
}
}
Metode importante din aceasta clasa.
Reset
Sets the state of the specified event to nonsignaled.
Set
Sets the state of the specified event to signaled.
ToString (inherited from Object)
Returns a String that represents the current Object.
WaitOne (inherited from WaitHandle)
Overloaded. When overridden in a derived class,
blocks the current thread until the current
WaitHandle receives a signal.

Exemplu pentru WaitOne. Se calculeaza suma a trei numere.
using System;
using System.Threading;
class CalculateTest
{
static void Main()
{
Calculate calc = new Calculate();
Console.WriteLine("Result = {0}.",
calc.Result(234).ToString());
Console.WriteLine("Result = {0}.",
calc.Result(55).ToString());
}
}
class Calculate
{
double baseNumber, firstTerm, secondTerm, thirdTerm;
AutoResetEvent[] autoEvents;
ManualResetEvent manualEvent;
// Generate random numbers to simulate the actual calculations.
Random randomGenerator;
public Calculate()
{
autoEvents = new AutoResetEvent[]
{
new AutoResetEvent(false),
Dezvoltare aplicatii folosind platforma .NET - 177/233
new AutoResetEvent(false),
new AutoResetEvent(false)
};
manualEvent = new ManualResetEvent(false);
}
void CalculateBase(object stateInfo)
{
baseNumber = randomGenerator.NextDouble();

// Signal that baseNumber is ready.
manualEvent.Set();
}
// The following CalculateX methods all perform the same
// series of steps as commented in CalculateFirstTerm.
void CalculateFirstTerm(object stateInfo)
{
// Perform a precalculation.
double preCalc = randomGenerator.NextDouble();

// Wait for baseNumber to be calculated.
manualEvent.WaitOne();

// Calculate the first term from preCalc and baseNumber.
firstTerm = preCalc * baseNumber *
randomGenerator.NextDouble();

// Signal that the calculation is finished.
autoEvents[0].Set();
}
void CalculateSecondTerm(object stateInfo)
{
double preCalc = randomGenerator.NextDouble();
manualEvent.WaitOne();
secondTerm = preCalc * baseNumber *
randomGenerator.NextDouble();
autoEvents[1].Set();
}
void CalculateThirdTerm(object stateInfo)
{
double preCalc = randomGenerator.NextDouble();
manualEvent.WaitOne();
thirdTerm = preCalc * baseNumber *
randomGenerator.NextDouble();
autoEvents[2].Set();
}
public double Result(int seed)
{
randomGenerator = new Random(seed);

// Simultaneously calculate the terms.
ThreadPool.QueueUserWorkItem(
new WaitCallback(CalculateBase));
ThreadPool.QueueUserWorkItem(
new WaitCallback(CalculateFirstTerm));
ThreadPool.QueueUserWorkItem(
new WaitCallback(CalculateSecondTerm));
ThreadPool.QueueUserWorkItem(
Dezvoltare aplicatii folosind platforma .NET - 178/233
new WaitCallback(CalculateThirdTerm));
// Wait for all of the terms to be calculated.
WaitHandle.WaitAll(autoEvents);
// Reset the wait handle for the next calculation.
manualEvent.Reset();
return firstTerm + secondTerm + thirdTerm;
}
}
Clasa Monitor
Folosita pentru a sincroniza accesul la obiecte.
Clasa Monitor controleaza accesul la obiecte plasand o blocare pe obiect la nivel de fir.
Blocarea obiectului are ca efect restrictionarea accesului la o anumita parte de cod,
cunoscuta sub numele de sectiune critica.
Monitor se foloseste pentru a bloca obiectele ce au tipul referinta (nu tip valoare).

Monitor are urmatoarele trasaturi:
este asociata pe un obiect la cerere.
Poate fi apelata direct din orice context.
Nu poate fi creata o instanta a clasei Monitor.

Pentru fiecare obiect sincronizat se pastreaza urmatoarele informatii:
O referinta la firul curent ce blocheaza obiectul.
O referinta la o coada ce pastreaza fire ce sunt interesate sa obtina acces la obiect
si sa-l blocheze.
O referinta la o coada de asteptare, ce contine fire ce asteapta notificari in cazul
cand starea obiectului s-a schimbat (obiectul nu mai este blocat).

Actiunile ce trebuiesc executate de firele ce acceseaza obiecte sincronizate sunt
urmatoarele:

Enter, TryEnter = incearca obtinerea blocarii unui obiect. Marcheaza inceputul unei
sectiuni critice. Practic, aceasta inseamna initializarea sectiunii critice, care este
obligatorie.
Wait = Sterge blocarea de pe un obiect pentru a permite altor fire sa-l blocheze si sa-l
acceseze. Firul apelant asteapta pina cand un alt fir acceseaza obiectul. Se foloseste
metoda Pulse pentru a notifica firului ce asteapta despre schimbarea starii obiectului.
Pulse = trimite un semnal la unul sau mai multe fire ce asteapta (au lansat functia Wait)
cu semnificatia ca starea obiectului s-a schimbat si ca proprietarul blocarii obiectului este
gata sa elibereze blocarea. Firul ce asteapta este plasat in coada ready a obiectului
astfel incat acesta poate primi eventual blocarea pentru obiect. Dupa ce a primit blocarea
pentru obiect (firul ce a apelat Pulse a eliberat obiectul), acesta poate controla noua stare
a obiectului pentru a vedea daca starea ceruta a fost modificata. Metoda Wait este folosita
intr-un fir, metoda Pulse in alt fir. Ordinea corecta este Wait (in firul 1) si apoi Pulse (in
firul 2, care realizeaza si deblocarea).
Exit = Sterge blocarea de pe obiect, echivalent cu sfarsitul sectiunii critice.

Se foloseste pentru a sincroniza accesul la metode statice sau de instanta ale clasei.
Dezvoltare aplicatii folosind platforma .NET - 179/233

Functionalitatea metodelor Enter si Exit este identica cu cea furnizata de instructiunea
lock din C#.

Daca dorim sa sincronizam o metoda (sincronizare la nivel de metoda), folosim atributul
System.Runtime.CompilerServices.MethodImplAttribute pentru aceasta metoda si
specificam valoarea Synchronized in constructorul lui MethodImplAttribute. Folosind
acest atribut, Enter si Exit nu mai sunt necesare.
Blocarea metodei se face pina la return.

Blocarile se fac pe obiecte interne sau private; blocarile pe obiecte externe pot cauza
deadlock.
Mutex Class
Folosita pentru sincronizarea intre procese. Permite accesul exclusiv la o resursa.
Putem folosi Waithandle.WaitOne pentru a deveni proprietarul unui mutex. Eliberarea
mutexului se face folosind metoda ReleaseMutex.

Daca un fir se termina normal in timp ce are un mutex, atunci mutexul devine semnalat
astfel incat alt fir poate avea acces la acel mutex.

Mutexul este un obiect nucleu si deci se supune regulilor acestuia. Poate fi creat in starea
semnalat sau nu, poate avea un nume sau nu, dupa modul cum dorim sa-l folosim. Pentru
sincronizarea intre procese este obligatoriu sa aiba un nume.
Exemplu

// This example shows how a Mutex is used to synchronize access
// to a protected resource. Unlike Monitor, Mutex can be used with
// WaitHandle.WaitAll and WaitAny, and can be passed across
// AppDomain boundaries.

using System;
using System.Threading;

class Test
{
// Create a new Mutex. The creating thread does not own the
// Mutex.
private static Mutex mut = new Mutex();
private const int numIterations = 1;
private const int numThreads = 3;

static void Main()
{
// Create the threads that will use the protected resource.
for(int i = 0; i < numThreads; i++)
{
Thread myThread = new Thread(
new ThreadStart(MyThreadProc));
myThread.Name = String.Format("Thread{0}", i + 1);
myThread.Start();
Dezvoltare aplicatii folosind platforma .NET - 180/233
}
// The main thread exits, but the application continues to
// run until all foreground threads have exited.
}

private static void MyThreadProc()
{
for(int i = 0; i < numIterations; i++)
{
UseResource();
}
}
// This method represents a resource that must be synchronized
// so that only one thread at a time can enter.
private static void UseResource()
{
// Wait until it is safe to enter.
mut.WaitOne();

Console.WriteLine("{0} has entered the protected area",
Thread.CurrentThread.Name);
// Place code to access non-reentrant resources here.
// Simulate some work.
Thread.Sleep(500);
Console.WriteLine("{0} is leaving the protected area\r\n",
Thread.CurrentThread.Name);
// Release the Mutex.
mut.ReleaseMutex();
}
}
Dezvoltare aplicatii folosind platforma .NET - 181/233
Sincronizarea firelor

Sincronizarea in cadrul unui fir se realizeaza in Win32 API cu ajutorul sectiunilor critice.
In .NET nu exista sectiuni critice, dar exista un mecanism asemanator dat de clasele
SyncBlock si System.Threading.Monitor.

.NET

Fiecare obiect din heap are asociata o structura de date (asemanatoare cu structura
CRITICAL_SECTION) ce poate fi utilizata pentru sincronizarea firelor. FCL furnizeaza
metode, astfel incat cand se face referinta la un obiect, se foloseste aceasta structura
pentru sincronizare. Dezvoltatorul are obligatia de ascrie cod pentru a folosi aceasta
structura (ceva similar in sensul EnterCriticalSection si LeaveCriticalSection). CLR aloca
memorie pentru SyncBlocks, ce poate fi asociat cu orice obiect, dupa necesitati.
SyncBlock contine aceleasi campuri ca si sectiunea critica. Un obiect creat contine doua
campuri in plus fata de cele declarate: MethodTablePointer si SyncBlockIndex, ce
contine un index pe 32 biti la obiecte SyncBlocks. La crearea obiectului, SyncBlockIndex
este initializat cu o valoare negativa ceea ce inseamna ca nu exista blocuri SyncBlock
asociate cu obiectul. Cand se apeleaza o metoda pentru sincronizarea obiectului, CLR
cauta un bloc SyncBlock liber si il asociaza obiectului. Cand nu mai este nevoie de
sincronizare se elibereaza acest bloc si se reactulizeaza SyncBlockIndex la o valoare
negativa.

Clasa Monitor se foloseste pentru a manipula un SyncBlock
Metodele clasei Monitor sunt statice.
Metoda apelata pentru a bloca un obiect:
public static void Enter(object obj);
Se verifica daca exista bloc SyncBlock asociat si daca acesta este liber.
Putem apela si metoda (pentru a programa in mod defensiv):
public static Boolean TryEnter(object obj);
public static Boolean TryEnter(object obj, int millisecondsTimeout);
public static Boolean TryEnter(object obj, TimeSpan timeout);

Blocul SyncBlock este eliberat cu ajutorul metodei:
public static void Exit(object obj);
Fiecarui apel pentru Enter/TryEnter (incrementare contor de utilizare) trebuie sa-i
corespunda un apel pentru Exit (decrementare contor de utilizare).
Exemplu de sincronizare pe campuri ale instantei

class Transaction {

// Private field holding the time of
// the last transaction performed
private DateTime timeOfLastTransaction;

public void PerformTransaction() {
// Lock this object
Monitor.Enter(this);
Dezvoltare aplicatii folosind platforma .NET - 182/233

// Perform the transaction...

// Record time of the most recent transaction
timeOfLastTransaction = DateTime.Now;

// Unlock this object
Monitor.Exit(this);
}

// Public read-only property returning
// the time of the last transaction
public DateTime LastTransaction {
get {
// Lock this object
Monitor.Enter(this);

// Save the time of the last transaction
// in a temporary variable
DateTime dt = timeOfLastTransaction;

// Unlock this object
Monitor.Exit(this);

// Return the value in the temporary variable
return(dt);
}
}
}

Instructiunea C# lock

Programare mai simpla.

// Regular function
public void SomeMethod() {
// Lock the object
Object oTemp = this;
Monitor.Enter(oTemp);
try {
// Access the object
...
// Unlock the object
}
finally {
Monitor.Exit(oTemp);
}
// Return
}

// Simple function
public void SomeMethod() {
// Lock the object
Dezvoltare aplicatii folosind platforma .NET - 183/233
lock (this)
{

// Access the object
...
// Unlock the object
}

// Return
}
class Transaction {

// Private field holding the time of
// the last transaction performed
private DateTime timeOfLastTransaction;

public void PerformTransaction() {
lock (this)
{
// Perform the transaction...

// Record time of the most recent transaction
timeOfLastTransaction = DateTime.Now;
}
}

// Public read-only property returning
// the time of the last transaction
public DateTime LastTransaction {
get {
lock (this)
{
// Return the time of the last transaction
return timeOfLastTransaction;
}
}
}
}

Sincronizare metode statice si campuri statice

In acest caz nu mai exista SyncBlockIndex si nici SyncBlock.
Ideea este de a transforma un tip valoare in tip referinta. Acest lucru se face cu ajutorul
operatorului typeof din C#.
Exemplu
class Transaction {

// Private field holding the time of
// the last transaction performed
private static DateTime timeOfLastTransaction;

public static void PerformTransaction() {
Dezvoltare aplicatii folosind platforma .NET - 184/233
lock (typeof(Transaction)) {
// Perform the transaction...

// Record time of the most recent transaction
timeOfLastTransaction = DateTime.Now;
}
}

// Public read-only property returning
// the time of the last transaction
public static DateTime LastTransaction {
get {
lock (typeof(Transaction)) {
// Return the time of the last transaction
return timeOfLastTransaction;
}
}
}
}

Diverse probleme
Sa examinam urmatorul cod.

using System;
using System.Threading;

class App {
static void Main() {
// Construct an instance of the App object
App a = new App();

// This malicious code enters a lock on
// the object but never exits the lock
Monitor.Enter(a);

// For demonstration purposes, let's release the
// root to this object and force a garbage collection
a = null;
GC.Collect();

// For demonstration purposes, wait until all Finalize
// methods have completed their execution - deadlock!
GC.WaitForPendingFinalizers();

// We never get to the line of code below!
Console.WriteLine("Leaving Main");
}

// This is the App type's Finalize method
~App()
{
// For demonstration purposes, have the CLR's
// Finalizer thread attempt to lock the object.
Dezvoltare aplicatii folosind platforma .NET - 185/233
// NOTE: Since the Main thread owns the lock,
// the Finalizer thread is deadlocked!
lock (this)
{
// Pretend to do something in here...
}
}
}
Eliminarea acestui incovenient se face prin sincronizarea cu ajutorul unui camp privat
declarat in clasa.

class Transaction {

// Private Object field used
// purely for synchronization
private Object objLock = new Object();

// Private field holding the time of
// the last transaction performed
private DateTime timeOfLastTransaction;

public void PerformTransaction() {
lock (objLock) {
// Perform the transaction...

// Record time of the most recent transaction
timeOfLastTransaction = DateTime.Now;
}
}

// Public read-only property returning
// the time of the last transaction
public DateTime LastTransaction {
get {
lock (objLock) {
// Return the time of the last transaction
return timeOfLastTransaction;
}
}
}
}

daca toti membri din calsa Transaction sunt statici, rezolvarea problemei de sincronizare
se poate realiza ca in exemplul urmator.

class Transaction {

// Private, static Object field
// used purely for synchronization
private static Object objLock = new Object();

// Private field holding the time of
Dezvoltare aplicatii folosind platforma .NET - 186/233
// the last transaction performed
private static DateTime timeOfLastTransaction;

public static void PerformTransaction() {
lock (objLock) {
// Perform the transaction...

// Record time of the most recent transaction
timeOfLastTransaction = DateTime.Now;
}
}

// Public read-only property returning
// the time of the last transaction
public static DateTime LastTransaction {
get {
lock (objLock) {
// Return the time of the last transaction
return timeOfLastTransaction;
}
}
}
}

Erori tipice in sincronizare
Sa examinam exemplul urmator.
class AnotherType
{
// An unboxed Boolean value type
private Boolean flag = false;

public Boolean Flag {
set {
Monitor.Enter(flag); // Boxes flag and locks the object
flag = value; // The actual value is unprotected
Monitor.Exit(flag); // Boxes flag, attempts to unlock
// the object
}
}
}

Nu are loc sincronizarea pentru ca flag este de tip valoare. Deci nu exista
SyncBlockIndex si nici MethodTablePointer.

Problema de mai sus se poate rezolva prin considerarea unui camp auxiliar (privat) de tip
referinta si blocarea se va face pe acest camp.

Acelasi exemplu, dar modificat si care lucreaza corect.
class AnotherType {
Dezvoltare aplicatii folosind platforma .NET - 187/233

// An unboxed Boolean value type
private Boolean flag = false;

// A private Object field used to
// synchronize access to the flag field
private Object flagLock = new Object();

public Boolean Flag {
set {
Monitor.Enter(flagLock);
flag = value;
Monitor.Exit(flagLock);
}
}
}
Dezvoltare aplicatii folosind platforma .NET - 188/233
Folosirea componentelor COM in aplicatii scrise in C#

In cele ce urmeaza vom descrie modul de utilizare a componentelor COM in .NET, altfel
spus avem cod binar dezvoltat in C++ (sau alt limbaj ce poate crea componente COM) si
dorim sa-l utilizam intr-o aplicatie dezvoltata in C#.
Presupunem ca avem o componenta (server dll) Airline ce contine interfata IAirlineInfo,
metoda GetAirlineTiming si o proprietate folosita pentru a regasi timpul curent al
aeroportului, LocaleTimeAtOrlando.
Codul partial pentru aceasta componenta este urmatorul.
Fisierul idl (descriere interfata):

interface IAirlineInfo : IDispatch
{
// metoda
[id(1), helpstring("method GetAirlineTiming")]
HRESULT GetAirlineTiming([in] BSTR bstrAirline,
[out,retval] BSTR* pBstrDetails);

// proprietatea
[propget, id(2), helpstring("property LocalTimeAtOrlando")]
HRESULT LocalTimeAtOrlando([out, retval] BSTR *pVal);
};
Aceasta interfata este dezvoltata in clasa CAirlineInfo (class CAirlineInfo : public
IAirlineInfo(...)). Codul pentru metoda GetAirlineTiming poate fi urmatorul.
CAirlineInfo::GetAirlineTiming(BSTR bstrAirline, BSTR *pBstrDetails)
{
_bstr_t bstrQueryAirline(bstrAirline);
if(NULL == pBstrDetails) return E_POINTER;

if(_bstr_t("Air Scooby IC 5678") == bstrQueryAirline)
{
// Return the timing for this Airline.
*pBstrDetails =
_bstr_t(_T("16:45:00 - Will arrive at Terminal 3")).copy();

}
else
{
// Return an error message if the Airline wasn't found.
return Error(LPCTSTR(
_T("Airline Timings not available for this Airline" )),
__uuidof(AirlineInfo), AIRLINE_NOT_FOUND);
}
return S_OK;
}

Dezvoltare aplicatii folosind platforma .NET - 189/233
Dupa compilare si editare de legaturi rezulta fisierul dll ce gazduieste aceasta
componenta si biblioteca de tipuri (extensia tlb). Daca avem drepturi de administrator, in
momentul editarii de legaturi componenta este inregistrata in registri, in caz contrar va
trebui sa o inregistram manual.

Utilizarea componentei dintr-o aplicatie .NET
Pentru a putea fi folosita aceasta componenta, .NET are nevoie de informatii auxiliare, pe
care trebuie sa le citeasca din metadata. Dar metadata nu exista in acest moment pentru
componenta noastra.
Prima etapa: Construirea metadatei se realizeaza cu ajutorul utilitarului tlbimp.exe,
folosit ca in continuare:
TLBIMP AirlineInformation.tlb /out:AirlineMetadata.dll

Metadata este folosita pentru a genera in mod dinamic Runtime Callable Wrapper
(RCW). RCW gestioneaza activarea componentei, problemele legate de gestionarea
timpului de viata a obiectului COM, de gestionarea interfetelor, identitatea obiectului, de
marshaling cand .NET interactioneaza cu obiectul COM, problemele ce apar datorita
legarii timpurii sau tarzii la obiectul COM. Pe scurt RCW asigura interfata intre
codul managed si cel unmanaged din COM.

In schema urmatoare se precizeaza actiunile din partea de cod managed si cel
unmanaged.

(Managed code)
Aplicatie .NET
Managed metadata proxy
(tlbimp.exe)
(biblioteca de tip a componentei)
RCW (Runtime Callable Wrapper)
(Unmanaged code)
Serverul COM
(IUnknown, IDispatch, IInterface1, IInterface2)

TLBIMP.EXE citeste biblioteca de tipuri a componentei si genereaza metadata in fisierul
AirlineMetadata.dll. Daca inspectam metadata creata vom observa ca s-a creat clasa
AirlineInfo (AirlineInfo este definita in coclass din fisierul IDL) cu interfata IAirlineInfo.
Metoda GetAirlineTiming este declarata ca fiind publica. Tipurile de date pentru
parametrii metodei sunt schimbati la echivalentul lor din .NET (vezi la sfarsitul
documentului). Cand o interfata returneaza o valoare HRESULT ce indica o eroare,
valoarea este in mod automat convertita la o exceptie .NET.
Etapa a doua. Legarea la componenta COM si utilizarea acesteia se face in mod simplu
prin instantierea clasei ce descrie componenta. Sa urmarim exemplul de mai jos.

String strAirline = "Air Scooby IC 5678";
Dezvoltare aplicatii folosind platforma .NET - 190/233
String strEronat = "Air Jughead TX 1234";
try
{
AirlineInfo objAirlineInfo;
objAirlineInfo = new AirlineInfo();
// Call the GetAirlineTiming method.
System.Console.WriteLine("Details for Airline {0} --> {1}",
strAirline,
objAirlineInfo.GetAirlineTiming(strAirline));

// This should make the COM object throw us the
// AIRLINE_NOT_FOUND error as a COMException.
System.Console.WriteLine("Details for Airline {0} --> {1}",
strFoodJunkieAirline,
objAirlineInfo.GetAirlineTiming(strEronat));
}
catch(COMException e)
{
System.Console.WriteLine("Oops, an error occured! " +
"Error Code is : {0}. Error message is : {1}",
e.ErrorCode,e.Message);
}
Rezultatul este:
Details for Airline Air Scooby IC 5678 --> 16:45:00 - Will arrive at
Terminal 3
Oops, an error occured! Error Code is : -2147221502.
Error message is : Airline Timings not available for this Airline
Erorile returnate de metodele din componenta sunt preluate de RCW si convertite intr-o
clasa echivalenta COMException din spatiul de nume System.Runtime.InteropServices.
Pentru ca acest lucru sa functioneze, obiectul COM trebuie sa implementeze interfetele
ISupportErrorInfo si IErrorInfo. Clientul .NET preia aceste erori intr-un bloc try...catch.
In .NET exista implementata o corespondenta intre valorile de tip HRESULT returnate si
exceptiile .NET.
Ce se intimpla cu QueryInterface?
In C++, apelam QI pentru a vedea daca obiectul suporta o anumita interfata, ca in
exemplul urmator.
// pUnk pointer valid la interfata IUnknown, obtinut din
// CoCreateInstance.
IInterface1 * pInterface1;
pInterface1 = NULL;
HRESULT hr = pUnk->QueryInterface(...,(void*)&pInterface1);
if (FAIL(hr))
{..}
else
{ // avem interfata }

Dezvoltare aplicatii folosind platforma .NET - 191/233
In .NET lucrurile sunt mai simple. Va trebui sa facem o conversie (cast) al obiectului la
interfata pe care dorim sa o apelam. Daca conversia reuseste, atunci interfata exista. In
cazul cand conversia nu reuseste se arunca exceptia System.InvalidCastException, ceea ce
semnifica ca interfata nu exista.
O alta posibilitate (programare defensiva) este folosirea operatorului is sau as, ca in
exemplul urmator.
try
{
AirlineInfo objAirlineInfo = null;
IAirportFacilitiesInfo objFacilitiesInfo = null;

// Create a new AirlineInfo object.
objAirlineInfo = new AirlineInfo();

// Invoke the GetAirlineTiming method.
String strDetails = objAirlineInfo.GetAirlineTiming(strAirline);

// Check to see whether the AirlineInfo object supports the
// IAirportFacilitiesInfo interface using C#'s is operator.
if(objAirlineInfo is IAirportFacilitiesInfo)
{
// Perform a cast to get the QueryInterface done.
objFacilitiesInfo = (IAirportFacilitiesInfo)objAirlineInfo;

// You could even perform the cast using C#'s as operator.
objFacilitiesInfo = objAirlineInfo as IAirportFacilitiesInfo;

// Invoke a method on the IAirportFacilitiesInfo interface.
System.Console.WriteLine("{0}",
objFacilitiesInfo.GetInternetCafeLocations());

}

// Let's check against an arbitrary interface type.
if(objAirlineInfo is IJunkInterface)
{
System.Console.WriteLine("We should never get here ");
}
else
{
System.Console.WriteLine("I'm sorry I don't implement " +
"the IJunkInterface interface ");
}

// And now let's ask for some trouble and have the
// COM interop throw us an invalid cast exception.
IJunkInterface objJunk = null;
objJunk = (IJunkInterface)objAirlineInfo;

}
catch(InvalidCastException eCast)
{
System.Console.WriteLine("Here comes trouble ... " +
Dezvoltare aplicatii folosind platforma .NET - 192/233
"Error Message : {0}",eCast.Message);

}
Iesirea este:
Your nearest Internet Cafe is at Pavilion 3 in Terminal 2
- John Doe's Sip 'N' Browse Cafe
I'm sorry I don't implement the IJunkInterface interface
Here comes trouble ... Error Message : An exception of
type System.InvalidCastException was thrown.

Legarea tarzie (late binding) la obiectele COM
In legarea timpurie se utilizeaza metadata proxy. Se recomanda folosirea legarii timpurii
deoarece controlul tipurilor se face in momentul compilarii, si de asemenea performanta
este mai buna. Legarea tarzie se realizeaza prin mecanismul cunoscut sub numele
reflection . In situatia cand obiectul COM implementeaza o interfata pura
dispinterface utilizarea metodei reflection este limitata. Pentru obiectele .NET putem
utiliza reflection in cazul cand dorim incarcarea si legarea intarziata la acestea.
Pentru a realiza legarea intarziata la un obiect COM avem nevoie de ProgID-ul sau
CLSID-ul obiectului. Metoda statica CreateInstance din clasa System.Activator ne
permite sa specificam informatia de tip pentru o anumita clasa si apoi putem crea
obiectul. Informatia de tip o obtinem din ProgId sau CLSID folosind metodele statice
GetTypeFromProgID sau GetTypeFromCLSID din clasa System.Type. Clasa
System.Type este foarte importanta in folosirea mecanismului reflection.
Dupa crearea instantei obiectului COM cu Activator.CreateInstance, putem apela
metodele si proprietatile suportate de obiect folosind metoda
System.Type.InvokeMember. Tot ce trebuie sa stim este numele metodei sau proprietatii
pe care obiectul le accepta.
Parametrii metodei sunt pasati intr-un vector de tip System.Object. De asemenea trebuie
setati anumiti biti (flags) pentru a indica daca proprietatea este de tip set (put) sau get.
Exemplu
try
{
object objAirlineLateBound;
Type objTypeAirline;

// Create an object array containing the input
// parameters for the method.
object[] arrayInputParams= { "Air Scooby IC 5678" };

// Get the type information from the ProgID.
objTypeAirline = Type.GetTypeFromProgID(
"AirlineInformation.AirlineInfo");

// Here's how you use the COM CLSID to get
// the associated .NET System.Type:
// objTypeAirline = Type.GetTypeFromCLSID(new Guid(
Dezvoltare aplicatii folosind platforma .NET - 193/233
// "{F29EAEEE-D445-403B-B89E-C8C502B115D8}"));

// Create an instance of the object.
objAirlineLateBound = Activator.CreateInstance(objTypeAirline);

// Invoke the GetAirlineTiming method.
String str = (String)objTypeAirline.InvokeMember(
"GetAirlineTiming",
BindingFlags.Default BindingFlags.InvokeMethod,
null,
objAirlineLateBound,
arrayInputParams);

System.Console.WriteLine("Late Bound Call - Air Scooby " +
"Arrives at : {0}",str);

// Get the value of the LocalTimeAtOrlando property.
String strTime = (String)objTypeAirline.InvokeMember(
"LocalTimeAtOrlando",
BindingFlags.Default BindingFlags.GetProperty,
null,
objAirlineLateBound,
new object[]{});

Console.WriteLine ("Late Bound Call - Local Time at " +
"Orlando, Florida is: {0}", strTime);

}/* end try */
catch(COMException e)
{
System.Console.WriteLine("Error code : {0},
Error message : {1}",
e.ErrorCode, e.Message);
}/* end catch */
Iesirea este:
Late Bound Call - Air Scooby Arrives at 16:45:00 - Will arrive
at Terminal 3
Late Bound Call - Local Time at Orlando, Florida is: Sun Jul 15
16:50:01 2001

Pentru detalii despre metoda System.Type.InvokeMember consultati MSDN.

Tratarea evenimentelor
Comunicarea bidirectionala in COM. Puncte de conexiune.
Modelul este urmatorul.
Dezvoltare aplicatii folosind platforma .NET - 194/233

1. Obiectul COM are o interfata outgoing, marcate cu atributul [source] in sectiunea
coclass din IDL. Aceasta inseamna ca obiectul COM defineste o interfata, iar
implementarea acesteia este facuta de client.
2. Clientul implementeaza interfata intr-un obiect numit sink. Clientul trebuie sa
transmita componentei un pointer valid la interfata obiectului sink, acest lucru fiind
echivalent cu acela al subscrierii pentru un eveniment publicat de componenta.
3. Componenta inregistreaza acest pointer intr-o harta a pointerilor la obiecte sink.
4. Cand componenta seteaza acest eveniment, aceasta va itera prin aceasta harta si va
apela metoda din interfata obiectului sink.
Exemplu.
Presupunem ca avem o componenta WebSite ce anunta clientii cand un articol nou este
pus pe site. Interfata outgoing se numeste INewArticleEvent.
Acum presupunem ca avem o aplicatie client ce vrea sa trateze acest eveniment.
Clientul va avea un obiect sink ce implementeaza interfata INewArticleEvent.
Clientul va insttinta componenta despre valoarea pointerului la obiectul sink.
Componenta va memora acest pointer.
Componenta are o constructie speciala. Trebuie sa implementeze interfetele
IConnectionPointContainer si IConnectionPoint.
Interfata IConnectionPointContainer determina daca componenta suporta comunicarea
bidirectionala. Apoi se determina cu metoda FindConnection pentru IID interfetei
outgoing daca aceasta este suportata. Valoarea returnata este un pointer la interfata
IConnectionPoint. Clientul apeleaza metoda Advise pentru a inregistra sink in
Dezvoltare aplicatii folosind platforma .NET - 195/233
componenta. Valoarea returnata este un index ce va fi folosit pentru a sterge sink din
componenta.
Fisierul IDL arata astfel:

interface IAirlineArrivalPager : IDispatch
{
[id(1), helpstring("method AddArrivalDetails")]
HRESULT AddArrivalDetails([in] BSTR bstrAirlineName,
[in] BSTR bstrArrivalTerminal);
};

dispinterface _IAirlineArrivalPagerEvents
{
properties:
methods:
[id(1), helpstring("method OnAirlineArrivedEvent")]
HRESULT OnAirlineArrivedEvent(
[in] BSTR bstrAirlineName,
[in] BSTR bstrArrivalTerminal);
};

coclass AirlineArrivalPager // se face referire in .NET
{
[default] interface IAirlineArrivalPager;
[default, source] dispinterface _IAirlineArrivalPagerEvents;
};
Implementarea metodei AddArrivalDetails este urmatoarea.
STDMETHODIMP CAirlineArrivalPager::AddArrivalDetails(
BSTR bstrAirlineName,BSTR bstrArrivalTerminal)
{
// Notify all subscribers that an airline arrived.
Fire_OnAirlineArrivedEvent(bstrAirlineName,
bstrArrivalTerminal);

// Return the status to the caller.
return S_OK;
}

Tratarea evenimentelor folosind delegate
Simplist, modelul delegate arata ca in exemplul urmator.
// Here's the SayGoodMorning delegate.
delegate string SayGoodMorning();

public class HelloWorld
{
public string SpeakEnglish() {
return "Good Morning";
}

Dezvoltare aplicatii folosind platforma .NET - 196/233
public string SpeakFrench() {
return "Bonjour";
}

public static void Main(String[] args) {

HelloWorld obj = new HelloWorld();

// Associate the delegate with a method reference.
SayGoodMorning english =
new SayGoodMorning(obj.SpeakEnglish);
SayGoodMorning french =
new SayGoodMorning(obj.SpeakFrench);

// Invoke the delegate.
System.Console.WriteLine(english());
System.Console.WriteLine(french());

}

}/* end class */
Iesirea este:
Good Morning
Bonjour

Schema generala este urmatoarea.



Dezvoltare aplicatii folosind platforma .NET - 197/233
Reutilizarea componentelor in codul managed
Context : doua componente, una interna si una externa. Componenta externa reutilizeaza
componenta interna.
COM pune la dispozitie doua mecanisme de reutilizare a componentelor: Containment si
Agregare.
Containment



Reguli
Componenta externa implementeaza interfata componentei interne, creaza obiectul intern
si va implementa metodele din interfata componentei interne printr-o simpla redirectare.

Agregarea



Clientul interactioneaza in mod direct cu componenta interna. Componenta externa
mentine un pointer la componenta interna si reciproc. Componenta interna
implementeaza doua interfete IUnknown dintre care numai una singura este publica.
Cealalta interfata IUnknown va fi folosita pentru a mapa corect interfetele obiectului
intern cand acesta este folosit stand alone (fara agregare). Componenta externa nu mai
implementeaza interfetele componentei interne.
Reutilizare mixta: extindere functionalitate clasa (mostenire)
Dezvoltare aplicatii folosind platforma .NET - 198/233
Clasa managed are optiunea suprascrierii metodelor din interfetele definite in coclass sau
sa le accepte asa cum sunt.
Modelul poate fi descris astfel.

Interfata IFlyer este descrisa astfel.
interface IFlyer : IDispatch
{
[id(1), helpstring("method TakeOff")]
HRESULT TakeOff([out,retval] BSTR* bstrTakeOffStatus);
[id(2), helpstring("method Fly")]
HRESULT Fly([out,retval] BSTR* bstrFlightStatus);
};

coclass Flyer
{
[default] interface IFlyer;
};
Implementarea metodelor este urmatoarea.
STDMETHODIMP CFlyer::TakeOff(BSTR *bstrTakeOffStatus)
{
*bstrTakeOffStatus =
_bstr_t(
_T("CFlyer::TakeOff - This is COM taking off")).copy();
return S_OK;
}

STDMETHODIMP CFlyer::Fly(BSTR *bstrFlyStatus)
Dezvoltare aplicatii folosind platforma .NET - 199/233
{
*bstrFlyStatus =
_bstr_t(
_T("CFlyer::Fly - This is COM in the skies")).copy();
return S_OK;
}
Pentru reutilizare construim metadata.
tlbimp MyFlyer.tlb /out:MyFlyerMetadata.dll
Construim clasa ce este derivata din Flyer.
using System;
using MyFlyerMetadata;

// Inherit from the metadata type representing
// the unmanaged COM component.
// Use the COM component's implementation
// of TakeOff and Fly. (Use the base class's implementation.)
public class Bird : Flyer
{

}/* end class Bird */

// Inherit from the metadata type representing
// the unmanaged COM component.
// Override the COM object's method implementations in our
// derived managed class.
// (Also call base class implementation when necessary using
// base.MethodName().)
public class Airplane : Flyer
{
// Override the COM component's Flyer::TakeOff implementation
// with our own implementation.
public override String TakeOff() {

return "Airplane::TakeOff - This is .NET taking off";

}/* end TakeOff */

// Override the COM component's Flyer::Fly implementation
// with our own implementation.
public override String Fly() {

// Can call the base class's implementation too if you
// want.
System.Console.WriteLine(base.Fly());
return "Airplane::Fly - This is .NET in the skies";

}/* end Fly */

}/* end class Airplane */

public class FlightController
Dezvoltare aplicatii folosind platforma .NET - 200/233
{
public static void Main(String[] args)
{
Bird falcon = new Bird();
System.Console.WriteLine("BIRD: CLEARED TO TAKE OFF");
System.Console.WriteLine(falcon.TakeOff());
System.Console.WriteLine(falcon.Fly());

Airplane skyliner = new Airplane();
System.Console.WriteLine("AIRPLANE: CLEARED TO TAKE OFF");
System.Console.WriteLine(skyliner.TakeOff());
System.Console.WriteLine(skyliner.Fly());

}/* end Main */

}/* end FlightController */
Compilarea se face astfel:
csc /target:exe /out:FlightClient.exe /r:MyFlyerMetadata.dll
FlightClient.cs
Iesirea este urmatoarea:
BIRD: CLEARED TO TAKE OFF
CFlyer::TakeOff - This is COM taking off
CFlyer::Fly - This is COM in the skies
AIRPLANE: CLEARED TO TAKE OFF
Airplane::TakeOff - This is .NET taking off
CFlyer::Fly - This is COM in the skies
Airplane::Fly - This is .NET in the skies

Reutilizare mixta prin containment
Exemplu de cod
using System;
using MyFlyerMetadata;

// Contains an instance of the metadata type representing
// the unmanaged COM component.
public class HangGlider
{
private Flyer flyer = new Flyer();

// Forward the call to the contained class's implementation.
public String TakeOff()
{
return flyer.TakeOff();
}

// Forward the call to the contained class's implementation.
public String Fly()
{
Dezvoltare aplicatii folosind platforma .NET - 201/233
// Do what you need to do before or after fowarding the
// call to flyer.
System.Console.WriteLine("In HangGlider::Fly - Before " +
"delegating to flyer.Fly");
return flyer.Fly();
}

}/* end class HangGlider */

public class FlightController
{
public static void Main(String[] args)
{
...
HangGlider glider = new HangGlider();
System.Console.WriteLine("HANGGLIDER: CLEARED TO TAKEOFF");
System.Console.WriteLine(glider.TakeOff());
System.Console.WriteLine(glider.Fly());

}/* end Main */

}/* end FlightController */
Iesirea este urmatoarea:
HANGGLIDER: CLEARED TO TAKEOFF
CFlyer::TakeOff - This is COM taking off
In HangGlider::Fly - Before delegating to flyer.Fly
CFlyer::Fly - This is COM in the skies
Folosire componente .NET in cod unmanaged
Spatiul de nume System.Runtime.InteropServices furnizeaza membrii ce suporta
interactiunea cu COM. Membrii din acest spatiu de nume sunt impartiti pe categorii. De
exemplu atributele controleaza comportarea marshaling-ului, modul cum se aranjeaza
structurile, cum se reprezinta stringurile, etc.
Cele mai importante atribute sunt DllImportAttribute (folosit pentru accesare cod
unmanaged) si MarshalAttribute (folosit pentru transfer date intre memoria managed si
cea unmanaged).

Introducerea interfetei clasei

Interfata clasei nu este definita in mod explicit in codul managed. Interfata expune toate
metodele publice, proprietatile, campurile si evenimentele clasei ce sunt in mod explicit
pe obiectul .NET.
Aceasta interfata poate fi duala sau dispatch.
Interfata clasei primeste numele clasei din .NET, precedata de un underscore.
De exemplu pentru clasa Informatica, interfata clasei este _Informatica.
Pentru clasele derivate, interfata clasei expune de asemenea toate metodele publice,
campurile si proprietatile clasei de baza.
Clasa derivata expune de asemenea o interfata a clasei pentru fiecare clasa de baza.

Dezvoltare aplicatii folosind platforma .NET - 202/233
De exemplu daca clasa Informatica extinde clasa InformaticaBaza care la rindul ei
extinde System.Object, obiectul .NET expune clientilor COM trei clase de interfata,
_Informatica, _InformaticaBaza si _Object.
Exemplu
// Facem interfata sa fie duala
[ClassInterface(ClassInterfaceType.AutoDual)]
// In mod implicit extinde System.Object.
public class Informatica
{
void Curs();
void Laborator():
void Vacanta();
}
Clientul COM poate obtine un pointer la o interfata a clasei numita _Informatica, ce este
descrisa in biblioteca de tipuri generata de utilitarul TLBEXP.EXE (Type Library
Exporter). Daca clasa Informatica implementeaza una sau mai multe interfete, interfetele
vor aparea in coclass.

[odl, uuid(...), hidden, dual, nonextensible, oleautomation]
interface _Informatica : IDispatch
{
[id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
pRetVal);
[id(0x60020001)] HRESULT Equals([in] VARIANT obj,
[out, retval] VARIANT_BOOL* pRetVal);
[id(0x60020002)] HRESULT GetHashCode(
[out, retval] short* pRetVal);
[id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);

[id(0x6002000d)] HRESULT Curs();
[id(0x6002000e)] HRESULT Laborator();
[id(0x6002000f)] HRESULT Vacanta();
}
[uuid(...)]
coclass Informatica
{
[default] interface _Informatica;
}

Generarea interfetei clasei este optionala. Implicit, COM interop genereaza numai o
interfata dispatch pentru fiecare clasa pe care o exportam intr-o biblioteca de tipuri.

Putem preveni sau modifica crearea automata a acestei interfete aplicand
ClassInterfaceAttribute clasei noastre.

Atentie: Folosind interfata clasei in loc de a o defini in mod explicit creaza probleme
legate de versionare. Aceste probleme sunt generate de modul de definire si
implementare al modelului COM/DCOM.
Trebuiesc respectate urmatoarele reguli inainte de a utiliza interfata clasei.

R1. Sa se defineasca o interfata explicita ce sa fie folosita de clientii COM in loc de a
genera interfata clasei.
Dezvoltare aplicatii folosind platforma .NET - 203/233

COM interop genereaza in mod automat o interfata a clasei, iar noile versiuni ale clasei
pot schimba structura clasei in memorie. Acest lucru provine din faptul ca in COM o
interfata facuta publica nu mai poate fi schimbata, trebuie creata o alta noua.
Trebuie sa folosim ClassInterfaceAttribute pentru a interzice generarea automata a
interfetei clasei si sa implementam o interfata explicita pentru clasa, ca in urmatorul
exemplu:
//
// Nu se genereaza automat o interfata pentru clasa.
// Se implementeaza in mod explicit o interfata.
//
[ClassInterface(ClassInterfaceType.None)]
public class ExplicitApp : IExplicit {
void M();
}

Valoarea ClassInterfaceType.None are ca efect negenerarea interfetei clasei cand
metadata clasei este exportata intr-o biblioteca de tipuri.
In exemplul anterior, clientii COM pot accesa clasa ExplicitApp numai prin interfata
IExplicit.

R2. Interzicerea memorarii identificatorilor dispatch (DispIds).

DispID-urile identifica membrii interfetei pentru a permite legarea tarzie. Pentru interfata
clasei, generarea dispId-urilor este bazata pe pozitia membrului in cadrul interfetei. Daca
schimbam aceasta ordine a membrilor si exportam clasa intr-o biblioteca de tipuri, se vor
altera aceste valori.
Pentru a preveni acest lucru vom folosi atributul ClassInterfaceAttribute cu valoarea
ClassInterfaceType.AutoDispatch.
Aceasta valoare are ca efect implementarea unei interfete dispatch, dar omite descrierea
interfetei din biblioteca de tipuri. Fara o asemenea descriere a interfetei, clientii COM nu
au posibilitatea memorarii dispID in momentul compilarii (se interzice astfel legarea
timpurie).

[ClassInterface(ClassInterfaceType.AutoDispatch]
public class ExplicitApp : IAnother {
void M();
}

Pentru a obtine DispId-ul unui membru al interfetei clientii COM vor apela
IDispatch.GetIdsOfNames, iar pentru a executa o metoda pe aceasta interfata vor apela
IDispatch.Invoke in care printre altele vor specifica dispID-ul obtinut anterior.

R3. Restrictionarea utilizarii optiunii interfata duala pentru interfata clasei
(ClassInterface.AutoDual).

Interfata duala permite legarea timpurie si cea tarzie. Pentru clasele ce nu se vor schimba
niciodata dupa ce au fost definite, o interfata duala este OK. Clasele suspecte de a fi
schimbate ar trebui sa nu aiba aceasta facilitate (interfata duala).
Dezvoltare aplicatii folosind platforma .NET - 204/233

R4. Calificarea tipurilor .NET pentru interoperare
Daca intentionam sa expunem tipuri intr-un assembly pentru aplicatii COM, trebuie sa
luam in considerare modul cum vom accesa aceste tipuri.
Tipurile managed (clase, interfete, structuri, enumerari) pot fi integrate cu COM
respectand urmatoarele reguli :
Clasele ar trebui sa implementeze in mod explicit interfetele.
Tipurile managed trebuie sa fie publice. Constructorii parametrizati, metodele
statice si campurile constante nu sunt expuse clientilor. Pentru transferul datelor
(in/out) acestea suporta anumite transformari.
Metodele, proprietatile, campurile si evenimentele trebuie sa fie publice. Putem
restrictiona vizibilitatea unui assembly, a unui tip public, sau membru public
aplicand atributul ComVisibleAttribute. Implicit toate tipurile publice si membri
publici sunt vizibili.
Tipurile trebuie sa aiba un constructor implicit public pentru a fi activate din
COM.
Tipurile nu pot fi abstracte.

Accesibilitate. Clasa ComVisibleAttribute controleaza accesibilitatea unui tip sau
membru managed sau a tuturor tipurilor din interiorul uni assembly catre COM.

Se poate aplica acest atribut pentru assemblies, interfete, clase, structuri, delegates,
enumerari, campuri, metode sau proprietati. Implicit este true, ceea ce inseamna ca tipul
managed este vizibil in COM. Numai tipurile public pot fi facute vizibile.
Setarea atributului pe false pentru assembly va ascunde toate tipurile publice din
interiorul acelui assembly. Daca o clasa be baza foloseste setarea pe false iar o clasa
derivata din aceasta foloseste setarea pe true, clasa de baza va fi vizibila.

Exemplu

using System.Runtime.InteropServices;

[ComVisible(false)]
class MyClass
{
public MyClass()
{
//Insert code here.
}
[ComVisible(false)]
public int MyMethod(string param)
{
return 0;
}

public bool MyOtherMethod()
{
return true;
}
[ComVisible(false)]
Dezvoltare aplicatii folosind platforma .NET - 205/233
public int MyProperty
{
get
{
return MyProperty;
}
}
}

Tipul interfetei
Clasa ClassInterfaceAttribute

Indica tipul interfetei clasei ce va fi generat pentru o clasa expusa catre COM, daca
interfata este generata in intregime.

Atributul se aplica claselor sau assemblies.
Acest atribut controleaza daca TLBEXP.EXE va genera in mod automat o interfata
pentru clasa si pentru atributele clasei. Tlbexp.exe genereaza un identificator unic (IID) al
interfetei.

Pentru inceput vom analiza etapele realizarii unei componente in .NET. Primul lucru
important este sa ne documentam despre InteropServices.
Construim o componenta cu C#.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public enum WeatherIndications
{
Sunny = 0,
Cloudy,
Rainy,
Snowy
}

[ClassInterface(ClassInterfaceType.AutoDual)]
public class TemperatureComponent
{
private float m_fTemperature = 0;

// Public constructor
public TemperatureComponent()
{
m_fTemperature = 30.0f;
}

//Public property accessors (define get/set methods)
public float Temperature
{
get { return m_fTemperature; }
Dezvoltare aplicatii folosind platforma .NET - 206/233

set { m_fTemperature = value;}

}/* end Temperature get/set property */

// Public method that displays the current temperature
public void DisplayCurrentTemperature()
{
String strTemp = String.Format("The current " +
"temperature at Marlinspike is : " +
"{0:####} degrees Fahrenheit", m_fTemperature);

MessageBox.Show(strTemp,"Today's temperature");

}/* end DisplayCurrentTemperature */

// Another public method that returns an enumerated type
public WeatherIndications GetWeatherIndications()
{
if(m_fTemperature > 70) {

return WeatherIndications.Sunny;
}
else {

// Let's keep this simple and just return Cloudy.
return WeatherIndications.Cloudy;
}

}/* end GetWeatherIndications */

}/* end class Temperature */
De remarcat atributele folosite.
Urmatoarea linie de comanda creaza un assembly cu numele Temperature.dll.
csc /target:library /r:System.Windows.Forms.dll
/out:Temperature.dll TemperatureComponent.cs

Generare biblioteca de tip si inregistrare assembly

Pentru aceste lucruri se folosesc utilitarele tlbexp.exe si regasm.exe.

regasm Temperature.dll /tlb:Temperature.tlb
Regasm.exe creaza intrarile in registri si de asemenea genereaza o biblioteca de tip
(Temperature.tlb) din assembly .NET.
Interfata clasei se genereaza cand folosim atributul ClassAttribute pentru acea clasa.
Daca nu se foloseste acest atribut atunci implicit se genereaza o interfata AutoDispatch,
ce permite legarea tarzie.
Dezvoltare aplicatii folosind platforma .NET - 207/233
Pentru exemplul de mai sus se genereaza si metodele din clasa de baza.
ToString
Equals
GetHashCode
GetType
[ClassInterface(ClassInterfaceType.AutoDual)]
public class TemperatureComponent
{}
AutoDual = se va genera o interfata duala si se vor exporta toate informatiile de tip
(pentru metode, proprietati, etc.) in biblioteca de tipuri.
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class TemperatureComponent
{}

AutoDispatch = se interzice generarea informatiilor de tip in biblioteca de tipuri.
Metoda recomandata pentru a modela o componenta .NET ce va fi expusa clientilor
COM este de a construi interfata clasei in mod explicit.
Exemplu:

// Define the ITemperature interface.
public interface ITemperature {

float Temperature { get; set; }
void DisplayCurrentTemperature();
WeatherIndications GetWeatherIndications();

}/* end interface ITemperature */

// (1) Implement the ITemperature interface in
// the TemperatureComponent class.
// (2) Set the ClassInterfaceType for the ClassInterface
// attribute to ClassInterfaceType.None.

[ClassInterface(ClassInterfaceType.None)]
public class TemperatureComponent : ITemperature
{
//Implement the methods in your class.

// Property accessors (define get/set methods)
public float Temperature
{
get { return m_fTemperature; }
set { m_fTemperature = value;}

}/* end Temperature get/set property */

// Displays the current temperature
Dezvoltare aplicatii folosind platforma .NET - 208/233
public void DisplayCurrentTemperature() {

}/* end DisplayCurrentTemperature */

// Returns an enumerated type indicating weather condition
public WeatherIndications GetWeatherIndications() {

}/* end GetWeatherIndications */

}/* end class Temperature */
Pentru acest exemplu IDL arata astfel:
// Generated IDL file (by the OLE/COM object viewer)
// typelib filename: Temperature.tlb

[
uuid(A9F20157-FDFE-36D6-90C3-BFCD3C8C8442),
version(1.0)
]
library Temperature
{
[
odl,
uuid(72AA177B-C6B2-3694-B083-4FF535B40AD2),
version(1.0),
dual,
oleautomation,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
"ITemperature")
]
interface ITemperature : IDispatch {
[id(0x60020000), propget]
HRESULT Temperature([out, retval] single* pRetVal);
[id(0x60020000), propput]
HRESULT Temperature([in] single pRetVal);
[id(0x60020002)]
HRESULT DisplayCurrentTemperature();
[id(0x60020003)]
HRESULT GetWeatherIndications(
[out, retval] WeatherIndications* pRetVal);
};

[
uuid(01FAD74C-3DC4-3DE0-86A9-8490FAEE8964),
version(1.0),
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
"TemperatureComponent")
]
coclass TemperatureComponent {
interface _Object;
[default] interface ITemperature;
};
};

Dezvoltare aplicatii folosind platforma .NET - 209/233
Ghid de utilizare
1. A nu se folosi ClassInterfaceType.AutoDual.
2. ClassInterfaceType.AutoDispatch permite numai legarea tarzie.
3. A nu se folosi interfetele implicite ale claselor. Se vor folosi interfetele explicite.
4. De folosit ClassInterfaceType.None pentru a face interfetele explicite ca fiind
interfete implicite.
Pentru urmatorul exemplu
public interface MyInterface
{
String HelloWorld();
String HelloWorld(int nInput);
}
IDL este:
[
]
interface MyInterface : IDispatch {

[id(0x60020000)]
HRESULT HelloWorld([out, retval] BSTR* pRetVal);

[id(0x60020001)]
HRESULT HelloWorld_2([in] long nInput,
[out,retval] BSTR* pRetVal);
};

Modificarea tipului interfetei
Implicit interfetele din .NET sunt transformate in interfete duale in IDL, ceea ce permite
legarea timpurie cit si cea tarzie.
Implicit nu se trateaza interfetele pure dispinterface sau definite de utilizator bazate pe
interfata IUnknown.
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ISnowStorm
{
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IHurricane
{
}

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ITyphoon
{
}
Dezvoltare aplicatii folosind platforma .NET - 210/233

public interface IWeatherStatistics{}

IDL corespunzator este:

[
odl,
uuid(1423FBFA-BE13-3766-9729-9C1AAF5DB08A),
version(1.0),
dual,
oleautomation,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
"ISnowStorm")
]
interface ISnowStorm : IDispatch {

};

[
odl,
uuid(D95E54B8-FABC-3BDA-AA45-AC4EFF49AF92),
version(1.0),
oleautomation,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
"IHurricane")
]
interface IHurricane : IUnknown {};

[
uuid(676B3B85-7DB8-306D-A1E9-B6AA1008EDF2),
version(1.0),
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
"ITyphoon")
]
dispinterface ITyphoon {
properties:
methods:
};

[
odl,
uuid(A1A37136-341A-3631-9275-FC7B0F0DB695),
version(1.0),
dual,
oleautomation,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
"IWeatherStatistics")
]
interface IWeatherStatistics : IDispatch {}

Dupa cum se observa folosirea atributului ComInterfaceType.InterfaceIsIUnknown
are ca efect derivarea interfetelor din IUnknown, iar folosirea atributului
ComInterfaceType.InterfaceIsIDispatch are ca efect generarea unei interfete pure
dipinterface.

Dezvoltare aplicatii folosind platforma .NET - 211/233
Pentru a specifica un GUID si ProgId specific vom folosi urmatorul atribut:
[
GuidAttribute("AD4760A9-6F5C-4435-8844-D0BA7C66AC50"),
ProgId("WeatherStation.TornadoTracker")
]
public class TornadoTracker {}
iar IDL devine
[
uuid(AD4760A9-6F5C-4435-8844-D0BA7C66AC50),
version(1.0),
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
"TornadoTracker")
]
coclass TornadoTracker {};

Ascunderea tipurilor publice pentru a nu fi expuse in COM
Acest lucru se face folosind atributul ComVisible. Setand acest atribut pe fals vom face
ca tipurile pentru care se aplica sa nu mai apara in biblioteca de tipuri.
[ComVisible(false)]
public interface IWeatherStatistics
{
float GetLowestTemperatureThisMonth();
float GetHighestTemperatureThisMonth();
}
In acest exemplu, interfata IWeatherStatistics nu mai este exportata in biblioteca de
tipuri. Daca tipul IWeatherStatistics este folosit de un alt tip ce este expus catre COM,
tipul este inlocuit de interfata IUnknown in biblioteca de tipuri.
Exemplu

HRESULT SetWeatherStatistics(
[in] IWeatherStatistics* pWeatherIndications);
devine
HRESULT SetWeatherStatistics(
[in] IUnknown* pUnkWeatherIndications);
ComVisible se aplica pentru assemblies, metode, campuri, proprietati, delegates si
structuri.
Vezi exemplul urmator :
public interface IHurricaneWatch {
void AlertWeatherStations(String strDetails);

Dezvoltare aplicatii folosind platforma .NET - 212/233
// Nu este expusa in COM
[ComVisible(false)]
void PlotCoordinates();

// Este expusa in COM

void IssueEvacuationOrders();
}
IDL arata astfel:

[]
interface IHurricaneWatch : IDispatch {
[id(0x60020000)] HRESULT AlertWeatherStations(
[in] BSTR strDetails);
[id(0x60020002)] HRESULT IssueEvacuationOrders();
};
Tratare exceptii: .NET vs COM
CCW implementeaza interfata ISupprtErrorInfo si IErrorInfo preluand exceptiile din
.NET si transformandu-le in tip HRESULT.
Sa examinam urmatorul exemplu.
public class TemperatureComponent
{
private float m_fTemperature = 0;

/* Temperature property (in Fahrenheit)*/
public float Temperature
{
get
{
return m_fTemperature;
}/* end get */

set
{
if((value < -30) (value > 150))
{
TemperatureException excep =
new TemperatureException(
"Marlinspike has never " +
"experienced such extreme temperatures. " +
"Please recalibrate your thermometer");
throw excep;
}

m_fTemperature = value;

}/* end set */
}

} /* end class TemperatureComponent */

class TemperatureException : ApplicationException
Dezvoltare aplicatii folosind platforma .NET - 213/233
{
public TemperatureException(String message) : base(message)
{
}

}/* end class TemperatureException*/
Un client VB trateaza eroarea in modul urmator:
Private Sub MyButton_Click()
On Error GoTo ErrHandler

' Create an instance of the temperature component.
Dim objTemperature As New TemperatureComponent

' Set the temperature to the boiling point of water.
objTemperature.Temperature = 212

Exit Sub

ErrHandler:
MsgBox "Error Message : " & Err.Description, _
vbOKOnly, "Error Code: " & CStr(Err.Number)

End Sub


ADO.NET
Microsoft ADO.NET este o multime de clase ce expun servicii de acces la date.
ADO.NET este o parte a .NET framework ce furnizeaza acces la date relationale,
documente XML, etc.
Pentru a ne conecta la baza de date, a executa comenzi, a regasi informatii, etc. avem
nevoie de un .NET Framework data provider.
Pentru a lucra cu baze de date sub ADO.NET ar trebui sa avem urmatoarele lucruri
instalate pe calculator:
Un server de baze de date (de ex. Firebird).
Un manager (tool) pentru baza de date (de ex IBOConsole).
Un provider .NET (biblioteci) pentru baza de date (de ex Firebird ADO.NET
provider).

Observatie : Firebird suporta doua versiuni : monouser (Clasic) si multiuser
(SuperServer). Exemplele de mai jos vor fi date numai pentru serverul de baze de date
Firebird.


Dezvoltare aplicatii folosind platforma .NET - 214/233
Exemple de provider-i de date:
.NET Framework
data provider
Description
.NET Framework
Data Provider for
SQL Server
Provides data access for Microsoft SQL Server version 7.0 or
later. Uses the System.Data.SqlClient namespace.
.NET Framework
Data Provider for
OLE DB
For data sources exposed using OLE DB. Uses the
System.Data.OleDb namespace.
.NET Framework
Data Provider for
ODBC
For data sources exposed using ODBC. Uses the
System.Data.Odbc namespace.
.NET Framework
Data Provider for
Oracle
For Oracle data sources. The .NET Framework Data Provider for
Oracle supports Oracle client software version 8.1.7 and later,
and uses the System.Data.OracleClient namespace.


In ADO.NET intalnim doua categorii de obiecte: obiecte conectate si obiecte
deconectate.
Obiectele conectate au nevoie permanenta de o conexiune la baza de date.
Obiectele deconectate lucreaza in memorie cu o parte a bazei de date si nu necesita
conexiune permanenta la baza de date.
Cititorul interesat poate gasi destule informatii despre avantajele si dezavantajele acestui
model facand o simpla cautare in Internet.

In categoria obiectelor conectate gasim : Connection, Transaction, DataAdapter (nu
depinde de Connection), Command, Parameter, DataReader. Obiectele deconectate sunt
constituite din DataSet, DataTable, DataView, DataRow, DataColumn, Constraint,
DataRelation.

Observatie : Functie de furnizorul ADO.NET clasele pot avea nume diferite pentru
obiectele enumerate mai sus. De exemplu pentru Firebird ADO.NET provider exista
clasa FbConncetion in timp ce pentru SQL Server clasa se numeste SqlConnection, iar
pentru OleDB clasa se numeste OleDbConnection. In material vom descrie aceste obiecte
la modul general, iar in exemple vom folosi clasele date de furnizorul ADO.NET.


Obiectul Connection

Un obiect Connection reprezinta o conexiune la sursa de date. Putem specifica tipul
sursei de date, locul unde se afla, user, password, obiecte de securitate, etc.

Dezvoltare aplicatii folosind platforma .NET - 215/233
Un obiect de conexiune este folosit de obiectele DataAdapter, Command.
Exemplu (Firebird)

public void InsertRow(string connectionString)
{
//Daca stringul de conexiune este null, folosim unul implicit
if(connectionString == "")
{
FbConnectionStringBuilder cs = new FbConnectionStringBuilder();

cs.DataSource = "localhost";
cs.Database = @"C:\IDD\Personal.fdb";
cs.UserID = "SYSDBA";
cs.Password = "masterkey";
cs.Dialect = 3;

connectionString = cs.ToString();
}

FbConnection connection = new FbConnection(connectionString);
connection.Open();
FbTransaction transaction = connection.BeginTransaction();

string insertQuery = "INSERT INTO PROJECT(proj_id, proj_name,
product) Values('FBNP', '.NET Provider', 'N/A')";
FbCommand command = new FbCommand(insertQuery, myConn, myTxn);
command.ExecuteNonQuery();
transaction.Commit();
connection.Close();
// In mod normal ar trebui sa folosim un try/catch/finally
// In try avem executia si Commit()
// In catch avem Rollback() si o tratare specifica a exceptiei
// In finally avem inchiderea conexiunii.
}

Clasa FbConnection contine proprietati si metode dintre care amintim pe cele mai
importante.

Proprietati publice
ConnectionString
Returneaza sau seteaza stringul folosit
pentru a deschide o baza de date.
Database Returneaza numele bazei de date
curente, dupa ce sesiunea a fost
deschisa.
DataSource Returneaza numele instantei serverului
bazei de date la care ne-am conectat.
Metode publice
BeginTransaction
Overloaded. Incepe o tranzactie pe baza
de date.
Dezvoltare aplicatii folosind platforma .NET - 216/233
Close Inchide conexiunea la baza de date.
CreateCommand Creaza si returneaza un obiect
FbCommand asociat cu FbConnection.

Dispose (inherited from Component) Overloaded. Elibereaza resursele folosite
de Component.
Open

Deschide o conexiune la baza de date cu
proprietatile setate in stringul de
conexiune.

Obiectul Command

Aceste obiecte sunt similare in structura cu cele folosite de ADO Command sau DAO
QueryDef.
Aceste obiecte pot reprezenta o cerere la baza de date, un apel de procedura stocata, sau o
cerere directa pentru a returna continutul unei anumite tabele din baza de date.

Cererile pot fi de tip Select, Insert, Update, Delete sau de creare / modificare / stergere
tabele, view-uri, proceduri stocate, triggere, constrangeri.

Obiectul Command prezinta diferite modalitati de a executa o cerere: ExecuteNonQuery,
ExecuteReader, ExecuteXmlReader.

Metoda obisnuita pentru a lucra cu astfel de obiecte este urmatoarea:
Se creaza stringul ce contine fraza Sql.
Se deschide conexiunea la baza de date.
Se creaza obiectul Command.
Se executa cererea.

Exemplu : afisam inregistrari dintr-o tabela din baza de date.

string stmt = Select Id, Nume, Prenume From Student Where Id >
100 ;
connection.Open();
FbCommand cmd = new FbCommand(stmt, connection);
FbDataReader rd = cmd.ExecuteReader();
while (rd.Read())
{
Console.WriteLine(Id = {0}, Nume = {1}, Prenume = {2},
rd.GetInt16(0), rd.GetString(1), rd.GetString(2));
}
rd.Close();
connection.Close();

Observatie: Daca cererea ar returna o singura valoare atunci putem folosi metoda
ExecuteScalar fara a mai fi nevoie sa folosim un DataReader.

Dezvoltare aplicatii folosind platforma .NET - 217/233


Metode pentru executarea comenzilor Sql
Metoda Descriere
ExecuteReader Executa comenzi ce returneaza inregistrari.
ExecuteNonQuery Executa comenzi din gama INSERT, DELETE,
UPDATE.
ExecuteScalar Regaseste o singura valoare din baza de date.
ExecuteXmlReader Se construieste un obiect XmlReader.

Proprietati publice (considerate printre cele mai importante)
CommandText
Returneaza sau seteaza fraza sql sau
procedura stocata ce se va executa asupra
bazei de date.
CommandType

Returneaza sau seteaza o valoare ce arata
cum este interpretata fraza sql (fraza simpla
sau procedura stocata).
Transaction

Returneaza sau seteaza tranzactia sub care se
va executa comanda. La crearea tranzactiei se
pot specifica nivele de izolare (isolation level)
suportate de serverul de baze de date.
Pentru a obtine informatii despre toate proprietatile publice consultati documentatia
furnizorului de ADO.NET.

Obiectul DataReader

Acest obiect este proiectat pentru a regasi si examina inregistrarile returnate de cererea
facuta serverului bazei de date.

Cand navigam printre inregistrari, inregistrarea anterioara este descarcata, nu mai avem
acces la ea. DataReader nu suporta update.
Valorile returnate de DataReader sunt read only.
Nu exista o metoda pentru a determina apriori numarul de inregistrari pe care-l contine un
DataReader (trebuie parcurs secvential si contorizate inregistrarile).

Atentiune: Nu uitati deschis un DataReader. Inchideti DataReader folosind metoda
Close().

Obiectul Transaction
Dezvoltare aplicatii folosind platforma .NET - 218/233

Obiectul Connection poseda o metoda BeginTransaction pe care o putem utiliza pentru
a crea obiecte Transaction. Pentru a lucra cu tranzactii consultati documentatia bazei de
date pentru a va da seama ce tipuri de tranzactii sa folositi si a vedea ce tipuri de
tranzactii suporta serverul de baze de date.

Obiectul Parameter

Obiectul Parameter este folosit pentru parametrizarea cererilor.

Consideram urmatoarea cerere:

SELECT CustomerID, CompanyName, CompanyName, Phone FROM Customers
WHERE CustomerID = ?

Observam ca in clauza Where pentru CustomerID am plasat un ?. Acest lucru inseamna
ca in momentul executiei acestei fraze va trebui sa furnizam o valoare in loc de ?.

Pentru a realiza aceste cerinte procedam in felul urmator:
Se creaza un obiect Parameter pentru fiecare parametru din cerere si apoi
adaugam aceste obiecte la colectia de obiecte Parameter al obiectului Command.
Se pot defini tipurile de data si valorile parametrilor.

Observatie :
Se poate renunta la cereri parametrizate folosind formatarea comenzilor inainte de
executie. Reluand acelasi exemplu de mai sus si presupunand ca valoarea pentru
CustomerID o avem memorata in variabila locala (de tip int) idc, atunci fraza sql poate fi
construita astfel

string stmt = SELECT CustomerID, CompanyName, CompanyName, Phone FROM
Customers WHERE CustomerID = + idc;

Obiectul DataAdapter

Obiectele DataAdapter actioneaza ca un pod intre baza de date si obiectele deconectate.
Metoda Fill din DataAdapter va plasa rezultatele cererii intr-un DataSet sau
DataTable, pasata ca parametru in aceasta metoda.
Modificarile facute intr-un DataSet pot fi transferate in baza de date prin diverse
mecanisme puse la dispozitie de DataAdapter.

DataAdapter este folosit impreuna cu Connection si Command pentru a mari
performanta cannd ne conectam la un server de baze de date.
Dezvoltare aplicatii folosind platforma .NET - 219/233
FbDataAdapter include proprietatile SelectCommand, InsertCommand,
DeleteCommand, UpdateCommand, si TableMappings pentru a facilita incarcarea si
actualizarea datelor.
Exemplu
public static void CreateFbDataAdapter()
{
FbConnection conn = new FbConnection("Database=C:\\PROGRAM
FILES\\FIREBIRD\\EXAMPLES\\EMPLOYEE.GDB;User=SYSDBA;Password=masterkey;
Dialect=3;Server=localhost");
FbDataAdapter custDA = new FbDataAdapter();
FbTransaction txn = conn.BeginTransaction();

custDA.MissingSchemaAction = MissingSchemaAction.AddWithKey;

custDA.SelectCommand = new FbCommand("SELECT custno, customer FROM
CUSTOMER", conn, txn);
custDA.InsertCommand = new FbCommand("INSERT INTO customer
(CustomerID, customer) " + "VALUES (?, ?)", conn, txn);
custDA.UpdateCommand = new FbCommand("UPDATE customer SET custno =
?, customer = ? " + "WHERE custno = ?", conn, txn);
custDA.DeleteCommand = new FbCommand("DELETE FROM customer WHERE
custno = ?", conn, txn);

custDA.InsertCommand.Parameters.Add("@custno", FbDbType.Int32, 4,
"custno");
custDA.InsertCommand.Parameters.Add("@customer", FbDbType.VarChar,
25, "customer");

custDA.UpdateCommand.Parameters.Add("@custno", FbDbType.Int32, 4,
"custno");
custDA.UpdateCommand.Parameters.Add("@customer", FbDbType.VarChar,
25, "customer");
custDA.UpdateCommand.Parameters.Add("@oldcustno", FbDbType.Int32,
4, "custno").SourceVersion = DataRowVersion.Original;

custDA.DeleteCommand.Parameters.Add("@oldcustno", FbDbType.Int32,
4, "custno").SourceVersion = DataRowVersion.Original;
}

Vezi colectiile TableMappings, ColumnMappings.

Obiecte deconectate

Obiectul DataTable

DataTable Class
Represents one table of in-memory data.
For a list of all members of this type, see DataTable Members.
System.Object
System.ComponentModel.MarshalByValueComponent
System.Data.DataTable
[C#]
[Serializable]
public class DataTable : MarshalByValueComponent, IListSource,
Dezvoltare aplicatii folosind platforma .NET - 220/233
ISupportInitialize, ISerializable
Remarks
The DataTable is a central object in the ADO.NET library. Other objects that use the
DataTable include the DataSet and the DataView.
When accessing DataTable objects, note that they are conditionally case-sensitive.
For example, if one DataTable is named "mydatatable" and another is named
"Mydatatable", a string used to search for one of the tables is regarded as case-
sensitive. However, if "mydatatable" exists and "Mydatatable" does not, the search
string is regarded as case-insensitive. For more information about working with
DataTable objects, see Creating a DataTable.
If you are creating a DataTable programmatically, you must first define its schema
by adding DataColumn objects to the DataColumnCollection (accessed through the
Columns property). For more information about adding DataColumn objects, see
Adding Columns to a Table.
To add rows to a DataTable, you must first use the NewRow method to return a
new DataRow object. The NewRow method returns a row with the schema of the
DataTable, as it is defined by the table's DataColumnCollection. The maximum
number of rows that a DataTable can store is 16,777,216. For more information,
see Adding Data to a Table.
The schema of a table is defined by the DataColumnCollection, the collection of
DataColumn objects. The DataColumnCollection is accessed through the
Columns property. See DataColumn and DataColumnCollection for more
information about defining a schema for the table.
The DataTable contains a collection of Constraint objects that can be used to ensure
the integrity of the data. For more information, see Adding Constraints to a Table.
To determine when changes are made to a table, use one of the following events:
RowChanged, RowChanging, RowDeleting, and RowDeleted. For more information,
see Working with DataTable Events.
When an instance of DataTable is created, some of the read/write properties are set
to initial values. For a list of these values, see the DataTable constructor.

Note
The DataSet and DataTable objects inherit from MarshalByValueComponent, and
support the ISerializable interface for remoting. These are the only ADO.NET objects
that can be remoted.
Example
[Visual Basic, C#, C++] The following example creates two DataTable objects, one
DataRelation object, and adds the new objects to a DataSet. The tables are then
displayed in a DataGrid control by invoking the DataGrid.SetDataBinding method.
[C#]
// Put the next line into the Declarations section.
private System.Data.DataSet myDataSet;

private void MakeDataTables()
{
// Run all of the functions.
MakeParentTable();
MakeChildTable();
MakeDataRelation();
BindToDataGrid();
}

Dezvoltare aplicatii folosind platforma .NET - 221/233
private void MakeParentTable(){
// Create a new DataTable.
System.Data.DataTable myDataTable = new DataTable("ParentTable");
// Declare variables for DataColumn and DataRow objects.
DataColumn myDataColumn;
DataRow myDataRow;

// Create new DataColumn, set DataType, ColumnName and add to DataTable.
myDataColumn = new DataColumn();
myDataColumn.DataType = System.Type.GetType("System.Int32");
myDataColumn.ColumnName = "id";
myDataColumn.ReadOnly = true;
myDataColumn.Unique = true;
// Add the Column to the DataColumnCollection.
myDataTable.Columns.Add(myDataColumn);

// Create second column.
myDataColumn = new DataColumn();
myDataColumn.DataType = System.Type.GetType("System.String");
myDataColumn.ColumnName = "ParentItem";
myDataColumn.AutoIncrement = false;
myDataColumn.Caption = "ParentItem";
myDataColumn.ReadOnly = false;
myDataColumn.Unique = false;
// Add the column to the table.
myDataTable.Columns.Add(myDataColumn);

// Make the ID column the primary key column.
DataColumn[] PrimaryKeyColumns = new DataColumn[1];
PrimaryKeyColumns[0] = myDataTable.Columns["id"];
myDataTable.PrimaryKey = PrimaryKeyColumns;

// Instantiate the DataSet variable.
myDataSet = new DataSet();
// Add the new DataTable to the DataSet.
myDataSet.Tables.Add(myDataTable);

// Create three new DataRow objects and add them to the DataTable
for (int i = 0; i<= 2; i++){
myDataRow = myDataTable.NewRow();
myDataRow["id"] = i;
myDataRow["ParentItem"] = "ParentItem " + i;
myDataTable.Rows.Add(myDataRow);
}
}

private void MakeChildTable(){
// Create a new DataTable.
DataTable myDataTable = new DataTable("childTable");
DataColumn myDataColumn;
DataRow myDataRow;

// Create first column and add to the DataTable.
Dezvoltare aplicatii folosind platforma .NET - 222/233
myDataColumn = new DataColumn();
myDataColumn.DataType= System.Type.GetType("System.Int32");
myDataColumn.ColumnName = "ChildID";
myDataColumn.AutoIncrement = true;
myDataColumn.Caption = "ID";
myDataColumn.ReadOnly = true;
myDataColumn.Unique = true;
// Add the column to the DataColumnCollection.
myDataTable.Columns.Add(myDataColumn);

// Create second column.
myDataColumn = new DataColumn();
myDataColumn.DataType= System.Type.GetType("System.String");
myDataColumn.ColumnName = "ChildItem";
myDataColumn.AutoIncrement = false;
myDataColumn.Caption = "ChildItem";
myDataColumn.ReadOnly = false;
myDataColumn.Unique = false;
myDataTable.Columns.Add(myDataColumn);

// Create third column.
myDataColumn = new DataColumn();
myDataColumn.DataType= System.Type.GetType("System.Int32");
myDataColumn.ColumnName = "ParentID";
myDataColumn.AutoIncrement = false;
myDataColumn.Caption = "ParentID";
myDataColumn.ReadOnly = false;
myDataColumn.Unique = false;
myDataTable.Columns.Add(myDataColumn);

myDataSet.Tables.Add(myDataTable);
// Create three sets of DataRow objects, five rows each, and add to DataTable.
for(int i = 0; i <= 4; i ++){
myDataRow = myDataTable.NewRow();
myDataRow["childID"] = i;
myDataRow["ChildItem"] = "Item " + i;
myDataRow["ParentID"] = 0 ;
myDataTable.Rows.Add(myDataRow);
}
for(int i = 0; i <= 4; i ++){
myDataRow = myDataTable.NewRow();
myDataRow["childID"] = i + 5;
myDataRow["ChildItem"] = "Item " + i;
myDataRow["ParentID"] = 1 ;
myDataTable.Rows.Add(myDataRow);
}
for(int i = 0; i <= 4; i ++){
myDataRow = myDataTable.NewRow();
myDataRow["childID"] = i + 10;
myDataRow["ChildItem"] = "Item " + i;
myDataRow["ParentID"] = 2 ;
myDataTable.Rows.Add(myDataRow);
}
Dezvoltare aplicatii folosind platforma .NET - 223/233
}

private void MakeDataRelation(){
// DataRelation requires two DataColumn (parent and child) and a name.
DataRelation myDataRelation;
DataColumn parentColumn;
DataColumn childColumn;
parentColumn = myDataSet.Tables["ParentTable"].Columns["id"];
childColumn = myDataSet.Tables["ChildTable"].Columns["ParentID"];
myDataRelation = new DataRelation("parent2Child", parentColumn, childColumn);
myDataSet.Tables["ChildTable"].ParentRelations.Add(myDataRelation);
}

private void BindToDataGrid(){
// Instruct the DataGrid to bind to the DataSet, with the
// ParentTable as the topmost DataTable.
dataGrid1.SetDataBinding(myDataSet,"ParentTable");
}

string strSQL = "SELECT CustomerID, CompanyName FROM Customers";
string strConn = "Provider=SQLOLEDB;Data Source=(local);..."
OleDbDataAdapter daCustomers = new OleDbDataAdapter(strSQL, strConn);
DataTable tblCustomers = new DataTable();
daCustomers.Fill(tblCustomers);

Fiecare DataTable are o colectie de Columns ce contine obiecte DataColumn. Un obiect
DataColumn reprezinta o coloana in obiectul DataTable, dar nu contine date ci numai
structura coloanei. DataColumn expune proprietatea Expression, ce ne permite sa
definim campuri calculate.

DataTable Members
DataTable overview
Public Constructors
DataTable Constructor
Supported by the .NET Compact
Framework.
Overloaded. Initializes a new instance of
the DataTable class.
Public Properties
CaseSensitive
Supported by the .NET Compact
Framework.
Indicates whether string comparisons
within the table are case-sensitive.
ChildRelations
Supported by the .NET Compact
Framework.
Gets the collection of child relations for
this DataTable.
Columns
Supported by the .NET Compact
Framework.
Gets the collection of columns that
belong to this table.
Constraints
Supported by the .NET Compact
Gets the collection of constraints
maintained by this table.
Dezvoltare aplicatii folosind platforma .NET - 224/233
Framework.
Container (inherited from
MarshalByValueComponent)
Gets the container for the component.
DataSet
Supported by the .NET Compact
Framework.
Gets the DataSet that this table belongs
to.
DefaultView
Supported by the .NET Compact
Framework.
Gets a customized view of the table
which may include a filtered view, or a
cursor position.
DesignMode (inherited from
MarshalByValueComponent)
Gets a value indicating whether the
component is currently in design mode.
DisplayExpression
Supported by the .NET Compact
Framework.
Gets or sets the expression that will
return a value used to represent this
table in the user interface.
ExtendedProperties
Supported by the .NET Compact
Framework.
Gets the collection of customized user
information.
HasErrors
Supported by the .NET Compact
Framework.
Gets a value indicating whether there are
errors in any of the rows in any of the
tables of the DataSet to which the table
belongs.
Locale
Supported by the .NET Compact
Framework.
Gets or sets the locale information used
to compare strings within the table.
MinimumCapacity
Supported by the .NET Compact
Framework.
Gets or sets the initial starting size for
this table.
Namespace
Supported by the .NET Compact
Framework.
Gets or sets the namespace for the XML
represenation of the data stored in the
DataTable.
ParentRelations
Supported by the .NET Compact
Framework.
Gets the collection of parent relations for
this DataTable.
Prefix
Supported by the .NET Compact
Framework.
Gets or sets the namespace for the XML
represenation of the data stored in the
DataTable.
PrimaryKey
Supported by the .NET Compact
Framework.
Gets or sets an array of columns that
function as primary keys for the data
table.
Rows
Supported by the .NET Compact
Framework.
Gets the collection of rows that belong to
this table.
Dezvoltare aplicatii folosind platforma .NET - 225/233
Site
Overridden. Gets or sets an
System.ComponentModel.ISite for the
DataTable.
TableName
Supported by the .NET Compact
Framework.
Gets or sets the name of the DataTable.
Public Methods
AcceptChanges
Supported by the .NET Compact
Framework.
Commits all the changes made to this
table since the last time AcceptChanges
was called.
BeginInit
Supported by the .NET Compact
Framework.
Begins the initialization of a DataTable
that is used on a form or used by another
component. The initialization occurs at
runtime.
BeginLoadData
Supported by the .NET Compact
Framework.
Turns off notifications, index
maintenance, and constraints while
loading data.
Clear
Supported by the .NET Compact
Framework.
Clears the DataTable of all data.
Clone
Supported by the .NET Compact
Framework.
Clones the structure of the DataTable,
including all DataTable schemas and
constraints.
Compute
Supported by the .NET Compact
Framework.
Computes the given expression on the
current rows that pass the filter criteria.
Copy
Copies both the structure and data for
this DataTable.
Dispose (inherited from
MarshalByValueComponent)
Overloaded. Releases the resources used
by the MarshalByValueComponent.
EndInit
Supported by the .NET Compact
Framework.
Ends the initialization of a DataTable
that is used on a form or used by another
component. The initialization occurs at
runtime.
EndLoadData
Supported by the .NET Compact
Framework.
Turns on notifications, index
maintenance, and constraints after
loading data.
Equals (inherited from Object)
Supported by the .NET Compact
Framework.
Overloaded. Determines whether two
Object instances are equal.
GetChanges
Overloaded. Gets a copy of the
DataTable containing all changes made
to it since it was last loaded, or since
Dezvoltare aplicatii folosind platforma .NET - 226/233
AcceptChanges was called.
GetErrors
Supported by the .NET Compact
Framework.
Gets an array of DataRow objects that
contain errors.
GetHashCode (inherited from Object)
Supported by the .NET Compact
Framework.
Serves as a hash function for a particular
type, suitable for use in hashing
algorithms and data structures like a
hash table.
GetService (inherited from
MarshalByValueComponent)
Gets the implementer of the
IServiceProvider.
GetType (inherited from Object)
Supported by the .NET Compact
Framework.
Gets the Type of the current instance.
ImportRow
Supported by the .NET Compact
Framework.
Copies a DataRow into a DataTable,
preserving any property settings, as well
as original and current values.
LoadDataRow
Supported by the .NET Compact
Framework.
Finds and updates a specific row. If no
matching row is found, a new row is
created using the given values.
NewRow
Supported by the .NET Compact
Framework.
Creates a new DataRow with the same
schema as the table.
RejectChanges
Supported by the .NET Compact
Framework.
Rolls back all changes that have been
made to the table since it was loaded, or
the last time AcceptChanges was called.
Reset
Supported by the .NET Compact
Framework.
Resets the DataTable to its original
state.
Select
Supported by the .NET Compact
Framework.
Overloaded. Gets an array of DataRow
objects.
ToString
Supported by the .NET Compact
Framework.
Overridden. Gets the TableName and
DisplayExpression, if there is one as a
concatenated string.
Public Events
ColumnChanged Occurs after a value has been changed
for the specified DataColumn in a
DataRow.
ColumnChanging Occurs when a value is being changed for
the specified DataColumn in a DataRow.
Disposed (inherited from
MarshalByValueComponent)
Adds an event handler to listen to the
Disposed event on the component.
Dezvoltare aplicatii folosind platforma .NET - 227/233
RowChanged Occurs after a DataRow has been
changed successfully.
RowChanging Occurs when a DataRow is changing.
RowDeleted Occurs after a row in the table has been
deleted.
RowDeleting Occurs before a row in the table is about
to be deleted.
Protected Constructors
DataTable Constructor
Overloaded. Initializes a new instance of
the DataTable class.
Protected Properties
Events (inherited from
MarshalByValueComponent)
Gets the list of event handlers that are
attached to this component.
Protected Methods
CreateInstance
Creates a new instance of the DataTable
class.
Dispose (inherited from
MarshalByValueComponent)
Overloaded. Releases the resources used
by the MarshalByValueComponent.
Finalize (inherited from Object)
Supported by the .NET Compact
Framework.
Overridden. Allows an Object to attempt
to free resources and perform other
cleanup operations before the Object is
reclaimed by garbage collection.
In C# and C++, finalizers are expressed
using destructor syntax.
MemberwiseClone (inherited from
Object)
Supported by the .NET Compact
Framework.
Creates a shallow copy of the current
Object.
NewRowFromBuilder
Supported by the .NET Compact
Framework.
Creates a new row from an existing row.
OnColumnChanged
Supported by the .NET Compact
Framework.
Raises the ColumnChanged event.
OnColumnChanging
Supported by the .NET Compact
Framework.
Raises the ColumnChanging event.
OnRemoveColumn
Notifies the DataTable that a
DataColumn is being removed.
OnRowChanged
Supported by the .NET Compact
Framework.
Raises the RowChanged event.
Dezvoltare aplicatii folosind platforma .NET - 228/233
OnRowChanging
Supported by the .NET Compact
Framework.
Raises the RowChanging event.
OnRowDeleted
Supported by the .NET Compact
Framework.
Raises the RowDeleted event.
OnRowDeleting
Supported by the .NET Compact
Framework.
Raises the RowDeleting event.

Obiectul DataRow

Pentru a accesa valorile memorate intr-un obiect DataTable va trebui sa folosim obiecte
Row ce contin o colectie de obiecte DataRow.
Examinarea datei dintr-o coloana specifica a unei inregistrari se face folosind proprietatea
Item a obiectului DataRow.

string strSQL, strConn;
...
OleDbDataAdapter da = new OleDbDataAdapter(strSQL, strConn);
DataTable tbl = new DataTable();
da.Fill(tbl);
foreach (DataRow row in tbl.Rows)
Console.WriteLine(row[0]);

Vezi BeginEdit(), EndEdit(), CancelEdit().

DataSet Class
Reprezinta un cache in memorie al datelor aduse dintr-o sursa de date.
System.Object
System.ComponentModel.MarshalByValueComponent
System.Data.DataSet
[C#]
[Serializable]
public class DataSet : MarshalByValueComponent, IListSource,
ISupportInitialize, ISerializable
Thread Safety
Este sigur la operatiile de citire. Operatiile de scriere trebuiesc sincronizate.

Observatii
DataSet consta dintr-o colectie de obiecte DataTable pe care le putem pune in
relatie folosind obiecte DataRelation.
Putem folosi de asemenea obiecte UniqueConstraint sau ForeignKeyConstraint.

Colectia DataRelationCollection ne permite sa navigam prin ierarhia de tabele.
Tabelele sunt continute in DataTable Collection accesata prin proprietatea Tables.

Un DataSet poate citi si scrie date si schema de inregistrare (structura) ca
documente XML. Datele si schema pot fi transportate prin retea folosind HTTP si apoi
folosite de orice aplicatie ce poate lucra cu XML.
Dezvoltare aplicatii folosind platforma .NET - 229/233
Schema poate fi salvata cu metoda WriteXmlSchema, iar cu ajutorul metodei
WriteXml putem salva si date si schema.
Pentru a citi un document XML ce include si schema si date vom folosi metoda
ReadXml.


Etapele pentru crearea, reimprospatarea si actualizarea unui DataSet sunt:
1. Construim si completam fiecare DataTable din DataSet cu date dintr-o sursa
de date folosind DataAdapter.
2. Schimbam datele din obiectele individuale DataTable prin adaugarea,
actualizarea sau stergerea de obiecte DataRow.
3. Invocam metoda GetChanges pentru a crea un al doilea DataSet ce pastreaza
numai datele modificate.
4. Apelam metoda Update din DataAdapter, pasind ca parametru cel de-al doilea
DataSet.
5. Apelam metoda Merge pentru a salava modificarile din cel de-al doilea
dataSet in primul DataSet.
6. Apelam metoda AcceptChanges pe primul DataSet sau daca vrem sa
renuntam apelam metoda RejectChanges.

Observatie
Obiectele DataSet si DataTable sunt mostenite din MarshalByValueComponent si
suporta interfata ISerializable pentru a permite remoting.
Acestea sunt singurele obiecte ADO .NET ce suporta remoting.


Example
[Visual Basic, C#, C++] The following example consists of several methods that, combined, create and fill a
DataSet from the Northwind database installed as a sample database with SQLServer 7.0.

[C#]
using System;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Windows.Forms;

public class DataGridSample:Form
{
DataSet ds;
DataGrid myGrid;

static void Main()
{
Application.Run(new DataGridSample());
}

public DataGridSample(){
InitializeComponent();
}

void InitializeComponent(){
this.ClientSize = new System.Drawing.Size(550, 450);
myGrid = new DataGrid();
myGrid.Location = new Point (10,10);
myGrid.Size = new Size(500, 400);
Dezvoltare aplicatii folosind platforma .NET - 230/233
myGrid.CaptionText = "Microsoft .NET DataGrid";
this.Text = "C# Grid Example";
this.Controls.Add(myGrid);
ConnectToData();
myGrid.SetDataBinding(ds, "Suppliers");
}
void ConnectToData(){
// Create the ConnectionString and create a SqlConnection.
// Change the data source value to the name of your computer.

string cString = "Persist Security Info=False;Integrated
Security=SSPI;database=northwind;server=mySQLServer";
SqlConnection myConnection = new SqlConnection(cString);
// Create a SqlDataAdapter.
SqlDataAdapter myAdapter = new SqlDataAdapter();
myAdapter.TableMappings.Add("Table", "Suppliers");
myConnection.Open();
SqlCommand myCommand = new SqlCommand("SELECT * FROM Suppliers",
myConnection);
myCommand.CommandType = CommandType.Text;

myAdapter.SelectCommand = myCommand;
Console.WriteLine("The connection is open");
ds = new DataSet("Customers");
myAdapter.Fill(ds);
// Create a second Adapter and Command.
SqlDataAdapter adpProducts = new SqlDataAdapter();
adpProducts.TableMappings.Add("Table", "Products");
SqlCommand cmdProducts = new SqlCommand("SELECT * FROM Products",
myConnection);
adpProducts.SelectCommand = cmdProducts;
adpProducts.Fill(ds);
myConnection.Close();
Console.WriteLine("The connection is closed.");
System.Data.DataRelation dr;
System.Data.DataColumn dc1;
System.Data.DataColumn dc2;
// Get the parent and child columns of the two tables.
dc1 = ds.Tables["Suppliers"].Columns["SupplierID"];
dc2 = ds.Tables["Products"].Columns["SupplierID"];
dr = new System.Data.DataRelation("suppliers2products", dc1,
dc2);
ds.Relations.Add(dr);
}
}


Dezvoltare aplicatii folosind platforma .NET - 231/233
Exemple

Citire inregistrari
Metoda inceata

string strConn, strSQL;
strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" +
"Initial Catalog=Northwind;Trusted_Connection=Yes;";
OleDbConnection cn = new OleDbConnection(strConn);
cn.Open();
strSQL = "SELECT CustomerID, CompanyName FROM Customers";
OleDbCommand cmd = new OleDbCommand(strSQL, cn);
OleDbDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
Console.WriteLine(rdr["CustomerID"] + " " + rdr["CompanyName"]);
rdr.Close();

Metoda mai rapida

...
OleDbDataReader rdr = cmd.ExecuteReader();
int intCustomerIDOrdinal = rdr.GetOrdinal("CustomerID");
int intCompanyNameOrdinal = rdr.GetOrdinal("CompanyName");
while (rdr.Read())
Console.WriteLine(rdr[intCustomerIDOrdinal] + " " +
rdr[intCompanyNameOrdinal]);
rdr.Close();

Metoda si mai rapida

...
OleDbDataReader rdr = cmd.ExecuteReader();
int intCustomerIDOrdinal = rdr.GetOrdinal("CustomerID");
int intCompanyNameOrdinal = rdr.GetOrdinal("CompanyName");
while (rdr.Read())
Console.WriteLine(rdr.GetString(intCustomerIDOrdinal) + " " +
rdr.GetString(intCompanyNameOrdinal));
rdr.Close();

DataReader trebuie inchis cat mai rapid.

string strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" +
"Initial Catalog=Northwind;Trusted_Connection=Yes;";
OleDbConnection cn = new OleDbConnection(strConn);
cn.Open();
OleDbCommand cmd = cn.CreateCommand();
cmd.CommandText = "SELECT COUNT(*) FROM Customers";
int intCustomers = Convert.ToInt32(cmd.ExecuteScalar());
cmd.CommandText = "SELECT CompanyName FROM Customers " +
"WHERE CustomerID = 'ALFKI'";
string strCompanyName = Convert.ToString(cmd.ExecuteScalar());

Dezvoltare aplicatii folosind platforma .NET - 232/233
Cerere parametrizata

string strConn, strSQL;
strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" +
"Initial Catalog=Northwind;Trusted_Connection=Yes;";
OleDbConnection cn = new OleDbConnection(strConn);
cn.Open();
strSQL = "SELECT OrderID, CustomerID, EmployeeID, OrderDate " +
"FROM Orders WHERE CustomerID = ?";
OleDbCommand cmd = new OleDbCommand(strSQL, cn);
cmd.Parameters.Add("@CustomerID", OleDbType.WChar, 5);
cmd.Parameters[0].Value = "ALFKI";
OleDbDataReader rdr = cmd.ExecuteReader();

...
OleDbConnection cn = new OleDbConnection(strConn);
cn.Open();
OleDbCommand cmd = cn.CreateCommand();
cmd.CommandText = "GetCustomer";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@CustomerID", OleDbType.WChar, 5);
cmd.Parameters[0].Value = "ALFKI";
OleDbDataReader rdr = cmd.ExecuteReader();
if (rdr.Read())
Console.WriteLine(rdr["CompanyName"]);
else
Console.WriteLine("No customer found");
rdr.Close();
cn.Close();

Folosirea tranzactiilor

cn.Open();
OleDbTransaction txn = cn.BeginTransaction();
string strSQL = "INSERT INTO Customers (...) VALUES (...)";
OleDbCommand cmd = new OleDbCommand(strSQL, cn, txn);
int intRecordsAffected = cmd.ExecuteNonQuery();
if (intRecordsAffected == 1)
{
Console.WriteLine("Update succeeded");
txn.Commit();
}
else
{
//Assume intRecordsAffected = 0
Console.WriteLine("Update failed");
txn.Rollback();
}
DataAdapter

OleDbDataAdapter da;
//Initialize DataAdapter.
Dezvoltare aplicatii folosind platforma .NET - 233/233
DataTableMapping TblMap;
DataColumnMapping ColMap;
TblMap = da.TableMappings.Add("Table", "Employees");
ColMap = TblMap.ColumnMappings.Add("EmpID", "EmployeeID");
ColMap = TblMap.ColumnMappings.Add("LName", "LastName");
ColMap = TblMap.ColumnMappings.Add("FName", "FirstName");


Bibliografie

1. J. Richter - Applied Microsoft .Net Framework Programming
2. Ch. Petzold Programming with Windows Forms
3. T. Archer Inside C#, Second Edition
4. MSDN
5. Site codeguru, codeproject, ondotnet

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