Inside “Extension Methods” … call vs callvirt

Capita sempre di dover utilizzare delle classi delle quali non abbiamo il codice sorgente (basti pensare a quelle del .Net Framework) ma sulle quali non possiamo eseguire determinate operazioni in assenza dei corrispondenti metodi. Non abbiamo quindi la possibilità di estenderle e dotarle del nuovo comportamento di cui abbiamo bisogno.

In questi casi, risolviamo il problema implementando tale metodo al di fuori della classe stessa e ad esempio aggiungendolo in una nuova classe statica.

Per esempio, supponiamo di aver bisogno del metodo IndexOf() sulla classe StringBuilder (di cui essa non è dotata). Possiamo realizzare una nuova classe statica all’interno della quale implementiamo tale metodo.

public static class StringBuilderExtensions
{
    public static int IndexOf(StringBuilder sb, char value)
    {
        for (int index = 0; index < sb.Length; index++)
        {
            if (sb[index] == value)
                return index;
        }
        return -1;
    }
}

Ovviamente, per invocare tale metodo dobbiamo definire un’istanza della classe StringBuildere passare tale oggetto al metodo statico implementato.

int index;
StringBuilder sb = new StringBuilder("Paolo");
index = StringBuilderExtensions.IndexOf(sb, 'o');
Per fortuna, attraverso l’utilizzo degli Extension Methods, abbiamo la possibilità di estendere in maniera naturale una classe ed invocare direttamente sull’istanza dell’oggetto il nuovo comportamento implementato.
L’implementazione di un extension method viene realizzata con la tecnica precedente ma con la differenza nell’aggiungere la parola chiave this davanti al primo parametro del nuovo metodo, per indicare che esso sarà l’oggetto su cui andrà ad agire.
public static class StribgBuilderExtensionMethods
{
    public static int IndexOf(this StringBuilder sb, char value)
    {
        for (int index = 0; index < sb.Length; index++)
        {
            if (sb[index] == value)
                return index;
        }
        return -1;
    }
}
Attraverso questa tecnica, l’Intellisense di Visual Studio ci mette a disposizione tale nuovo metodo, direttamente sull’istanza della classe StringBuilder.
4520.em_1_62641764

A questo punto, possiamo però dimostrare che le due implementazioni sono perfettamente identiche e che la feature degli extension methods è più che altro una facility per lo sviluppatore.

Infatti, invocare un extension method equivale esattamente ad invocare un metodo statico come nel caso precedente con la differenza che non dobbiamo eseguire esplicitamente il passaggio del parametro su cui il metodo andrà ad agire. Basta confrontare le due seguenti invocazioni :

index = StringBuilderExtensions.IndexOf(sb, 'o');
index = sb.IndexOf('o');
Sarà il compilatore a generare il codice IL che passa il parametro “sb” all’extension method.
Consideriamo il seguente esempio con il relativo codice IL :
static void Main(string[] args)
{
    int index;
    StringBuilder sb = new StringBuilder("Paolo");
    sb.Append("Embedded Life");
    index = StringBuilderExtensions.IndexOf(sb, 'o');
    index = sb.IndexOf('o');
}
3482.em_2_5927DC23
Come si può osservare nel codice IL generato, le due invocazioni di IndexOf() sono perfettamente identiche ed il fatto che siano invocazioni di metodi statici lo evidenzia anche l’utilizzo della call al posto della callvirt come nel caso del metodo Append() che è un metodo di istanza della classe StringBuilder.

Infatti, esiste una sostanziale differenza tra call e callvirt :

  • call : può essere usata per l’invocazione di metodi statici, di istanza e virtuali. Tipicamente soprattutto per i metodi statici, preferendo la callvirt agli altri tipi di metodi. Essa assume che l’oggetto su cui viene eseguita la chiamata non sia null e quindi non esegue questo tipo di check;
  • callvirt : utilizzata per l’invocazione di metodi virtuali e di istanza, quindi non viene mai utilizzata per i metodi statici. Essa esegue sempre un check per verificare che l’oggetto su cui viene eseguita l’invocazione non sia null ed in tal caso viene sollevata una NullReferenceException;

La differenza sul check di oggetto null o meno lo si può apprezzare con il seguente esempio :

static void Main(string[] args)
{
    int index;
    StringBuilder sb = null;
    sb.Append("Paolo");
    index = sb.IndexOf('o');
}

Nel caso dell’invocazione del metodo Append() attraverso la callvirt, il CLR solleverà l’eccezione proprio in corrispondenza della chiamata.

7242.em_3_262825EF

Viceversa, nel caso dell’invocazione dell’extension method IndexOf() attraverso la call, l’invocazione viene eseguita (non esiste il check sul fatto che l’oggetto è null) ma ovviamente l’eccezione viene sollevata all’interno del metodo quando si tenta di accedere all’oggetto.

1586.em_4_63D50DAB

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s