Estensioni dei tipi

Le estensioni dei tipi (dette anche estensioni) sono una famiglia di funzionalità che consentono di aggiungere nuovi membri a un tipo di oggetto definito in precedenza. Le tre funzionalità sono:

  • Estensioni intrinseche dei tipi
  • Estensioni di tipo facoltative
  • Metodi di estensione

Ognuno può essere usato in scenari diversi e presenta compromessi diversi.

Sintassi

// Intrinsic and optional extensions
type typename with
    member self-identifier.member-name =
        body
    ...

// Extension methods
open System.Runtime.CompilerServices

[<Extension>]
type Extensions() =
    [<Extension>]
    static member extension-name (ty: typename, [args]) =
        body
    ...

Estensioni intrinseche dei tipi

Un'estensione di tipo intrinseco è un'estensione di tipo che estende un tipo definito dall'utente.

Le estensioni intrinseche del tipo devono essere definite nello stesso file e nello stesso spazio dei nomi o modulo del tipo che stanno estendendo. Qualsiasi altra definizione comporterà la creazione di estensioni di tipo facoltative.

Le estensioni intrinseche dei tipi sono talvolta un modo più pulito per separare le funzionalità dalla dichiarazione del tipo. L'esempio seguente illustra come definire un'estensione di tipo intrinseco:

namespace Example

type Variant =
    | Num of int
    | Str of string
  
module Variant =
    let print v =
        match v with
        | Num n -> printf "Num %d" n
        | Str s -> printf "Str %s" s

// Add a member to Variant as an extension
type Variant with
    member x.Print() = Variant.print x

L'uso di un'estensione di tipo consente di separare ognuna delle opzioni seguenti:

  • Dichiarazione di un Variant tipo
  • Funzionalità per stampare la Variant classe in base alla relativa "forma"
  • Un modo per accedere alla funzionalità di stampa con notazione in stile .oggetto

Si tratta di un'alternativa alla definizione di tutti gli elementi come membro in Variant. Anche se non è un approccio intrinsecamente migliore, può essere una rappresentazione più pulita delle funzionalità in alcune situazioni.

Le estensioni intrinseche del tipo vengono compilate come membri del tipo che aumentano e vengono visualizzate nel tipo quando il tipo viene esaminato dalla reflection.

Estensioni di tipo facoltative

Un'estensione di tipo facoltativa è un'estensione che viene visualizzata all'esterno del modulo originale, dello spazio dei nomi o dell'assembly del tipo esteso.

Le estensioni di tipo facoltative sono utili per estendere un tipo che non è stato definito manualmente. Ad esempio:

module Extensions

type IEnumerable<'T> with
    /// Repeat each element of the sequence n times
    member xs.RepeatElements(n: int) =
        seq {
            for x in xs do
                for _ in 1 .. n -> x
        }

È ora possibile accedere RepeatElements come se fosse un membro di IEnumerable<T> purché il Extensions modulo venga aperto nell'ambito in cui si sta lavorando.

Le estensioni facoltative non vengono visualizzate nel tipo esteso quando esaminate dalla reflection. Le estensioni facoltative devono trovarsi nei moduli e sono incluse nell'ambito solo quando il modulo che contiene l'estensione è aperto o è in altro modo nell'ambito.

I membri di estensione facoltativi vengono compilati in membri statici per i quali l'istanza dell'oggetto viene passata in modo implicito come primo parametro. Tuttavia, agiscono come se fossero membri dell'istanza o membri statici in base alla modalità di dichiarazione.

I membri di estensione facoltativi non sono visibili anche ai consumer C# o Visual Basic. Possono essere usati solo in altro codice F#.

Limitazione generica delle estensioni di tipo intrinseche e facoltative

È possibile dichiarare un'estensione di tipo in un tipo generico in cui la variabile di tipo è vincolata. Il requisito è che il vincolo della dichiarazione di estensione corrisponda al vincolo del tipo dichiarato.

Tuttavia, anche quando i vincoli vengono confrontati tra un tipo dichiarato e un'estensione del tipo, è possibile dedurre un vincolo dal corpo di un membro esteso che impone un requisito diverso per il parametro di tipo rispetto al tipo dichiarato. Ad esempio:

open System.Collections.Generic

// NOT POSSIBLE AND FAILS TO COMPILE!
//
// The member 'Sum' has a different requirement on 'T than the type IEnumerable<'T>
type IEnumerable<'T> with
    member this.Sum() = Seq.sum this

Non è possibile ottenere questo codice da usare con un'estensione di tipo facoltativa:

  • Così com'è, il Sum membro ha un vincolo diverso su 'T (static member get_Zero e static member (+)) rispetto a quello definito dall'estensione del tipo.
  • Se si modifica l'estensione del tipo in modo che abbia lo stesso vincolo Sum , non corrisponderà più al vincolo definito in IEnumerable<'T>.
  • La modifica member this.Sum di in member inline this.Sum restituirà un errore che indica che i vincoli di tipo non corrispondono.

I metodi statici desiderati sono i "float in space" e possono essere presentati come se si estendesse un tipo. In questo caso, i metodi di estensione diventano necessari.

Metodi di estensione

Infine, i metodi di estensione (talvolta denominati "membri dell'estensione di stile C#") possono essere dichiarati in F# come metodo membro statico in una classe.

I metodi di estensione sono utili per quando si desidera definire estensioni in un tipo generico che vincola la variabile di tipo. Ad esempio:

namespace Extensions

open System.Collections.Generic
open System.Runtime.CompilerServices

[<Extension>]
type IEnumerableExtensions =
    [<Extension>]
    static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs

Se usato, questo codice lo farà apparire come se Sum fosse definito in IEnumerable<T>, purché Extensions sia stato aperto o sia incluso nell'ambito.

Affinché l'estensione sia disponibile per VB.NET codice, è necessario un componente aggiuntivo ExtensionAttribute a livello di assembly:

module AssemblyInfo
open System.Runtime.CompilerServices
[<assembly:Extension>]
do ()

Altre osservazioni

Le estensioni dei tipi hanno anche gli attributi seguenti:

  • È possibile estendere qualsiasi tipo a cui è possibile accedere.
  • Le estensioni intrinseche e facoltative dei tipi possono definire qualsiasi tipo di membro, non solo metodi. Le proprietà di estensione sono quindi possibili, ad esempio.
  • Il self-identifier token nella sintassi rappresenta l'istanza del tipo richiamato, proprio come i membri ordinari.
  • I membri estesi possono essere membri statici o dell'istanza.
  • Le variabili di tipo in un'estensione di tipo devono corrispondere ai vincoli del tipo dichiarato.

Esistono anche le limitazioni seguenti per le estensioni dei tipi:

  • Le estensioni di tipo non supportano metodi virtuali o astratti.
  • Le estensioni dei tipi non supportano metodi di override come estensioni.
  • Le estensioni dei tipi non supportano parametri di tipo risolti in modo statico.
  • Le estensioni di tipo facoltative non supportano i costruttori come estensioni.
  • Le estensioni di tipo non possono essere definite nelle abbreviazioni dei tipi.
  • Le estensioni di tipo non sono valide per byref<'T> (anche se possono essere dichiarate).
  • Le estensioni di tipo non sono valide per gli attributi (anche se possono essere dichiarate).
  • È possibile definire estensioni che eseguono l'overload di altri metodi con lo stesso nome, ma il compilatore F# dà la preferenza ai metodi non di estensione se è presente una chiamata ambigua.

Infine, se esistono più estensioni di tipo intrinseche per un tipo, tutti i membri devono essere univoci. Per le estensioni di tipo facoltative, i membri in estensioni di tipo diversi allo stesso tipo possono avere gli stessi nomi. Gli errori di ambiguità si verificano solo se il codice client apre due ambiti diversi che definiscono gli stessi nomi dei membri.

Vedi anche