Classi (F#)

Le classi sono tipi che rappresentano oggetti che possono avere proprietà, metodi ed eventi.

Sintassi

// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...

Osservazioni:

Le classi rappresentano la descrizione fondamentale dei tipi di oggetto .NET; la classe è il concetto di tipo primario che supporta la programmazione orientata agli oggetti in F#.

Nella sintassi precedente, è type-name qualsiasi identificatore valido. type-params Descrive i parametri di tipo generico facoltativi. È costituito da nomi di parametri di tipo e vincoli racchiusi tra parentesi angolari (< e >). Per altre informazioni, vedere Generics and Constraints.For more information, see Generics and Constraints. parameter-list Descrive i parametri del costruttore. Il primo modificatore di accesso riguarda il tipo; il secondo riguarda il costruttore primario. In entrambi i casi, il valore predefinito è public.

Specificare la classe base per una classe usando la inherit parola chiave . È necessario specificare argomenti, tra parentesi, per il costruttore della classe base.

Si dichiarano campi o valori di funzione locali per la classe usando let associazioni ed è necessario seguire le regole generali per let le associazioni. La do-bindings sezione include il codice da eseguire durante la costruzione di oggetti.

È member-list costituito da costruttori aggiuntivi, dichiarazioni di metodi statici e di istanza, dichiarazioni di interfaccia, associazioni astratte e dichiarazioni di proprietà ed eventi. Questi sono descritti in Membri.

L'oggetto identifier utilizzato con la parola chiave facoltativa as assegna un nome alla variabile di istanza o all'identificatore automatico, che può essere usato nella definizione del tipo per fare riferimento all'istanza del tipo. Per altre informazioni, vedere la sezione Identificatori self più avanti in questo argomento.

Le parole chiave class e end che contrassegnano l'inizio e la fine della definizione sono facoltative.

I tipi ricorsivi, che fanno riferimento l'uno all'altro, vengono uniti insieme alla and parola chiave esattamente come le funzioni ricorsive a vicenda. Per un esempio, vedere la sezione Tipi ricorsivi a vicenda.

Costruttori

Il costruttore è il codice che crea un'istanza del tipo di classe. I costruttori per le classi funzionano in modo leggermente diverso in F# rispetto a quelli in altri linguaggi .NET. In una classe F# è sempre presente un costruttore primario i cui argomenti sono descritti in parameter-list che seguono il nome del tipo e il cui corpo è costituito dalle let associazioni (e let rec) all'inizio della dichiarazione di classe e dalle do associazioni che seguono. Gli argomenti del costruttore primario sono inclusi nell'ambito della dichiarazione di classe.

È possibile aggiungere altri costruttori usando la new parola chiave per aggiungere un membro, come indicato di seguito:

new(argument-list) = constructor-body

Il corpo del nuovo costruttore deve richiamare il costruttore primario specificato nella parte superiore della dichiarazione di classe.

Nell'esempio seguente viene illustrato questo concetto. Nel codice seguente sono MyClass presenti due costruttori, un costruttore primario che accetta due argomenti e un altro costruttore che non accetta argomenti.

type MyClass1(x: int, y: int) =
    do printfn "%d %d" x y
    new() = MyClass1(0, 0)

let e do Bindings

Le let associazioni e do in una definizione di classe formano il corpo del costruttore della classe primaria e quindi vengono eseguite ogni volta che viene creata un'istanza di classe. Se un'associazione let è una funzione, viene compilata in un membro. Se l'associazione let è un valore che non viene usato in alcuna funzione o membro, viene compilato in una variabile locale per il costruttore. In caso contrario, viene compilato in un campo della classe . Le do espressioni seguenti vengono compilate nel costruttore primario ed eseguono il codice di inizializzazione per ogni istanza. Poiché tutti i costruttori aggiuntivi chiamano sempre il costruttore primario, le let associazioni e do le associazioni vengono sempre eseguite indipendentemente dal costruttore chiamato.

È possibile accedere ai campi creati dalle let associazioni in tutti i metodi e le proprietà della classe. Non è tuttavia possibile accedervi da metodi statici, anche se i metodi statici accettano una variabile di istanza come parametro. Non è possibile accedervi usando l'identificatore self, se presente.

Identificatori self

Un identificatore self è un nome che rappresenta l'istanza corrente. Gli identificatori self sono simili alla this parola chiave in C# o C++ o Me in Visual Basic. È possibile definire un identificatore automatico in due modi diversi, a seconda che si desideri che l'identificatore self sia nell'ambito per l'intera definizione della classe o solo per un singolo metodo.

Per definire un identificatore autonomo per l'intera classe, usare la as parola chiave dopo le parentesi di chiusura dell'elenco di parametri del costruttore e specificare il nome dell'identificatore.

Per definire un identificatore automatico per un solo metodo, specificare l'identificatore automatico nella dichiarazione del membro, subito prima del nome del metodo e di un punto (.) come separatore.

Nell'esempio di codice seguente vengono illustrati i due modi per creare un identificatore autonomo. Nella prima riga viene usata la as parola chiave per definire l'identificatore automatico. Nella quinta riga, l'identificatore this viene usato per definire un identificatore automatico il cui ambito è limitato al metodo PrintMessage.

type MyClass2(dataIn) as self =
    let data = dataIn
    do
        self.PrintMessage()
    member this.PrintMessage() =
        printf "Creating MyClass2 with Data %d" data

A differenza di altri linguaggi .NET, è possibile denominare l'identificatore automatico, tuttavia; non si è limitati ai nomi, ad selfesempio , Meo this.

L'identificatore auto dichiarato con la as parola chiave non viene inizializzato fino a quando il costruttore di base non viene inizializzato. Pertanto, se usato prima o all'interno del costruttore di base, System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized. verrà generato durante il runtime. È possibile usare liberamente l'identificatore automatico dopo il costruttore di base, ad esempio in let associazioni o do associazioni.

Parametri di tipo generico

I parametri di tipo generico vengono specificati tra parentesi angolari (< e >), sotto forma di virgolette singole seguite da un identificatore. Più parametri di tipo generico sono separati da virgole. Il parametro di tipo generico è incluso nell'ambito in tutta la dichiarazione. Nell'esempio di codice seguente viene illustrato come specificare parametri di tipo generico.

type MyGenericClass<'a>(x: 'a) =
    do printfn "%A" x

Gli argomenti di tipo vengono dedotti quando viene usato il tipo . Nel codice seguente il tipo dedotto è una sequenza di tuple.

let g1 = MyGenericClass(seq { for i in 1..10 -> (i, i * i) })

Specifica dell'ereditarietà

La inherit clausola identifica la classe base diretta, se presente. In F# è consentita una sola classe di base diretta. Le interfacce implementate da una classe non sono considerate classi di base. Le interfacce sono illustrate nell'argomento Interfacce .

È possibile accedere ai metodi e alle proprietà della classe base dalla classe derivata usando la parola chiave base language come identificatore, seguito da un punto (.) e dal nome del membro.

Per altre informazioni, vedere Ereditarietà.

Sezione Members

In questa sezione è possibile definire metodi statici o di istanza, proprietà, implementazioni dell'interfaccia, membri astratti, dichiarazioni di eventi e costruttori aggiuntivi. Le associazioni non possono essere visualizzate in questa sezione. Poiché i membri possono essere aggiunti a un'ampia gamma di tipi F# oltre alle classi, vengono discussi in un argomento separato, Membri.

Tipi ricorsivi a vicenda

Quando si definiscono tipi che si fanno riferimento tra loro in modo circolare, si stringono insieme le definizioni dei tipi usando la and parola chiave . La and parola chiave sostituisce la type parola chiave in tutti tranne la prima definizione, come indicato di seguito.

open System.IO

type Folder(pathIn: string) =
    let path = pathIn
    let filenameArray: string array = Directory.GetFiles(path)
    member this.FileArray = Array.map (fun elem -> new File(elem, this)) filenameArray

and File(filename: string, containingFolder: Folder) =
    member this.Name = filename
    member this.ContainingFolder = containingFolder

let folder1 = new Folder(".")

for file in folder1.FileArray do
    printfn "%s" file.Name

L'output è un elenco di tutti i file nella directory corrente.

Quando usare classi, unioni, record e strutture

Data la varietà di tipi tra cui scegliere, è necessario avere una buona conoscenza di ciò che ogni tipo è progettato per selezionare il tipo appropriato per una determinata situazione. Le classi sono progettate per l'uso in contesti di programmazione orientati agli oggetti. La programmazione orientata agli oggetti è il paradigma dominante usato nelle applicazioni scritte per .NET Framework. Se il codice F# deve collaborare con .NET Framework o un'altra libreria orientata agli oggetti e soprattutto se è necessario estendersi da un sistema di tipi orientato agli oggetti, ad esempio una libreria dell'interfaccia utente, è probabile che le classi siano appropriate.

Se non si interagisce strettamente con il codice orientato agli oggetti o se si scrive codice autonomo e pertanto protetto da frequenti interazioni con codice orientato agli oggetti, è consigliabile usare una combinazione di classi, record e unioni discriminate. Un'unica unione discriminante ben ponderata, insieme al codice di corrispondenza dei criteri appropriato, può spesso essere usata come alternativa più semplice a una gerarchia di oggetti. Per altre informazioni sulle unioni discriminate, vedere Unioni discriminate.

I record hanno il vantaggio di essere più semplici delle classi, ma i record non sono appropriati quando le richieste di un tipo superano ciò che è possibile ottenere con la loro semplicità. I record sono fondamentalmente semplici aggregazioni di valori, senza costruttori separati che possono eseguire azioni personalizzate, senza campi nascosti e senza implementazioni di ereditarietà o interfaccia. Anche se i membri, ad esempio proprietà e metodi, possono essere aggiunti ai record per rendere il comportamento più complesso, i campi archiviati in un record sono ancora una semplice aggregazione di valori. Per altre informazioni sui record, vedere Record.

Le strutture sono utili anche per piccole aggregazioni di dati, ma differiscono da classi e record in quanto sono tipi valore .NET. Le classi e i record sono tipi di riferimento .NET. La semantica dei tipi valore e dei tipi riferimento è diversa in quanto i tipi valore vengono passati per valore. Ciò significa che vengono copiati bit per bit quando vengono passati come parametro o restituiti da una funzione. Vengono anche archiviati nello stack o, se vengono usati come campo, incorporati all'interno dell'oggetto padre anziché archiviati nella propria posizione separata nell'heap. Pertanto, le strutture sono appropriate per i dati a cui si accede di frequente quando l'overhead di accesso all'heap è un problema. Per altre informazioni sulle strutture, vedere Struct.

Vedi anche