Metodi di estensione (Guida per programmatori C#)

Aggiornamento: novembre 2007

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 query standard LINQ che aggiungono la funzionalità di query ai tipi System.Collections.IEnumerable e System.Collections.Generic.IEnumerable<T> esistenti. Per utilizzare gli operatori query standard, inserirli prima 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 nel 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 nidificata 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 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.

namespace Extensions
{
  using System;
  using ExtensionMethodsDemo1;

     // Define extension methods for any type 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, because the three classes implement MethodB.
        public static void MethodB(this IMyInterface myInterface) 
        {
            Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}
namespace ExtensionMethodsDemo1
{
    using System;
    using Extensions;

    public interface IMyInterface
    {
        void MethodB();
    }

    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)
        {
            A a = new A();
            B b = new B();
            C c = new C();
            TestMethodBinding(a,b,c);
        }

        static void TestMethodBinding(A a, B b, C c)
        {
            // A has no methods, so each call resolves to 
            // the extension methods whose signatures match.
            a.MethodA(1);           // Extension.MethodA(object, int)
            a.MethodA("hello");     // Extension.MethodA(object, string)
            a.MethodB();            // A.MethodB()

            // B itself has a method with this signature.
            b.MethodA(1);           // B.MethodA(int)
            b.MethodB();            // B.MethodB()

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

            // In each case C has a matching instance method.
            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;

È consigliabile che gli implementatori di librerie di classi non utilizzino i metodi di estensione per evitare di creare versioni nuove di assembly. Se si desidera aggiungere una nuova significativa funzionalità a una libreria e si è proprietari del codice sorgente, è necessario seguire le linee guida standard di .NET Framework per il controllo delle versioni degli assembly. Per ulteriori informazioni, vedere Controllo delle versioni degli assembly.

Vedere anche

Concetti

Guida per programmatori C#

Cenni preliminari sugli operatori di query standard

Riferimenti

Espressioni lambda (Guida per programmatori C#)

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