Nieuw in F# 11

Welkom bij F# 11! Deze release helpt u om domeinmodellen van elkaar te onderscheiden, generieke algoritmen te hergebruiken, asynchroon werk te coördineren en het geheugengebruik van de compiler terug te dringen.

Dank aan de F#-community voor de voorstellen, implementaties, beoordelingen en tests achter deze wijzigingen. Koppelingen naar bijdragers vergezellen de onderstaande functies.

Note

De voorbeelden vereisen een F# 11-compiler en FSharp.Core 11 met de getoonde functionaliteit. In de .NET 11.0.100 SDK vereisen F# 11.2-functies <LangVersion>preview</LangVersion>; zie taalinstellingen en geplande SDK-ondersteuning. Het runtime-async-voorbeeld heeft aanvullende runtimevereisten; een eerdere .NET 11 SDK alleen bevat mogelijk niet elke wijziging.

Records spreiden: afzonderlijke modellen zonder terugkerende toewijzingen

Als u opslag- en toepassingsmodellen gescheiden wilt houden, gebruiken record spreads hun gedeelde velden opnieuw zonder herhaalde declaraties. Hier delen de modellen klantgegevens, maar gebruiken ze verschillende id-typen:

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 levert velddeclaraties. Aan de grens kopieert ...row waarden terwijl de conversie de id parseert:

let toCustomer (row: CustomerRow) =
    match Guid.TryParse row.Id with
    | true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
    | false, _ -> Error "Invalid customer identifier."

De bestemming bewaart de gedeelde gegevens, vervangt Id en sluit InternalNote uit. Guid.TryParse, niet de spread, valideert de id; onjuiste invoer produceert Error.

Hiermee wordt het toewijzingsprobleem op toepassingslaagniveau aangepakt. Value spreads combineren ook benoemde en anonieme records; type spreads maken alleen benoemde records. Later gedefinieerde velden hebben voorrang en bronnen eenmaal in volgorde worden uitgevoerd. Zie Recordspreidingsoperatoren voor voorbeelden van composities en risico's voor gedeelde evolutie.

Dankzij @brianrourkeboll voor recordverschillen.

Uitbreidingsleden voor generieke algoritmen, operators en tuples

Als u een algoritme over bestaande typen heen wilt delen, kunnen extensieleden met F# 11.2 voldoen aan beperkingen voor statisch opgeloste typeparameters (SRTP). Eén transformatie kan bijvoorbeeld lijsten en opties verwerken zonder wrapper-typen.

Note

Schakel in de .NET 11.0.100 SDK <LangVersion>preview</LangVersion> in waar extensies worden gevonden en open de module ervan. Zie bibliotheekvereisten.

Een transformatie delen tussen lijsten en opties

Met de bibliotheek map binnen bereik kan de toepassing beide weergaven opschonen, elementtypen wijzigen en transformaties samenstellen:

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]

De adapters in Structural.Adapters gebruiken vertrouwde bewerkingen, in plaats van nieuwe containers op te leggen:

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

De shared-functie in Structural.Algorithms vraagt het inputtype om een statisch Map-lid:

let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
    (^Input :
        (static member Map: ^Input * ('T -> 'U) -> ^Output)
        (source, mapping))

^Input en ^Output zijn statisch opgeloste typen. Bij elke concrete aanroep selecteert de compiler het lid met de vereiste handtekening. De extensies voegen geen interface-implementatie toe. De volledige applicatie opent beide modules.

Operators, context en uitvoertypen

Hetzelfde mechanisme ondersteunt tupleoperators en contextdoorgifte en uitvoergestuurde constructie. Systeemeigen tuple-headers vereisen dat de F# 11.2-feature is ingeschakeld bij hun declaraties en op aanroepplaatsen waar extensies worden opgelost.

Met een afzonderlijke toevoeging, AllowOverloadOnReturnTypekan één fabriek overbelastingen declareren die worden onderscheiden door hun retourtypen:

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"

Voor dit Core 11-kenmerk is geen voorbeeldweergave vereist. De resultaataannotatie selecteert een overload; zonder deze annotatie blijft de aanroep ambigu. Zie Selecteer op retourtype.

Dank aan @gusty voor het uitbreiden van ondersteuning voor generiek programmeren.

.NET integratie en de ontwikkelervaring

Directe delegates behouden het werkelijke contract van de handler

Als u handlernamen en bindingskenmerken beschikbaar wilt maken voor frameworks, kan F# 11 rechtstreeks een compatibele benoemde functie gebruiken in plaats van een wrapper. ASP.NET Core Minimal API's kunnen vervolgens het route-, service- en headercontract lezen:

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)

Zie het uitvoerbaar project en de bouw-/compatibiliteitsregels. Captures en argumentaanpassing kunnen nog steeds wrappers vereisen.

Met dank aan @kerams voor directe gedelegeerden.

XML-documentatie opnieuw gebruiken

Als u gedeelde API-opmerkingen eenmaal wilt onderhouden, plaatst u deze in shared.xml:

<docs>
  <tracking>
    <remarks>An unrecognized tracking identifier has status Unknown.</remarks>
  </tracking>
</docs>

Met GenerateDocumentationFile ingeschakeld breidt F# 11 <include> uit naar de verzonden ledenvermeldingen:

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

De include-implementatie werkt beide beschrijvingen van één fragment bij. Afzonderlijk bevat de tooling-resolver basisbeschrijvingen voor <inheritdoc/>; de compiler laat die tag staan voor documentatiegebruikers. Zie de XML-documentatie voor voorbeelden en oplossingsregels.

Dank aan @T-Gro voor de gedeelde XML-documentatie in FSharp.Core.

Inlinefuncties debuggen in niet-geoptimaliseerde builds

Als u inline helperargumenten wilt inspecteren, stelt u een onderbrekingspunt let total in in een niet-geoptimaliseerde build en stapt u in addFee. F# 11 geeft nu de voorkeur aan aanroepbare methoden in deze builds:

let inline addFee (fee: decimal) (amount: decimal) =
    let total = amount + fee
    total

let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42

Geoptimaliseerde builds behouden de inlining. Sommige constructies moeten nog steeds worden uitgebreid. Zie Debug inline functions voor deze limieten en het --always-inline besturingselement.

Asynchrone coördinatie en taakcoördinatie

Als u F#-werkstromen wilt verbinden met op taken gebaseerde API's, voegt FSharp.Core 11 annuleringsbewuste bruggen, gebonden batches en resultaattransformaties toe.

Gekoppelde openstaande I/O, niet alleen de resultaatverzameling

Een grote batch leesbewerkingen kan meer werk starten dan een toepassing openstaand zou moeten hebben. Om te bepalen wanneer op taken gebaseerd werk begint, heeft een coördinator factories nodig, niet taken die al zijn gestart:

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

Maximaal vier leesbewerkingen worden gelijktijdig uitgevoerd; resultaten behouden de invoervolgorde. Een fout vraagt om annulering op hetzelfde niveau via de opgegeven tokens. Dit beperkt de gelijktijdigheid, niet verzoeken per seconde. Zie Async- en taakfuncties voor sequentiële varianten en varianten met eenheidsresultaat.

Behoud annulering en de onderliggende uitzondering over de grens heen

Wanneer een werkstroom extern werk start, moet het token met dat werk reizen. Async.StartTaskImmediate geeft het token van de actieve workflow door aan de taakfabriek en wacht tot deze is voltooid:

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
    }

Een enkele taakfout wordt door deze handler afgehandeld als de onderliggende FileNotFoundException. Gebruik voor een reeds gemaakte taak of waardetaak; Async.Awaitde oudere Async.AwaitTask behoudt het gedrag van uitzonderingsterugloop.

Voor taakgerichte bellers biedt Task.startAsyncImmediate token workflow de omgekeerde brug. Het begint op de aanroepende thread tot de eerste asynchrone ophanging.

Overeenkomende helpers gebruiken voor korte transformaties

Als u één resultaat wilt transformeren zonder een volledige berekeningsexpressie, gebruikt u de overeenkomende helpers in Microsoft.FSharp.Control. Lees bijvoorbeeld een bestand, haal de lengte ervan op en retourneer een gewone fout als Error:

open System.IO
open System.Threading

let fileLength (path: string) (ct: CancellationToken) =
    File.ReadAllTextAsync(path, ct)
    |> Task.map String.length
    |> Task.catch

Een bestand met Report 1 geeft Ok 8; een ontbrekend bestand geeft een Error bevat FileNotFoundException. Annulering wordt nog steeds doorgegeven. Async en ValueTask bieden overeenkomende map, binden fouthelpers; zie Async- en taakfuncties. Gebruik een berekeningsuitdrukking voor vertakkingen of verschillende afhankelijke waarden.

ValueTask-helpers vereisen Core's netstandard2.1 of net10.0 build en eenmalig gebruik.

Dank aan @bartelink voor bijdragen aan de async/task-API.

Opnieuw genereren na asynchrone foutrapportage

Operationele logging moet informatie toevoegen zonder te veranderen wat de aanroeper afvangt of te verhullen waar de fout is ontstaan. Handlers voor berekeningsexpressies kunnen nu ook reraise () gebruiken nadat ze op de logger hebben gewacht:

Note

Rethrow in berekeningsexpressies is een functie van F# 11.2. Schakel <LangVersion>preview</LangVersion> in de .NET SDK 11.0.100 in.

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 ()
    }

Na succesvol loggen worden dezelfde uitzondering en oorspronkelijke throw-site-informatie doorgegeven. Een loggerfout kan het origineel vervangen. Dit pakt het observability-verzoek voor async aan zonder runtime-async. Zie Opnieuw genereren vanuit een berekeningsexpressie.

Dank aan @xperiandri voor deze steun.

FSharp.Core kan worden gebouwd op moderne .NET

Als u moderne API's wilt gebruiken zonder oudere doelen te verwijderen, voegt net10.0 FSharp.Core er naast netstandard2.0 toe en netstandard2.1. NuGet selecteert de compatibele build. De bijgewerkte verpakking van hulpprogramma’s maakt ook taakgebaseerde asynchrone opschoning mogelijk in F# Interactive, net als in gecompileerde projecten. Zie doelframeworks voor FSharp.Core.

Dankzij @T-Gro voor moderne Core-targeting en het verpakken van SDK-hulpmiddelen.

Asynchrone runtime voor ontwikkelaars van builders

Ontwikkelaars van builders kunnen de runtime gemarkeerde methoden laten opschorten en hervatten in plaats van door de compiler gegenereerde toestandsmachines te gebruiken. Een experimentele, door de bibliotheek gedefinieerde builder biedt het mechanisme aan als een computation expression:

let readManifest (path: string) =
    runtimeTask {
        let! text = System.IO.File.ReadAllTextAsync path
        return text.Length
    }

Note

Deze experimentele opbouwfunctie maakt gebruik van een F# 11.2-functie waarbij de inline-implementatie wordt gedefinieerd of uitgebreid; schakel preview in de .NET 11.0.100 SDK in. Het vereist ook de build en verwijzingen van net10.0 Core 11 en een runtime met runtime-async-ondersteuning. De geverifieerde combinatie van runtime en referentie in dit geval is .NET 11 RC1. Alleen richten op .NET 10 is niet voldoende. Zie Runtime async-experimenten.

FSharp.Core levert dit voorbeeld niet mee als een builder voor algemeen gebruik. Gewone task en async code schakelen er niet automatisch naar over en aanroepers van een al gecompileerde gewone API die een taak retourneert hebben niet per se preview nodig.

Dank aan @majocha voor runtime-async.

Native AOT-geschikte tekst en tweedimensionale arrays

Ter ondersteuning van Native AOT verwijdert F# 11 opmaak en dynamische toewijzingsvereisten op basis van reflectie uit algemenere tekst- en arraybewerkingen.

Wanneer u al een tekenreeks hebt, hebt u geen "%s"-indeling nodig om deze te schrijven. Nieuwe kernfuncties print en printn accepteren waarden via de conversie van string F#; printn voegt een nieuwe regel toe:

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

Tekenreeksinterpolatie gebruikt directe conversie en String.Concat in meer gevallen. .NET-opmaakgaten zoals {total:F2} gebruiken invariante opmaak en boxen de waarde; %A en printf-vormen met precisiespecificatie kunnen nog steeds printf-opmaak nodig hebben. Selecteer een expliciete cultuur voor gelokaliseerde uitvoer.

Met --reflectionfree krijgen records en samenvoegingen ook gegenereerde ToString-uitvoer. Zie Interpolatie en Native AOT voor cultuurvoorbeelden en ondersteunde paden.

Met dank aan @bbatsov voor de afdrukfuncties en @charlesroddie voor de interpolatie-implementatie.

Gewone rasterbewerkingen op basis van nul behouden

Een nulgebaseerd raster zou niet alleen om een allocatiepad voor dynamische code te vermijden een vervangende Array2D-module nodig moeten hebben. Bijgewerkte core-implementaties ondersteunen vertrouwde initialisatie en transformatie:

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.createen initrebase gebruik het toewijzingspad op basis van nul in Core-builds. Grensbehoudende map, mapi en copy kunnen een nieuwere .NET-toewijzings-API gebruiken in de net10.0-build; de .NET Standard-builds behouden hun terugval. Arrays die niet op nul zijn gebaseerd, worden niet verouderd verklaard, maar hun systeemeigen toewijzingsbeperkingen blijven behouden.

Zie Tweedimensionale arrays en Native AOT voor toewijzingspaden en resterende beperkingen. Controleer de publiceringsdiagnose van de volledige toepassing.

Met dank aan @T-Gro voor de verbeteringen aan Array2D.

Minder geheugenretentie en minder herhaald werk

F# 11 vermindert herhaalde compileranalyse en sommige geheugentoewijzingen in toepassingen. Deze zijn van invloed op verschillende werkbelastingen.

Bibliotheken waarnaar wordt verwezen delen tussen projecten

Om herhaalde kopieën van gedeelde binaire afhankelijkheden te voorkomen, kan de taalservice geschikte grafen van geïmporteerde bibliotheken tussen projecten delen.

In het retained-memory-experiment van de implementerende PR is de tien projecten tellende ReSharper.FSharp-oplossing gedaald van 1.547,2 MB naar 1.002,2 MB met sharing ingeschakeld. Dat is een aan/uit-meting voor die workload, geen algemene F# 10-tot-11-reductie. Oplossingen die worden gedomineerd door projectverwijzingen in plaats van gedeelde binaire verwijzingen, kunnen veel minder voordeel opleveren.

Het lezen van metagegevens en inlinespecialisatie verminderen ook tijdelijke allocaties, een afzonderlijke maatstaf. Zie toolingprestaties voor omvang en opties.

Dankzij @auduchinok voor het importeren van gedeelde bibliotheken.

Zelfstandig werk standaard parallel compileren

Parallelle compilatie is nu standaard ingeschakeld, waarbij deterministische uitvoer en regels voor bronvolgorde behouden blijven. Verbeteringen in cacheopslag en beveiligde patronen voorkomen andere herhaalde werkzaamheden. Visual Studio/FCS vermindert ook ongewijzigde documentanalyse en afhankelijke gelijktijdige controles; zie de prestaties van hulpprogramma's.

Callback en overhead voor toewijzing van lege resultaten voorkomen

Geselecteerd List en Array functies zijn nu inline callbacks. Voor uw eigen inline helpers OptimizeClosureIfNotInlined past u een ondoorzichtige callback eenmaal aan:

let inline foldItems
    ([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
    state
    (items: 'T[]) =
    let mutable total = state
    for item in items do
        total <- folder total item
    total

Andere toewijzingen kunnen blijven. Zie Curried callbacks optimaliseren voor vereisten.

Lege resultaten zijn een ander veelvoorkomend geval van geslaagd werk. De workload die aan FSharpLint ten grondslag ligt combineerde herhaaldelijk regels die geen waarschuwingen opleverden. Core hergebruikt nu gedeelde lege matrices in meer bewerkingen:

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

De wijziging verwijdert redundante lege matrixobjecten, niet elke toewijzing in de pijplijn. Vertrouw niet op afzonderlijke lege resultaten met afzonderlijke referentie-identiteiten.

@T-Gro Dankzij callbacks voor inlineverzamelingen en gedeelde lege arrays mogelijk gemaakt.

Nauwkeurigere API-aanroepen en dagelijkse gemakken

Namen vereisen wanneer de gegenereerde parametervolgorde kan worden gewijzigd

Om gegenereerde API's te beschermen tegen stille wijzigingen in de volgorde van parameters, kan een auteur benoemde argumenten vereisen:

Note

Afdwingen is een F# 11.2-functie; schakel preview in de .NET 11.0.100 SDK in. Er is ook een beschikbare System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute definitie vereist. Het volledige voorbeeld bevat een compatibiliteitsdefinitie voor verwijzingen waaraan die ontbreekt.

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.

Als u de volgorde van de declaratie wilt wijzigen, blijft het resultaat van de benoemde aanroep behouden. 0.75MEen positionele aanroep wordt geweigerd. Voor de gegenereerde API-use-case moet de provider de annotatie verzenden.

Een reeds verpakt generiek argument herkennen

Om te voorkomen dat een bestaande task als een gewone resultaatwaarde wordt verpakt, geeft een nieuwe overloadregel als laatste redmiddel de voorkeur aan Task<'T> boven de kale 'T:

Note

Het gedrag voor de meest concrete tiebreaker en overbelastingsprioriteit is een functie van F# 11.2; schakel previewfuncties in de .NET 11.0.100 SDK in.

open System.Threading.Tasks

let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>

Het resultaat is ValueTask<int>, niet ValueTask<Task<int>>. Zie Overbelasting selecteren voor de limieten van de regel.

Geef de voorkeur aan een nieuwe overbelasting zonder het oude ingangspunt te verwijderen

Een bibliotheek kan een oude diagnostische methode voor bestaande binaries behouden en tegelijk een overload toevoegen die de bronlocatie van de aanroeper meegeeft. OverloadResolutionPriorityAttribute maakt deze voorkeur expliciet voor nieuw gecompileerde aanroepers:

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.

De ongewijzigde aanroep selecteert de regelbewuste overload. Zie Overloadselectie voor regels voor bereik en compatibiliteit.

De beloftes voor voorwaardelijke null-waarde van .NET gebruiken

Als uw bestandspad niet null is, moet het nullable resultaat van de Path-API geen controle afdwingen die volgens het eigen contract uitgesloten is. F# 11 maakt gebruik van de bestaande .NET NotNullIfNotNull belofte: een niet-null benoemde invoer impliceert een niet-null-resultaat.

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

Schakel <Nullable>enable</Nullable>in. Nullable invoerwaarden hebben nog steeds nullable handling nodig; zie Contracten voor voorwaardelijk retourneren. Gebruik de afzonderlijke T? voor onbeperkte C# Unchecked.withNull API's.

Met dank aan @kerams voor ondersteuning voor voorwaardelijke nullbaarheid.

Kleinere taalverbeteringen

Als u een recordtype wilt aanroepen met .NET constructorsyntaxis, gebruikt u F# 11.2 recordconstructors:

type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)

Benoemde argumenten maken je minder afhankelijk van de veldvolgorde. Met dank aan @charlesroddie voor deze functie.

Voor voorwaardelijke bron vermijdt #if geneste #elif groepen:

#if EU_WAREHOUSE
let dispatchTimeZone = "Europe/Paris"
#elif US_WAREHOUSE
let dispatchTimeZone = "America/New_York"
#else
let dispatchTimeZone = "UTC"
#endif

System.Console.WriteLine(dispatchTimeZone)

De eerste overeenkomende vertakking wint. Het voorbeeldproject definieert EU_WAREHOUSE en drukt Europe/Paris af; als geen van beide symbolen is gedefinieerd, is de uitvoer UTC.

Voor gerelateerde hulpprogramma's en interoperabiliteitscases:

Voordat u een upgrade uitvoert

Houd rekening met deze gevolgwijzigingen bij het upgraden:

Betrokken code Action
Bibliotheken met .fsi bestanden Afgedwongen contractkenmerken kopiëren naar handtekeningen; weglatingen zijn nu FS3888-fouten.
Op extensies gebaseerde inline-bibliotheken Bekijk uitgestelde handtekeningen en vereisten voor producenten/consumenten. In F# 11.2 kan type-inferentie meer algemene beperkingen behouden.
Gedelegeerden van instantiemethode Valideer ontvangers vóór directe constructie, wanneer er nu null-ontvangerfouten optreden.
Gelokaliseerde geïnterpoleerde uitvoer .NET-opmaakplaatshouders gebruiken de invariante cultuur. Kies een expliciete cultuur wanneer gelokaliseerde uitvoer is bedoeld.
Aangepaste builders met final do! Leveringsbinding en voltooiing van eenheid; final do! selecteert geen leden meer die definitief doorsturen.
Onvoltooid let! Voeg de volgende rekenkundige hoofdtekst toe; een onvolledige binding heeft nu een meer directe diagnose.
Oudere taalinstellingen Ondersteunde genummerde modi beginnen bij F# 8; de niet-strikte inspringingsmodus is verwijderd. Zie de taalversie en inspringing.
Bitsgewijze bewerkingen voor enums Gebruik enums met een integraal onderliggend type; ongeldige onderliggende typen worden geweigerd.