Condividi tramite


Classi (F#)

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

// 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 ...
...

Note

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

Nella sintassi precedente type-name indica qualsiasi identificatore valido. type-params descrive parametri di tipo generico facoltativi. Questo elemento è costituito da nomi di parametri di tipo e vincoli racchiusi tra parentesi acute (< e >). Per ulteriori informazioni, vedere Generics (F#) e Vincoli (F#). parameter-list descrive i parametri dei costruttori. Il primo modificatore di accesso è relativo al tipo; il secondo è relativo al costruttore primario. In entrambi i casi, l'impostazione predefinita è public.

Per specificare la classe di base per una classe, è possibile utilizzare la parola chiave inherit. È necessario fornire argomenti, tra parentesi, per il costruttore della classe di base.

Per dichiarare i valori di funzioni o campi locali per la classe, è possibile utilizzare associazioni let ed è necessario seguire le regole generali per le associazioni let. La sezione do-bindings include codice da eseguire durante la costruzione di un oggetto.

L'elemento member-list è costituito da costruttori, dichiarazioni di istanze e metodi statici, dichiarazioni di interfacce, associazioni astratte e dichiarazioni di proprietà ed eventi aggiuntivi. Per una descrizione di questi elementi, vedere Membri (F#).

L'elemento identifier utilizzato con la parola chiave as facoltativa consente di assegnare un nome alla variabile di istanza, o autoidentificatore, che è possibile utilizzare in una definizione del tipo per fare riferimento all'istanza del tipo. Per ulteriori informazioni, vedere la sezione Autoidentificatori 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 reciproci, ovvero tipi che fanno riferimento uno all'altro, sono uniti dalla parola chiave and analogamente alle funzioni ricorsive reciproche. Per un esempio, vedere la sezione Tipi ricorsivi reciproci.

Costruttori

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

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

new(argument-list) = constructor-body

Il corpo del nuovo costruttore deve richiamare il costruttore primario specificato all'inizio della dichiarazione di classe.

Nell'esempio seguente viene illustrato questo concetto. Nel codice seguente MyClass dispone di 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)

Per ulteriori informazioni, vedere Costruttori (F#).

Associazioni let e do

Le associazioni let e do in una definizione di classe formano il corpo del costruttore di classe primario e vengono pertanto 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 non utilizzato in alcuna funzione o membro, viene compilata in una variabile locale per il costruttore. In caso contrario, viene compilata in un campo della classe. Le espressioni do seguenti vengono compilate nel costruttore primario e consentono di eseguire codice di inizializzazione per ogni istanza. Poiché qualsiasi costruttore aggiuntivo chiama sempre il costruttore primario, le associazioni let e le associazioni do vengono sempre eseguite, indipendentemente dal costruttore chiamato.

Ai campi creati dalle associazioni let è possibile accedere da tutti i metodi e le proprietà della classe, tuttavia non è possibile accedervi dai metodi statici, anche se questi accettano una variabile di istanza come parametro. Non è inoltre possibile accedervi utilizzando l'autoidentificatore, se presente.

Autoidentificatori

Un autoidentificatore è un nome che rappresenta l'istanza corrente. Gli autoidentificatori sono simili alla parola chiave this in C# o C++ o a Me in Visual Basic. È possibile definire un autoidentificatore in due modi diversi, in base al fatto che si desideri che l'autoidentificatore si trovi nell'ambito per l'intera definizione di classe o solo per un singolo metodo.

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

Per definire un autoidentificatore solo per un metodo, fornire l'autoidentificatore 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 in cui è possibile creare un autoidentificatore. Nella prima riga la parola chiave as viene utilizzata per definire l'autoidentificatore. Nella quinta riga l'identificatore this viene utilizzato per definire un autoidentificatore 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 degli altri linguaggi .NET, è possibile assegnare all'autoidentificatore il nome desiderato e non è necessario utilizzare nomi come self, Me o this.

L'autoidentificatore dichiarato con la parola chiave as non viene inizializzato fino a quando non sono state eseguite le associazioni let. Esso non può pertanto essere utilizzato nelle associazioni let. È possibile utilizzare l'autoidentificatore nella sezione relativa alle associazioni do.

Parametri di tipo generico

I parametri di tipo generico vengono specificati tra parentesi acute (< e >), in forma di virgoletta singola seguita da un identificatore. Se sono presenti più parametri di tipo generico, vengono separati da virgole. Il parametro di tipo generico è incluso nell'ambito della 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 del tipo vengono derivati quando il tipo viene utilizzato. Nel codice seguente il tipo derivato è una sequenza di tuple.

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

Impostazione dell'ereditarietà

La clausola inherit identifica la classe di base diretta, se ne è presente una. In F# è consentita una sola classe di base diretta. Le interfacce implementate da una classe non sono considerate classi di base. Per informazioni sulle interfacce, vedere l'argomento Interfacce (F#).

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

Per ulteriori informazioni, vedere Ereditarietà (C#).

Sezione relativa ai membri

In questa sezione è possibile definire metodi di istanza o statici, proprietà, implementazioni di interfacce, membri astratti, dichiarazioni di eventi e costruttori aggiuntivi. In questa sezione non possono essere incluse le associazioni let e do. Poiché è possibile aggiungere membri a numerosi tipi F#, oltre che alle classi, questi vengono illustrati in un argomento distinto, Membri (F#).

Tipi ricorsivi reciproci

Quando si definiscono tipi che fanno riferimento uno all'altro in modo circolare, si uniscono le definizioni dei tipi utilizzando la parola chiave and. La parola chiave and sostituisce la parola chiave type in tutte le definizioni ad eccezione della prima, 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.

Situazioni in cui utilizzare classi, unioni, record e strutture

Poiché i tipi tra cui è possibile scegliere sono molto numerosi, è necessario comprendere bene le caratteristiche di ognuno di essi, per poter scegliere il tipo appropriato per una situazione specifica. Le classi sono progettate per l'utilizzo nei contesti di programmazione orientata a oggetti. La programmazione orientata a oggetti rappresenta il paradigma principale utilizzato in applicazioni scritte per .NET Framework. Se il codice F# deve essere utilizzato insieme a .NET Framework o a un'altra libreria orientata a oggetti e, in particolare, se è necessaria un'estensione da un sistema di tipi orientato a oggetti, ad esempio una libreria dell'interfaccia utente, le classi sono probabilmente appropriate.

Se non è necessaria una stretta interoperabilità con il codice orientato a oggetti, oppure se si scrive codice autonomo e pertanto non soggetto a interazione frequente con il codice orientato a oggetti, prendere in considerazione l'utilizzo di record e unioni discriminate. Una singola unione discriminata ben progettata, insieme a codice di corrispondenza dei modelli appropriato, può spesso rappresentare un'alternativa più semplice rispetto a una gerarchia di oggetti. Per ulteriori informazioni sulle unioni discriminate, vedere Unioni discriminate (F#).

I record hanno il vantaggio di essere più semplici delle classi, ma non sono appropriati quando le richieste di un tipo sono superiori rispetto a ciò che è possibile ottenere con la loro semplicità. I record sono fondamentalmente aggregazioni semplici di valori, senza costruttori distinti che consentono di eseguire azioni personalizzate, senza campi nascosti e senza implementazioni di interfaccia o ereditarietà. Sebbene sia possibile aggiungere ai record membri, come le proprietà e i metodi, per renderne più complesso il comportamento, i campi archiviati in un record sono comunque semplici aggregazioni di valori. Per ulteriori informazioni sui record, vedere Record (F#).

Anche le strutture sono utili per aggregazioni di dati di piccole dimensioni, ma differiscono dalle classi e dai record in quanto sono tipi di valore .NET. Le classi e i record sono tipi di riferimento .NET. La semantica dei tipi di valore è diversa da quella dei tipi di riferimento, in quanto i tipi di valore vengono passati per valore. Questo significa che vengono copiati bit per bit quando vengono passati come parametro o restituiti da una funzione. Vengono inoltre archiviati nello stack oppure, se vengono utilizzati come campo, vengono incorporati all'interno dell'oggetto padre anziché archiviati in una posizione separata nell'heap. Le strutture sono pertanto appropriate per i dati a cui si accede di frequente quando il sovraccarico dovuto all'accesso all'heap costituisce un problema. Per ulteriori informazioni sulle strutture, vedere Strutture (F#).

Vedere anche

Riferimenti

Ereditarietà (C#)

Interfacce (F#)

Concetti

Membri (F#)

Altre risorse

Riferimenti per il linguaggio F#