Metodi di estensione (Visual Basic)

I metodi di estensione consentono agli sviluppatori di aggiungere funzionalità personalizzate ai tipi di dati già definiti senza creare un nuovo tipo derivato. I metodi di estensione consentono di scrivere un metodo che può essere chiamato come se fosse un metodo di istanza del tipo esistente.

Osservazioni:

Un metodo di estensione può essere solo una routine Sub o una routine Function. Non è possibile definire una proprietà, un campo o un evento di estensione. Tutti i metodi di estensione devono essere contrassegnati con l'attributo di estensione <Extension> dello spazio dei nomi System.Runtime.CompilerServices e devono essere definiti in un modulo. Se un metodo di estensione viene definito all'esterno di un modulo, il compilatore Visual Basic genera l'errore BC36551: "I metodi di estensione possono essere definiti solo nei moduli".

Il primo parametro di una definizione di metodo di estensione specifica il tipo di dati esteso dal metodo. Quando viene eseguito il metodo, il primo parametro viene associato all'istanza del tipo di dati che richiama il metodo.

L'attributo Extension può essere applicato solo a un oggetto Module, Sub o Function di Visual Basic. Se lo si applica a un oggetto Class o Structure, il compilatore Visual Basic genera l'errore BC36550: "È possibile applicare l'attributo 'Extension' solo alle dichiarazioni 'Module', 'Sub' o 'Function'".

Esempio

Nell'esempio seguente viene definita un'estensione Print per il tipo di dati String. Il metodo usa Console.WriteLine per visualizzare una stringa. Il parametro del metodo Print, aString, stabilisce che il metodo estende la classe String.

Imports System.Runtime.CompilerServices

Module StringExtensions

    <Extension()> 
    Public Sub Print(ByVal aString As String)
        Console.WriteLine(aString)
    End Sub

End Module

Si noti che la definizione del metodo di estensione è contrassegnata con l'attributo di estensione <Extension()>. Contrassegnare il modulo in cui è definito il metodo è facoltativo, ma è necessario contrassegnare ogni metodo di estensione. System.Runtime.CompilerServices deve essere importato per accedere all'attributo di estensione.

I metodi di estensione possono essere dichiarati solo all'interno dei moduli. In genere, il modulo in cui viene definito un metodo di estensione non è lo stesso modulo in cui viene chiamato. Al contrario, il modulo che contiene il metodo di estensione viene importato, se necessario, per inserirlo nell'ambito. Dopo aver inserito nell'ambito il modulo che contiene Print, il metodo può essere chiamato come se fosse un normale metodo di istanza che non accetta argomenti, come ToUpper:

Module Class1

    Sub Main()

        Dim example As String = "Hello"
        ' Call to extension method Print.
        example.Print()

        ' Call to instance method ToUpper.
        example.ToUpper()
        example.ToUpper.Print()

    End Sub

End Module

Anche l'esempio successivo, PrintAndPunctuate, è un'estensione di String, questa volta definita con due parametri. Il primo parametro, aString, stabilisce che il metodo di estensione estende String. Il secondo parametro, punc, deve essere una stringa di segni di punteggiatura passata come argomento quando viene chiamato il metodo. Il metodo visualizza la stringa seguita dai segni di punteggiatura.

<Extension()> 
Public Sub PrintAndPunctuate(ByVal aString As String, 
                             ByVal punc As String)
    Console.WriteLine(aString & punc)
End Sub

Il metodo viene chiamato inviando un argomento stringa per punc: example.PrintAndPunctuate(".")

L'esempio seguente mostra Print e PrintAndPunctuate definiti e chiamati. System.Runtime.CompilerServices viene importato nel modulo di definizione per consentire l'accesso all'attributo di estensione.

Imports System.Runtime.CompilerServices

Module StringExtensions

    <Extension()>
    Public Sub Print(aString As String)
        Console.WriteLine(aString)
    End Sub

    <Extension()>
    Public Sub PrintAndPunctuate(aString As String, punc As String)
        Console.WriteLine(aString & punc)
    End Sub
End Module

Successivamente, i metodi di estensione vengono inseriti nell'ambito e chiamati:

Imports ConsoleApplication2.StringExtensions

Module Module1

    Sub Main()
        Dim example As String = "Example string"
        example.Print()

        example = "Hello"
        example.PrintAndPunctuate(".")
        example.PrintAndPunctuate("!!!!")
    End Sub
End Module

Per poter eseguire questi metodi di estensione o metodi simili, è necessario che siano inclusi nell'ambito. Se il modulo che contiene un metodo di estensione è nell'ambito, è visibile in IntelliSense e può essere chiamato come se fosse un normale metodo di istanza.

Si noti che quando i metodi vengono richiamati, non viene inviato alcun argomento per il primo parametro. Il parametro aString nelle definizioni dei metodi precedenti è associato a example, l'istanza di String che li chiama. Il compilatore userà example come argomento inviato al primo parametro.

Se viene chiamato un metodo di estensione per un oggetto impostato su Nothing, viene eseguito il metodo di estensione. Questo non si applica ai normali metodi di istanza. È possibile verificare in modo esplicito la presenza di Nothing nel metodo di estensione.

Tipi che possono essere estesi

È possibile definire un metodo di estensione per la maggior parte dei tipi che possono essere rappresentati in un elenco di parametri di Visual Basic, inclusi i seguenti:

  • Classi (tipi riferimento)
  • Strutture (tipi valore)
  • Interfacce
  • Delegati
  • Argomenti ByRef e ByVal
  • Parametri del metodo generico
  • Matrici

Poiché il primo parametro specifica il tipo di dati esteso dal metodo di estensione, è obbligatorio e non può essere facoltativo. Per questo motivo, i parametri Optional e i parametri ParamArray non possono essere il primo parametro nell'elenco di parametri.

I metodi di estensione non vengono considerati nell'associazione tardiva. Nell'esempio seguente l'istruzione anObject.PrintMe() genera un'eccezione MissingMemberException, la stessa eccezione che si verificherebbe se venisse eliminata la seconda definizione del metodo di estensione PrintMe.

Option Strict Off
Imports System.Runtime.CompilerServices

Module Module4

    Sub Main()
        Dim aString As String = "Initial value for aString"
        aString.PrintMe()

        Dim anObject As Object = "Initial value for anObject"
        ' The following statement causes a run-time error when Option
        ' Strict is off, and a compiler error when Option Strict is on.
        'anObject.PrintMe()
    End Sub

    <Extension()> 
    Public Sub PrintMe(ByVal str As String)
        Console.WriteLine(str)
    End Sub

    <Extension()> 
    Public Sub PrintMe(ByVal obj As Object)
        Console.WriteLine(obj)
    End Sub

End Module

Procedure consigliate

I metodi di estensione offrono un modo pratico e potente per estendere un tipo esistente. Tuttavia, per usarli correttamente, ci sono alcuni punti da considerare. Queste considerazioni si applicano principalmente agli autori di librerie di classi, ma potrebbero riguardare qualsiasi applicazione che usi i metodi di estensione.

In genere, i metodi di estensione aggiunti ai tipi di cui non si è proprietari sono più vulnerabili rispetto ai metodi di estensione aggiunti ai tipi che si controllano. Nelle classi di cui non si è proprietari possono verificarsi diversi eventi che possono interferire con i metodi di estensione.

  • Se esiste un membro di istanza accessibile con una firma compatibile con gli argomenti dell'istruzione chiamante, senza che siano necessarie conversioni da argomento a parametro che supportano un minor numero di dati, è preferibile usare il metodo di istanza rispetto a qualsiasi metodo di estensione. Pertanto, se a un certo punto viene aggiunto a una classe un metodo di istanza appropriato, un membro di estensione esistente su cui ci si basa potrebbe diventare inaccessibile.

  • L'autore di un metodo di estensione non può impedire ad altri programmatori di scrivere metodi di estensione in conflitto che potrebbero avere la precedenza sull'estensione originale.

  • È possibile migliorare l'affidabilità inserendo i metodi di estensione nel proprio spazio dei nomi. I consumer della libreria possono quindi includere uno spazio dei nomi o escluderlo oppure scegliere tra gli spazi dei nomi, separatamente dal resto della libreria.

  • Può essere più sicuro estendere le interfacce anziché le classi, soprattutto se non si è proprietari dell'interfaccia o della classe. Una modifica a un'interfaccia influisce su ogni classe che la implementa. Pertanto, è meno probabile che l'autore aggiunga o modifichi i metodi di un'interfaccia. Tuttavia, se una classe implementa due interfacce che dispongono di metodi di estensione con la stessa firma, nessuno dei due metodi di estensione sarà visibile.

  • Estendere il tipo più specifico possibile. In una gerarchia di tipi, se si seleziona un tipo da cui derivano molti altri tipi, esistono livelli di possibilità per l'introduzione di metodi di istanza o altri metodi di estensione che potrebbero interferire con i propri.

Metodi di estensione, metodi di istanza e proprietà

Quando un metodo di istanza nell'ambito ha una firma compatibile con gli argomenti di un'istruzione chiamante, è preferibile scegliere il metodo di istanza rispetto a qualsiasi metodo di estensione. Il metodo di istanza ha la precedenza anche se il metodo di estensione rappresenta una corrispondenza migliore. Nell'esempio seguente ExampleClass contiene un metodo di istanza denominato ExampleMethod con un parametro di tipo Integer. Il metodo di estensione ExampleMethod estende ExampleClass e ha un parametro di tipo Long.

Class ExampleClass
    ' Define an instance method named ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Integer)
        Console.WriteLine("Instance method")
    End Sub
End Class

<Extension()> 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Long)
    Console.WriteLine("Extension method")
End Sub

La prima chiamata a ExampleMethod nel codice seguente chiama il metodo di estensione, perché arg1 è Long ed è compatibile solo con il parametro Long nel metodo di estensione. La seconda chiamata a ExampleMethod ha un argomento Integer, arg2, e chiama il metodo di istanza.

Sub Main()
    Dim example As New ExampleClass
    Dim arg1 As Long = 10
    Dim arg2 As Integer = 5

    ' The following statement calls the extension method.
    example.exampleMethod(arg1)
    ' The following statement calls the instance method.
    example.exampleMethod(arg2)
End Sub

Invertire ora i tipi di dati dei parametri dei due metodi:

Class ExampleClass
    ' Define an instance method named ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Long)
        Console.WriteLine("Instance method")
    End Sub
End Class

<Extension()> 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Integer)
    Console.WriteLine("Extension method")
End Sub

Questa volta il codice in Main chiama il metodo di istanza entrambe le volte. Ciò è dovuto al fatto che arg1 e arg2 hanno una conversione che supporta un maggior numero di dati in Long e il metodo di istanza ha la precedenza sul metodo di estensione in entrambi i casi.

Sub Main()
    Dim example As New ExampleClass
    Dim arg1 As Long = 10
    Dim arg2 As Integer = 5

    ' The following statement calls the instance method.
    example.ExampleMethod(arg1)
    ' The following statement calls the instance method.
    example.ExampleMethod(arg2)
End Sub

Pertanto, un metodo di estensione non può sostituire un metodo di istanza esistente. Tuttavia, quando un metodo di estensione ha lo stesso nome di un metodo di istanza, ma le firme non sono in conflitto, è possibile accedere a entrambi i metodi. Ad esempio, se la classe ExampleClass contiene un metodo denominato ExampleMethod che non accetta argomenti, sono consentiti metodi di estensione con lo stesso nome ma firme diverse, come illustrato nel codice seguente.

Imports System.Runtime.CompilerServices

Module Module3

    Sub Main()
        Dim ex As New ExampleClass
        ' The following statement calls the extension method.
        ex.ExampleMethod("Extension method")
        ' The following statement calls the instance method.
        ex.ExampleMethod()
    End Sub

    Class ExampleClass
        ' Define an instance method named ExampleMethod.
        Public Sub ExampleMethod()
            Console.WriteLine("Instance method")
        End Sub
    End Class

    <Extension()> 
    Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal stringParameter As String)
        Console.WriteLine(stringParameter)
    End Sub

End Module

L'output di questo codice è il seguente:

Extension method
Instance method

La situazione è più semplice con le proprietà: se un metodo di estensione ha lo stesso nome di una proprietà della classe che estende, il metodo di estensione non è visibile e non può essere accessibile.

Precedenza del metodo di estensione

Quando due metodi di estensione con firme identiche sono inclusi nell'ambito e accessibili, verrà richiamato quello con precedenza più alta. La precedenza di un metodo di estensione si basa sul meccanismo usato per inserire il metodo nell'ambito. L'elenco seguente mostra la gerarchia delle precedenze, dalla più alta alla più bassa.

  1. Metodi di estensione definiti all'interno del modulo corrente.

  2. Metodi di estensione definiti all'interno dei tipi di dati nello spazio dei nomi corrente o in uno dei relativi spazi dei nomi padre, con gli spazi dei nomi figlio che hanno una precedenza maggiore rispetto agli spazi dei nomi padre.

  3. Metodi di estensione definiti all'interno di qualsiasi importazione di tipi nel file corrente.

  4. Metodi di estensione definiti all'interno di qualsiasi importazione di spazi dei nomi nel file corrente.

  5. Metodi di estensione definiti all'interno di qualsiasi importazione di tipi a livello di progetto.

  6. Metodi di estensione definiti all'interno di qualsiasi importazione di spazi dei nomi a livello di progetto.

Se la precedenza non risolve l'ambiguità, è possibile usare il nome completo per specificare il metodo che si sta chiamando. Se il metodo Print nell'esempio precedente viene definito in un modulo denominato StringExtensions, il nome completo è StringExtensions.Print(example) invece di example.Print().

Vedi anche