}}
De exemplu,daca doriti ca obiectul de tip Panel1 sa nu emita mesaje
la fiecare click de mouse,se va exclude mesajul WM_MOUSECLICK din lista
obiectului ( cu remove { handler -= WM_MOUSECLICK } ).
Se observa ca EventHandler functioneaza in obiectul sender la fel ca
o proprietate,dar in loc de Set() si Get(),accepta metodele Add() si
respectiv Remove().
Cu un astfel de mecanism,mesajele inutile nu numai ca nu vor mai fi
interpretate,dar nici macar nu vor mai fi emise,simplificand foarte mult
munca procesorului (se elibereaza memorie,pentru sute de pointeri sau
pentru alte operatii).
Nu este esential sa intelegeti mecanismul de lucru pentru evenimente,
decat daca proiectati programe profesionale.Platforma Visual C# ofera o
interfata extrem de facila,in care este suficient sa executati un click
pe evenimentul dorit si apoi sa completati codul pentru metoda de tratare
a evenimentului.Nu este bine sa atribuiti prea multe operatii unui singur
eveniment.Daca este esential,impartiti codul in mai multe functii de
raspuns,sau intre mai multe evenimente apropiate (butoane diferite).
-32-
Un tip special de metode,sunt cele ce pot fi utilizate in expresii,
pentru a executa diferite operatii.Acest gen de metode poarta si numele
de "operatori".Se pot defini astfel de clase,atunci cand lucrati frecvent
cu un anumit gen de operatii,pentru care nu exista un operator standard.
De exemplu,daca doriti sa transformati un numar analog,intr-un numar
digital,puteti scrie o clasa care executa conversia,salvati fila sub forma
de biblioteca DLL si apoi utilizati clasa pe post de operator.
EXEMPLU:
using System;
using System.Collections;
namespace NumarDigital {
public class Digital {
public static void Digit1(String Operand){
IEnumarator OperandEnum = Operand.GetEnumarator();
int CharCount = 0;
Console.WriteLine("Numarul in format digital este: ");
while (OperandEnum.MoveNext()){
CharCount++;
Console.Write(" '{0}' ",OperandEnum.Current);
}
Console.ReadLine();
}
}}
Salvati fila cu numele Digit1.cs.Pentru a crea fila DLL editati comanda:
csc /target:library /out:NumarDigital.DLL Digit1.cs
Apoi puteti exploata resursa cu o fila de genul:
using System;
using NumarDigital;
class telefon1 {
public static void Main(){
Console.WriteLine("Introduceti numarul de telefon (10 cifre): ");
string nr1 = Console.ReadLine();
Digital.Digit1(nr1);
}}
Salvati fila cu numele Digital1.cs.Pentru executabil editati comanda:
csc /out:Digital1.exe /reference:NumarDigital.DLL Digital1.cs
Exercitiul de mai sus este foarte simplist.Practic separa sirul in
caractere si afiseaza numarul sub forma de cifre izolate.Pentru a completa
programul,ar trebui ca numarul sa fie separat in digiti,in format numeric
de tip byte,ce vor fi arhivati intr-o arie.Apoi metoda returneaza aria,de
unde pot fi preluati si utilizati (de exemplu pentru a apela un numar de
telefon).Pentru exercitiu,puteti incerca sa realizati un astfel de
program complet,ce preia numarul sub forma de string si returneaza digiti
in format byte.
Cu acelasi tip de solutie,puteti sa definiti orice alt tip de operator
personalizat.Exemplu: executa automat calculul taxelor si impozitelor
pentru o anumita suma.
O singura clasa,poate contine mai multe astfel de metode,fiecare
dintre ele reprezentand un operator diferit.Acest mecanism,nu numai ca
va poate simplifica foarte mult viata,dar confera si un grad foarte mare
de siguranta (nu oricine poate apela biblioteca DLL personalizata).
-33-
Clasele C# permit supraincarcarea constructorilor cu conditia sa existe
o diferenta intre cele doua definitii: numarul de parametri,tipul lor,
modificatorii,domeniul de vizibilitate etc.Prin acest mecanism,este
posibil ca o clasa sa contina mai multi constructori diferiti,ce pot fi
apelati in functie de contextul programului.Fiecare astfel de constructor
va crea o instanta diferita a clasei respective (obiectele create vor fi
diferite,in functie de constructorul apelat).
EXEMPLU:
using System
class Point {
public double x,y;
public Point(){ this.x=0; this.y=0; }
public Point(double x double y){
this.x = x; this.y = y; }
}
class Test {
static void Main() {
Point a = new Point();
Console.WriteLine("a.x= {0}",a.x);
Point b = new Point(7,3);
Console.WriteLine("b.x= (0}",b.x);
Console.ReadLine();
}}
Salvati fila cu numele Constructori.cs si compilati.
Observati ca in clasa Point sunt definiti doi constructori distincti.
Primul nu are parametri,in timp ce cel de al doilea accepta doi parametri
de tip double.Daca se apeleaza primul constructor,variabila x va fi
initializata cu valoarea 0 (cea implicita),iar daca se apeleaza cel de
al doilea constructor,variabiala x va fi initializata cu valoarea din
apel (primul parametru).Acest mecanism este destul de frecvent si pentru
multe dintre clasele standardizate.Exista un constructor "default" in
care obiectul creat este initializat cu valori "default" si unul sau mai
multi constructori ce permit personalizarea obiectului in functie de
necesitatile de moment ale programatorului.
Destructorul se semanlizeaza cu o tilda:
EXEMPLU: using System;
class Point{ public double x,y;
public Point(double x double y){ this.x=x; this.y=y; }
~Point(){ Console.WriteLine("A fost apelat constructorul!");}
class Test{ static void Main() {
Point b = new Point(7,3);
Console.WriteLine("b.x= {0}",b.x);
Console.ReadLine();
}}
Salvati fila cu numele Destructor.cs si compilati.
Destructorul este apelat intotdeauna inainte de a elibera complet
obiectul din memorie,inclusiv de catre "garbage collector".Pentru a
putea observa apelul destructorului,lansati executabilul Destructor.exe
din Command Prompt cu comanda: Destructor.exe si apoi tastati Enter
ATENTIE: destructorul nu elibereaza memoria,ci doar executa un set
oarecare de operatii,inainte de eliberarea automata a memoriei.
-34-
Limbajul C# introduce si o inovatie: constructorul static,ce poate fi
apelat fara a construi o instanta a clasei respective.O astfel de clasa
se va utiliza in programe,pentru a putea initializa seturi intregi de
variabile,cu valori "default",fara sa se incarce memoria si cu un obiect
sursa.
EXEMPLU:
using System
class Text {
public static string s;
static Text() { s = "Text default"; }
}
class Test{
static void Main() {
Console.WriteLine(Text.s);
Console.ReadLine();
}}
Salvati fila cu numele Constructori2.cs si compilati.
Clasa Text contine un constructor static.Daca se va salva aceasta
clasa sub forma de biblioteca DLL,atunci membrul sau static s,va putea
fi apelat direct,fara a mai construi un obiect de tip s.
Daca intr-o clasa toate metodele si proprietatile sunt de tip static,
se poate crea o clasa intreaga de tip static.
EXEMPLU: static class StaticUtilities {
public static void HelperMethod() { ... }
}
Intr-o astfel de clasa,toti membrii pot fi apelati direct,fara sa mai
fie necesara crearea unui obiect din clasa respectiva.
EXEMPLU: StaticUtilities.HelperMethod(); // este un apel valid.
Clasele pot mosteni o alta clasa,la fel ca si in C++.
EXEMPLU:
using System;
class A {
public void F() { Console.WriteLine("Metoda F din clasa A");}
}
class B : A {
public void G() { Console.WriteLine("Metoda G din clasa B");}
}
class Test {
static void Main() {
B obiect1 = new B();
obiect1.F(); // mostenita din A
obiect1.G();
A obiect2 = obiect1; //primeste metoda F() din obiect1
obiect2.F();
Console.ReadLine();
}}
Salvati fila cu numele Mostenire.cs si compilati.
In exemplul de mai sus,observati ca obiect1 de tip B mosteneste clasa
A,in timp ce obiect2 de tip A nu primeste decat metoda F() din clasa A,
chiar daca o primeste prin intermediul obiectului obiect1.Daca se incearca
apelul obiect2.G() se returneaza o eroare (metoda nedefinita).
-35-
Exista si situatii cand o clasa poate fi definita in mai multe file
sursa separate.Acest gen de situatii poarta numele de clase partiale si
se intanesc fie atunci cand definitia clasei este prea stufoasa,fie cand
o clasa complexa este creata automat,cu date preluate de la mai multi
clienti din retea.In toate aceste situatii,se va utiliza cuvantul cheie
"partial" pentru a semnala faptul ca exista si definitii declarate in alta
fila sursa.
EXEMPLU: // Fila1.cs contine:
partial class ClasaMea {
private int[] counts;
public string ToString() { ....} }
// Fila2.cs contine:
partial class ClasaMea{
private int value;
private void Helper() { ......} }
Cand se vor compila cele doua file,se va construi o singura clasa in care
sunt incluse toate definitiile din cele doua file surse.Pentru un exemplu
complet,vezi constructia bibliotecii DLL "FunctiileMele" din capitolul
Compilator.Acest mecansim este valabil pentru orice tip de data,nu doar
pentru clase.Principalul avanataj al acestui mecanism se poate observa
atunci cand se actualizeaza o biblioteca DLL,sau o fila sursa.In loc sa
fie rescrise toate filele sursa,se adauga doar fila auxiliara ce contine
codurile pentru actualizare.
STRUCTURI
Structurile sunt un fel de strabunice ale claselor.De fapt,tipul class
a derivat progresiv din tipul struct,pana cand s-a ajuns la forma actuala.
Deosebirea fundamentala consta in faptul ca structurile sunt date de tip
valoare (sunt obiecte reale) in timp ce clasele sunt date de tip referinta
(sunt obiecte virtuale).Si structurile pot accepta membri si metode,
inclusiv constructori si destructori,dar nu includ si mecanismul de
mostenire,specific claselor.Tipul struct este pastrat ca tip de data,nu
doar pentru compatibilitatea cu programele mai vechi,ci si pentru faptul
ca ofera o solutie mult mai economica,pentru numaroase situatii de
programare.Declararea unei clase si apoi construirea unui obiect din clasa
respectiva,nu numai ca necesita declararea unui pointer,dar ocupa un volum
cel putin dublu de memorie (o data pentru clasa,si apoi pentru obiect).
In toate situatiile in care este necesar un spatiu denumit,doar pentru a
delimita domeniul de vizibilitate al unui grup oarecare de date,este mult
mai economic sa se utilizeze tipul struct,decat tipul class.
La fel ca si clasele,structurile pot organiza grupuri mari de date,
sub forma de stiva,pot include constructori si metode pentru prelucrarea
datelor,dar cu un consum mai mic de memorie.Tipul struct este preferabil
ori de cate ori se cunoaste exact grupul de date cu care se lucreaza si
tipul de operatii ce urmeaza sa fie executate asupra lor.
Prin contrast,tipul class,este recomandabil ori de cate se lucreaza in
mod curent cu un anumit tip de date,dar este probabil ca vor exista mai
multe implementari,sau va fi nevoie de numeroase solutii personalizate.
In plus,clasele sunt esentiale,atunci cand se valorifica solutii standard,
mostenite din clasele definite in bibliotecile standard.
-36-
EXEMPLU:
using System;
using System.Collections;
class Point {
public int x,y;
public Point(int x,int y) {
this.x = x;
this.y = y; }}
class Test {
static void Main() {
long mem1,mem2,mem3;
mem1 = System.GC.GetTotalMemory(false);
Point[] points = new Point[10000];
for (int i=0;i<10000;i++)
points[i] = new Point(i,i*i);
mem2 = System.GC.GetTotalMemory(false);
mem3 = mem2-mem1;
Console.WriteLine("Memoria initiala: {0:F}",mem1);
Console.WriteLine("Memoria dupa alocare: {0:F}",mem2);
Console.WriteLine("Diferenta = {0:F}",mem3);
Console.ReadLine();
}}
Salvati fila cu numele Struct1.cs si compilati.La executie puteti
observa ca pentru a gestiona cele 10000 de puncte de tip Point,au fost
create 10000 de obiecte,cu un consum de memorie de 204256 bytes.Copiati
exemplul si inlocuiti tipul class prin struct:
using System;
using System.Collections;
struct Point {
public int x,y;
public Point(int x,int y) {
this.x = x;
this.y = y; }}
class Test {
static void Main() {
long mem1,mem2,mem3;
mem1 = System.GC.GetTotalMemory(false);
Point[] points = new Point[10000];
for (int i=0;i<10000;i++)
points[i] = new Point(i,i*i);
mem2 = System.GC.GetTotalMemory(false);
mem3 = mem2-mem1;
Console.WriteLine("Memoria initiala: {0:F}",mem1);
Console.WriteLine("Memoria dupa alocare: {0:F}",mem2);
Console.WriteLine("Diferenta = {0:F}",mem3);
Console.ReadLine();
}}
Salvati fila cu numele Struct2.cs si compilati.La executie,puteti
observa ca in acest caz,s-au consumat doar 80024 de bytes,pentru cele
10000 de puncte de tip Point,deoarece s-a construit un singur obiect
de tip arie,cu 10000 de elemente.
-37-
Exemplul de mai sus,este un exemplu tipic de solutie pentru optimizarea
codului.Nu numai ca s-au inlocuit 10000 de obiecte prin unul singur,dar
s-a renuntat si la cei 10000 de pointeri spre fiecare obiect.
Din acest motiv,tipul struct este utilizat extensiv si in bibliotecile
standard,alaturi de clase.De exemplu,biblioteca System contine si o astfel
de structura dedicata pentru operatiile cu date calendaristice,denumita
DateTime.Membrii sai,pot fi apelati la fel ca si cei ai claselor standard.
EXEMPLU:
using System;
class DataNasterii {
static void Main(){
int an,luna,zi;
Console.WriteLine("Introduceti anul nasterii: ");
an = System.Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Introduceti luna in cifre -Ex. 5 pentru luna Mai:");
luna = System.Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Introduceti ziua nasterii: ");
zi = System.Convert.ToInt32(Console.ReadLine());
DateTime datanasterii = new dateTime(an,luna,zi);
DateTime acum = DateTime.Now;
long impulsuri = acum.Ticks - datanasterii.Ticks;
TimeSpan durata = new TimeSpan(impulsuri);
Console.WriteLine("De la data nasterii,au trecut pana azi {0:f}:",acum);
Console.WriteLine(" {0:N0} impulsuri",impulsuri);
Console.WriteLine(" {0:N0} secunde ",durata.TotalSeconds);
Console.WriteLine(" {0:N0} minute",durata.TotalMinutes);
Console.WriteLine(" {0:N0} zile,{1} ore,{2} minute,{3} secunde",
durata.Days,durata.Hours,durata.Minutes,durata.Seconds);
Console.ReadLine();
}}
Salvati fila cu numele Struct3.cs si compilati.
In mod similar,puteti scrie o aplicatie ce masoara intervalul de timp
dintre doua evenimente,sau dintre doua date calendaristice.
Daca analizati putin membrii structurii DateTime,puteti observa ca
exista 11 constructori pentru tipul DateTime cu parametrii de tip int32
sau int64,ce permit formule foarte sofisticate de formatare a datelor de
tip data calendaristica.Cu maximum de economie de memorie,se pot face
operatii extrem de complexe.
Ori de cate ori oscilati intre tipul class si tipul struct,este bine
sa evaluati necesitatile viitoare si solutiile de procesare scontate,dar
sa faceti si o analiza sumara a consumului de memorie.Cu cat se va
utiliza mai putina memorie,si cu cat memoria consumata va fi eliberata
mai rapid,cu atat aplicatia va fi mai performata si va fi executata mai
usor.Pentru analiza memoriei,cel mai simplu este sa apelati la membrii
clasei GC (garbage collector) din System (vezi si Struct1.cs).Evaluati
memoria consumata in diferite puncte de executie ale programului,si apoi
alegeti solutia cea mai buna.In cazul programelor mari,este bine sa
programati si o caseta de control,in care se afiseaza in permanenta
consumul de memorie.Pastrati aceasta caseta,pana cand terminati,apoi
optimizati programul.In final,dupa epuizarea etapei de depanare si control
caseta poate fi eliminata,pentru a nu deruta utilizatorul.
-38-
INTERFETE
Interfetele sunt un contract intre programator si aplicatie.O astfel
de interfata,impune implementarea in program a tuturor datelor declarate
in interfata.Sunt un fel de clase abstracte,create special pentru a fi
mostenite.O interfata nu poate fi utilizata pentru a crea obiecte sau
functionalitati,ci doar pentru a forma un fel de sablon al aplicatiei.
Sunt utilizate mai ales in mediul vizual,unde poata numele de interfete
grafice.Au rostul de a organiza componentele unui program,intr-o maniera
fixa,cu care utilizatorul a fost deja familiarizat anterior.
EXEMPLU: In interfata se declara o fereastra si un meniu.
Toate programele in care se utilizeaza aceasta interfata vor
utiliza pentru prezentarea datelor o fereastra cu meniul respectiv.
Pentru a declara o interfata,se utilizeaza cuvantul cheie "interface",
in loc de "class".Din punct de vedere tehnic,o interfata este un fel de
clasa abstracta,ce nu poate fi utilizata pentru a construi obiecte.Intr-o
interfata,se includ doar declaratii,fara nici o solutie de implementare.
O interfata poate contine metode,proprietati si evenimente,dar nu poate
contine constructori,destructori sau operatori supraincarcati.Interfata
este prin definitie creata pentru a fi mostenita,asa ca toate datele
incluse in interfata sunt publice.Nu se pot utiliza nici un fel de
modificatori (gen virtual,static etc.).
O clasa care implementeaza o interfata,trebuie sa asigure si declaratia
completa a tuturor datelor importate din interfata.
EXEMPLU:
using System;
interface Conversie { void SetText(); }
class Test : Conversie {
static void Main() {
Test t1 = new Test();
t1.SetText();
Console.ReadLine(); }
public void SetText(){
Console.WriteLine("Se executa metoda contractata !");}
}
Salvati fila cu numele Interfata1.cs si compilati.
Se observa urmatoarele etape:
1.-se declara interfata Conversie ce contine metoda SetText()
2.-se creaza clasa Test ce mosteneste interfata Conversie
3.-se declara explicit metoda contractata (metoda SetText())
4.-se construieste un obiect din clasa Test
5.-se apeleaza metoda SetText()
In exempul de mai sus,interfata contine o singura metoda ce returneaza
un text default.Se observa cu usurinta ca o astfel de functionalitate se
poate exploata pentru a forta presetarea unor anumite valori (setarea
mediului de executie),inainte de a incepe executia propriu zisa a unui
program.De cele mai multe ori,aceasta presetare implica construirea unui
set de obiecte vizuale,prin care utilizatorul va putea comunica cu
aplicatia,cu ajutorul unor clik-uri de mouse.Acest gen de interfata,poarta
numele de "interfata grafica cu utilizatorul".Cea mai cunoscuta este
interfata API Windows,in care utilizatorul va beneficia doar de obiecte
vizuale de tip Windows.
-39-
Interfetele permit un mecanism de mostenire multipla.O singura clasa,
poate mosteni simultan mai multe interfete.
EXEMPLU: class ClasaMea: Interfata1,Interfata2,Interfata3{...}
Deasemenea,o interfata poate mosteni o alta interfata.
EXEMPLU: interface Interfata2: Interfata1 { ...}
Atunci cand se utilizeaza un astfel de mecanism de mostenire multipla,
depanarea unei metode mostenite multiplu poate deveni uneori destul de
confuza.Pentru a evita supraincarcarea unui metode mostenite din mai
multe interfete succesiv,este posibil ca la nivel de clasa de implementare
sa se faca o declaratie explicita a metodei respective.In acest caz,
metoda respectiva nu va mai fi declarata public,ci va fi declarata cu
ajutorul unui nume calificat (spatiu denumit).O astfel de metoda nu va
mai putea fi apelata ca simplu membru al clasei respective ci va trebui
construit initial un obiect,apoi se va apela metoda obiectului.Prin
acest mecanism,se garanateaza ca metoda este cea implementata explicit.
EXEMPLU:
using System;
interface Conversie { void SetText();}
class Test: Conversie {
void Conversie.SetText{
Console.WriteLine("Se executa metoda contractata !");
}}
class Executie {
static void Main(){
Test t1 = new Test();
Conversie t2 = t1;
t2.SetText();
Console.ReadLine();
}}
Salvati fila cu numele Interfata2.cs si compilati.
In exemplul de mai sus,daca se face apelul t1.SetText() se va returna
un mesaj de eroare,deoarece metoda Test.SetText() nu este statica sau
publica.Presupundand ca in exemplul de mai sus ar fi existat mai multe
metode SetText(),mostenite din interfete diferite,acest mecanism asigura
apelul metodei SetText() definita in clasa Test,spre deosebire de orice
alta metoda SetText() implementata altfel.
Bibliotecile standard contin numeroase astfel de interfete,ce pot
fi utilizate pentru a implementa functionalitati standard.De exemplu,
billioteca System contine si intefetele: IComparable,IConvertible,
IDisposable etc.
EXEMPLU: daca o clasa oarecare mosteneste interfata IDisposable din
System,atunci este sigur ca exista si o metoda denumita Dispose() pentru
operatii de eliberare a memoriei (deoarece interfata IDisposable contine
ca membru unic metoda Dispose()).
Interfetele permit crearea de solutii standard,ce pot fi utilizate si
de alti programatori.Apeland utilitarul ildasm.exe,un programator va putea
observa ce intefete sunt implementate si ce functionalitati pot fi apelate
in modulul importat.
EXEMPLU: deschideti modulul Interfata1.exe cu ildasm.exe pentru a
putea observa: intefata Conversie si clasa de implementare Test.
Codul metodei SetText va putea fi studiat in clasa de implementare (Test).
-40-
CLASELE DE TIP DELEGATES
Exista numeroase situatii de programare in care este utila inlocuirea
apelului unei functii,cu un pointer.Exemplu: atunci cand o functie este
utilizata pentru a furniza un parametru unei alte functii.In alte situatii
este necesar ca o anumita functie sau metoda sa fie executata atunci cand
se indeplineste o anumita conditie.Pentru acest scop,este necesar ca
metoda respectiva sa fie conectata la evenimentul respectiv,printr-o
structura oarecare de date.Limbajul C# a rezolvat acest gen de situatii,cu
Dostları ilə paylaş: |