miercuri, 9 decembrie 2015

Lecția 11 - Delegați

Definiție


Delegații sunt tipuri de date care încapsulează referințe către una sau mai multe metode  cu aceeași semnătură. Funcția referită nu este cunoscută la compilare ci doar la run-time. Pentru a utiliza un delegat  sunt necesare următoarele operații:

1. declararea:
               
   public delegate  tip_delegat  nume_delegat (lista_parametri_formali)

Declararea de mai sus se face în exteriorul claselor, deoarece delegații sunt tipuri asimilate claselor.
 Acest delegat va putea referi numai metode care au aceeași semnătură ca mai sus, respectiv care întorc tipul tip_delegat și au lista de parametri formali identică cu  a delegatului.

2. instanțierea:
           
          tip_delegat  nume_instanță_delegat = new tip_delegat(nume_funcție_eligibila)
sau

           tip_delegat    nume_instanță_delegat
           nume_instanță_delegat = nume_funcție_eligibila

Prin această  operație se atașează o referință către funcția eligibilă (care are aceeași semnătură cu a delegatului) la lista de invocare a delegatului  care poartă numele nume_instanță_delegat.

Pot exista oricâte instanțe ale delegatului. Funcția eligibilă poate fi atât metodă statică cât și metodă de instanță (nestatică).

 3. invocarea:

     nume_instanță_delegat

    Efectul  expresiei de invocare de mai sus este legarea apelului metodei  din lista de invocare a delegatului. Lista de invocare poate conține una sau mai multe metode care se vor executa pe rând, în ordinea atașării la lista delegatului.

Explicarea exemplului care urmează:

Scopul programului care urmează este de a realiza  sortarea oricărei  perechi posibile de obiecte de același tip. Două obiecte de tip persoane nu se pot sorta decât după nume sau după înălțime sau după vârstă, de pildă. Alegem să le sortăm după nume. Două obiecte de tip câine însă, nu are sens să le sortăm după nume, ci după rasă sau după greutate. Le vom sorta după greutate.

Ficare dintre cele două clase (persoane și câini) va furniza propria metodă de sortare, care accesează datele membru semnificative ale problemei: numele pentru persoane, greutatea pentru câini.

Programul principal nu are nevoie să cunoască principiul  după care se realizează sortarea, ci doar să primească lista ordonată. 

Clasa Student conține data membru nume, constructorul și metoda de sortare proprie studenților.
Clasa Caine conține data membru greutate, constructorul și metoda de sortare proprie cainilor.
Ambele clase conțin o metodă proprie ToString de conversie personalizată la tipul string a datei specifice, în vederea afișării la  consolă.

Clasa Pereche instanțiază un cuplu de obiecte de același tip: studenți sau câini.  Cele două obiecte se așează pe pozițiile 0 și 1 ale unui tablou uni-dimensional.
În clasa pereche există o metodă de sortare a perechii, care însă nu cunoaște la momentul compilării ce tip de obiecte va instanția: studenți sau câini.
Pentru a putea funcționa la fel de bine și pentru studenți și pentru câini, metoda de sortare a perechii va primi ca parametru actual referința către metoda de sortare fie a câinilor fie a  studenților prin intermediul unui delegat.

Așadar, dacă perechea este pereche de studenți, se va lega la run-time funcția de sortare a studenților, și invers, a câinilor, după caz.
Clasa Pereche mai conține, similar claselor Caine și Student, o metodă de conversie personalizată la tipul string, în vederea afișării a ceva la consolă.

Metoda Main:
De remarcat instanțierea delegaților, câte unul pentru fiecare metodă de sortare din cele două. deci avem două obiecte de tip delegat, la fiecare fiind atașată o singură funcție.
Se creează o pereche de studenți și separat o pereche de câini. Fiecare pereche este sortată separat, prin invocarea delegatului specific: perechea de studenți  prin delegatul studenților, perechea de câini prin delegatul câinilor.

namespace Delegati
{
    /*  declarare delegat
    delegatul este un tip referinta catre o metoda cu o anumita semnatura specificata
    la run-time delegatul poate referi pe rand una sau mai multe metode
    de acest tip, simultan sau succesiv
    care_iPrimul este un tip delegat care se declara aici:
    */
   //delegatul se declară în afara claselor, fiind el însuși o formă de clasă care suferă instanțiere
 
    public delegate int care_iPrimul(object o1, object o2);
    class Pereche
    {
        private object[] perechea = new object[2];

        public Pereche(object o1, object o2)
        {
            perechea[0] = o1;
            perechea[1] = o2;
        }
        // metoda publica de sortare a celor doua obiecte
        // dupa orice criteriu
        // invocare delegat în calitate de parametru formal al unei metode
        public void Sort(care_iPrimul delegat)
        { // functia Sort primeste ca parametru  un obiect de tipul delegat care_iPrimul
          //corpul acestei metode este scris știind că în locul parametrului formal delegat
          //la run-time va fi una dintre funcțiile atașate
          // de aceea restricția de semnătură identică
          if(delegat(perechea[0], perechea[1])==1)
            // al doilea vine primul deci se inverseaza
            {
                object temp = perechea[0];
                perechea[0] = perechea[1];
                perechea[1] = temp;
            }
        }
        public override string ToString()
        {
            return perechea[0].ToString()+", "+perechea[1].ToString();
        }
    }
    public class Caine
    {
        int greutate;
        public Caine(int greutate)
        { this.greutate = greutate; }
        // metoda de sortare a cainilor
        public static int SortareCaini(object o1,object o2)
        {
            Caine c1 = o1 as Caine; // operatorul cast as converteste la un tip derivat daca este
            Caine c2 = o2 as Caine; // posibil iar daca nu intoarce NULL
            return c1.greutate > c2.greutate ? 0 : 1;
        }
        // pentru afisarea greutatii unui caine
        public override string ToString()
        {
            return greutate.ToString();
        }
    }
    public class Student
    {
        public Student(string nume)
        { this.nume = nume;  }
        //studentii sunt ordonati alfabetic
        public static int SortStudenti(object o1,object o2)
        {
            Student s1 = o1 as Student;
            Student s2 = o2 as Student;
            return (String.Compare(s1.nume, s2.nume) < 0 ? 0 : 1);
        }
        // afisarea numelui unui student
        public override string ToString()
        {
            return nume;
        }
        string nume;
    }
    class Program
    {
        static void Main()
        {
            // creaza cate 2 obiecte de tip Caine si Student
            Student Vasile = new Student("Vasile");
            Student Ilie = new Student("Ilie");
            Caine Labus = new Caine(10);
            Caine Lup = new Caine(15);
            Pereche studenti = new Pereche(Vasile, Ilie);
            Pereche caini = new Pereche(Lup, Labus);
            Console.WriteLine("studenti\t:{0}", studenti);
            Console.WriteLine("caini\t:{0}", caini);
            //instantiere delegati
            care_iPrimul delegatStudenti = new care_iPrimul(Student.SortStudenti);
            care_iPrimul delegatCaini = new care_iPrimul(Caine.SortareCaini);
            //invocare delegați în calitate de  parametri actuali ai unei metode de sortare
            studenti.Sort(delegatStudenti);
            caini.Sort(delegatCaini);
            Console.WriteLine("dupa sortarea pe studenti\t: {0}", studenti.ToString());
            Console.WriteLine("dupa sortarea pe caini\t: {0}", caini.ToString());
            Console.ReadKey();
        }
    }
}

luni, 30 noiembrie 2015

Lecța 10 Proprietăți și indexatori. This.


PROPRIETĂȚI


Proprietatea este un membru de tip  metodă al clasei care ne permite să accesăm în citire sau în scriere  o dată membru privată sau protejată  a  clasei folosind o sintaxă intuitivă, ca și cum am accesa o dată publică.  Proprietatea este o metodă cu o sintaxă deosebită, în corpul căreia se găsesc două sub-metode speciale numite accesori,  get și setGet citește valoarea datei, set o modifică.

particularitățile proprietăților:
  • sunt metode fără listă de parametri formali. Semnătura lor conține numai tip și nume.
  • tipul este identic cu tipul datei membru asociate.
  • accesorul set are un parametru implicit cu numele value care se folosește de regulă în atribuirea variabilă = value.
  • numele proprietății este recomandat să fie același cu numele datei asociate, cu diferența că prima literă trebuie să fie majusculă.
  • numele variabilei nu este menționat nicăieri la apel, ci doar numele proprietății. De aici rezultă accesul la date private și protejate.


namespace Proprietati
{
    class ClasaMea
    {
        private int x;
        public int P
        {
            get
            {
                Console.WriteLine("get");
                return x;
            }
            set
            {
                Console.WriteLine("set");
                x = value;
            }
        }
    }
    class Program
    {
        public static void Main()
        {
            ClasaMea obiect = new ClasaMea();
            //linia urmatoare apeleaza accesorul
            //'set' din proprietatea P si ii
            //paseaza 10 lui ‘value’
            obiect.P = 10;
            int xVal = obiect.P;
            // linia urmatoare apeleaza accesorul
            //'get' din proprietatea P
            Console.WriteLine(xVal);
            Console.ReadLine();
        }
    }
}

INDEXATORI

Fie o clasă care are un membru de tip tablou.
Indexatorii sunt metode membre ale clasei care ne  permit să aplicăm operatorul []  instanței clasei, în loc să-l aplicăm tabloului.

Sintaxa declarării indexatorului este asemănătoare cu sintaxa declarării proprietății cu unele diferențe:
  • numele indexatorului este întotdeauna cuvântul cheie this
  • lista de parametri conține obligatoriu un parametru formal care va fi folosit ca și index în tablou.
  • există accesorii get și set.
  • get nu are parametri și returnează tipul indexatorului.
  • set are un parametru implicit numit value.
  • proprietățile pot fi statice, indexatorii nu pot fi decât nestatici.
  • ca și proprietățile, indexatorii permit accesul la date private. În fapt, numele datei private nici nu apare la apelul indexatorului ci este acoperit de numele obiectului (referința la obiect).
În exemplul următor, tabloul string[] data este declarat public numai în scopul de a se putea afișa elementele lui din clasa Rezultat fără utilizarea indexatorului, în stilul clasic. Dacă  tabloul ar fi fost declarat private, nu ar fi fost posibil. Deci indexatorii au, ca și proprietățile, rolul de a permite accesul la date private.


namespace Indexatori
{
    class ClasaMea
    {
        public string[] data = new string[6];
        public string this[int index]  // in mod normal trebuia private, dar s-a pus public pentru a se
                                                      // putea afisa si fara indexator.
        {
            get
            {
                return data[index];
            }
            set
            {
                data[index] = value;
            }
        }
    }
    class Rezultat
    {
        public static void Main()
        {
            ClasaMea v = new ClasaMea();
            v[0] = "Exemplu";
            v[1] = "cu";
            v[2] = "indexatori";
            Console.WriteLine("{0} {1} {2}.", v[0], v[1], v[2]);  //acces elemente  tablou cu indexator
            Console.WriteLine("{0} {1} {2}.",v.data[0], v.data[1], v.data[2]); //acces tablou in mod clasic
            Console.ReadLine();
        }
    }
}

Cuvântul cheie this

Folosit în orice alte contexte decât într-un indexator, this este o referință către obiectul curent. Toate metodele nestatice ale clasei au această referință care se crează în momentul instanțierii obiectului sub forma 
ClasaMea obiect =  new ClasaMea(); în acest  moment se crează alias-ul this=obiect.
This se folosește în două circumstanțe:
  •  pentru a referi o dată membru a obiectului curent care a re același nume cu numele unui parametru formal a metodei.
  •  pentru a întoarce o referință către obiectul curent.
 Class Copil
{
    private string nume;
    private int varsta;
    public Copil(string nume, int varsta)  //parametrii formali au acelasi nume ca datele membru
    {
         this.nume=nume;
         this.varsta=varsta;
     }
}

Dacă scriam doar nume=nume, se făcea o auto-atribuire deoarece numele parametrului formal ascunde numele datelor membru. 

 

marți, 17 noiembrie 2015

Lectia 9 Supraîncărcarea constructorilor şi definirea constructorilor în clasele derivate



Metodele sunt funcțiii membru ale unei clase. Pot fi statice (metode de clasă) sau nestatice (metode de instanță). 
Pot exista mai multe metode diferite cu acelasi nume intr-o clasa, cu conditia sa difere lista de parametri prin numar sau tip.
Legarea uneia sau alteia dintre aceste funcții omonime se face la momentul compilării (legare timpurie),  pe baza listei de parametri: se va lega funcția a cărei semnătură se potrivește cel mai bine cu lista de parametri actuali. Acest mecanism se numește polimorfism ad-hoc.
Clasele derivate pot redefini metode ale clasei de baza, pastrand numarul si tipul parametrilor, cu conditia aplicarii modificatorilor virtual (aplicat metodei din superclasa) si override (aplicat  metodei din  clasa derivata). Mecanismul este valabil si  pentru constructori. În cazul acesta, funcția nu se  leagă de către compilatorul C#  ci de către sistemul .NET (Windows) la momentul execuției (legare întârziată, la run-time), pe criteriul identificării tipului obiectului al cărui membru este metoda apelată. Acesta este  polimorfism de moștenire.

Clasa  Copil are doi constructori: unul fara parametri si unul cu 1 parametru string.
Clasa derivata Fetita  redefineste ambii constructori.
Clasa derivata Baiat nu defineste nici un consructor, ca urmare se apeleaza constructorul implicit al clasei fara parametri.

namespace Lectia9
{
    public class Copil
    {
        protected string nume; //data accesibila numai in interiorul
                               //clasei si a claselor derivate
        public const int nr_max = 10; //constanta
        public static int nr_copii = 0; //camp simplu (variabila)
        static Copil[] copii = new Copil[nr_max]; //camp de tip
        //tablou de referinte  tip Copil alocat static, maxim 10 elemente
        public static void adaug_copil(Copil c) //metodă  cu parametru referinta Copil
        {
            copii[nr_copii++] = c;
            if (nr_copii == nr_max)
                throw new Exception("Prea multi copii");
        }
        public static void afisare() //metodă
        {
            Console.WriteLine("Sunt {0} copii:", nr_copii);
            for (int i = 0; i < nr_copii; i++)
                Console.WriteLine("Nr.{0}. {1}", i + 1, copii[i].nume);
        }
        public Copil() //constructorul fara parametri ai clasei
        {
            Console.Write("Nume {0} ", nr_copii+1);
            nume = Console.ReadLine();
        }
        public Copil(string s) //constructor cu parametru
        {
            nume = s;
        }
    }
    class Baiat : Copil { }
    class Fetita : Copil
    {
        public Fetita(string s) : base(s)
  //base semnifica faptul ca   se face mai intai apel la constructorul
  //din clasa de baza, apoi se inlocuieste numele cu „Fetita „+nume
        }  
nume = "Fetita " + nume;
        }
        public Fetita():base()
        { }
    }
    class Program
    {
        public static void Main()
    {
            Fetita f = new Fetita();
            Copil.adaug_copil(f);
//apel la metoda statica (metoda de clasa)
//referinţa noului obiect se memorează în tabloul static copii
//(caracteristic clasei) şi se incrementează data statică nr_copii
            Baiat b = new Baiat();
            Copil.adaug_copil(b);
            Copil c = new Copil();
            Copil.adaug_copil(c);
            Copil.afisare(); //se afişează o listă cu numele celor 3 copii
            Console.ReadKey();
       }
    }

}


Mostenirea si suprascrierea  metodelor nestatice ale  clasei Copil in clasele derivate
In programul de mai sus se adauga in clasa de baza metodele se_joaca() cu modificatorul virtual si se_joaca(sting s)  fara virtual. In clasa derivata Fetita se suprascrie metoda se_joaca() cea virtuala.
public class Copil
{
protected string nume;
// …  constructorii
public virtual void se_joaca( ) //virtual –> asadar  functia se poate
{ //suprascrie in clasele derivate, dar nu obligatoriu. In absenta lui virtual nu s-ar putea suprascrie.
Console.WriteLine("{0} se joaca.", this.nume);
}
// urmează un polimorfism ad-hoc: definirea unei funcții omonime cu altă semnătură
public void se_joaca(string jucaria) //supradefinirea metodei
{ //se_joaca
Console.WriteLine("{0} se joaca cu {1}.",this.nume,jucaria);
}
...
}
class Fetita: Copil
{
public override void se_joaca( ) //suprascriere = override a unei metode virtuale din clasa de baza Copil. Apelul acestei metode se va rezolva la run-time - polimorfism de moștenire
{
Console.WriteLine("{0} plimba pisica.", this.nume);
}
}
...

In metoda Main() se adauga apelul catre functiile se_joaca, atat cea suprascrisa (fara parametru) cat si cea nesuprascrisa  (cu parametru), plecand de la un obiect de tip Fetita si de la un obiect de tip Baiat. Functia apelata este diferita, de la caz la caz.

Fetita f = new Fetita( );
f.se_joaca("pisica");  // Andreea se joaca cu pisica – apel metoda cu parametru din clasa de baza Copil. Legare timpurie.  Este un apel nepolimorfic către o metodă moștenită.
f.se_joaca( );              // Andreea plimba pisica – apel metoda suprscrisa din clasa derivata Fetita. Legare întârziată. Apel către o funcție moștenită supraîncărcată  = polimorfică.
Baiat b = new Baiat ( );
b.se_joaca("calculatorul"); //Andrei se joaca cu calculatorul – apel metoda cu parametru din clasa de baza
b.se_joaca( );       // Andrei se joaca – apel metoda fara parametru din clasa de baza. Toate apelurile către metode ale clasei de bază sunt legate timpuriu. Moștenire fără polimorfism.

Evidențierea legării întârziate a metodelor supraîncărcate ale  clasei derivate.

Adaugam in metoda Main() urmatoarele instructiuni:

Copil d = f; 

// referinta d de tip Copil refera un obiect de tip Fetita. 

d.se_joaca();

// Apel catre functia supraincarcata se_joaca() a subclasei Fetita. Legarea apelului se face la run-time catre //functia clasei Fetita, deoarece abia atunci se identifica tipul obiectului referit de d in urma atribuirii d=f.

Rezultatul este ca se va afisa la consola textul Andreea plimba pisica.