Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Willkommen bei F# 11! Diese Version hilft Ihnen dabei, Domänenmodelle klar getrennt zu halten, generische Algorithmen wiederzuverwenden, asynchrone Vorgänge zu koordinieren und den Speicherverbrauch des Compilers zu reduzieren.
- Datensatz-Spreads verwenden Felder wieder, ohne Speicher- und Anwendungsmodelle zu zwingen, einen Typ gemeinsam zu nutzen.
- Erweiterungsmember in generischen Algorithmen ermöglichen es vorhandenen Typen, dieselben Operationen ohne Wrapper zu nutzen.
- .NET Integration behält Delegatenmetadaten bei und nutzt die API-Dokumentation erneut.
- Hilfsfunktionen für asynchrone Vorgänge und Tasks geben Abbruchanforderungen über API-Grenzen hinweg weiter und begrenzen gleichzeitige Vorgänge.
- Compiler- und Bibliotheksverbesserungen reduzieren wiederholte Analysen und vermeiden einige Allokationen.
Vielen Dank an die F#-Community für die Vorschläge, Implementierungen, Begutachtungen und Tests, die diesen Änderungen zugrunde liegen. Links zu Mitwirkenden begleiten die folgenden Funktionen.
Note
Die Beispiele erfordern einen F# 11-Compiler und FSharp.Core 11 mit den gezeigten Features. Im .NET 11.0.100 SDK erfordern F# 11.2-Features <LangVersion>preview</LangVersion>; siehe Spracheinstellungen und geplante SDK-Unterstützung. Das runtime-async-Beispiel hat zusätzliche Anforderungen an die Laufzeit; ein früheres .NET 11 SDK allein enthält womöglich nicht alle Änderungen.
Datensatzverteilungen: verschiedene Modelle ohne wiederholte Zuordnungen
Um Speicher- und Anwendungsmodelle getrennt zu halten, verwenden Datensatz-Spreads ihre freigegebenen Felder ohne wiederholte Deklarationen wieder. Hier teilen die Modelle Kundendetails, verwenden jedoch unterschiedliche Bezeichnertypen:
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 liefert Felddeklarationen. An der Grenze kopiert ...row Werte, während die Konvertierung den Bezeichner analysiert:
let toCustomer (row: CustomerRow) =
match Guid.TryParse row.Id with
| true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
| false, _ -> Error "Invalid customer identifier."
Das Ziel behält die freigegebenen Details bei, ersetzt Id und schließt InternalNote aus.
Guid.TryParse, nicht der Spread, überprüft den Bezeichner; falsch formatierte Eingaben erzeugen Error.
Dies behebt das Zuordnungsproblem auf Anwendungsschicht-Ebene. Value-Spreads kombinieren ebenfalls benannte und anonyme Records; Typ-Spreads erzeugen nur benannte Records. Spätere Felder gewinnen, und Quellen werden der Reihe nach einmal ausgeführt. Siehe Record-Spread-Operatoren für Kompositionsbeispiele und Risiken bei gemeinsamer Weiterentwicklung.
Danke an @brianrourkeboll für Rekordspreads.
Erweiterungsmitglieder für generische Algorithmen, Operatoren und Tupel
Um einen Algorithmus über vorhandene Typen hinweg gemeinsam zu nutzen, ermöglicht F# 11.2, dass Erweiterungsmitglieder SRTP-Beschränkungen erfüllen. Beispielsweise kann eine Transformation Listen und Optionen ohne Wrappertypen verarbeiten.
Note
Aktivieren Sie im .NET 11.0.100 SDK <LangVersion>preview</LangVersion>, damit Erweiterungen aufgelöst und ihre Module geöffnet werden. Siehe Anforderungen an die Bibliothek.
Freigeben einer Transformation über Listen und Optionen hinweg
Mit dem map der Bibliothek im Geltungsbereich kann die Anwendung beide Darstellungen bereinigen, Elementtypen ändern und Transformationen verfassen:
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]
Die Adapter in Structural.Adapters verwenden vertraute Vorgänge, anstatt neue Container aufzuzwingen:
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
Die freigegebene Funktion in Structural.Algorithms fragt den Eingabetyp nach einem statischen Map-Element:
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
^Input und ^Output sind statisch aufgelöste Typen. Bei jedem konkreten Aufruf wählt der Compiler das Element mit der erforderlichen Signatur aus. Die Erweiterungen fügen keine Schnittstellenimplementierung hinzu. Die komplette Anwendung öffnet beide Module.
Operatoren, Kontext und Ausgabetypen
Derselbe Mechanismus unterstützt Tupeloperatoren und Kontextweitergabe und ausgabegesteuerte Konstruktion. Native Tupelheader benötigen das F# 11.2-Feature, das bei ihren Deklarationen sowie bei Aufrufstellen zur Auflösung von Erweiterungen aktiviert werden muss.
Eine separate Ergänzung ermöglicht es einer Factory, Überladungen zu deklarieren, AllowOverloadOnReturnTypedie von ihren Rückgaben unterschieden werden:
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"
Dieses Core 11-Attribut erfordert keine Vorschau. Die Ergebnisanmerkung wählt eine Überladung aus. Ohne sie bleibt der Aufruf mehrdeutig. Siehe Auswählen nach Rückgabetyp.
Dank an @gusty für die Erweiterung der Unterstützung für generische Programmierung.
.NET-Integration und die Erfahrung bei der Entwicklung
Direkte Delegaten behalten den echten Schnittstellenvertrag des Handlers bei
Um Handlernamen und Bindungsattribute für Frameworks verfügbar zu machen, kann F# 11 eine kompatible benannte Funktion direkt anstelle eines Wrappers als Ziel verwenden. ASP.NET Core Minimal APIs können dann den Routen-, Dienst- und Headervertrag lesen:
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)
Siehe das ausführbare Projekt und die Konstruktions- und Kompatibilitätsregeln. Erfassungen und Argumentanpassungen können weiterhin Wrapper erfordern.
Dank @kerams für direkte Stellvertretungen.
XML-Dokumentation wiederverwenden
Um gemeinsame API-Hinweise zentral zu pflegen, platzieren Sie sie in shared.xml:
<docs>
<tracking>
<remarks>An unrecognized tracking identifier has status Unknown.</remarks>
</tracking>
</docs>
Wenn GenerateDocumentationFile aktiviert ist, erweitert F# 11 <include> zu den generierten Membereinträgen:
/// <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)
Die Include-Implementierung aktualisiert beide Beschreibungen aus einem Fragment. Separat legt der Tooling-Resolver Basisbeschreibungen für <inheritdoc/> offen; der Compiler belässt dieses Tag für Dokumentations-Clients. Beispiele und Lösungsregeln finden Sie in der XML-Dokumentation .
Dank an @T-Gro für die gemeinsame XML-Dokumentation in FSharp.Core.
Inlinefunktionen in nicht optimierten Builds debuggen
Um die Argumente der Inlinehilfsfunktion zu überprüfen, setzen Sie in einem nicht optimierten Build bei let total einen Haltepunkt und führen Sie bei addFee einen Einzelschritt hinein aus. F# 11 bevorzugt in diesen Builds jetzt Methoden mit Aufrufsyntax:
let inline addFee (fee: decimal) (amount: decimal) =
let total = amount + fee
total
let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42
Optimierte Builds behalten Inlining bei. Einige Konstrukte müssen noch erweitert werden. Siehe Inlinefunktionen debuggen für diese Beschränkungen und das Steuerelement --always-inline.
Asynchronität und Aufgabenkoordination
Um F#-Workflows mit taskbasierten APIs zu verbinden, werden mit FSharp.Core 11 abbruchunterstützende Brücken, begrenzte Batches und Ergebnistransformationen hinzugefügt.
Begrenze ausstehende E/A, nicht nur die Ergebnisauflistung
Eine große Anzahl von Lesevorgängen kann mehr Arbeit starten, als eine Anwendung gleichzeitig offen haben sollte. Um zu entscheiden, wann die aufgabenbasierte Arbeit beginnt, benötigt ein Koordinator Factories, nicht Aufgaben, die bereits begonnen haben:
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
Es laufen höchstens vier Lesevorgänge gleichzeitig; die Ergebnisse behalten die Reihenfolge der Eingabe bei. Ein Fehlschlag fordert den Abbruch von Geschwistervorgängen über die bereitgestellten Token an. Dies begrenzt die Parallelität, nicht Anforderungen pro Sekunde. Siehe Async- und Taskfunktionen für sequenzielle und Varianten mit Einheitsergebnis.
Abbruch und die zugrunde liegende Ausnahme über die Grenzen hinweg beibehalten
Wenn ein Workflow externe Arbeit startet, sollte sein Token mit dieser Arbeit mitgeführt werden.
Async.StartTaskImmediate übergibt das Token des ausgeführten Workflows an die Taskfactory und wartet auf den Abschluss:
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
}
Ein einzelner Vorgangsfehler erreicht diesen Handler als zugrunde liegendes FileNotFoundException. Verwenden Sie Async.Await für eine bereits erstellte Aufgabe oder Wertaufgabe; die ältere Async.AwaitTask behält das Verhalten beim Umbrechen von Ausnahmen bei.
Für taskbasierte Aufrufer stellt Task.startAsyncImmediate token workflow die Rückbrücke bereit. Sie beginnt im aufrufenden Thread bis zur ersten asynchronen Aussetzung.
Verwenden passender Hilfsfunktionen für kurze Transformationen
Um ein Ergebnis ohne vollständigen Berechnungsausdruck zu transformieren, verwenden Sie die entsprechenden Hilfsprogramme in Microsoft.FSharp.Control. Lesen Sie z. B. eine Datei, rufen Sie die Länge ab, und geben Sie einen gewöhnlichen Fehler zurück als Error:
open System.IO
open System.Threading
let fileLength (path: string) (ct: CancellationToken) =
File.ReadAllTextAsync(path, ct)
|> Task.map String.length
|> Task.catch
Eine Datei, die Ok 8 enthält, liefert Report 1; eine fehlende Datei liefert ein Error, das FileNotFoundException enthält. Der Abbruchvorgang wird weiterhin weitergegeben.
Async und ValueTask bieten entsprechende map, bind und Fehlerhilfen; siehe Async- und Taskfunktionen. Verwenden Sie eine Computation Expression für Verzweigungen oder mehrere abhängige Werte.
ValueTask-Hilfsprogramme erfordern den Build von Core netstandard2.1 oder net10.0 und einmalige Verwendung.
Danke an @bartelink für die Beiträge zur Async/Task-API.
Erneut auslösen nach asynchroner Fehlermeldung
Die Betriebsprotokollierung sollte Informationen hinzufügen, ohne zu ändern, was der Aufrufer erfasst oder ausgeblendet, wo der Fehler begonnen hat. Berechnungsausdruck-Handler können jetzt reraise () auch nach dem Warten auf den Logger verwenden:
Note
Rethrow in Berechnungsausdrücken ist ein F# 11.2-Feature. Aktivieren Sie <LangVersion>preview</LangVersion> im .NET 11.0.100 SDK.
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 ()
}
Nach erfolgreicher Anmeldung werden die gleichen Ausnahme- und ursprünglichen Throw-Site-Informationen weitergegeben. Ein Loggerfehler kann das Original ersetzen. Dies erfüllt die Observability-Anfrage für asynchrone Vorgänge ohne asynchrone Laufzeit. Siehe Rethrow from a computation expression.
Danke an @xperiandri für diese Unterstützung.
FSharp.Core kann auf modernem .NET aufbauen
Um moderne APIs zu verwenden, ohne ältere Zielplattformen aufzugeben, ergänzt FSharp.Core net10.0 zusätzlich zu netstandard2.0 und netstandard2.1. NuGet wählt die passende Version aus. Die aktualisierte Paketierung von Tools ermöglicht auch die aufgabenbasierte asynchrone Freigabe in F# Interactive, genauso wie in kompilierten Projekten. Siehe FSharp.Core-Ziel-Frameworks.
@T-Gro Dank für modernes Core-Targeting und SDK-Toolpaketierung.
Asynchrone Laufzeit für Builder-Autoren
Builder-Autoren können die markierten Methoden zur Laufzeit anhalten und fortsetzen, anstatt compilergenerierte Zustandsmaschinen zu verwenden. Ein experimenteller bibliotheksdefinierter Builder stellt den Mechanismus als Berechnungsausdruck bereit:
let readManifest (path: string) =
runtimeTask {
let! text = System.IO.File.ReadAllTextAsync path
return text.Length
}
Note
Dieser experimentelle Builder verwendet ein F# 11.2-Feature, bei dem seine Inlineimplementierung definiert oder erweitert wird; aktivieren Sie im .NET 11.0.100 SDK die Vorschaufunktion. Außerdem werden der net10.0-Build und die Verweise von Core 11 sowie eine Laufzeit mit Unterstützung für runtime-async benötigt. Die überprüfte Laufzeit-/Referenzkombination hier ist .NET 11 RC1. .NET 10 allein als Ziel festzulegen reicht nicht aus. Siehe Async-Experimente in Runtime.
FSharp.Core liefert dieses Beispiel nicht als allgemein verwendbaren Builder mit. Normaler task und async Code wechselt nicht automatisch darauf, und Aufrufer einer bereits kompilierten normalen Aufgabenrückgabe-API benötigen nicht zwangsläufig Preview.
Dank @majocha für Runtime-Async.
Native AOT-freundlicher Text und zweidimensionale Arrays
Zur Unterstützung von nativem AOT entfernt F# 11 reflexionsbasierte Formatierungen und dynamische Speicherzuweisungsanforderungen aus gängigeren Text- und Arrayvorgängen.
Drucken von Werten und Erstellen von Zeichenfolgen ohne redundante Formatebene
Wenn Sie bereits über eine Zeichenfolge verfügen, benötigen Sie kein "%s"-Format, um sie zu schreiben. Neue Core-Funktionen print und printn Akzeptieren von Werten durch die Konvertierung von string F#; printn fügt eine Neue zeile an:
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
Die Zeichenfolgeninterpolation verwendet direkte Konvertierung und String.Concat in weiteren Fällen. .NET-Formatplatzhalter wie {total:F2} verwenden die invariante Formatierung und verpacken den Wert; %A und printf-Formen mit Präzisionsangabe können weiterhin printf-Formatierung benötigen. Wählen Sie ein explizites Gebietsschema für lokalisierte Ausgabe aus.
Mit --reflectionfree erhalten Datensätze und Unions ebenfalls generierte ToString-Ausgaben. Siehe Interpolation und Native AOT für Kulturbeispiele und unterstützte Pfade.
Dank @bbatsov für die Druckfunktionen und @charlesroddie für die Interpolationsimplementierung.
Normale 0-basierte Rastervorgänge beibehalten
Ein nullbasiertes Raster sollte kein Ersatz-Array2D-Modul erfordern, nur um einen Pfad für dynamische Codeallokation zu vermeiden. Aktualisierte Core-Implementierungen unterstützen vertraute Initialisierung und Transformation:
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, init und rebase verwenden den nullbasierten Zuordnungspfad für Core-Builds. Grenzenerhaltende map, mapi und copy können im net10.0-Build eine neuere .NET-Zuordnungs-API verwenden; die .NET-Standard-Builds behalten ihren Fallback. Nicht nullbasierte Arrays sind nicht als veraltet markiert, aber ihre systemeigenen Allokierungsbeschränkungen bleiben erhalten.
Siehe Zweidimensionale Arrays und Native AOT für Zuweisungspfade und verbleibende Einschränkungen. Überprüfen Sie die Veröffentlichungsdiagnosen der vollständigen Anwendung.
Dank @T-Gro für die Array2D-Verbesserungen.
Weniger gebundener Speicher und weniger wiederholte Arbeit
F# 11 reduziert wiederholte Compileranalyse und einige Speicherzuweisungen. Diese wirken sich auf unterschiedliche Workloads aus.
Referenzbibliotheken projektübergreifend freigeben
Um wiederholte Kopien freigegebener binärer Abhängigkeiten zu vermeiden, kann der Sprachdienst geeignete importierte Bibliotheksgraphen projektübergreifend gemeinsam nutzen.
Im Retained-Memory-Experiment des implementierenden PRs fiel die Zehn-Projekt-ReSharper.FSharp-Lösung von 1.547,2 MB auf 1.002,2 MB bei aktivierter Freigabe. Dies ist eine Ein/Aus-Messung für diesen Workload, nicht eine allgemeine F# 10-zu-11-Reduzierung. Lösungen, die von Projektbezügen und nicht von gemeinsam genutzten binären Referenzen dominiert werden, können deutlich geringere Verbesserungen erzielen.
Metadatenlesen und Inline-Spezialisierung reduzieren auch temporäre Allokationen, eine separate Metrik. Siehe Toolerstellungsleistung für Bereich und Steuerelemente.
Dank an @auduchinok für Shared-Library-Importe.
Unabhängige Aufgaben standardmäßig parallel kompilieren
Die parallele Kompilierung ist nun standardmäßig aktiviert, wobei deterministische Ausgaben und Regeln für die Quellreihenfolge beibehalten werden. Verbesserungen beim Caching von Überladungen und bei Guarded Patterns vermeiden weitere wiederholte Arbeit. Visual Studio/FCS reduzieren auch die Analyse unveränderter Dokumente und begrenzen gleichzeitige Prüfungen; siehe Toolingleistung.
Vermeiden von Callback- und Overhead bei der Allokierung leerer Ergebnisse
Ausgewählte List und Array Funktionen betten Callbacks jetzt inline ein. Passen Sie für Ihre eigenen Inlinehilfsprogramme OptimizeClosureIfNotInlined eine undurchsichtige gekurryte Callback-Funktion einmalig an:
let inline foldItems
([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
state
(items: 'T[]) =
let mutable total = state
for item in items do
total <- folder total item
total
Andere Zuordnungen können bestehen bleiben. Siehe Optimieren von curried callbacks zu den Voraussetzungen.
Leere Ergebnisse sind ein weiterer häufiger Fall erfolgreicher Arbeit. FSharpLints maßgeblicher Anwendungsfall kombinierte wiederholt Regeln, die keine Warnungen erzeugten. Core verwendet jetzt freigegebene leere Arrays in weiteren Vorgängen wieder:
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
Die Änderung entfernt redundante Leere-Array-Objekte, nicht jede Allokierung in der Pipeline. Verlassen Sie sich nicht darauf, dass separate leere Ergebnisse keine unterschiedlichen Referenzidentitäten haben.
Dank an @T-Gro für Inline-Collection-Callbacks und freigegebene leere Arrays.
Präzisere API-Aufrufe und alltägliche Erleichterungen
Namen erfordern, wenn die Reihenfolge der generierten Parameter geändert werden kann
Um generierte APIs vor unbemerkten Änderungen der Parameterreihenfolge zu schützen, kann ein Autor benannte Argumente erfordern:
Note
Enforcement ist eine Funktion in F# 11.2; aktivieren Sie in der .NET SDK-Version 11.0.100 die Previewfeatures. Außerdem ist eine verfügbare System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute Definition erforderlich. Im vollständigen Beispiel wird eine Kompatibilitätsdefinition für Verweise bereitgestellt, denen sie fehlt.
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.
Durch neu anordnen der Deklaration bleibt das Ergebnis des benannten Aufrufs erhalten; 0.75Mein Positionsaufruf wird abgelehnt. Für den Anwendungsfall "generated-API" muss der Anbieter die Anmerkung ausgeben.
Erkennen eines bereits gekapselten generischen Arguments
Um das Umschließen eines vorhandenen Vorgangs als einfachen Ergebniswert zu vermeiden, bevorzugt eine neue Last-Resort-Überladungsregel Task<'T> gegenüber nacktem 'T:
Note
Das konkreteste Tiebreaker- und Überladungsprioritätsverhalten sind F# 11.2-Features; aktivieren Sie im .NET 11.0.100 SDK die Vorschau.
open System.Threading.Tasks
let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>
Das Ergebnis lautet ValueTask<int>, nicht ValueTask<Task<int>>. Siehe Overload selection für die Grenzwerte der Regel.
Bevorzugen Sie eine neue Überladung, ohne die alte Einstiegsmethode zu entfernen.
Eine Bibliothek behält möglicherweise eine alte Diagnosemethode für vorhandene Binärdateien bei und fügt eine überladene Methode hinzu, die die Quellcodeposition des Aufrufers enthält.
OverloadResolutionPriorityAttribute legt diese Einstellung für neu kompilierte Aufrufer explizit fest:
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.
Der unveränderte Aufruf wählt die zeilenbezogene Überladung aus. Siehe Overload selection für Bereichs- und Kompatibilitätsregeln.
Die bedingten Nullbarkeitszusicherungen von .NET verwenden
Wenn Ihr Dateipfad nicht null ist, sollte das Ergebnis, das null sein kann, der Path-API keine Prüfung erzwingen, die laut eigenem Vertrag ausgeschlossen ist. F# 11 verwendet die vorhandene .NET NotNullIfNotNull Zusage: Eine benannte Eingabe, die nicht null ist, impliziert ein Nicht-NULL-Ergebnis.
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
<Nullable>enable</Nullable>aktivieren. Nullable Eingaben benötigen weiterhin eine Nullable-Behandlung; siehe Bedingte Rückgabeverträge. Verwenden Sie für nicht eingeschränkte C# T?-APIs den separaten Unchecked.withNull Helper.
Danke an @kerams für die Unterstützung der bedingten Nullierbarkeit.
Kleinere Sprachverbesserungen
Um einen Datensatztyp mit .NET-Konstruktorsyntax aufzurufen, verwenden Sie F# 11.2 Datensatzkonstruktoren:
type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)
Benannte Argumente vermeiden die Abhängigkeit von der Reihenfolge der Felder. Vielen Dank an @charlesroddie für dieses Feature.
Bei bedingter Quelle #elif werden geschachtelte #if Gruppen vermieden:
#if EU_WAREHOUSE
let dispatchTimeZone = "Europe/Paris"
#elif US_WAREHOUSE
let dispatchTimeZone = "America/New_York"
#else
let dispatchTimeZone = "UTC"
#endif
System.Console.WriteLine(dispatchTimeZone)
Der erste übereinstimmende Branch gewinnt. Das Beispielprojekt definiert EU_WAREHOUSE und druckt Europe/Paris; wenn keines der Symbole definiert ist, ist UTC die Ausgabe.
Für verwandte Tool- und Interoperabilitätsfälle:
- Verwenden Sie
#version, um Ihre interaktive Toolkette zu identifizieren. - Um die Quelle für einen externen Host freizugeben, akzeptiert
#:F# Host-Direktivzeilen, ohne sie als Paket- oder Buildanweisungen zu interpretieren. - Verwenden Sie die Standardimplementierung einer abgeleiteten Schnittstelle, um redundante Schnittstellendeklarationen zu vermeiden.
- Um den geerbten Zustand innerhalb einer gewöhnlichen Closure zu lesen, verwenden Sie die neue Unterstützung für geschützte Basisklassenfelder.
Vor dem Upgrade
Berücksichtigen Sie bei der Aktualisierung diese weitreichenden Änderungen:
| Betroffener Code | Action |
|---|---|
Bibliotheken mit .fsi-Dateien |
Erforderliche Vertragsattribute in Signaturen kopieren; Auslassungen führen jetzt zu FS3888-Fehlern. |
| Durch Erweiterungen unterstützte Inlinebibliotheken | Überprüfen Sie abgeleitete Signaturen und Producer/Consumer-Anforderungen. Die Typinferenz in F# 11.2 kann allgemeinere Constraints beibehalten. |
| Delegaten von Instanzmethoden | Validieren Sie Receiver vor direkter Konstruktion, wenn jetzt Null-Receiver-Fehler auftreten. |
| Lokalisierte interpolierte Ausgabe | .NET-Formatplatzhalter verwenden die invariante Kultur. Wählen Sie eine explizite Kulturangabe aus, wenn eine lokalisierte Ausgabe beabsichtigt ist. |
Benutzerdefinierte Builder mit finalen do! |
Bindung bereitstellen und Einheitenvervollständigung; final do! wählt keine finalen Weiterleitungsmember mehr aus. |
Unvollständig let! |
Fügen Sie den folgenden Berechnungsausdruckskörper hinzu; eine unvollständige Bindung gibt jetzt eine direktere Diagnose aus. |
| Spracheinstellungen für ältere Versionen | Unterstützte nummerierte Modi beginnen bei F# 8; der nicht strenge Einrückungsmodus ist entfernt. Siehe Sprachversion und Einzug. |
| Bitweise Operationen für Enums | Verwenden Sie integralgesicherte Enumerationen; ungültige Sicherungstypen werden abgelehnt. |