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();
        }
    }
}