Condividi tramite


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 Sub routine o una Function 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> di estensione dello System.Runtime.CompilerServices spazio dei nomi e devono essere definiti in un modulo. Se un metodo di estensione viene definito all'esterno di un modulo, il compilatore di Visual Basic genera un 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 il metodo viene eseguito, 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 Visual Basic Module, Subo Function. Se si applica a Class o Structure, il compilatore di 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 per il 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 di estensione <Extension()>. 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. Al contrario, il modulo che contiene il metodo di estensione viene importato, se necessario, per inserirlo nell'ambito. Dopo che il modulo che Print contiene è nell'ambito, il metodo può essere chiamato come se fosse un metodo di istanza comune 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 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 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 passando un argomento di tipo 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 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 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 metodo di istanza comune.

Si noti che quando i metodi vengono richiamati, non viene inviato alcun argomento in per il primo parametro. Il parametro aString nelle definizioni di metodo precedenti è associato a example, l'istanza di String che le 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 metodi di istanza ordinari. È possibile cercare Nothing in modo esplicito nel metodo di estensione.

Tipi che possono essere estesi

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

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

Poiché il primo parametro specifica il tipo di dati esteso dal metodo di estensione, è obbligatorio e non può essere facoltativo. Per questo motivo, Optional i parametri e ParamArray i parametri 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 noterà 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 di cui non si è proprietari sono più vulnerabili rispetto ai metodi di estensione aggiunti ai tipi che si controllano. Alcune cose possono verificarsi nelle classi di cui non si è proprietari che possono interferire con i metodi di estensione.

  • Se esiste un membro dell'istanza accessibile con una firma compatibile con gli argomenti nell'istruzione chiamante, senza conversioni di tipo narrowing richieste dall'argomento al parametro, il metodo di istanza verrà usato in preferenza per qualsiasi metodo di estensione. Pertanto, se a un certo punto viene aggiunto un metodo di istanza appropriato a una classe, 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 possono avere la precedenza sull'estensione originale.

  • È possibile migliorare l'affidabilità inserendo i metodi di estensione nel proprio spazio dei nomi. Gli utenti della tua libreria possono quindi includere un namespace o escluderlo, oppure selezionare tra diversi namespace, separatamente dal resto della libreria.

  • Può essere più sicuro estendere le interfacce rispetto a quelle di estendere le classi, soprattutto se non si è proprietari dell'interfaccia o della classe. Una modifica in un'interfaccia influisce su ogni classe che la implementa. Pertanto, l'autore potrebbe essere meno probabile aggiungere o modificare metodi in un'interfaccia. Tuttavia, se una classe implementa due interfacce con metodi di estensione con la stessa firma, nessun metodo di estensione è 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, 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 è più adatto. Nell'esempio ExampleClass seguente contiene 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 è Long e compatibile solo con il parametro Long nel metodo di estensione. La seconda chiamata a ExampleMethod ha un Integer argomento, 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

Ora inverti 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ò è dovuto al fatto che sia arg1 che arg2 hanno una conversione espansiva a 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, i metodi di estensione con lo stesso nome ma firme diverse sono consentiti, 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 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 in uno dei relativi elementi padre, con 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 delle importazioni di namespace nel file in uso.

  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 utilizzare il nome completo per specificare il metodo che si sta chiamando. Se il Print metodo nell'esempio precedente viene definito in un modulo denominato StringExtensions, il nome completo è StringExtensions.Print(example) invece di example.Print().

Vedere anche