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.

Commenti

Un metodo di estensione può essere solo una routine o una SubFunction routine. Non è possibile definire una proprietà di estensione, un campo o un evento. Tutti i metodi di estensione devono essere contrassegnati con l'attributo <Extension>System.Runtime.CompilerServices di estensione dallo spazio dei nomi 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 in una definizione del metodo di estensione specifica il tipo di dati esteso dal metodo. Quando viene eseguito il metodo, il primo parametro è associato all'istanza del tipo di dati che richiama il metodo.

L'attributo Extension può essere applicato solo a un oggetto Visual Basic Module, Subo Function. Se lo si applica a o a ClassStructure, il compilatore Visual Basic genera l'errore BC36550, l'attributo "'Extension" può essere applicato solo alle dichiarazioni 'Module', 'Sub'o 'Function'.

Esempio

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

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 <Extension()>di estensione . Contrassegnare il modulo in cui è definito il metodo è facoltativo, ma ogni metodo di estensione deve essere contrassegnato. 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. Il modulo che contiene il metodo di estensione viene invece importato, se necessario, per inserirlo nell'ambito. Dopo che il modulo che contiene Print è nell'ambito, il metodo può essere chiamato come se fosse un metodo di istanza normale che non accetta argomenti, ad esempio 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

L'esempio successivo, PrintAndPunctuate, è anche un'estensione a String, questa volta definita con due parametri. Il primo parametro, , aStringstabilisce che il metodo di estensione estende String. Il secondo parametro, punc, è destinato a essere una stringa di segni di punteggiatura passati 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 in un argomento stringa per punc: example.PrintAndPunctuate(".")

L'esempio seguente mostra Print e definito e PrintAndPunctuate chiamato. System.Runtime.CompilerServices viene importato nel modulo di definizione per abilitare 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

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

Si noti che quando vengono richiamati i metodi, non viene inviato alcun argomento per il primo parametro. Il parametro aString nelle definizioni di metodo 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, il metodo di estensione viene eseguito. Questa operazione non si applica ai metodi di istanza normali. È possibile verificare Nothing in modo esplicito 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, incluso quanto segue:

  • Classi (tipi di riferimento)
  • Strutture (tipi di 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, è necessario e non può essere facoltativo. Per questo motivo, Optional i parametri e ParamArray i parametri non possono essere il primo parametro nell'elenco dei parametri.

I metodi di estensione non vengono considerati in associazione tardiva. Nell'esempio seguente l'istruzione anObject.PrintMe() genera un'eccezione MissingMemberException , la stessa eccezione viene visualizzata se la seconda PrintMe definizione del metodo di estensione è stata eliminata.

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 influire su qualsiasi applicazione che usa metodi di estensione.

In genere, i metodi di estensione aggiunti ai tipi che non sono proprietari sono più vulnerabili rispetto ai metodi di estensione aggiunti ai tipi che si controllano. Un numero di cose può verificarsi nelle classi che non è proprietario che può interferire con i metodi di estensione.

  • Se esiste un membro di istanza accessibile con una firma compatibile con gli argomenti nell'istruzione chiamante, senza alcuna conversione limitata richiesta dall'argomento al parametro, il metodo di istanza verrà usato in preferenza per qualsiasi metodo di estensione. Pertanto, se un metodo di istanza appropriato viene aggiunto a una classe a un certo punto, un membro di estensione esistente a cui si fa affidamento 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 la robustezza inserendo metodi di estensione nel proprio spazio dei nomi. I consumer della libreria possono quindi includere uno spazio dei nomi o escluderlo oppure selezionarlo tra spazi dei nomi, separatamente dal resto della libreria.

  • Può essere più sicuro estendere le interfacce che per estendere le classi, soprattutto se non si possiede l'interfaccia o la classe. Una modifica in un'interfaccia influisce su ogni classe che lo implementa. Pertanto, l'autore potrebbe essere meno probabile aggiungere o modificare i metodi in un'interfaccia. Tuttavia, se una classe implementa due interfacce con metodi di estensione con la stessa firma, nessuno dei due metodi di estensione è visibile.

  • Estendere il tipo più specifico che è 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, il metodo di istanza viene scelto in preferenza per qualsiasi metodo di estensione. Il metodo di istanza ha la precedenza anche se il metodo di estensione è una corrispondenza migliore. Nell'esempio seguente contiene ExampleClass un metodo di istanza denominato ExampleMethod con un parametro di tipo Integer. Il metodo ExampleMethod di estensione estende ExampleClasse 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 è ed è Long compatibile solo con il Long parametro nel metodo di estensione. La seconda chiamata a ExampleMethod ha un Integer argomento, arg2e 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 nei 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ò avviene perché entrambi arg1 e arg2 hanno una conversione più ampia in Longe 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, entrambi i metodi possono essere accessibili. Ad esempio, se la classe ExampleClass contiene un metodo denominato ExampleMethod che non accetta argomenti, i metodi di estensione con lo stesso nome, ma sono consentite 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 estesa, 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 nell'ambito e accessibili, verrà richiamato quello con precedenza superiore. La precedenza di un metodo di estensione è basata sul meccanismo usato per portare il metodo nell'ambito. L'elenco seguente mostra la gerarchia di precedenza, dal più alto al più basso.

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

  2. I metodi di estensione definiti all'interno dei tipi di dati nello spazio dei nomi corrente o uno dei relativi genitori, con spazi dei nomi figlio con precedenza superiore rispetto agli spazi dei nomi padre.

  3. I metodi di estensione definiti all'interno di qualsiasi tipo importano nel file corrente.

  4. Metodi di estensione definiti all'interno di qualsiasi importazione dello spazio 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 dello spazio 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 nell'esempio precedente è definito in un modulo denominato StringExtensions, il Print nome completo è StringExtensions.Print(example) anziché example.Print().

Vedi anche