Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Bem-vindo ao F# 11! Esta versão ajuda você a manter modelos de domínio distintos, reutilizar algoritmos genéricos, coordenar trabalho assíncrono e reduzir o uso de memória do compilador.
- O registro espalha campos de reutilização sem forçar o armazenamento e os modelos de aplicativo a compartilhar um tipo.
- Os membros de extensão em algoritmos genéricos permitem que os tipos existentes compartilhem operações sem wrappers.
- Integração com .NET preserva metadados de delegados e reutiliza a documentação da API.
- Auxiliares assíncronos e de tarefas carregam o cancelamento entre os limites da API e limitam o trabalho simultâneo.
- As melhorias do compilador e da biblioteca reduzem a análise repetida e evitam algumas alocações.
Obrigado à comunidade F# pelas propostas, implementações, revisões e testes por trás dessas alterações. Os links para os colaboradores acompanham as funcionalidades abaixo.
Note
Os exemplos exigem um compilador F# 11 e o FSharp.Core 11 com os recursos mostrados. No SDK do .NET 11.0.100, os recursos do F# 11.2 exigem<LangVersion>preview</LangVersion>; consulte as configurações de idioma e o suporte planejado ao SDK. O exemplo de runtime-async tem requisitos adicionais de runtime; uma versão anterior do SDK do .NET 11 pode não incluir todas as alterações.
Espalhamento de registros: modelos distintos sem mapeamentos repetitivos
Para manter distintos os modelos de armazenamento e da aplicação, os record spreads reutilizam seus campos compartilhados sem repetir declarações. Aqui, os modelos compartilham dados do cliente, mas usam diferentes tipos de identificador:
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 fornece declarações de campos. Na fronteira, ...row copia valores enquanto a conversão faz o parsing do identificador:
let toCustomer (row: CustomerRow) =
match Guid.TryParse row.Id with
| true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
| false, _ -> Error "Invalid customer identifier."
O destino mantém os detalhes compartilhados, substitui Id e exclui InternalNote.
Guid.TryParse, não o spread, valida o identificador; a entrada malformada produz Error.
Isso resolve o problema de mapeamento da camada de aplicação. Os spreads de valores também combinam registros nomeados e anônimos; os spreads de tipos criam apenas registros nomeados. Os campos posteriores prevalecem, e as origens são executadas uma vez em ordem. Consulte operadores spread de registro para obter exemplos de composição e riscos de evolução compartilhada.
Graças a @brianrourkeboll pelas distribuições de registros.
Membros de extensão para algoritmos genéricos, operadores e tuplas
Para compartilhar um algoritmo entre tipos existentes, o F# 11.2 permite que membros de extensão satisfaçam as restrições de parâmetro de tipo resolvido estaticamente (SRTP). Por exemplo, uma transformação pode lidar com listas e opções sem tipos de wrapper.
Note
No SDK do .NET 11.0.100, habilite <LangVersion>preview</LangVersion> onde as extensões são resolvidas e abra seus módulos. Consulte os requisitos da biblioteca.
Compartilhar uma transformação entre listas e opções
Com a map da biblioteca disponível, o aplicativo pode limpar qualquer uma das representações, alterar tipos de elemento e compor transformações:
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]
Os adaptadores usam operações familiares, em Structural.Adapters vez de impor novos contêineres:
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
A função shared em Structural.Algorithms solicita ao tipo de entrada um membro estático Map:
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
^Input e ^Output são tipos resolvidos estaticamente. Em cada chamada concreta, o compilador seleciona o membro com a assinatura necessária. As extensões não adicionam uma implementação de interface. O aplicativo completo abre os dois módulos.
Operadores, contexto e tipos de saída
O mesmo mecanismo dá suporte a operadores de tupla e propagação de contexto e construção direcionada à saída. Os cabeçalhos de tupla nativa precisam do recurso F# 11.2 habilitado em suas declarações, bem como nos locais de chamada de resolução de extensões.
Uma adição AllowOverloadOnReturnType, , permite que uma fábrica declare sobrecargas distinguidas por seus retornos:
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"
Esse atributo Core 11 não requer pré-visualização. A anotação de resultado seleciona uma sobrecarga; sem ela, a chamada permanece ambígua. Consulte Selecionar por tipo de retorno.
Graças a @gusty por estender o suporte de programação genérica.
integração com .NET e a experiência de desenvolvimento
Os delegados diretos preservam o contrato real do handler
Para expor nomes de manipuladores e atributos de associação a estruturas, o F# 11 pode direcionar uma função nomeada compatível diretamente em vez de um wrapper. ASP.NET Core APIs mínimas podem ler a rota, o serviço e o contrato de cabeçalho:
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)
Consulte o projeto executável/passível de execução e as regras de construção/compatibilidade. Capturas de variáveis e adaptação de argumento ainda podem exigir wrappers.
Graças a @kerams para delegados diretos.
Reutilizar documentação XML
Para manter os comentários compartilhados da API em um só lugar, coloque-os em shared.xml:
<docs>
<tracking>
<remarks>An unrecognized tracking identifier has status Unknown.</remarks>
</tracking>
</docs>
Com GenerateDocumentationFile habilitado, o F# 11 expande <include> nas entradas de membro emitidas:
/// <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)
A implementação de inclusão atualiza ambas as descrições de um fragmento. Separadamente, o resolvedor de ferramentas expõe descrições base para <inheritdoc/>; o compilador deixa essa marca para os consumidores de documentação. Consulte a documentação XML para obter exemplos e regras de resolução.
Agradecimentos a @T-Gro por compartilhar a documentação XML no FSharp.Core.
Depurar funções inline em compilações não otimizadas
Para inspecionar argumentos auxiliares inline, defina um ponto let total de interrupção em um build não otimizado e use Entrar em addFee. O F# 11 agora prefere métodos que podem ser chamados nestes builds:
let inline addFee (fee: decimal) (amount: decimal) =
let total = amount + fee
total
let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42
As compilações otimizadas preservam a inserção embutida. Alguns construtos ainda devem ser expandidos. Consulte Depuração de funções embutidas para ver esses limites e o controle --always-inline.
Coordenação assíncrona de tarefas
Para conectar fluxos de trabalho F# com APIs baseadas em tarefas, o FSharp.Core 11 adiciona pontes com reconhecimento de cancelamento, lotes limitados e transformações de resultados.
Limitado por E/S pendente, não apenas a coleção de resultados
Um grande lote de leituras pode iniciar mais trabalho do que um aplicativo deve ter pendente. Para decidir quando o trabalho baseado em tarefas começa, um coordenador precisa de fábricas de tarefas, não de tarefas que já tenham sido iniciadas:
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
No máximo quatro leituras são executadas simultaneamente; os resultados retêm a ordem de entrada. Uma falha solicita o cancelamento de solicitações relacionadas por meio dos tokens fornecidos. Isso limita a simultaneidade, não as solicitações por segundo. Consulte funções assíncronas e task para variantes sequenciais e de retorno unitário.
Manter o cancelamento e a exceção subjacente através da barreira
Quando um fluxo de trabalho inicia o trabalho externo, seu token deve viajar com esse trabalho.
Async.StartTaskImmediate passa o token do fluxo de trabalho em execução para a fábrica de tarefas e aguarda a conclusão:
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
}
Uma única falha de tarefa chega a este manipulador como o FileNotFoundException subjacente. Para uma tarefa ou tarefa de valor já criada, use Async.Await; o mais antigo Async.AwaitTask mantém seu comportamento de encapsulamento de exceção.
Para processos chamadores baseados em tarefas, Task.startAsyncImmediate token workflow fornece a ponte inversa. A execução começa na thread de chamada até a primeira suspensão assíncrona.
Usar auxiliares correspondentes para transformações curtas
Para transformar um resultado sem uma expressão de computação completa, use os helpers correspondentes em Microsoft.FSharp.Control. Por exemplo, leia um arquivo, obtenha seu tamanho e retorne uma falha comum como Error:
open System.IO
open System.Threading
let fileLength (path: string) (ct: CancellationToken) =
File.ReadAllTextAsync(path, ct)
|> Task.map String.length
|> Task.catch
Um arquivo que contém Report 1 gera Ok 8; um arquivo inexistente gera um Error contendo FileNotFoundException. O cancelamento ainda é propagado.
Async e ValueTask oferecem map, bind e auxiliares de erro correspondentes; consulte funções assíncronas e de tarefa. Use uma expressão de computação para branches ou vários valores dependentes.
Os auxiliares do ValueTask exigem o build ou net10.0 do Core e netstandard2.1.
Agradecimentos a @bartelink pelas contribuições para a API async/task.
Propagar novamente após relatório de falha assíncrona
O registro em log operacional deve adicionar informações sem alterar o que o chamador captura ou ocultar onde a falha começou. Os manipuladores de expressão de computação agora podem usar reraise () mesmo depois de aguardarem o logger:
Note
O relançamento de expressão de computação é um recurso do F# 11.2. No SDK do .NET 11.0.100, habilite <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 ()
}
Após o registro em log bem-sucedido, a mesma exceção e informações originais do site de lançamento são propagadas. Uma falha do logger pode substituir o original. Isso aborda a solicitação de observabilidade assíncrona sem assincronia em tempo de execução. Consulte Relançamento em uma expressão de computação.
Agradecemos a @xperiandri por este suporte.
O FSharp.Core pode se basear em .NET modernas
Para usar APIs modernas sem abandonar plataformas mais antigas, FSharp.Core adiciona net10.0 ao lado de netstandard2.0 e netstandard2.1. O NuGet seleciona a compilação compatível. O empacotamento atualizado da ferramenta também permite o descarte assíncrono baseado em tarefas no F# Interactive, assim como em projetos compilados. Consulte os frameworks de destino do FSharp.Core.
Agradecimentos a por @T-Gro e empacotamento das ferramentas do SDK.
Assíncrono de runtime para autores do construtor
Os autores de builders podem permitir que o tempo de execução suspenda e retome os métodos marcados, em vez de usar máquinas de estado geradas pelo compilador. Um builder experimental definido pela biblioteca expõe o mecanismo como uma expressão computacional:
let readManifest (path: string) =
runtimeTask {
let! text = System.IO.File.ReadAllTextAsync path
return text.Length
}
Note
Este construtor experimental usa um recurso do F# 11.2 no qual a implementação inline é definida ou expandida; no SDK .NET 11.0.100, ative a versão prévia. Também requer a compilação e as referências de net10.0 do Core 11 e um runtime com suporte a runtime-async. A combinação verificada de runtime e referência aqui é o .NET 11 RC1. Ter como destino apenas o .NET 10 não é suficiente. Consulte experimentos assíncronos de runtime.
O FSharp.Core não fornece este exemplo como um builder para uso geral. O código comum task e async não muda automaticamente para isso, e os chamadores de uma API comum já compilada que retorna tarefa não precisam necessariamente de versão prévia.
Graças a @majocha por runtime assíncrono.
Texto compatível com Native AOT e arrays bidimensionais
Para oferecer suporte ao Native AOT, o F# 11 remove a formatação baseada em reflexão e a necessidade de alocação dinâmica das operações mais comuns de texto e de matrizes.
Imprimir valores e criar strings sem uma camada redundante de formatação
Quando você já tem uma string, não precisa de um formato "%s" só para escrevê-la. As novas funções Core print e printn aceitam valores por meio da conversão de string do F#; printn acrescenta uma nova linha:
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
A interpolação de strings usa conversão direta e String.Concat em um número maior de casos. Especificadores de formato do .NET, como {total:F2}, usam formatação invariante e empacotam o valor; %A e formatos printf com especificador de precisão ainda podem exigir formatação printf. Selecione uma cultura específica para saída localizada.
Com --reflectionfree, registros e uniões também obtêm a saída gerada ToString . Consulte Interpolação e AOT Nativo para obter exemplos de cultura e caminhos com suporte.
Agradecimentos a @bbatsov pelas funções de impressão e a @charlesroddie pela implementação de interpolação.
Manter operações normais de grade de base zero
Uma grade baseada em zero não deve exigir um módulo Array2D de substituição apenas para evitar um caminho de alocação de código dinâmico. As implementações atualizadas do Core suportam a inicialização e a transformação já conhecidas:
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 e rebase usam o caminho de alocação baseado em zero nas compilações Core.
map, mapi e copy com preservação de limites podem usar uma API de alocação .NET mais recente no build net10.0; os builds do .NET Standard mantêm seu fallback. Arrays que não começam em zero não são obsoletas, mas suas restrições nativas de alocação permanecem.
Consulte matrizes bidimensionais e AOT nativo para obter caminhos de alocação e restrições restantes. Verifique os diagnósticos de publicação da aplicação completa.
Agradecimentos a @T-Gro pelas melhorias no Array2D.
Menos memória retida e menos trabalho repetido
O F# 11 reduz a análise repetida do compilador e algumas alocações de memória no aplicativo. Elas afetam cargas de trabalho diferentes.
Compartilhar bibliotecas referenciadas entre projetos
Para evitar cópias repetidas de dependências binárias compartilhadas, o serviço de idioma pode compartilhar grafos de bibliotecas importadas elegíveis entre projetos.
No experimento de memória retida da PR de implementação, a solução ReSharper.FSharp de dez projetos caiu de 1.547,2 MB para 1.002,2 MB com compartilhamento habilitado. Essa é uma medida off/on para essa carga de trabalho, não uma redução geral de 10 para 11 em F#. Soluções dominadas por referências de projeto em vez de referências binárias compartilhadas podem ver ganhos muito menores.
A leitura de metadados e a especialização embutida também reduzem as alocações temporárias, outra métrica. Consulte desempenho das ferramentas para ver o escopo e os controles.
Graças a @auduchinok para importações de biblioteca compartilhada.
Compilar trabalho independente em paralelo por padrão
A compilação paralela agora está habilitada por padrão, com regras determinísticas de saída e ordem de origem preservadas. O armazenamento em cache de sobrecargas e as melhorias nos padrões com guarda evitam repetir outras operações. Visual Studio/FCS também reduzem a análise de documentos inalterados e as verificações simultâneas associadas; consulte o desempenho de Ferramentas.
Evitar callback e sobrecarga de alocação de resultado vazio
As funções List e Array selecionadas agora são callbacks inline. Para seus próprios helpers inline, OptimizeClosureIfNotInlined adapta uma vez um callback currificado opaco:
let inline foldItems
([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
state
(items: 'T[]) =
let mutable total = state
for item in items do
total <- folder total item
total
Outras alocações podem permanecer. Consulte Otimizar callbacks currificados para ver os requisitos.
Resultados vazios são outro caso comum de trabalho bem-sucedido. A carga de trabalho motivadora do FSharpLint combinou repetidamente regras que não geravam avisos. O Core agora reutiliza arrays vazios compartilhados em mais operações:
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
A alteração remove objetos de array vazio redundantes, não todas as alocações do pipeline. Não conte com resultados vazios separados tendo identidades de referência distintas.
Graças a @T-Gro por callbacks de coleção embutidos e arrays vazios compartilhados.
Chamadas de API mais precisas e conveniências cotidianas
Exigir nomes dos parâmetros quando a ordem dos parâmetros gerados pode ser alterada
Para proteger as APIs geradas contra alterações silenciosas de ordem de parâmetro, um autor pode exigir argumentos nomeados:
Note
Enforcement é um recurso do F# 11.2; no SDK do .NET 11.0.100, habilite a versão prévia. Uma definição disponível System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute também é necessária. O exemplo completo fornece uma definição de compatibilidade para referências que não têm ela.
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.
Reordenar a declaração preserva o resultado, 0.75M; uma chamada posicional é rejeitada. Para o caso de uso da API gerada, o provedor deve emitir a anotação.
Reconhecer um argumento genérico já encapsulado
Para evitar encapsular uma tarefa existente como um valor de resultado simples, uma nova regra de sobrecarga de último recurso prefere Task<'T> em vez de 'T puro:
Note
O desempate mais concreto e o comportamento de prioridade de sobrecarga são recursos do F# 11.2; no SDK do .NET 11.0.100, habilite a versão prévia.
open System.Threading.Tasks
let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>
O resultado é ValueTask<int>, não ValueTask<Task<int>>. Consulte Overload selection para obter os limites da regra.
Preferir uma nova sobrecarga sem remover o ponto de entrada antigo
Uma biblioteca pode manter um método de diagnóstico antigo para binários existentes, ao mesmo tempo em que adiciona uma sobrecarga que inclui a localização no código-fonte do chamador.
OverloadResolutionPriorityAttribute torna essa preferência explícita para chamadores recém-compilados:
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.
A chamada inalterada seleciona a sobrecarga sensível à linha. Consulte Overload selection para obter regras de escopo e compatibilidade.
Usar as promessas de nulidade condicional do .NET
Se o caminho do arquivo não for nulo, o resultado anulável de uma API Path não deverá forçar uma verificação que seu próprio contrato exclui. O F# 11 consome a promessa existente do .NET NotNullIfNotNull: um parâmetro de entrada nomeado não nulo implica um resultado não nulo.
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
Habilitar <Nullable>enable</Nullable>. As entradas nullable ainda precisam de tratamento de nullable; ver Contratos de retorno condicional. Para APIs C# T? sem restrições, use o Unchecked.withNull utilitário separado.
Agradecemos a @kerams pelo suporte à anulabilidade condicional.
Pequenas melhorias de linguagem
Para chamar um tipo de registro com a sintaxe de construtor do .NET, use os construtores de registro do F# 11.2:
type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)
Argumentos nomeados evitam dependência da ordem de campo. Obrigado a @charlesroddie por este recurso.
Para origem condicional, #elif evita grupos #if aninhados:
#if EU_WAREHOUSE
let dispatchTimeZone = "Europe/Paris"
#elif US_WAREHOUSE
let dispatchTimeZone = "America/New_York"
#else
let dispatchTimeZone = "UTC"
#endif
System.Console.WriteLine(dispatchTimeZone)
A primeira ramificação correspondente prevalece. O projeto de exemplo define EU_WAREHOUSE e imprime Europe/Paris; sem nenhum símbolo definido, a saída é UTC.
Para ferramentas relacionadas e casos de interoperabilidade:
- Para identificar sua cadeia de ferramentas interativa, use
#version. - Para compartilhar a origem com um host externo, o F# aceita linhas
#:de diretiva de host sem interpretá-las como instruções de pacote ou build. - Para evitar declarações de interface redundantes, use a implementação padrão de uma interface derivada.
- Para ler o estado herdado dentro de um fechamento comum, use o novo suporte para campos base protegidos.
Antes de atualizar
Tenha estas alterações conseqüentes em mente ao atualizar:
| Código afetado | Action |
|---|---|
Bibliotecas que contêm .fsi arquivos |
Copia atributos de contrato obrigatórios para assinaturas; as omissões agora são erros FS3888. |
| Bibliotecas inline com suporte por extensão | Analise as assinaturas inferidas e os requisitos do produtor/consumidor. A inferência F# 11.2 pode manter restrições mais gerais. |
| Delegados de métodos de instância | Valide os receptores antes da construção direta, quando agora ocorrem falhas de receptor nulo. |
| Saída interpolada localizada | Os especificadores de formato do .NET usam a cultura invariável. Escolha uma cultura explícita quando se pretende uma saída localizada. |
Construtores personalizados com o modificador final do! |
Fornecimento de vinculação e conclusão de unidade; final do! não seleciona mais membros de encaminhamento final. |
Inacabado let! |
Adicione o seguinte corpo de computação; uma vinculação incompleta agora tem um diagnóstico mais direto. |
| Configurações de idioma anteriores | Os modos numerados com suporte começam em F# 8; o modo de recuo não estrito foi removido. Consulte Versão do idioma e recuo. |
| operações bit a bit de enum | Use enumerações com tipo subjacente integral; tipos subjacentes inválidos são rejeitados. |