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 self
esempio , Me
o 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.