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.
Nota:
Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos y se incorporan en la especificación ECMA actual.
Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de lenguaje (LDM) correspondientes.
Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C#, en el artículo sobre especificaciones.
Problema planteado por el experto: https://github.com/dotnet/csharplang/issues/8637
Resumen
Permitir que los parámetros lambda se declaren con modificadores sin necesidad de sus nombres de tipo. Por ejemplo, (ref entry) =>
en lugar de (ref FileSystemEntry entry) =>
.
Como otro ejemplo, dado este delegado:
delegate bool TryParse<T>(string text, out T result);
Permita esta declaración de parámetro simplificada:
TryParse<int> parse1 = (text, out result) => Int32.TryParse(text, out result);
Actualmente solo esto es válido:
TryParse<int> parse2 = (string text, out int result) => Int32.TryParse(text, out result);
Diseño detallado
Gramática
Sin cambios. La gramática lambda más reciente es:
lambda_expression
: modifier* identifier '=>' (block | expression)
| attribute_list* modifier* type? lambda_parameter_list '=>' (block | expression)
;
lambda_parameter_list
: lambda_parameters (',' parameter_array)?
| parameter_array
;
lambda_parameter
: identifier
| attribute_list* modifier* type? identifier default_argument?
;
Esta gramática ya considera modifiers* identifier
que es sintácticamente legal.
Notas
- Esto no se aplica a una expresión lambda sin una lista de parámetros.
ref x => x.ToString()
no sería legal. - Una lista de parámetros lambda todavía no puede mezclar
implicit_anonymous_function_parameter
parámetros yexplicit_anonymous_function_parameter
. (ref readonly p) =>
,(scoped ref p) =>
y(scoped ref readonly p) =>
se permitirán, de la misma manera que se permiten con parámetros explícitos, debido a:- Mejoras de estructura de bajo nivel en C# 11
ref readonly
parámetros en C# 12
- La presencia o ausencia de un tipo no tiene ningún impacto en si un modificador es obligatorio o opcional. En otras palabras, si se requería un modificador con un tipo presente, sigue siendo necesario con el tipo ausente. Del mismo modo, si un modificador era opcional con un tipo presente, es opcional con el tipo ausente.
Semántica
https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/expressions#12192-anonymous-function-signatures se actualiza de la siguiente manera:
En un lambda_parameter_list
todos los lambda_parameter
elementos deben tener un type
presente o no tener un type
presente. La primera es una "lista de parámetros con tipo explícito", mientras que esta última es una "lista de parámetros con tipo implícito".
Los parámetros en una lista de parámetros con tipo implícito no pueden tener un default_argument
. Pueden tener una attribute_list
.
El siguiente cambio es necesario para las conversiones de funciones anónimas:
[...]
Si F tiene una lista de parámetros tipados explícita o implícitamente, cada parámetro en D tiene el mismo tipo y modificadores que el parámetro correspondiente en F, omitiendo los modificadores de parámetros y los valores por defecto.
Notas y aclaraciones
scoped
y params
se permiten como modificadores explícitos en una expresión lambda sin un tipo explícito presente. La semántica sigue siendo la misma para ambas. En concreto, tampoco forma parte de la determinación realizada en:
Si una función anónima tiene un explicit_anonymous_function_signature, el conjunto de tipos de delegado y tipos de árbol de expresiones compatibles está restringido a los que tienen los mismos tipos de parámetros y modificadores en el mismo orden.
Los únicos modificadores que restringen los tipos de delegado compatibles son ref
, out
in
y ref readonly
.
Por ejemplo, en una lambda con tipo explícito, lo siguiente es actualmente ambiguo:
delegate void D<T>(scoped T t) where T : allows ref struct;
delegate void E<T>(T t) where T : allows ref struct;
class C
{
void M<T>() where T : allows ref struct
{
// error CS0121: The call is ambiguous between the following methods or properties: 'C.M1<T>(D<T>)' and 'C.M1<T>(E<T>)'
// despite the presence of the `scoped` keyword.
M1<T>((scoped T t) => { });
}
void M1<T>(D<T> d) where T : allows ref struct
{
}
void M1<T>(E<T> d) where T : allows ref struct
{
}
}
Esto sigue siendo el caso cuando se usan lambdas con tipo implícito:
delegate void D<T>(scoped T t) where T : allows ref struct;
delegate void E<T>(T t) where T : allows ref struct;
class C
{
void M<T>() where T : allows ref struct
{
// This will remain ambiguous. 'scoped' will not be used to restrict the set of delegates.
M1<T>((scoped t) => { });
}
void M1<T>(D<T> d) where T : allows ref struct
{
}
void M1<T>(E<T> d) where T : allows ref struct
{
}
}
Preguntas abiertas
¿
scoped
Siempre debe ser un modificador en una expresión lambda en C# 14? Esto es importante para un caso como:M((scoped s = default) => { });
En este caso, esto no se encuentra bajo la especificación de "parámetro lambda simple", ya que un "lambda simple" no puede contener un inicializador (
= default
). Por lo tanto,scoped
aquí se trata como untype
(como estaba en C# 13). ¿Queremos mantener esto? ¿O sería más sencillo tener una regla más general quescoped
siempre sea modificador y, por tanto, seguiría siendo un modificador aquí en un parámetro simple no válido?Recomendación: convierta esto en un modificador. Disuadimos a las personas de utilizar tipos que están en minúsculas, Y hemos hecho ilegal crear un tipo llamado
scoped
en C# también. Por lo tanto, esto solo podría ser algún tipo de caso de hacer referencia a un tipo de otra biblioteca. La solución alternativa es trivial si de alguna manera le pasa esto. Simplemente use@scoped
para que sea un nombre de tipo en vez de un modificador.¿Permitir
params
en un parámetro lambda simple? El trabajo anterior con lambda ya ha agregado compatibilidad paraparams T[] values
en una expresión lambda. Este modificador es opcional, y la expresión lambda y el delegado original pueden tener un desajuste con respecto a este modificador (aunque advertimos si el delegado no tiene el modificador y lambda sí). Si seguimos permitiendo esto con un parámetro lambda simple. Por ejemplo,M((params values) => { ... })
Recomendación: Sí. Permítalo. El propósito de esta especificación es permitir simplemente quitar el "tipo" de un parámetro lambda, al tiempo que mantiene los modificadores. Este es sólo otro caso de eso. Esto también se queda fuera de la impl (como los atributos auxiliares en estos parámetros), por lo que es más trabajo intentar bloquear esto.
Conclusión 15/01/2025: No. Esto siempre será un error. No parece haber casos útiles para esto y nadie está pidiendo esto. Es más fácil y seguro restringir este caso sin sentido desde el principio. Si se presentan casos de uso pertinentes, podemos reconsiderar.
¿'scoped' influye en la resolución de sobrecargas? Por ejemplo, si existieran varias sobrecargas de un delegado y una incluyera un parámetro 'scoped', mientras que la otra no, ¿la presencia de 'scoped' influye en la resolución de la sobrecarga?
Recomendación: No. No permita que 'scoped' influya en la resolución de sobrecarga. Así es como funcionan ahora las cosas con las lambdas normales explícitamente tipados. Por ejemplo:
delegate void D<T>(scoped T t) where T : allows ref struct; delegate void E<T>(T t) where T : allows ref struct; class C { void M<T>() where T : allows ref struct { M1<T>((scoped T t) => { }); } void M1<T>(D<T> d) where T : allows ref struct { } void M1<T>(E<T> d) where T : allows ref struct { } }
Esto es ambiguo hoy. A pesar de tener 'scoped' en
D<T>
y 'scoped' en el parámetro lambda, no resolvemos esto. No creemos que esto deba cambiar con las lambdas de tipado implícito.Conclusión 15/01/2025: Lo anterior también se aplicará para 'lambas simples'. 'scoped' no influirá en la resolución de sobrecargas (mientras que
ref
yout
seguirán haciéndolo).¿Permitir lambdas '(scoped x) => ...'?
Recomendación: Sí. Si no permitimos esto, podríamos terminar en escenarios donde un usuario puede escribir una lambda completamente tipada explícitamente, pero no la versión tipada implícitamente. Por ejemplo:
delegate ReadOnlySpan<int> D(scoped ReadOnlySpan<int> x); class C { static void Main(string[] args) { D d = (scoped ReadOnlySpan<int> x) => throw null!; D d = (ReadOnlySpan<int> x) => throw null!; // error! 'scoped' is required } }
La eliminación de "alcance" aquí provocaría un error (el idioma requiere la correspondencia en este caso entre la expresión lambda y el delegado). Como queremos que el usuario pueda escribir lambdas como esta, sin especificar el tipo explícitamente, eso significa que
(scoped x) => ...
debe permitirse.Conclusión 15/01/2025: Permitiremos lambdas
(scoped x) => ...
.
C# feature specifications