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 idioma (LDM) pertinentes.
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 las especificaciones.
Problema planteado por el experto: https://github.com/dotnet/csharplang/issues/7700
Resumen
En el lenguaje C# 12 se ha agregado compatibilidad para crear instancias de tipos de colección más allá de solo matrices.
Consulte expresiones de colección.
Esta propuesta amplía params la compatibilidad con todos estos tipos de recopilación.
Motivación
Un params parámetro de matriz proporciona una manera cómoda de llamar a un método que toma una lista de argumentos de longitud arbitraria.
El parámetro today params debe ser un tipo de matriz. Sin embargo, puede resultar beneficioso que un desarrollador pueda tener la misma comodidad al llamar a las API que toman otros tipos de recopilación. Por ejemplo, un ImmutableArray<T>, ReadOnlySpan<T>o sin formato IEnumerable. Especialmente en los casos en los que el compilador puede evitar una asignación de matriz implícita con el fin de crear la colección (ImmutableArray<T>, ReadOnlySpan<T>, etc.).
Actualmente, en situaciones en las que una API toma un tipo de colección, los desarrolladores suelen agregar una sobrecarga que toma una params matriz, construir la colección de destino y llamar a la sobrecarga original con esa colección, por lo que los consumidores de la API tienen que intercambiar una asignación de matriz adicional para mayor comodidad.
Otra motivación es la capacidad de agregar una sobrecarga de intervalo de parámetros y tener prioridad sobre la versión de la matriz, simplemente mediante la recompilación del código fuente existente.
Diseño detallado
Parámetros de métodos
La sección Parámetros de método se ajusta de la manera siguiente.
formal_parameter_list
: fixed_parameters
- | fixed_parameters ',' parameter_array
+ | fixed_parameters ',' parameter_collection
- | parameter_array
+ | parameter_collection
;
-parameter_array
+parameter_collection
- : attributes? 'params' array_type identifier
+ : attributes? 'params' 'scoped'? type identifier
;
Un parameter_collection consta de un conjunto opcional de atributos, un params modificador, un modificador opcional scoped , un tipo y un identificador. Una colección de parámetros declara un único parámetro del tipo especificado con el nombre especificado.
El tipo de una colección de parámetros debe ser uno de los siguientes tipos de destino válidos para una expresión de colección (vea https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions):
- Tipo de matriz, en cuyo caso el tipo
T[]elemento es - Un tipo de intervalo
System.Span<T>System.ReadOnlySpan<T>
en qué casos el tipo de elemento esT
- Tipo con un método de creación adecuado que se puede invocar sin argumentos adicionales, que es al menos tan accesible como miembro declarante, y con un tipo de elemento correspondiente resultante de esa determinación
- Un tipode clase o estructura que implementa dónde
System.Collections.IEnumerable:El tipo tiene un constructor que se puede invocar sin argumentos y el constructor es al menos tan accesible como el miembro declarante.
El tipo tiene un método
Addde instancia (no una extensión) donde:- El método se puede invocar con un único argumento de valor.
- Si el método es genérico, los argumentos de tipo se pueden deducir del argumento.
- El método es al menos tan accesible como el miembro declarante.
En cuyo caso el tipo de elemento es el tipo de iteración del tipo.
- Un tipo de interfaz
-
System.Collections.Generic.IEnumerable<T>, -
System.Collections.Generic.IReadOnlyCollection<T>, -
System.Collections.Generic.IReadOnlyList<T>, -
System.Collections.Generic.ICollection<T>, System.Collections.Generic.IList<T>
en qué casos el tipo de elemento esT
-
En una invocación de método, una colección de parámetros permite especificar un único argumento del tipo de parámetro especificado, o permite especificar cero o más argumentos del tipo de elemento de la colección. Las colecciones de parámetros se describen más adelante en Colecciones de parámetros.
Una parameter_collection puede producirse después de un parámetro opcional, pero no puede tener un valor predeterminado: la omisión de argumentos de un parameter_collection daría lugar a la creación de una colección vacía.
Colecciones de parámetros
Se cambia el nombre de la sección Matrices de parámetros y se ajusta de la manera siguiente.
Un parámetro declarado con un params modificador es una colección de parámetros. Si una lista de parámetros formal incluye una colección de parámetros, será el último parámetro de la lista y será de tipo especificado en la sección Parámetros de método.
Nota: No es posible combinar el
paramsmodificador con los modificadoresin,outoref. nota final
Una colección de parámetros permite especificar argumentos de una de estas dos maneras en una invocación de método:
- El argumento proporcionado para una colección de parámetros puede ser una expresión única que se puede convertir implícitamente al tipo de colección de parámetros. En este caso, la colección de parámetros actúa exactamente como un parámetro de valor.
- Como alternativa, la invocación puede especificar cero o más argumentos para la colección de parámetros, donde cada argumento es una expresión que se puede convertir implícitamente al tipo de elemento de la colección de parámetros. En este caso, la invocación crea una instancia del tipo de colección de parámetros según las reglas especificadas en Expresiones de colección como si los argumentos se usaran como elementos de expresión en una expresión de colección en el mismo orden y usa la instancia de colección recién creada como argumento real. Al construir la instancia de la colección, se usan los argumentos originales sin convertir.
Excepto para permitir un número variable de argumentos en una invocación, una colección de parámetros es exactamente equivalente a un parámetro de valor del mismo tipo.
Al realizar la resolución de sobrecarga, un método con una colección de parámetros puede ser aplicable, ya sea en su forma normal o en su forma expandida. La forma expandida de un método solo está disponible si la forma normal del método no es aplicable y solo si un método aplicable con la misma firma que el formulario expandido aún no está declarado en el mismo tipo.
Surge una posible ambigüedad entre la forma normal y la forma expandida del método con un único argumento de colección de parámetros cuando se puede usar como la propia colección de parámetros y como elemento de la colección de parámetros al mismo tiempo. Sin embargo, la ambigüedad no presenta ningún problema, ya que se puede resolver insertando una conversión o usando una expresión de colección, si es necesario.
Firmas y sobrecarga
Todas las reglas en torno al params modificador en Firmas y sobrecargas permanecen tal como está.
Miembro de función aplicable
La sección Miembro de función aplicable se ajusta de la manera siguiente.
Si un miembro de función que incluye una colección de parámetros no es aplicable en su forma normal, el miembro de función podría ser aplicable en su forma expandida:
- Si la colección de parámetros no es una matriz, un formulario expandido no es aplicable a las versiones de lenguaje C# 12 y posteriores.
- El formulario expandido se construye reemplazando la colección de parámetros en la declaración de miembro de función por cero o más parámetros de valor del tipo de elemento de la colección de parámetros, de modo que el número de argumentos de la lista
Ade argumentos coincide con el número total de parámetros. SiAtiene menos argumentos que el número de parámetros fijos en la declaración del miembro funcional, la forma expandida del miembro funcional no puede construirse y, por tanto, no es aplicable. - En caso contrario, la forma expandida es aplicable si para cada argumento en
A, se cumple una de las siguientes condiciones:- el modo de paso de parámetros del argumento es idéntico al modo de paso de parámetros del parámetro correspondiente y
- para un parámetro de valor fijo o un parámetro de valor creado por la expansión, existe una conversión implícita desde la expresión de argumento al tipo del parámetro correspondiente, o bien
- para un
inparámetro ,outoref, el tipo de la expresión de argumento es idéntico al tipo del parámetro correspondiente.
- el modo de paso de parámetros del argumento es value y el modo de paso de parámetros del parámetro correspondiente es input y existe una conversión implícita desde la expresión de argumento al tipo del parámetro correspondiente.
- el modo de paso de parámetros del argumento es idéntico al modo de paso de parámetros del parámetro correspondiente y
Mejor miembro de función
La sección Miembro de función Better se ajusta de la manera siguiente.
Dada una lista de argumentos A con un conjunto de expresiones de argumentos {E₁, E₂, ..., Eᵥ} y dos miembros de función aplicables Mᵥ y Mₓ con tipos de parámetros {P₁, P₂, ..., Pᵥ} y {Q₁, Q₂, ..., Qᵥ}, Mᵥ se define que es un miembro de función mejor que Mₓ si
- para cada argumento, la conversión implícita de
EᵥaQᵥno es mejor que la conversión implícita deEᵥaPᵥ, y - para al menos un argumento, la conversión de
EᵥaPᵥes mejor que la conversión deEᵥaQᵥ.
En caso de que las secuencias de tipo de parámetro {P₁, P₂, ..., Pᵥ} y {Q₁, Q₂, ..., Qᵥ} sean equivalentes (es decir, cada Pᵢ tiene una conversión de identidad a la Qᵢcorrespondiente), se aplican las siguientes reglas de desempate, para determinar el mejor miembro de función.
- Si
Mᵢes un método no genérico yMₑes un método genérico, entoncesMᵢes mejor queMₑ. - De lo contrario, si
Mᵢes aplicable en su forma normal yMₑtiene una colección de parámetros y solo es aplicable en su forma expandida,Mᵢentonces es mejor queMₑ. - De lo contrario, si ambos métodos tienen colecciones params y solo son aplicables en sus formularios expandidos, y si la colección de parámetros de
Mᵢtiene menos elementos que la colección de parámetros deMₑ, entoncesMᵢes mejor queMₑ. - En caso contrario, si los tipos de los parámetros de
Mᵥson más específicos que los deMₓ, entoncesMᵥes mejor queMₓ. Deje que{R1, R2, ..., Rn}y{S1, S2, ..., Sn}representen los tipos de parámetros no instanciados y no expandidos deMᵥyMₓ. los tipos de parámetro deMᵥson más específicos queMₓsi, para cada parámetro,Rxno es menos específico queSx, y, para al menos un parámetro,Rxes más específico queSx:- Un parámetro de tipo es menos específico que un parámetro que no es de tipo.
- Recursivamente, un tipo construido es más específico que otro tipo construido (con el mismo número de argumentos de tipo) si al menos un argumento de tipo es más específico y ningún argumento de tipo es menos específico que el correspondiente argumento de tipo en el otro.
- Un tipo de matriz es más específico que otro tipo de matriz (con el mismo número de dimensiones) si el tipo de elemento del primero es más específico que el tipo de elemento del segundo.
- En caso contrario, si un miembro es un operador no elevado y el otro es un operador elevado, el no elevado es mejor.
- Si ninguno de los miembros de la función resulta ser mejor y todos los parámetros de
Mᵥtienen un argumento correspondiente mientras que los argumentos por defecto deben sustituirse por al menos un parámetro opcional enMₓ, entoncesMᵥes mejor queMₓ. - Si al menos un parámetro
Mᵥutiliza la mejor opción de paso de parámetros (sección 12.6.4.4) que el parámetro correspondiente enMₓy ninguno de los parámetros enMₓutiliza la mejor opción de paso de parámetros queMᵥ,Mᵥes mejor queMₓ. -
De lo contrario, si ambos métodos tienen colecciones params y solo son aplicables en sus formularios expandidos,
Mᵢes mejor queMₑsi el mismo conjunto de argumentos corresponde a los elementos de colección para ambos métodos y una de las siguientes suspensiones (esto corresponde a https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md):-
ambas colecciones de parámetros no son span_types y existe una conversión implícita de la colección params de
Mᵢa la colección params deMₑ -
la colección params de
MᵢesSystem.ReadOnlySpan<Eᵢ>, y la colección params deMₑesSystem.Span<Eₑ>y existe una conversión de identidad deEᵢa .Eₑ -
Eₑ
-
ambas colecciones de parámetros no son span_types y existe una conversión implícita de la colección params de
- En caso contrario, ningún miembro de la función es mejor.
La razón por la que se coloca la nueva regla de desempate al final de la lista es el último subproceso.
- ambas colecciones de parámetros no son span_types y existe una conversión implícita de la colección params de
Mᵢa la colección params deMₑ
es aplicable a las matrices y, por lo tanto, al realizar el desempate anteriormente se introducirá un cambio de comportamiento para los escenarios existentes.
Por ejemplo:
class Program
{
static void Main()
{
Test(1);
}
static void Test(in int x, params C2[] y) {} // There is an implicit conversion from `C2[]` to `C1[]`
static void Test(int x, params C1[] y) {} // Better candidate because of "better parameter-passing choice"
}
class C1 {}
class C2 : C1 {}
Si se aplica alguna de las reglas de desempate anteriores (incluida la regla "mejores conversiones de argumentos"), el resultado de la resolución de sobrecarga puede ser diferente en comparación con el caso cuando se usa una expresión de colección explícita como argumento en su lugar.
Por ejemplo:
class Program
{
static void Test1()
{
M1(['1', '2', '3']); // IEnumerable<char> overload is used because `char` is an exact match
M1('1', '2', '3'); // IEnumerable<char> overload is used because `char` is an exact match
}
static void M1(params IEnumerable<char> value) {}
static void M1(params System.ReadOnlySpan<MyChar> value) {}
class MyChar
{
private readonly int _i;
public MyChar(int i) { _i = i; }
public static implicit operator MyChar(int i) => new MyChar(i);
public static implicit operator char(MyChar c) => (char)c._i;
}
static void Test2()
{
M2([1]); // Span overload is used
M2(1); // Array overload is used, not generic
}
static void M2<T>(params System.Span<T> y){}
static void M2(params int[] y){}
static void Test3()
{
M3("3", ["4"]); // Ambiguity, better-ness of argument conversions goes in opposite directions.
M3("3", "4"); // Ambiguity, better-ness of argument conversions goes in opposite directions.
// Since parameter types are different ("object, string" vs. "string, object"), tie-breaking rules do not apply
}
static void M3(object x, params string[] y) {}
static void M3(string x, params Span<object> y) {}
}
Sin embargo, nuestra principal preocupación son escenarios en los que las sobrecargas solo difieren por el tipo de colección params, pero los tipos de colección tienen el mismo tipo de elemento. El comportamiento debe ser coherente con expresiones de colección explícitas para estos casos.
La condición "si el mismo conjunto de argumentos corresponde a los elementos de colección para ambos métodos" es importante para escenarios como:
class Program
{
static void Main()
{
Test(x: 1, y: 2); // Ambiguous
}
static void Test(int x, params System.ReadOnlySpan<int> y) {}
static void Test(int y, params System.Span<int> x) {}
}
No se siente razonable comparar colecciones creadas a partir de diferentes elementos.
Esta sección se revisó en LDM y se aprobó.
Un efecto de estas reglas es que cuando params se exponen distintos tipos de elementos, estos serán ambiguos cuando se llama a con una lista de argumentos vacía.
Por ejemplo:
class Program
{
static void Main()
{
// Old scenarios
C.M1(); // Ambiguous since params arrays were introduced
C.M1([]); // Ambiguous since params arrays were introduced
// New scenarios
C.M2(); // Ambiguous in C# 13
C.M2([]); // Ambiguous in C# 13
C.M3(); // Ambiguous in C# 13
C.M3([]); // Ambiguous in C# 13
}
public static void M1(params int[] a) {
}
public static void M1(params int?[] a) {
}
public static void M2(params ReadOnlySpan<int> a) {
}
public static void M2(params Span<int?> a) {
}
public static void M3(params ReadOnlySpan<int> a) {
}
public static void M3(params ReadOnlySpan<int?> a) {
}
}
Dado que priorizamos el tipo de elemento sobre todo, esto parece razonable; no hay nada que indicar al idioma si el usuario prefiere int? en int este escenario.
Vinculación dinámica
Los formularios expandidos de candidatos que usan colecciones de parámetros que no son de matriz no se considerarán candidatos válidos por el enlazador en tiempo de ejecución de C# actual.
Si el primary_expression no tiene el tipo dynamicen tiempo de compilación , la invocación del método se somete a una comprobación limitada en tiempo de compilación, tal como se describe en §12.6.5 Comprobación en tiempo de compilación de la invocación de miembro dinámico.
Si solo un solo candidato supera la prueba, la invocación del candidato se enlaza estáticamente cuando se cumplen todas las condiciones siguientes:
- el candidato es una función local
- el candidato no es genérico o sus argumentos de tipo se especifican explícitamente;
- no hay ambigüedad entre las formas normales y expandidas del candidato que no se pueden resolver en tiempo de compilación.
De lo contrario, el invocation_expression está enlazado dinámicamente.
Si solo un candidato ha superado la prueba anterior:
- si ese candidato es una función local, se produce un error en tiempo de compilación;
- Si ese candidato solo se aplica en formato expandido mediante colecciones de parámetros que no son de matriz, se produce un error en tiempo de compilación.
También debemos considerar la reversión o corrección de infracciones de especificaciones que afectan a las funciones locales en la actualidad, consulte https://github.com/dotnet/roslyn/issues/71399.
LDM confirmó que queremos corregir esta infracción de especificación.
Árboles de expresión
Las expresiones de colección no se admiten en árboles de expresión. Del mismo modo, las formas expandidas de colecciones de parámetros que no son de matriz no se admitirán en árboles de expresión. No cambiaremos cómo el compilador enlaza lambdas para árboles de expresión con el objetivo de evitar el uso de API que usan formas expandidas de colecciones de parámetros que no son de matriz.
Orden de evaluación con colecciones que no son de matriz en escenarios no triviales
Esta sección se revisó en LDM y se aprobó. A pesar de que los casos de matriz se desvía de otras colecciones, la especificación de lenguaje oficial no tiene que especificar reglas diferentes para matrices. Las desviaciones podrían tratarse simplemente como un artefacto de implementación. Al mismo tiempo, no pretendemos cambiar el comportamiento existente en torno a matrices.
Argumentos con nombre
Se crea y rellena una instancia de colección después de evaluar el argumento léxico anterior, pero antes de evaluar el argumento léxico siguiente.
Por ejemplo:
class Program
{
static void Main()
{
Test(b: GetB(), c: GetC(), a: GetA());
}
static void Test(int a, int b, params MyCollection c) {}
static int GetA() => 0;
static int GetB() => 0;
static int GetC() => 0;
}
El orden de evaluación es el siguiente:
-
GetBse llama a -
MyCollectionse crea y rellena,GetCse llama a en el proceso. -
GetAse llama a -
Testse llama a
Tenga en cuenta que, en el caso de la matriz de parámetros, la matriz se crea justo antes de invocar el método de destino, después de que todos los argumentos se evalúen en su orden léxico.
Asignación compuesta
Se crea y rellena una instancia de colección después de evaluar el índice léxico anterior, pero antes de evaluar el índice léxico siguiente. La instancia se usa para invocar el captador y el establecedor del indexador de destino.
Por ejemplo:
class Program
{
static void Test(Program p)
{
p[GetA(), GetC()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
static int GetC() => 0;
}
El orden de evaluación es el siguiente:
-
GetAse llama a y almacena en caché -
MyCollectionse crea, rellena y almacena en caché,GetCse llama en el proceso. - El captador del indexador se invoca con valores almacenados en caché para índices
- El resultado se incrementa
- El establecedor del indexador se invoca con valores almacenados en caché para índices y el resultado del incremento.
Ejemplo con una colección vacía:
class Program
{
static void Test(Program p)
{
p[GetA()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
}
El orden de evaluación es el siguiente:
-
GetAse llama a y almacena en caché - Se crea un vacío
MyCollectiony se almacena en caché. - El captador del indexador se invoca con valores almacenados en caché para índices
- El resultado se incrementa
- El establecedor del indexador se invoca con valores almacenados en caché para índices y el resultado del incremento.
Inicializador de objeto
Se crea y rellena una instancia de colección después de evaluar el índice léxico anterior, pero antes de evaluar el índice léxico siguiente. La instancia se usa para invocar el captador del indexador tantas veces como sea necesario, si existe.
Por ejemplo:
class C1
{
public int F1;
public int F2;
}
class Program
{
static void Test()
{
_ = new Program() { [GetA(), GetC()] = { F1 = GetF1(), F2 = GetF2() } };
}
C1 this[int a, params MyCollection c] => new C1();
static int GetA() => 0;
static int GetC() => 0;
static int GetF1() => 0;
static int GetF2() => 0;
}
El orden de evaluación es el siguiente:
-
GetAse llama a y almacena en caché -
MyCollectionse crea, rellena y almacena en caché,GetCse llama en el proceso. - El captador del indexador se invoca con valores almacenados en caché para índices
-
GetF1se evalúa y se asigna alF1campo deC1reajustado en el paso anterior. - El captador del indexador se invoca con valores almacenados en caché para índices
-
GetF2se evalúa y se asigna alF2campo deC1reajustado en el paso anterior.
Tenga en cuenta que, en el caso de la matriz de parámetros, sus elementos se evalúan y almacenan en caché, pero se usa una nueva instancia de una matriz (con los mismos valores dentro) para cada invocación del captador del indexador en su lugar. En el ejemplo anterior, el orden de evaluación es el siguiente:
-
GetAse llama a y almacena en caché -
GetCse llama a y almacena en caché - El captador del indexador se invoca con la caché
GetAy la nueva matriz rellenada con cachéGetC -
GetF1se evalúa y se asigna alF1campo deC1reajustado en el paso anterior. - El captador del indexador se invoca con la caché
GetAy la nueva matriz rellenada con cachéGetC -
GetF2se evalúa y se asigna alF2campo deC1reajustado en el paso anterior.
Ejemplo con una colección vacía:
class C1
{
public int F1;
public int F2;
}
class Program
{
static void Test()
{
_ = new Program() { [GetA()] = { F1 = GetF1(), F2 = GetF2() } };
}
C1 this[int a, params MyCollection c] => new C1();
static int GetA() => 0;
static int GetF1() => 0;
static int GetF2() => 0;
}
El orden de evaluación es el siguiente:
-
GetAse llama a y almacena en caché - Se crea un vacío
MyCollectiony se almacena en caché. - El captador del indexador se invoca con valores almacenados en caché para índices
-
GetF1se evalúa y se asigna alF1campo deC1reajustado en el paso anterior. - El captador del indexador se invoca con valores almacenados en caché para índices
-
GetF2se evalúa y se asigna alF2campo deC1reajustado en el paso anterior.
Seguridad de referencias
La sección de seguridad de expresiones de colección ref se aplica a la construcción de colecciones de parámetros cuando se invocan las API en su forma expandida.
Los parámetros params se encuentran implícitamente scoped cuando su tipo es una estructura ref. UnscopedRefAttribute se puede usar para invalidarlo.
Metadatos
En los metadatos podríamos marcar parámetros que no son de matriz params con System.ParamArrayAttribute, ya params que las matrices se marcan hoy en día.
Sin embargo, parece que será mucho más seguro usar un atributo diferente para los parámetros que no son de matriz params .
Por ejemplo, el compilador de VB actual no podrá consumirlos decorados sin ParamArrayAttribute formato normal ni expandido. Por lo tanto, es probable que una adición del modificador "params" interrumpa los consumidores de VB y, muy probablemente, los consumidores de otros lenguajes o herramientas.
Dado que, los parámetros que no son de matriz params se marcan con un nuevo System.Runtime.CompilerServices.ParamCollectionAttribute.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
public sealed class ParamCollectionAttribute : Attribute
{
public ParamCollectionAttribute() { }
}
}
Esta sección se revisó en LDM y se aprobó.
Preguntas abiertas
Asignaciones de pila
Esta es una cita de https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#unresolved-questions: "Las asignaciones de pila para colecciones enormes podrían volar la pila. ¿El compilador debe tener una heurística para colocar estos datos en el montón?
¿Debe no especificarse el idioma para permitir esta flexibilidad?
Deberíamos seguir las especificaciones para params Span<T>". Parece que tenemos que responder a las preguntas en el contexto de esta propuesta.
[Resuelto] Params implícitamente scoped
Se ha sugerido que, cuando params modifica un ref struct parámetro, se debe considerar como declarado scoped.
El argumento se hace que el número de casos en los que se quiere que el parámetro tenga ámbito es prácticamente del 100 % al examinar los casos de BCL. En algunos casos que lo necesitan, el valor predeterminado podría sobrescribirse con [UnscopedRef].
Sin embargo, podría no ser deseable cambiar el valor predeterminado simplemente en función de la presencia del params modificador. Especialmente, en invalidaciones o implementa el modificador de escenarios params no tiene que coincidir.
Resolución:
Los parámetros params tienen un ámbito implícito: https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements.
[Resuelto] Considere la posibilidad de aplicar scoped o params entre invalidaciones
Anteriormente hemos indicado que params los parámetros deben ser scoped de forma predeterminada. Sin embargo, esto introduce un comportamiento impar en la invalidación, debido a nuestras reglas existentes en torno a la indicación paramsde :
class Base
{
internal virtual Span<int> M1(scoped Span<int> s1, params Span<int> s2) => throw null!;
}
class Derived : Base
{
internal override Span<int> M1(Span<int> s1, // Error, missing `scoped` on override
Span<int> s2 // Proposal: Error: parameter must include either `params` or `scoped`
) => throw null!;
}
Tenemos una diferencia en el comportamiento entre llevar y params llevar las scoped invalidaciones aquí: params se hereda implícitamente, y con él , mientras scoped que scopedpor sí solo no se hereda implícitamente y debe repetirse en todos los niveles.
Propuesta: Debemos exigir que las invalidaciones de params parámetros deben indicar params explícitamente o scoped si la definición original es un scoped parámetro. En otras palabras, s2 en Derived debe tener params, scopedo ambos.
Resolución:
Se requerirá explícitamente que se indique scoped o se invalide un params parámetro cuando se requiera un parámetro que noparams sea para hacerlo: paramshttps://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-and-scoped-across-overrides .
[Resuelto] ¿Debe haber miembros necesarios para impedir la declaración del params parámetro?
Considere el ejemplo siguiente:
using System.Collections;
using System.Collections.Generic;
public class MyCollection1 : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(long l) => throw null;
public required int F; // Collection has required member and constructor doesn't initialize it explicitly
}
class Program
{
static void Main()
{
Test(2, 3); // error CS9035: Required member 'MyCollection1.F' must be set in the object initializer or attribute constructor.
}
// Proposal: An error is reported for the parameter indicating that the constructor that is required
// to be available doesn't initialize required members. In other words, one is able
// to declare such a parameter under the specified conditions.
static void Test(params MyCollection1 a)
{
}
}
Resolución:
Validaremos required a los miembros en el constructor que se usa para determinar la idoneidad de ser un params parámetro en el sitio de declaración: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters.
Alternativas
Además, podría decirse que, con expresiones de colección ahora en el lenguaje, no es necesario ampliar params la compatibilidad en absoluto. Para cualquier tipo de colección. Para consumir una API con tipo de colección, un desarrollador simplemente necesita agregar dos caracteres, [ antes de la lista expandida de argumentos y ] después de ella. Dado que, la extensión params de la compatibilidad puede ser un exceso, especialmente que es poco probable que otros lenguajes admitan el consumo de parámetros que no son de matriz params en cualquier momento.
Propuestas relacionadas
- https://github.com/dotnet/csharplang/issues/1757
- https://github.com/dotnet/csharplang/blob/main/proposals/rejected/format.md#extending-params
Reuniones de diseño relacionadas
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-08.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-10.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-29.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-31.md#params-collections-evaluation-orders
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-collections
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-22.md#effect-of-language-version-on-overload-resolution-in-presence-of-params-collections
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-24.md#adjust-dynamic-binding-rules-for-a-situation-of-a-single-applicable-candidate
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-01.md#adjust-binding-rules-in-the-presence-of-a-single-candidate
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-03.md#params-collections-and-dynamic
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-12.md#params-span-breaks
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#params-span-breaks
C# feature specifications