Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Benvenuti in F# 11! Questa versione consente di mantenere distinti i modelli di dominio, riutilizzare algoritmi generici, coordinare il lavoro asincrono e ridurre l'utilizzo della memoria del compilatore.
- Gli spread dei record riutilizzano i campi senza imporre ai modelli di archiviazione e applicativi di condividere un tipo.
- I membri di estensione negli algoritmi generici consentono ai tipi esistenti di condividere le operazioni senza wrapper.
- .NET'integrazione mantiene i metadati delegati e riutilizza la documentazione dell'API.
- Gli helper async e per le attività propagano l'annullamento oltre i confini delle API e limitano il lavoro concorrente.
- I miglioramenti del compilatore e della libreria riducono le analisi ripetute ed evitano alcune allocazioni di memoria.
Grazie alla community di F# per le proposte, le implementazioni, le revisioni e i test che hanno reso possibili queste modifiche. I collegamenti collaboratori accompagnano le funzionalità seguenti.
Note
Gli esempi richiedono un compilatore F# 11 e FSharp.Core 11 con le funzionalità illustrate. Nella .NET 11.0.100 SDK, le funzionalità F# 11.2 richiedono <LangVersion>preview</LangVersion>; vedere le impostazioni della lingua e il supporto dell'SDK pianificato. L'esempio di runtime-async presenta requisiti di runtime aggiuntivi; il solo SDK .NET 11 precedente potrebbe non contenere tutte le modifiche.
Espansione dei record: modelli distinti senza mappature ripetitive
Per mantenere distinti i modelli di archiviazione e dell'applicazione, le espansioni di record riutilizzano i campi condivisi senza ripeterne la dichiarazione. Qui i modelli condividono i dati del cliente, ma usano tipi di identificatore diversi:
open System
type CustomerId = CustomerId of Guid
type CustomerDetails = { Name: string; PreferredLanguage: string }
type CustomerRow =
{ ...CustomerDetails
Id: string
InternalNote: string }
type Customer =
{ ...CustomerDetails
Id: CustomerId }
...CustomerDetails fornisce dichiarazioni di campo. Al confine, ...row copia i valori mentre la conversione analizza l'identificatore:
let toCustomer (row: CustomerRow) =
match Guid.TryParse row.Id with
| true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
| false, _ -> Error "Invalid customer identifier."
La destinazione mantiene i dettagli condivisi, sostituisce Id ed esclude InternalNote.
Guid.TryParse, non la distribuzione, convalida l'identificatore; L'input in formato non valido produce Error.
Questo affronta il problema di mapping a livello di applicazione. I valori spread combinano anche record denominati e anonimi; gli spread di tipo creano solo record denominati. I campi successivi vincono e le sorgenti vengono eseguite una sola volta nell'ordine. Vedere operatori spread dei record per esempi di composizione e rischi di evoluzione condivisa.
Grazie a @brianrourkeboll per la distribuzione dei record.
Metodi di estensione per algoritmi, operatori e tuple generici
Per condividere un algoritmo tra tipi esistenti, F# 11.2 consente ai membri di estensione di soddisfare i vincoli SRTP (statically resolved type parameter constraints). Ad esempio, una trasformazione può gestire elenchi e opzioni senza tipi wrapper.
Note
In .NET 11.0.100 SDK, abilitare <LangVersion>preview</LangVersion> quando vengono risolte le estensioni e aprire il relativo modulo. Vedere i requisiti della libreria.
Condividere una trasformazione tra elenchi e opzioni
Con map della libreria disponibile nel contesto, l'applicazione può pulire una delle due rappresentazioni, modificare i tipi degli elementi e comporre trasformazioni:
let inline cleanNames values =
map (fun (name: string) -> name.Trim().ToUpperInvariant()) values
let batch: string list = cleanNames [ " Ada "; " grace " ]
let optional: string option = cleanNames (Some " linus ")
let missing: string option = cleanNames (None: string option)
let lengths: int list = map String.length batch
let nested: int option list = map (map String.length) [ Some "abc"; None ]
printfn "%A; %A; %A" batch optional missing
printfn "%A; %A" lengths nested
// ["ADA"; "GRACE"]; Some "LINUS"; None
// [3; 5]; [Some 3; None]
Gli adattatori in Structural.Adapters usano operazioni familiari, anziché imporre nuovi contenitori:
type List<'T> with
static member Map(source: 'T list, mapping: 'T -> 'U) : 'U list =
List.map mapping source
type Option<'T> with
static member Map(source: 'T option, mapping: 'T -> 'U) : 'U option =
Option.map mapping source
La funzione Shared in Structural.Algorithms chiede al tipo di input un membro statico Map:
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
^Input e ^Output sono tipi risolti staticamente. A ogni chiamata concreta, il compilatore seleziona il membro con la firma richiesta. Le estensioni non aggiungono un'implementazione dell'interfaccia.
L'applicazione completa apre entrambi i moduli.
Operatori, contesti e tipi di output
Lo stesso meccanismo supporta gli operatori di tupla e la propagazione del contesto e la costruzione diretta dall'output. Le intestazioni di tupla native richiedono che la funzionalità F# 11.2 sia abilitata nelle dichiarazioni, nonché nei punti di chiamata in cui viene risolta l’estensione.
Un'aggiunta separata, AllowOverloadOnReturnType, consente a una factory di dichiarare overload distinti in base ai relativi valori di ritorno:
type Inject =
[<AllowOverloadOnReturnType>]
static member Pure(value: 'T) : 'T list = [value]
[<AllowOverloadOnReturnType>]
static member Pure(value: 'T) : 'T option = Some value
let batch: string list = Inject.Pure "ready"
let optional: string option = Inject.Pure "ready"
// ["ready"] and Some "ready"
Questo attributo Core 11 non richiede l'anteprima. L'annotazione del risultato seleziona un overload; senza di essa la chiamata rimane ambigua. Vedi Selezionare per tipo di ritorno.
Grazie a @gusty per aver esteso il supporto per la programmazione generica.
l'integrazione .NET e l'esperienza di sviluppo
Le delegate dirette preservano il contratto reale dell'handler
Per esporre ai framework i nomi dei gestori e gli attributi di binding, F# 11 può puntare direttamente a una funzione con nome compatibile anziché a un wrapper. ASP.NET Core Minimal APIs possono quindi leggere il contratto di route, servizio e intestazione:
open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.DependencyInjection
type ShipmentStore() =
member _.Status trackingId =
if trackingId = "SHP-1042" then "Dispatched" else "Unknown"
let status
(trackingId: string)
([<FromServices>] store: ShipmentStore)
([<FromHeader(Name = "X-Customer")>] customer: string) =
$"{customer}: {trackingId} is {store.Status trackingId}"
let handler = Func<string, ShipmentStore, string, string>(status)
Vedi il progetto avviabile e le regole di costruzione/compatibilità. Le catture e l'adattamento degli argomenti possono comunque richiedere wrapper.
Grazie a @kerams per le deleghe dirette.
Riutilizzare la documentazione XML
Per mantenere i commenti condivisi dell'API in un unico punto, inseriscili in shared.xml:
<docs>
<tracking>
<remarks>An unrecognized tracking identifier has status Unknown.</remarks>
</tracking>
</docs>
Con GenerateDocumentationFile abilitato, F# 11 espande <include> nelle voci dei membri generate:
/// <summary>Find the latest status of a shipment.</summary>
/// <include file="shared.xml" path="/docs/tracking/*"/>
let status trackingId =
if trackingId = "SHP-1042" then "Dispatched" else "Unknown"
/// <summary>Find the latest statuses of several shipments.</summary>
/// <include file="shared.xml" path="/docs/tracking/*"/>
let statuses trackingIds =
trackingIds |> List.map (fun id -> id, status id)
L'implementazione include aggiorna entrambe le descrizioni da un frammento. Separatamente, il resolver del tooling espone le descrizioni di base per <inheritdoc/>. Il compilatore lascia tale tag per i consumer della documentazione. Vedi la documentazione XML per gli esempi e le regole di risoluzione.
Grazie a @T-Gro per la documentazione XML condivisa in FSharp.Core.
Eseguire il debug di funzioni inline in compilazioni non ottimizzate
Per esaminare gli argomenti helper inline, impostare un punto di interruzione su let total in una compilazione non ottimizzata e entrare in addFee. F# 11 ora preferisce metodi invocabili nelle build seguenti:
let inline addFee (fee: decimal) (amount: decimal) =
let total = amount + fee
total
let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42
Le build ottimizzate mantengono l'inlining. Alcuni costrutti devono comunque essere espansi. Vedi Eseguire il debug di funzioni inline per informazioni su tali limiti e sul controllo --always-inline.
Coordinamento asincrono e coordinamento delle attività
Per connettere flussi di lavoro F# con API basate su task, FSharp.Core 11 aggiunge bridge con supporto all'annullamento, batch delimitati e trasformazioni dei risultati.
I/O in sospeso vincolato, non solo la raccolta dei risultati
Un batch di letture di grandi dimensioni può iniziare più lavoro rispetto a quello che un'applicazione deve avere in sospeso. Per decidere quando inizia il lavoro basato su attività, un coordinatore necessita di fabbriche, non di attività già avviate:
open System.IO
open System.Threading
let readFiles (paths: seq<string>) (ct: CancellationToken) =
paths
|> Seq.map (fun path ->
fun token -> File.ReadAllTextAsync(path, token))
|> Task.parallelLimit 4 ct
Al massimo quattro operazioni di lettura vengono eseguite in contemporanea; i risultati mantengono l'ordine di input. Un errore richiede l'annullamento dei processi correlati tramite i token forniti. Questo limita la concorrenza, non le richieste al secondo. Vedere Funzioni asincrone e task per varianti sequenziali e con risultato Unit.
Mantenere l'annullamento e l'eccezione sottostante attraverso il confine
Quando un flusso di lavoro avvia un'attività esterna, il token deve spostarsi con tale attività.
Async.StartTaskImmediate passa il token del flusso di lavoro in esecuzione alla factory delle attività e attende il completamento:
open System.IO
let tryReadFile path =
async {
try
let! contents =
Async.StartTaskImmediate(fun ct -> File.ReadAllTextAsync(path, ct))
return Some contents
with :? FileNotFoundException ->
return None
}
Un errore di un singolo task viene gestito da questo handler come oggetto sottostante FileNotFoundException. Per un'attività o un'attività valore già creata, usare Async.Await; il precedente Async.AwaitTask mantiene il comportamento di wrapping delle eccezioni.
Per i chiamanti basati su task, Task.startAsyncImmediate token workflow fornisce il reverse bridge. Inizia sul thread chiamante fino alla prima sospensione asincrona.
Usa helper abbinati per trasformazioni brevi
Per trasformare un risultato senza un'espressione di calcolo completa, usare gli helper corrispondenti in Microsoft.FSharp.Control. Ad esempio, leggere un file, determinarne la lunghezza e restituire un errore ordinario come Error:
open System.IO
open System.Threading
let fileLength (path: string) (ct: CancellationToken) =
File.ReadAllTextAsync(path, ct)
|> Task.map String.length
|> Task.catch
Un file contenente Report 1 restituisce Ok 8. Un file mancante fornisce un Error contenente FileNotFoundException. L'annullamento continua a propagarsi.
Async e ValueTask offrono helper bind, map e di errore corrispondenti; vedere Funzioni asincrone e task. Usare un'espressione di calcolo per i rami condizionali o diversi valori dipendenti.
Gli helper ValueTask richiedono la build net10.0 o netstandard2.1 di Core e utilizzo singolo.
Grazie a @bartelink per i contributi all'API async/task.
Rilancia dopo la segnalazione di errori asincroni
La registrazione operativa deve aggiungere informazioni senza modificare ciò che il chiamante intercetta o nascondere dove è iniziato l'errore. I gestori di espressioni di calcolo possono ora usare reraise () anche dopo aver usato await sul logger:
Note
Computation-expression rethrow è una funzionalità F# 11.2. Nella .NET 11.0.100 SDK abilitare <LangVersion>preview</LangVersion>.
open System
open System.IO
open System.Threading.Tasks
let readReport (logFailure: exn -> Task) path =
task {
try
return! File.ReadAllTextAsync(path)
with :? IOException as error ->
do! logFailure error
return reraise ()
}
Dopo aver completato correttamente la registrazione nei log, vengono propagate la stessa eccezione e le informazioni originali sul punto in cui è stata generata. Un errore del logger può sostituire l'originale. Questo risponde alla richiesta di osservabilità async senza async di runtime. Vedere Rethrow in un'espressione di calcolo.
Grazie a @xperiandri per il supporto.
FSharp.Core può basarsi sul moderno .NET
Per usare le API moderne senza abbandonare i target meno recenti, FSharp.Core aggiunge net10.0 insieme a netstandard2.0 e netstandard2.1. NuGet seleziona la build compatibile. Il packaging aggiornato degli strumenti consente anche il rilascio asincrono basato su attività in F# Interactive, come nei progetti compilati. Vedi i framework di destinazione di FSharp.Core.
Grazie a @T-Gro per il targeting di Core moderno e la pacchettizzazione degli strumenti SDK.
Runtime asincrono per gli autori del builder
Gli autori del generatore possono consentire al runtime di sospendere e riprendere i metodi contrassegnati anziché usare macchine a stati generate dal compilatore. Un builder sperimentale definito dalla libreria espone il meccanismo come espressione di calcolo:
let readManifest (path: string) =
runtimeTask {
let! text = System.IO.File.ReadAllTextAsync path
return text.Length
}
Note
Questo builder sperimentale usa una funzionalità di F# 11.2 in cui la relativa implementazione inline viene definita o espansa; nell'SDK .NET 11.0.100 abilita l'anteprima. Richiede anche la compilazione e i riferimenti di net10.0 Core 11 e un runtime con supporto runtime-async. La combinazione di runtime/riferimento verificata è .NET 11 RC1. Usare solo .NET 10 come destinazione non è sufficiente. Vedi esperimenti asincroni del runtime.
FSharp.Core non fornisce questo esempio come builder di uso generale. Il normale codice task e async non passa automaticamente a esso e i chiamanti di un'API già compilata che restituisce Task non richiedono intrinsecamente l'anteprima.
Grazie a @majocha per l'asincronia del runtime.
Testo aot nativo e matrici bidimensionali
Per supportare Native AOT, F# 11 rimuove i requisiti di formattazione basati sulla reflection e di allocazione dinamica nelle più comuni operazioni su testo e array.
Stampare valori e creare stringhe senza un livello di formato ridondante
Quando si dispone già di una stringa, non è necessario un "%s" format solo per scriverla. Nuove funzioni print Core e printn accettano valori tramite la conversione di string F#; printn aggiunge un carattere di nuova riga:
let report (processed: int) (total: decimal) (elapsed: TimeSpan) : string =
$"{processed} items, total {total:F2}, elapsed {elapsed.TotalMilliseconds:F0} ms"
printn (report 12 145.50m (TimeSpan.FromMilliseconds 250.0))
// 12 items, total 145.50, elapsed 250 ms
L'interpolazione di stringhe usa la conversione diretta e String.Concat in più casi. I segnaposto di formato .NET, come {total:F2}, usano la formattazione invariante e incapsulano il valore; %A e le forme printf con specifica di precisione possono comunque richiedere la formattazione printf. Selezionare un'impostazione cultura esplicita per l'output localizzato.
Con --reflectionfree, anche i record e le unioni ottengono l'output generato ToString . Per esempi di impostazioni cultura e percorsi supportati, vedere Interpolazione e AOT nativo .
Grazie a @bbatsov per le funzioni di stampa e @charlesroddie per l'implementazione dell'interpolazione.
Mantenere le normali operazioni sulla griglia con indice iniziale 0
Una griglia in base zero non deve richiedere un modulo Array2D sostitutivo solo per evitare un percorso di allocazione di codice dinamico. Le implementazioni di base aggiornate supportano l'inizializzazione e la trasformazione familiari:
let grid = Array2D.init 3 4 (fun row column -> row * 4 + column)
let doubled = grid |> Array2D.map (fun value -> value * 2)
let snapshot = Array2D.copy doubled
printn $"{snapshot.GetLength(0)} x {snapshot.GetLength(1)}; last = {snapshot[2, 3]}"
// 3 x 4; last = 22
Array2D.create, inite rebase usano il percorso di allocazione in base zero nelle build Core. Le operazioni che preservano i limiti map, mapi e copy possono usare un'API di allocazione .NET più recente nella build net10.0; le build .NET Standard mantengono il fallback. Le matrici non basate su zero non sono deprecate, ma le restrizioni di allocazione native rimangono invariate.
Vedere Matrici bidimensionali e AOT nativo per i percorsi di allocazione e le restrizioni rimanenti. Controllare la diagnostica della pubblicazione dell'applicazione completa.
Grazie a @T-Gro per i miglioramenti di Array2D.
Minore memoria occupata e meno lavoro ripetuto
F# 11 riduce l'analisi ripetuta del compilatore e alcune allocazioni dell'applicazione. Questi elementi influiscono su carichi di lavoro diversi.
Condividere librerie a cui si fa riferimento tra progetti
Per evitare copie ripetute delle dipendenze binarie condivise, il servizio del linguaggio può condividere tra i progetti i grafi delle librerie importate idonee.
Nell'esperimento sulla memoria mantenuta della PR di implementazione, la soluzione ReSharper.FSharp di dieci progetti è diminuita da 1.547,2 MB a 1.002,2 MB con condivisione abilitata. Si tratta di una misura off/on per il carico di lavoro, non una riduzione generale di F# da 11 a 10. Le soluzioni dominate dai riferimenti di progetto anziché dai riferimenti binari condivisi possono registrare miglioramenti molto inferiori.
La lettura dei metadati e la specializzazione inline riducono anche le allocazioni temporanee, secondo una metrica separata. Vedere Prestazioni degli strumenti per ambito e controlli.
Grazie a @auduchinok per l'importazione di librerie condivise.
Compila attività indipendenti in parallelo per impostazione predefinita
La compilazione parallela è ora abilitata per impostazione predefinita, mantenendo un output deterministico e le regole di ordinamento del sorgente. I miglioramenti ai pattern con guardia e alla cache degli overload evitano ulteriore lavoro ripetuto. Visual Studio/FCS riducono anche l'analisi dei documenti invariati e i controlli simultanei associati. Vedere Prestazioni degli strumenti.
Evitare il callback e l'overhead di allocazione del risultato vuoto
Le funzioni selezionate List e Array ora incorporano callback inline. Per gli helper inline personalizzati, OptimizeClosureIfNotInlined adatta un callback curried opaco in un unico passaggio:
let inline foldItems
([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
state
(items: 'T[]) =
let mutable total = state
for item in items do
total <- folder total item
total
Le altre allocazioni possono restare. Per i requisiti, vedere Ottimizzare i callback curried .
I risultati vuoti sono un altro caso comune di lavoro riuscito. Il carico di lavoro motivante di FSharpLint ha combinato ripetutamente regole che non hanno trovato nessun avviso. Core ora riutilizza array vuoti condivisi in più operazioni:
let checkTitle (title: string) =
if String.IsNullOrWhiteSpace title then [|"A title is required."|]
else Array.empty
let warnings = ["Introduction"; "Installation"] |> List.toArray |> Array.collect checkTitle
let sharedEmpty = obj.ReferenceEquals(warnings, Array.empty<string>)
// true: no new empty result array
La modifica rimuove oggetti array vuoti ridondanti, non tutte le allocazioni nella pipeline. Non fare affidamento sul fatto che risultati vuoti separati abbiano identità di riferimento distinte.
Grazie a @T-Gro per i callback di raccolta inline e le matrici vuote condivise.
Chiamate API più precise e comodità quotidiane
Richiedere nomi quando l'ordine dei parametri generato può cambiare
Per proteggere le API generate da modifiche silenziose all'ordine dei parametri, un autore può richiedere argomenti denominati:
Note
Enforcement è una funzionalità di F# 11.2; in .NET SDK 11.0.100, abilitare l'anteprima. È necessaria anche una definizione disponibile System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute .
L'esempio completo fornisce una definizione di compatibilità per i riferimenti che ne sono privi.
open System.Diagnostics.CodeAnalysis
type Query =
[<RequireNamedArguments>]
static member Execute(numerator: decimal, denominator: decimal) =
numerator / denominator
let ratio = Query.Execute(numerator = 3m, denominator = 4m)
// 0.75M; Query.Execute(3m, 4m) is rejected.
Il riordinamento della dichiarazione mantiene il risultato della chiamata denominata, 0.75M; viene rifiutata una chiamata posizionale. Per il caso d'uso dell'API generata, il provider deve emettere l'annotazione.
Riconoscere un argomento generico già sottoposto a wrapping
Per evitare di incapsulare un'attività esistente come valore di risultato normale, una nuova regola di overload dell'ultima risorsa preferisce Task<'T> rispetto a semplice 'T:
Note
Il tiebreaker più concreto e il comportamento di priorità dell'overload sono le funzionalità di F# 11.2; nell'SDK .NET 11.0.100, abilitare l'anteprima.
open System.Threading.Tasks
let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>
Il risultato è ValueTask<int>, non ValueTask<Task<int>>. Vedere Selezione dell'overload per i limiti della regola.
Preferisce un nuovo overload senza rimuovere il punto di ingresso precedente
Una libreria potrebbe mantenere un metodo di diagnostica precedente per i file binari esistenti durante l'aggiunta di un overload che include la posizione nel codice sorgente del chiamante.
OverloadResolutionPriorityAttribute rende esplicita tale preferenza per i chiamanti appena compilati:
open System.Runtime.CompilerServices
type Check =
static member That(condition: bool) =
if condition then "OK" else "Check failed."
[<OverloadResolutionPriority(1)>]
static member That(condition: bool, [<CallerLineNumber>] ?line: int) =
if condition then "OK" else $"Check failed at line {defaultArg line 0}."
let stock = 0
let diagnostic = Check.That(stock > 0)
// The diagnostic includes the caller's source line.
La chiamata non modificata seleziona l'overload che tiene conto della riga. Vedere Selezione dell'overload per le regole di compatibilità e ambito.
Usare le promesse di nullità condizionale di .NET
Se il percorso del file non è Null, il risultato nullable di un'API Path non deve forzare un controllo che il contratto stesso esclude. F# 11 fa uso della garanzia esistente di .NET: un input dichiarato non Null implica un risultato non Null.
open System.IO
let fileName: string = Path.GetFileName("reports/annual.csv")
let extension: string = Path.GetExtension(fileName)
printn (extension.ToUpperInvariant())
// .CSV
let fileNameOf (path: string | null) : string | null =
Path.GetFileName path
Abilitare <Nullable>enable</Nullable>. Gli input annullabili richiedono ancora una gestione degli annullabili; vedere Contratti di ritorno condizionali. Per le API C# T? non vincolate, usare il Unchecked.withNull helper separato.
Grazie a @kerams per il supporto condizionale dei valori Null.
Miglioramenti minori del linguaggio
Per chiamare un tipo di record con la sintassi del costruttore .NET, usare i costruttori di record di F# 11.2:
type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)
Gli argomenti denominati evitano la dipendenza dall'ordine dei campi. Grazie a @charlesroddie per questa funzionalità.
Per l'origine condizionale, #elif evita i gruppi annidati #if :
#if EU_WAREHOUSE
let dispatchTimeZone = "Europe/Paris"
#elif US_WAREHOUSE
let dispatchTimeZone = "America/New_York"
#else
let dispatchTimeZone = "UTC"
#endif
System.Console.WriteLine(dispatchTimeZone)
Il primo ramo corrispondente vince. Il progetto di esempio definisce EU_WAREHOUSE e stampa Europe/Paris; senza alcun simbolo definito, l'output è UTC.
Per gli strumenti correlati e i casi di interoperabilità:
- Per identificare la toolchain interattiva, usare
#version. - Per condividere il codice sorgente con un host esterno, F# accetta
#:righe di direttiva host senza interpretarle come istruzioni di creazione o pacchetto. - Per evitare dichiarazioni di interfaccia ridondanti, usare l'implementazione predefinita di un'interfaccia derivata.
- Per leggere lo stato ereditato all'interno di una chiusura normale, usare il nuovo supporto per i campi di base protetti.
Prima dell'aggiornamento
Tenere presenti queste modifiche consequenziali durante l'aggiornamento:
| Codice interessato | Action |
|---|---|
Librerie con .fsi file |
Copiare gli attributi del contratto applicati alle firme; le omissioni sono ora errori FS3888. |
| Librerie inline supportate dall'estensione | Esaminare le firme dedotte e i requisiti producer/consumer. L'inferenza F# 11.2 può mantenere vincoli più generali. |
| Delegate di metodo di istanza | Validare i receiver prima della costruzione diretta, quando si verificano errori dovuti a receiver null. |
| Output interpolato localizzato | I segnaposto di formato .NET usano le impostazioni cultura invarianti. Scegliere impostazioni cultura esplicite quando è previsto l'output localizzato. |
Builder personalizzati con finale do! |
Binding di fornitura e completamento delle unità; final do! non seleziona più i membri di inoltro finali. |
Non completato let! |
Aggiungere il corpo di calcolo seguente; un'associazione incompleta ha ora una diagnostica più diretta. |
| Impostazioni della lingua precedenti | Le modalità numerate supportate iniziano da F# 8; la modalità di rientro non strict viene rimossa. Vedere versione della lingua e rientro. |
| Operazioni bit per bit sugli enum | Utilizzare enum con tipo sottostante intero; i tipi sottostanti non validi vengono rifiutati. |