Implementare metodi di estensione per una classe

Completato

In genere, esistono due modi per aggiungere un metodo a un tipo esistente:

  • Modificare il codice sorgente per quel tipo. Se si aggiungono campi dati privati per supportare il metodo, la modifica dell'origine crea una modifica che causa un'interruzione.
  • Definire il nuovo metodo in una classe derivata. Non è possibile aggiungere un metodo in questo modo usando l'ereditarietà per altri tipi, ad esempio strutture ed enumerazioni. Né può essere usato per "aggiungere" un metodo a una classe sealed.

I metodi di estensione consentono di "aggiungere" un metodo a un tipo esistente senza modificare il tipo stesso o implementare il nuovo metodo in un tipo ereditato. Anche il metodo di estensione non deve trovarsi nello stesso assembly del tipo esteso. Si chiama un metodo di estensione come se fosse un membro definito di un tipo.

Metodi di estensione

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 metodi statici, ma vengono chiamati come se fossero metodi di istanza nel tipo esteso. Per il codice client scritto in C#, F# e Visual Basic, non esiste alcuna differenza evidente tra la chiamata di un metodo di estensione e i metodi definiti in un tipo.

Associazione di metodi di estensione in fase di compilazione

È possibile usare metodi di estensione per estendere una classe o un'interfaccia, ma non eseguirne l'override. Non viene mai chiamato un metodo di estensione con lo stesso nome e la stessa firma di un metodo di classe. In fase di compilazione, i metodi di estensione hanno sempre una priorità inferiore rispetto ai metodi di istanza definiti nel tipo stesso. In altre parole, se un tipo ha un metodo denominato Process(int i)e si dispone di un metodo di estensione con la stessa firma, il compilatore viene sempre associato al metodo di istanza. Quando il compilatore rileva una chiamata al metodo, cerca una corrispondenza nei metodi di istanza del tipo. Se non viene trovato alcun metodo di istanza corrispondente, il compilatore cerca i metodi di estensione definiti per il tipo. Il compilatore viene associato al primo metodo di estensione trovato.

Nell'esempio seguente vengono illustrate le regole che il compilatore C# segue per determinare se associare una chiamata di metodo a un metodo di istanza sul tipo o a un metodo di estensione.


namespace ExtensionMethodDemo.PersonNamespace
{
    // Define a simple class with properties and methods
    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }

        public void Introduce()
        {
            Console.WriteLine($"Hi, I'm {FirstName} {LastName}, and I'm {Age} years old.");
        }
    }
}

namespace ExtensionMethodDemo.PersonExtensionsNamespace
{
    using ExtensionMethodDemo.PersonNamespace;

    // Define an extension method for the Person class
    public static class PersonExtensions
    {
        public static void DisplayFullName(this Person person)
        {
            Console.WriteLine($"Full Name: {person.FirstName} {person.LastName}");
        }

        public static bool IsAdult(this Person person)
        {
            return person.Age >= 18;
        }

        public static void Introduce()
        {
            Console.WriteLine($"Extension - Hi, it's nice to meet you.");
        }

        // Extension method attempting to override Introduce
        public static void Introduce(this Person person, string greeting)
        {
            Console.WriteLine($"{greeting}, I'm {person.FirstName} {person.LastName}, and I'm {person.Age} years old.");
        }
    }
}

namespace ExtensionMethodDemo
{
    using ExtensionMethodDemo.PersonNamespace;
    using ExtensionMethodDemo.PersonExtensionsNamespace;

    class Program
    {
        static void Main(string[] args)
        {
            // Create instances of the Person class
            Person person1 = new Person { FirstName = "FName1", LastName = "LName1", Age = 25 };
            Person person2 = new Person { FirstName = "FName2", LastName = "LName2", Age = 16 };

            // Use the methods of the Person class
            person1.Introduce();
            person2.Introduce();

            // Use the extension methods
            person1.DisplayFullName();
            Console.WriteLine($"Is {person1.FirstName} an adult? {person1.IsAdult()}");

            person2.DisplayFullName();
            Console.WriteLine($"Is {person2.FirstName} an adult? {person2.IsAdult()}");

            // Use the extension method that attempts to override Introduce
            person1.Introduce("Hello");
            person2.Introduce("Greetings");
        }
    }
}
// Output:
//     Hi, I'm FName1 LName1, and I'm 25 years old.
//     Hi, I'm FName2 LName2, and I'm 16 years old.
//     Full Name: FName1 LName1
//     Is FName1 an adult? True
//     Full Name: FName2 LName2
//     Is FName2 an adult? False
//     Hello, I'm FName1 LName1, and I'm 25 years old.
//     Greetings, I'm FName2 LName2, and I'm 16 years old.

In questo esempio, la classe Person ha tre proprietà: FirstName, LastNamee Age. La classe Person include anche un metodo di istanza Introduce che scrive un messaggio nella console.

La classe PersonExtensions definisce i metodi di estensione per la classe Person. La classe PersonExtensions contiene i metodi di estensione seguenti:

  • DisplayFullName: scrive il nome completo della persona nella console.
  • IsAdult: restituisce un valore booleano che indica se la persona è un adulto.
  • Introduce: scrive un messaggio nella console. Questo metodo ha due overload: uno senza parametri e uno con un parametro stringa.
  • Introduce(this Person person, string greeting): scrive un messaggio nella console con un messaggio di saluto personalizzato.
  • Introduce(): scrive un messaggio predefinito nella console.

Il metodo Main crea due istanze della classe Person e chiama il metodo Introduce in ogni istanza. Viene eseguito il metodo Introduce definito nella classe Person. Il metodo Introduce senza parametri nella classe PersonExtensions non viene chiamato perché la classe Person ha già un metodo di istanza Introduce con la stessa firma. Il compilatore segue le regole per i metodi di estensione dell'associazione in fase di compilazione e assegna la priorità al metodo di istanza definito nel tipo stesso. Il metodo Main chiama anche i metodi di estensione DisplayFullName, IsAdulte il metodo di Introduce di overload con un messaggio di saluto personalizzato. I metodi di estensione vengono eseguiti come previsto.

Linee guida generali

I metodi di estensione sono un'opzione importante per la creazione di funzionalità riutilizzabili nell'ecosistema .NET. Tuttavia, la modifica del codice di un oggetto o la derivazione di un nuovo tipo ogni volta che è ragionevole e possibile farlo, viene comunque considerata preferibile. I metodi di estensione sono una scelta eccellente quando l'origine originale non è sotto il controllo, quando un oggetto derivato è inappropriato o impossibile o quando la funzionalità non deve essere esposta oltre l'ambito applicabile.

Quando si usano metodi di estensione per estendere un tipo il cui codice sorgente non è controllato, si corre il rischio che una modifica nell'implementazione del tipo causi un'interruzione del metodo di estensione. Se si implementano metodi di estensione per un determinato tipo, tenere presente i punti seguenti:

  • Un metodo di estensione non viene chiamato se ha la stessa firma di un metodo definito nel tipo.
  • I metodi di estensione vengono inseriti nell'ambito a livello di spazio dei nomi. Ad esempio, se sono presenti più classi statiche che contengono metodi di estensione in un singolo spazio dei nomi denominato Extensions, verranno tutti inseriti nell'ambito dalla direttiva using Extensions;.

Per una libreria di classi implementata, non è consigliabile usare metodi di estensione per evitare di incrementare il numero di versione di un assembly. Per aggiungere funzionalità significative a una libreria per cui si è proprietari del codice sorgente, seguire le linee guida .NET per il controllo delle versioni degli assembly.