lunedì 24 luglio 2017

C# DELEGATE - PARTE 1 - INTRODUZIONE

Benvenuti in un approfondimento, firmato Programmazione Applicata, dove andremo a capire cosa sono i delegati in ambiente Microsoft .Net e in quali modi andarli ad utilizzare. La fonte principale del contenuto teorico di questi articoli è la documentazione ufficiale Microsoft [link], tutti gli esempi che tratteremo saranno in linguaggio C#.


Introduzione


Un delegato è un particolare tipo di dato che possiamo equiparare ad un puntatore a funzione (o riferimento ad un metodo). Quando viene istanziato un delegato, è possibile assegnarli un qualsiasi metodo con medesima firma, la firma di un metodo è l'insieme dei parametri di ingresso e quelli di uscita. I delegati vengono utilizzati per passare metodi come parametri di ingresso di altri metodi.


L'utilizzo dei delegati risulta fondamentale per l'esecuzione di metodi di callback, metodi che vengono eseguiti senza la necessità di conoscerne l'oggetto chiamante. Comprendo che la teoria dei delegati possa sembrare complicata, specialmente se spiegata in quattro parole (rispetto alle decine di pagine della documentazione Microsoft) ma sono sicuro che con il primo esempio le cose saranno più chiare.



Esempio

Per dichiarare un delegato è sufficiente inserire la parola chiave delegate prima della definizione di funzione e non inserirne il corpo.
public delegate void test(string par1, int par2);

Immaginiamo di avere due classi che eseguono un calcolo specifico, una classe di somma (Plus) ed una di sottrazione (Minus), esiste poi una terza classe (InputAndCalc) che si occupa di fornire gli input alle funzioni di calcolo ma non occuparsi in prima persona del calcolo, sfruttando i delegati:

class Program
{

    static void Main(string[] args)
    {
        InputAndCalc iac = new InputAndCalc();
        Plus p = new Plus();
        Minus m = new Minus();
        iac.readInputAndPerformGivenCalc(p.AplusB);
        iac.readInputAndPerformGivenCalc(m.AminusB);
    }
        
}

class InputAndCalc
{
    public delegate int calcDelegate(int a, int b);

    public void readInputAndPerformGivenCalc(calcDelegate calc)
    {
        int a, b;
        Console.Write("A: ");
        a = int.Parse(Console.ReadLine());
        Console.Write("B: ");
        b = int.Parse(Console.ReadLine());
        Console.WriteLine(string.Format("C: {0}", calc(a, b)));
    }
}

class Plus
{
    public int AplusB(int a, int b)
    {
        return a + b;
    }
}

class Minus
{
    public int AminusB(int a, int b)
    {
        return a - b;
    }
}

Parafrasando quanto appena scritto, abbiamo scritto i metodi di calcolo nelle classi Plus e Minus, poi abbiamo utilizzato i metodi AplusB e AminusB come parametri della funzione readInputAndPerformGivenCalc che si occupa esclusivamente di leggere a tastiera gli input dell'utente non occupandosi in prima persona di effettuare il calcolo.

Lo stesso esempio.. con un Interface

Quanto abbiamo appena scritto è equiparabile all'utilizzo delle interfacce, vediamo come sarebbe
stato scritto utilizzando le interfacce invece dei delegati:

class Program
{

    static void Main(string[] args)
    {
        InputAndCalc iac = new InputAndCalc();
        Plus p = new Plus();
        Minus m = new Minus();
        iac.readInputAndPerformGivenCalc(p);
        iac.readInputAndPerformGivenCalc(m);
    }

}

class InputAndCalc
{

    public void readInputAndPerformGivenCalc(Calculator c)
    {
        int a, b;
        Console.Write("A: ");
        a = int.Parse(Console.ReadLine());
        Console.Write("B: ");
        b = int.Parse(Console.ReadLine());
        Console.WriteLine(string.Format("C: {0}", c.Calc(a, b)));
    }
}

interface Calculator
{
    int Calc(int a, int b);
}

class Plus:Calculator
{
    int Calculator.Calc(int a, int b)
    {
        return a + b;
    }
}

class Minus:Calculator
{
    int Calculator.Calc(int a, int b)
    {
        return a - b;
    }
}

Utilizzando le interfacce è stato necessario introdurre una nuova definizione (interfaccia) inoltre ciò che viene passato come parametro alla funzione readInputAndPerformGivenCalc non è un metodo ma l'intero oggetto che implementa l'interfaccia Calculator. Ma allora che differenza c'è fra delegate e interface, all'atto pratico?

Delegate vs Interface

Come ho sempre cercato di sottolineare negli articoli di Programmazione Applicata, il linguaggio di programmazione è uno strumento. Un buon programmatore non è colui che conosce più strumenti di altri, ma colui che conosce bene gli strumenti che utilizza. Questo per dire che anche se le interfacce e i delegati apparentemente sembrano simili, hanno delle differenze sostanziali che devono essere tenute in considerazione durante la fase di software design.

Vediamo insieme come schematizzare l'utilizzo di una o l'altra soluzione:

Usare i delegati quando:
  • si usa il design pattern ad evento/callback
  • si vuole incapsulare metodi statici (non vengono ereditati dalle sottoclassi)
  • il metodo non ha bisogno (o non deve) accedere agli attributi dell'oggetto che implementa il delegato
  • la stessa classe ha bisogno di implementare due versioni dello stesso metodo.
Usare una interfaccia quando:
  • la classe necessita di una ed una sola implementazione
  • il metodo ha bisogno di accedere agli attributi dell'oggetto chiamante (ad esempio metodi CompareTo o ToString, o l'interfaccia IComparable).

Nello specifico, l'interfaccia IComparable per confrontare due oggetti è una tecnica che si sposa con il design ad interfacce invece che delegati, perchè il metodo per poter confrontare due oggetti ha bisogno di accedere agli attributi di entrambi (specialmente il chiamante this).


Nessun commento:

Posta un commento