Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Bienvenido a F# 11! Esta versión le ayuda a mantener distintos modelos de dominio, reutilizar algoritmos genéricos, coordinar el trabajo asincrónico y reducir el uso de memoria del compilador.
- Los propagaciones de registros reutilizan campos sin forzar el almacenamiento y los modelos de aplicación para compartir un tipo.
- Los miembros de extensión en algoritmos genéricos permiten que los tipos existentes compartan operaciones sin contenedores.
- .NET integración conserva los metadatos delegados y reutiliza la documentación de la API.
- Las utilidades para tareas y asincronía propagan la cancelación a través de los límites de la API y limitan el trabajo concurrente.
- Las mejoras del compilador y de la biblioteca reducen el análisis repetido y evitan algunas asignaciones.
Gracias a la comunidad de F# por las propuestas, implementaciones, revisiones y pruebas que han hecho posibles estos cambios. Los vínculos de colaborador acompañan a las características siguientes.
Note
Los ejemplos requieren un compilador de F# 11 y FSharp.Core 11 con las características que se muestran. En el SDK de .NET 11.0.100, las características de F# 11.2 requieren <LangVersion>preview</LangVersion>; consulte la configuración de idioma y la compatibilidad planeada del SDK. El ejemplo en tiempo de ejecución asincrónico tiene requisitos adicionales de tiempo de ejecución; Es posible que un SDK de .NET 11 anterior no contenga todos los cambios.
Propagación de registros: modelos distintos sin asignaciones repetitivas
Para mantener distintos los modelos de almacenamiento y de aplicación, las expansiones de registros reutilizan sus campos compartidos sin repetir las declaraciones. Aquí los modelos comparten los detalles del cliente, pero usan tipos de identificador diferentes:
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 contribuye a las declaraciones de campos. En el límite, ...row copia los valores mientras la conversión analiza el identificador:
let toCustomer (row: CustomerRow) =
match Guid.TryParse row.Id with
| true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
| false, _ -> Error "Invalid customer identifier."
El destino mantiene los detalles compartidos, reemplaza Id y excluye InternalNote.
Guid.TryParse, no la propagación, valida el identificador; La entrada con formato incorrecto genera Error.
Esto aborda el problema de asignación de capa de aplicación. Los spreads de valor también combinan registros con nombre y anónimos; los spreads de tipo crean solo registros con nombre. Los campos posteriores ganan y las fuentes se ejecutan una vez en orden. Consulte Operadores de propagación de registros para ver ejemplos de composición y riesgos de evolución compartida.
Gracias a @brianrourkeboll por los diferenciales récord.
Miembros de extensión para algoritmos genéricos, operadores y tuplas
Para compartir un algoritmo entre los tipos existentes, F# 11.2 permite a los miembros de extensión satisfacer las restricciones de parámetros de tipo (SRTP) resueltas estáticamente. Por ejemplo, una transformación puede gestionar listas y opciones sin tipos de contenedor.
Note
En el SDK de .NET 11.0.100, habilite <LangVersion>preview</LangVersion> dónde se resuelven las extensiones y abra el módulo correspondiente. Consulte los requisitos de la biblioteca.
Compartir una transformación entre listas y opciones
Con map de la biblioteca en el ámbito, la aplicación puede limpiar cualquiera de las dos representaciones, cambiar tipos de elementos y crear transformaciones:
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]
Los adaptadores de Structural.Adapters usan operaciones habituales, en lugar de imponer contenedores nuevos:
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 función compartida en Structural.Algorithms solicita al tipo de entrada un miembro estático Map:
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
^Input y ^Output son tipos resueltos estáticamente. En cada llamada concreta, el compilador selecciona el miembro con la firma necesaria. Las extensiones no añaden la implementación de una interfaz. La aplicación completa abre ambos módulos.
Operadores, contexto y tipos de salida
El mismo mecanismo admite operadores de tupla y propagación de contexto y construcción orientada por la salida. Los encabezados de tupla nativos necesitan la característica F# 11.2 habilitada en sus declaraciones, así como en los sitios de llamadas de resolución de extensiones.
Una adición independiente, AllowOverloadOnReturnType, permite que una fábrica declare sobrecargas distinguidas por sus tipos de retorno:
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"
Este atributo Core 11 no requiere versión preliminar. La anotación de resultado selecciona una sobrecarga; sin ella, la llamada sigue siendo ambigua. Consulte Select by return type.
Gracias a @gusty por ampliar la compatibilidad de programación genérica.
Integración de .NET y la experiencia de desarrollo
Los delegados directos conservan el contrato real del manejador
Para exponer a los frameworks los nombres de los controladores y los atributos de enlace, F# 11 puede dirigirse directamente a una función con nombre que sea compatible en lugar de a una función contenedora. ASP.NET Core API mínimas pueden leer la ruta, el servicio y el contrato de encabezado:
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 el proyecto listo para ejecutar y las reglas de compilación y compatibilidad. Las capturas y la adaptación de argumentos todavía pueden requerir envoltorios.
Gracias a @kerams por los delegados directos.
Reutilizar documentación XML
Para mantener los comentarios compartidos de la API en un solo lugar, colóquelos en shared.xml:
<docs>
<tracking>
<remarks>An unrecognized tracking identifier has status Unknown.</remarks>
</tracking>
</docs>
Con GenerateDocumentationFile activado, F# 11 expande <include> en las entradas de miembro generadas:
/// <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)
La implementación de include actualiza ambas descripciones a partir de un único fragmento. Por separado, el módulo de resolución de herramientas expone descripciones básicas para <inheritdoc/>; el compilador deja esa etiqueta para quienes consumen la documentación. Consulte la documentación XML para ver ejemplos y reglas de resolución.
Gracias a @T-Gro por la documentación XML compartida en FSharp.Core.
Depurar funciones en línea en compilaciones no optimizadas
Para inspeccionar los argumentos de la función auxiliar insertada en línea, establezca un punto de interrupción en let total en una compilación no optimizada y entre en addFee. F# 11 ahora prefiere métodos invocables en estas compilaciones:
let inline addFee (fee: decimal) (amount: decimal) =
let total = amount + fee
total
let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42
Las compilaciones optimizadas conservan el inlining. Algunas construcciones todavía deben expandirse. Consulte Depurar funciones en línea para conocer esos límites y el control --always-inline.
Asincronía y coordinación de tareas
Para conectar flujos de trabajo de F# con API basadas en tareas, FSharp.Core 11 agrega puentes compatibles con cancelación, lotes limitados y transformaciones de resultados.
Limitar por la E/S pendiente, no solo la colección de resultados
Un gran lote de operaciones de lectura puede generar más trabajo del que una aplicación debería tener pendiente. Para decidir cuándo comienza el trabajo basado en tareas, un coordinador necesita factorías, no tareas que ya se hayan iniciado:
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
Como máximo, cuatro lecturas se realizan de forma concurrente; los resultados mantienen el orden de entrada. Un fallo solicita la cancelación de elementos hermanos a través de los tokens proporcionados. Esto limita la simultaneidad, no las solicitudes por segundo. Consulte funciones Async y Task para ver las variantes secuenciales y con resultado unit.
Mantener la cancelación y la excepción subyacente al cruzar el límite
Cuando un flujo de trabajo pone en marcha una tarea externa, su token debe acompañar a esa tarea.
Async.StartTaskImmediate pasa el token del flujo de trabajo en ejecución a la factoría de tareas y espera a que finalice:
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 único error de tarea llega a este controlador como el FileNotFoundException subyacente. Para una tarea o una tarea de valor ya creada, use Async.Await; el anterior Async.AwaitTask conserva su comportamiento de encapsulación de excepciones.
En el caso de los procesos que llaman basados en tareas, Task.startAsyncImmediate token workflow proporciona el puente inverso. Se inicia en el hilo de llamada hasta la primera suspensión asincrónica.
Use funciones auxiliares adecuadas para transformaciones breves
Para transformar un resultado sin una expresión de cálculo completa, use las funciones auxiliares de coincidencia en Microsoft.FSharp.Control. Por ejemplo, lea un archivo, obtenga su longitud y devuelva un error normal como Error:
open System.IO
open System.Threading
let fileLength (path: string) (ct: CancellationToken) =
File.ReadAllTextAsync(path, ct)
|> Task.map String.length
|> Task.catch
Un archivo que contiene Report 1 proporciona Ok 8; un archivo que falta proporciona un Error objeto que contiene FileNotFoundException. La cancelación todavía se propaga.
Async y ValueTask ofrecen map, bind y elementos auxiliares de errores correspondientes; consulte Funciones asincrónicas y de tareas. Utilice una expresión de cálculo para ramas o varios valores dependientes.
Los asistentes de ValueTask requieren la compilación de Core netstandard2.1 o net10.0 y consumo único.
Gracias a @bartelink por sus contribuciones a la API de async/task.
Volver a generar la excepción después de informar de un error asincrónico
El registro operativo debe agregar información sin cambiar lo que captura la función que llama ni ocultar dónde comenzó el fallo. Los manejadores de expresiones de cálculo ahora pueden usar reraise () incluso después de esperar al logger:
Note
Relanzamiento en expresiones de cálculo es una característica de F# 11.2. En el SDK de .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 ()
}
Después del inicio de sesión correcto, se propaga la misma excepción y la información original de la ubicación donde se lanzó la excepción. Un error del logger puede reemplazar el original. Esto aborda la solicitud de observabilidad asíncrona sin asincronía en tiempo de ejecución. Consulte Volver a producir desde una expresión de cálculo.
Gracias a @xperiandri por este apoyo.
FSharp.Core puede basase en .NET modernas
Para usar API modernas sin dejar de ser compatible con versiones de destino anteriores, FSharp.Core añade net10.0 junto a netstandard2.0 y netstandard2.1. NuGet selecciona la compilación compatible. El empaquetado de herramientas actualizado también permite la liberación asincrónica basada en tareas en F# Interactive, como en proyectos compilados. Consulte los marcos de destino de FSharp.Core.
Gracias a @T-Gro por la compatibilidad moderna con Core y el empaquetado de herramientas del SDK.
Ejecución asincrónica en tiempo de ejecución para autores de builders
Los autores de Builder pueden permitir que el tiempo de ejecución suspenda y reanude los métodos marcados en lugar de usar máquinas de estado generadas por el compilador. Un builder experimental definido por la biblioteca expone el mecanismo como una expresión de cálculo:
let readManifest (path: string) =
runtimeTask {
let! text = System.IO.File.ReadAllTextAsync path
return text.Length
}
Note
Este constructor experimental utiliza una característica de F# 11.2 en la que su implementación inline se define o se expande; en el SDK de .NET 11.0.100, habilite las características en vista previa. También requiere la compilación de Core 11 net10.0 y sus referencias, así como un entorno de ejecución con compatibilidad con runtime-async. La combinación comprobada de tiempo de ejecución y referencia aquí es .NET 11 RC1. Usar solo .NET 10 como destino no es suficiente. Consulte Experimentos asíncronos del entorno de ejecución.
FSharp.Core no incluye este ejemplo como constructor de propósito general. El código ordinario task y async no cambia automáticamente a este, y quienes llaman a una API ordinaria ya compilada que devuelve tareas no requieren de por sí una versión preliminar.
Gracias a @majocha por el async en tiempo de ejecución.
Texto nativo compatible con AOT y matrices bidimensionales
Para admitir AOT nativo, F# 11 quita el formato basado en reflexión y los requisitos de asignación dinámica de las operaciones de texto y matriz más comunes.
Imprimir valores y crear cadenas sin una capa de formato redundante
Si ya tienes una cadena, no necesitas "%s" formato solo para escribirla. Las nuevas funciones de Core print y printn aceptan valores mediante la conversión de F# string; printn anexa una nueva línea:
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
La interpolación de cadenas usa la conversión directa y String.Concat en más casos. Los marcadores de formato de .NET, como {total:F2}, usan formato de referencia cultural invariable y encajonan el valor; %A y los formatos printf con precisión pueden seguir necesitando formato printf. Seleccione una configuración regional explícita para la salida localizada.
Con --reflectionfree, los registros y las uniones también obtienen la salida generada ToString . Consulte Interpolación y AOT nativo para ver ejemplos de referencia cultural y rutas de acceso admitidas.
Gracias a @bbatsov por las funciones de impresión y a @charlesroddie por la implementación de la interpolación.
Mantener las operaciones ordinarias de cuadrícula con índice base cero
Una cuadrícula de base cero no debe requerir un módulo Array2D de reemplazo solo para evitar una ruta de asignación de código dinámico. Las implementaciones principales actualizadas admiten la inicialización y transformación conocidas:
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 y rebase usan la ruta de asignación basada en cero en las compilaciones de Core. Los map, mapi y copy que preservan los límites pueden usar una API de asignación de .NET más reciente en la compilación net10.0; las compilaciones de .NET Standard conservan su mecanismo alternativo. Las matrices con índice inicial distinto de cero no están en desuso, pero sus restricciones nativas de asignación se mantienen.
Consulte Matrices bidimensionales y AOT nativo para obtener rutas de asignación y restricciones restantes. Compruebe los diagnósticos de publicación de la aplicación completa.
Gracias a @T-Gro por las mejoras de Array2D.
Menor retención de memoria y menos trabajo repetido
F# 11 reduce el análisis repetido del compilador y algunas asignaciones de memoria de la aplicación. Estos afectan a diferentes cargas de trabajo.
Compartir bibliotecas referenciadas entre proyectos
Para evitar copias repetidas de dependencias binarias compartidas, el servicio de lenguaje puede compartir grafos de bibliotecas importadas elegibles entre proyectos.
En el experimento de memoria conservada de la PR de implementación, la solución ReSharper.FSharp de diez proyectos cayó de 1547,2 MB a 1002,2 MB con el uso compartido habilitado. Se trata de una medición de activado/desactivado para esa carga de trabajo, no una reducción general de F# de 10 a 11. Las soluciones dominadas por referencias a proyectos en lugar de por referencias binarias compartidas pueden experimentar mejoras mucho menores.
La lectura de metadatos y la especialización en línea también reducen las asignaciones temporales, una métrica independiente. Consulte Rendimiento de herramientas para el ámbito y los controles.
Gracias a @auduchinok por las importaciones de bibliotecas compartidas.
Compilación del trabajo independiente en paralelo de forma predeterminada
La compilación paralela ahora está habilitada de forma predeterminada, con reglas de salida deterministas y de orden de origen conservadas. Las mejoras de sobrecarga de almacenamiento en caché y patrón protegido evitan otro trabajo repetido. Visual Studio/FCS también reducen el análisis de documentos sin cambios y las comprobaciones simultáneas enlazadas; consulte Rendimiento de las herramientas.
Evitar la retrollamada y la sobrecarga de asignar resultados vacíos
Las List funciones seleccionadas y Array ahora las devoluciones de llamada insertadas. Para sus propios asistentes insertados, OptimizeClosureIfNotInlined adapta una devolución de llamada opaca currida una vez:
let inline foldItems
([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
state
(items: 'T[]) =
let mutable total = state
for item in items do
total <- folder total item
total
Otras asignaciones pueden permanecer. Consulte Optimización de devoluciones de llamada consultadas para conocer los requisitos.
Los resultados vacíos son otro caso común de funcionamiento correcto. La carga de trabajo de FSharpLint combinó repetidamente reglas que no encontraron ninguna advertencia. Core ahora reutiliza arrays vacíos compartidos en más operaciones:
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
El cambio quita los objetos redundantes de matriz vacía, no todas las asignaciones de la canalización. No confíe en resultados vacíos independientes que tengan identidades de referencia distintas.
Gracias a las @T-Grodevoluciones de llamada de colección insertadas y las matrices vacíascompartidas.
Llamadas a la API más precisas y comodidades cotidianas
Exigir nombres cuando puede cambiar el orden generado de los parámetros
Para proteger las API generadas frente a cambios silenciosos en el orden de los parámetros, un autor puede requerir argumentos con nombre:
Note
El cumplimiento es una característica F# 11.2; en el SDK de .NET 11.0.100, habilite la versión preliminar. También se requiere una definición disponible System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute . El ejemplo completo incluye una definición de compatibilidad para las referencias que carecen de ella.
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.
Al reordenar la declaración, se preserva el resultado de la llamada con argumentos con nombre, 0.75M; se rechaza una llamada con argumentos posicionales. Para el caso de uso de la API generada, el proveedor debe emitir la anotación.
Reconocimiento de un argumento genérico ya envuelto
Para evitar ajustar una tarea existente como un valor de resultado sin formato, una nueva regla de sobrecarga de último recurso prefiere Task<'T> sobre bare 'T:
Note
El comportamiento de desempate más concreto y prioridad de sobrecarga son las características F# 11.2; en el SDK de .NET 11.0.100, habilite la versión preliminar.
open System.Threading.Tasks
let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>
El resultado es ValueTask<int>, no ValueTask<Task<int>>. Consulte Selección de sobrecargas para ver los límites de la regla.
Preferir una nueva sobrecarga sin quitar el punto de entrada anterior
Es posible que una biblioteca mantenga un método de diagnóstico antiguo para los archivos binarios existentes al agregar una sobrecarga que incluya la ubicación en el código fuente de quien llama.
OverloadResolutionPriorityAttribute hace que esa preferencia sea explícita para el código que llama recién compilado:
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 llamada sin cambios selecciona la sobrecarga que tiene en cuenta las líneas. Consulte Overload selection para conocer el ámbito y las reglas de compatibilidad.
Use las promesas de nulabilidad condicional de .NET
Si la ruta de acceso del archivo no es NULL, el resultado que acepta valores NULL de la API de ruta de acceso no debe forzar una comprobación que su propio contrato descarta. F# 11 aprovecha la garantía existente de .NET NotNullIfNotNull: una entrada con nombre que no es NULL implica un resultado que no es 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
Habilite <Nullable>enable</Nullable>. Las entradas que aceptan valores NULL siguen necesitando control que acepta valores NULL; consulte Contratos de devolución condicional. Para las API de C# T? sin restricciones, use la utilidad auxiliar separada Unchecked.withNull.
Gracias a @kerams por la compatibilidad con la nulabilidad condicional.
Pequeñas mejoras lingüísticas
Para invocar un tipo de registro con sintaxis de constructor .NET, use los constructores de registros de F# 11.2:
type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)
Los argumentos con nombre evitan depender del orden de los campos. Gracias a @charlesroddie para esta característica.
Para el origen condicional, #elif evita los grupos anidados #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)
La primera rama coincidente gana. El proyecto de ejemplo define EU_WAREHOUSE e imprime Europe/Paris; sin ningún símbolo definido, la salida es UTC.
Para los casos relacionados de herramientas e interoperabilidad:
- Para identificar su cadena de herramientas interactiva, utilice
#version. - Para compartir el código fuente con un host externo, F# acepta líneas
#:de directiva de host sin interpretarlas como instrucciones de paquete o compilación. - Para evitar declaraciones de interfaz redundantes, use la implementación predeterminada de una interfaz derivada.
- Para leer el estado heredado dentro de un cierre normal, usa el nuevo soporte para campos protegidos de la clase base.
Antes de actualizar
Tenga en cuenta estos cambios consecuentes al actualizar:
| Código afectado | Action |
|---|---|
Bibliotecas con .fsi archivos |
Se copian los atributos de contrato exigidos en las signaturas; su omisión ahora genera errores FS3888. |
| Bibliotecas insertadas respaldadas por extensiones | Revise las firmas inferidas y los requisitos de productor/consumidor. La inferencia F# 11.2 puede conservar restricciones más generales. |
| Delegados de método de instancia | Validar los objetos receptores antes de la construcción directa, cuando ahora se producen errores de receptor nulo. |
| Salida interpolada localizada | Los marcadores de formato de .NET usan la configuración regional invariable. Elija una referencia cultural explícita cuando se pretenda una salida localizada. |
Generadores personalizados con final do! |
Enlace de suministro y finalización de unidades; final do! ya no selecciona miembros de reenvío final. |
Sin terminar let! |
Se añadió el siguiente cuerpo de computación; una vinculación incompleta ahora presenta un diagnóstico más directo. |
| Configuración de idiomas anteriores | Los modos numerados admitidos comienzan en F# 8; Se quita el modo de sangría no estricta. Consulte Versión del idioma y sangría. |
| Operaciones bit a bit de enum | Use enumeraciones con tipo subyacente entero; se rechazan los tipos subyacentes no válidos. |