Metodi di estensione (Guida per programmatori C#)

I metodi di estensione consentono di "aggiungere" metodi ai tipi esistenti senza creare un nuovo tipo derivato, ricompilare o modificare in altro modo il tipo originale. I metodi di estensione sono uno speciale tipo di metodo statico, ma vengono chiamati come se fossero metodi di istanza sul tipo esteso. Per il codice client scritto in C# e Visual Basic, non esistono differenze evidenti tra la chiamata a un metodo di estensione e ai metodi che sono effettivamente definiti in un tipo.

I metodi di estensione più comuni sono gli operatori di query standard LINQ che aggiungono funzionalità di query ai tipi System.Collections.IEnumerable e System.Collections.Generic.IEnumerable<T> esistenti. Per utilizzare gli operatori di query standard, è necessario in primo luogo introdurli nell'ambito con una direttiva using System.Linq. In questo modo qualsiasi tipo che implementa IEnumerable<T> avrà metodi di istanza quali GroupBy, OrderBy, Averagee così via. È possibile visualizzare questi metodi aggiuntivi con la funzionalità di completamento delle istruzioni IntelliSense quando si digita "punto" dopo un'istanza di un tipo IEnumerable<T>, ad esempio List<T> o Array.

Nell'esempio seguente viene illustrato come chiamare il metodo OrderBy di operatore query standard su una matrice di numeri interi. L'espressione nelle parentesi è un'espressione lambda. Molti operatori query standard accettano le espressioni lambda come parametri, sebbene non sia un requisito per i metodi di estensione. Per ulteriori informazioni, vedere Espressioni lambda (Guida per programmatori C#).

class ExtensionMethods2    
{

    static void Main()
    {            
        int[] ints = { 10, 45, 15, 39, 21, 26 };
        var result = ints.OrderBy(g => g);
        foreach (var i in result)
        {
            System.Console.Write(i + " ");
        }           
    }        
}
//Output: 10 15 21 26 39 45

Sebbene i metodi di estensione siano definiti come metodi statici, vengono chiamati utilizzando la sintassi del metodo di istanza. Il primo parametro, che specifica su quale tipo operi il metodo, è preceduto dal modificatore this. I metodi di estensione si trovano nell'ambito solo quando si importa in modo esplicito lo spazio dei nomi nel codice sorgente con una direttiva using.

Nell'esempio riportato di seguito viene illustrato un metodo di estensione definito per la classe System.String. Si noti che viene definito in una classe statica non annidata e non generica:

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this String str)
        {
            return str.Split(new char[] { ' ', '.', '?' }, 
                             StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }   
}

Il metodo di estensione WordCount può essere inserito nell'ambito con questa direttiva using:

using ExtensionMethods;

Può inoltre essere chiamato da un'applicazione utilizzando questa sintassi:

string s = "Hello Extension Methods";
int i = s.WordCount();

Nel codice si richiama il metodo di estensione con la sintassi del metodo di istanza. Microsoft Intermediate Language (IL) generato dal compilatore converte tuttavia il codice in una chiamata sul metodo statico. Il principio di incapsulamento non viene pertanto realmente violato. Infatti, i metodi di estensione non possono accedere a variabili private nel tipo che stanno estendendo.

Per ulteriori informazioni, vedere Procedura: implementare e chiamare un metodo di estensione personalizzato (Guida per programmatori C#).

In generale, è molto più frequente chiamare i metodi di estensione che implementarne di propri. Perché i metodi di estensione vengono chiamati utilizzando la sintassi del metodo di istanza, non è necessaria alcuna particolare conoscenza per utilizzarli dal codice client. Per attivare i metodi di estensione per un particolare tipo, aggiungere una direttiva using per lo spazio dei nomi nel quale sono definiti i metodi. Per utilizzare gli operatori query standard, ad esempio, aggiungere questa direttiva using al codice:

using System.Linq;

(Può inoltre essere necessario aggiungere un riferimento a System.Core.dll). Si noterà che gli operatori query standard vengono ora visualizzati in IntelliSense come metodi aggiuntivi disponibili per la maggior parte dei tipi IEnumerable<T>.

Nota

Anche se gli operatori query standard non sono visualizzati in IntelliSense per String, sono tuttavia disponibili.

Associazione di metodi di estensione in fase di compilazione

È possibile utilizzare metodi di estensione per estendere una classe o un'interfaccia, ma non per eseguirne l'override. Un metodo di estensione con lo stesso nome e la stessa firma di un metodo di interfaccia o di classe non verrà mai chiamato. In fase di compilazione, i metodi di estensione hanno sempre una priorità più bassa dei metodi di istanza definiti nel tipo stesso. In altre parole, se un tipo dispone di un metodo denominato Process(int i) e si dispone di un metodo di estensione con la stessa firma, il compilatore eseguirà sempre l'associazione al metodo di istanza. Quando il compilatore rileva una chiamata al metodo, prima cerca una corrispondenza nei metodi di istanza del tipo. Se non viene trovata alcuna corrispondenza, cercherà eventuali metodi di estensione definiti per il tipo ed eseguirà l'associazione al primo metodo di estensione trovato. Nell'esempio seguente viene dimostrato come il compilatore determina a quale metodo di estensione o metodo di istanza eseguire l'associazione.

Esempio

Nell'esempio seguente vengono illustrate le regole che il compilatore C# segue nel determinare se associare una chiamata al metodo a un metodo di istanza sul tipo o a un metodo di estensione. La classe Extensions statica contiene metodi di estensione definiti per qualsiasi tipo che implementa IMyInterface. Le classi A, B e C implementano tutte l'interfaccia.

Il metodo di estensione MethodB non viene mai chiamato perché il nome e la firma corrispondono esattamente a metodi già implementati dalle classi.

Quando il compilatore non è in grado di trovare un metodo di istanza con una firma corrispondente, eseguirà l'associazione a un metodo di estensione corrispondente se esistente.

// Define an interface named IMyInterface.
namespace DefineIMyInterface
{
    using System;

    public interface IMyInterface
    {
        // Any class that implements IMyInterface must define a method
        // that matches the following signature.
        void MethodB();
    }
}


// Define extension methods for IMyInterface.
namespace Extensions
{
    using System;
    using DefineIMyInterface;

    // The following extension methods can be accessed by instances of any 
    // class that implements IMyInterface.
    public static class Extension
    {
        public static void MethodA(this IMyInterface myInterface, int i)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, int i)");
        }

        public static void MethodA(this IMyInterface myInterface, string s)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, string s)");
        }

        // This method is never called in ExtensionMethodsDemo1, because each 
        // of the three classes A, B, and C implements a method named MethodB
        // that has a matching signature.
        public static void MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine
                ("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}


// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
namespace ExtensionMethodsDemo1
{
    using System;
    using Extensions;
    using DefineIMyInterface;

    class A : IMyInterface
    {
        public void MethodB() { Console.WriteLine("A.MethodB()"); }
    }

    class B : IMyInterface
    {
        public void MethodB() { Console.WriteLine("B.MethodB()"); }
        public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
    }

    class C : IMyInterface
    {
        public void MethodB() { Console.WriteLine("C.MethodB()"); }
        public void MethodA(object obj)
        {
            Console.WriteLine("C.MethodA(object obj)");
        }
    }

    class ExtMethodDemo
    {
        static void Main(string[] args)
        {
            // Declare an instance of class A, class B, and class C.
            A a = new A();
            B b = new B();
            C c = new C();

            // For a, b, and c, call the following methods:
            //      -- MethodA with an int argument
            //      -- MethodA with a string argument
            //      -- MethodB with no argument.

            // A contains no MethodA, so each call to MethodA resolves to 
            // the extension method that has a matching signature.
            a.MethodA(1);           // Extension.MethodA(object, int)
            a.MethodA("hello");     // Extension.MethodA(object, string)

            // A has a method that matches the signature of the following call
            // to MethodB.
            a.MethodB();            // A.MethodB()

            // B has methods that match the signatures of the following
            // nethod calls.
            b.MethodA(1);           // B.MethodA(int)
            b.MethodB();            // B.MethodB()

            // B has no matching method for the following call, but 
            // class Extension does.
            b.MethodA("hello");     // Extension.MethodA(object, string)

            // C contains an instance method that matches each of the following
            // method calls.
            c.MethodA(1);           // C.MethodA(object)
            c.MethodA("hello");     // C.MethodA(object)
            c.MethodB();            // C.MethodB()
        }
    }
}
/* Output:
    Extension.MethodA(this IMyInterface myInterface, int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    A.MethodB()
    B.MethodA(int i)
    B.MethodB()
    Extension.MethodA(this IMyInterface myInterface, string s)
    C.MethodA(object obj)
    C.MethodA(object obj)
    C.MethodB()
 */

Indicazioni generali

In generale, si consiglia di implementare i metodi di estensione sporadicamente e solo se necessario. Quando è possibile, è consigliabile che il codice client che deve estendere un tipo esistente esegua questa operazione creando un tipo nuovo derivato dal tipo esistente. Per ulteriori informazioni, vedere Ereditarietà (Guida per programmatori C#).

Quando si utilizza un metodo di estensione per estendere un tipo di cui non è possibile modificare il codice sorgente, si corre il rischio che una modifica nell'implementazione del tipo provochi l'interruzione del metodo di estensione.

Se si implementano metodi di estensione per un determinato tipo, è importante tenere presente i due seguenti punti:

  • Un metodo di estensione non verrà mai chiamato se dispone della stessa firma di un metodo definito nel tipo.

  • I metodi di estensione vengono inseriti nell'ambito al livello dello spazio dei nomi. Se, ad esempio, si dispone di più classi statiche contenenti metodi di estensione in un solo spazio dei nomi denominato Extensions, verranno tutti inseriti nell'ambito dalla direttiva using Extensions;

Vedere anche

Riferimenti

Espressioni lambda (Guida per programmatori C#)

Concetti

Guida per programmatori C#

Cenni preliminari sugli operatori di query standard

Altre risorse

Regole di conversione per parametri Istanza e il relativo impatto

Interoperabilità dei metodi di estensione tra linguaggi

Metodi di estensione e delegati supportati

Associazione di metodi di estensione e segnalazione errori

Cronologia delle modifiche

Data

Cronologia

Motivo

Ottobre 2010

Chiarimento dell'esempio finale.

Commenti e suggerimenti dei clienti.