params Collections
Remarque
Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Elle inclut les changements de spécification proposés, ainsi que les informations nécessaires à la conception et au développement de la fonctionnalité. Ces articles sont publiés jusqu'à ce que les changements proposés soient finalisés et incorporés dans la spécification ECMA actuelle.
Il peut y avoir des différences entre la spécification de la fonctionnalité et l'implémentation réalisée. Ces différences sont consignées dans les notes pertinentes de la réunion de conception linguistique (LDM).
Pour en savoir plus sur le processus d'adoption des speclets de fonctionnalité dans la norme du langage C#, consultez l'article sur les spécifications.
Problème phare : https://github.com/dotnet/csharplang/issues/7700
Récapitulatif
Le langage C# 12 a ajouté la prise en charge de la création d'instances de types de collection autres que les tableaux.
Voir expressions de collection.
Cette proposition étend la prise en charge de params
à tous ces types de collections.
Motivation
Un paramètre de tableau params
fournit un moyen pratique d’appeler une méthode qui prend une liste d’arguments de longueur arbitraire.
Aujourd’hui, le paramètre params
doit être un type de tableau. Toutefois, il peut être intéressant pour un développeur de pouvoir bénéficier de la même commodité lorsqu'il appelle des API qui prennent en charge d'autres types de collection. Par exemple, ImmutableArray<T>
, ReadOnlySpan<T>
ou un paramètre IEnumerable
brut. Notamment dans les cas où le compilateur est en mesure d’éviter une allocation de tableau implicite dans le but de créer la collection (ImmutableArray<T>
, ReadOnlySpan<T>
, etc.).
Aujourd'hui, lorsqu'une API prend un type de collection, les développeurs ajoutent généralement une surcharge params
qui prend un tableau, construisent la collection cible et appellent la surcharge originale avec cette collection, de sorte que les consommateurs de l'API doivent échanger une allocation de tableau supplémentaire pour des raisons de commodité.
Une autre motivation est la possibilité d’ajouter une surcharge de paramètres et de donner la priorité à celle-ci par rapport à la version du tableau, simplement en recompilant le code source existant.
Conception détaillée
Paramètres de la méthode
La section des paramètres de la méthode est ajustée comme suit.
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
;
Une collection de paramètres, parameter_collection, se compose d’un ensemble facultatif d’attributs, d’un modificateur params
, d’un modificateur scoped
facultatif, d’un type et d’un identificateur. Une collection de paramètres déclare un paramètre unique du type donné avec le nom donné.
Le type d’une collection de paramètres doit être l’un des types cibles valides suivants pour une expression de collection (voir https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) :
- Un type de tableau unidimensionnel
, dans ce cas le type d'élément est - Un type d'étendue
System.Span<T>
System.ReadOnlySpan<T>
auquel cas le type d’élément estT
- Un type avec une méthode create appropriée, qui est au moins aussi accessible que le membre déclarant, et avec un type d'élément correspondant résultant de cette détermination.
- Un type de structure ou de classe qui implémente
System.Collections.IEnumerable
où :Le type a un constructeur qui peut être appelé sans argument. Le constructeur est au moins aussi accessible que le membre déclarant.
Le type a une méthode d’instance (et non une extension)
Add
où :- La méthode peut être appelée avec un seul argument de valeur.
- Si la méthode est générique, les arguments de type peuvent être déduits de l’argument.
- La méthode est au moins aussi accessible que le membre déclarant.
Auquel cas le type d’élément est le type d’itération du type.
- Un type d'interface
-
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>
auquel cas le type d’élément estT
-
Dans un appel de méthode, une collection de paramètres permet de spécifier un seul argument du type de paramètre donné, ou d’autoriser zéro ou plusieurs arguments du type d’élément à spécifier, issu de la collection. Les collections de paramètres sont décrites plus loin dans collections de paramètres.
Une collection de paramètres, parameter_collection, peut avoir lieu après un paramètre facultatif, mais ne peut pas avoir de valeur par défaut. L’omission d’arguments pour parameter_collection entraînerait plutôt la création d’une collection vide.
Collections de paramètres
La section des tableaux de paramètres est renommée et ajustée comme suit.
Un paramètre déclaré avec un modificateur params
est une collection de paramètres. Si une liste de paramètres formelle inclut une collection de paramètres, il s’agit du dernier paramètre de la liste et il doit être du type spécifié dans la section Paramètres de la méthode.
Remarque : il n’est pas possible de combiner le modificateur
params
avec les modificateursin
,out
ouref
. fin de la remarque
Une collection de paramètres permet aux arguments d’être spécifiés de deux manières différentes lors d’un appel de méthode :
- L’argument donné pour une collection de paramètres peut être une expression unique qui est implicitement convertible en type de collection de paramètres. Dans ce cas, la collection de paramètres fonctionne exactement comme un paramètre de valeur.
- Dans le cas contraire, l’appel peut spécifier zéro ou plusieurs arguments pour la collection de paramètres, où chaque argument est une expression implicitement convertible en type d’élément de la collection de paramètres. Dans ce cas, l’appel crée une instance du type de collection de paramètres conformément aux règles spécifiées dans les expressions de collection comme si les arguments avaient été utilisés comme éléments d’expression dans une expression de collection dans le même ordre, et utilise l’instance de collection nouvellement créée comme argument réel. Lors de la construction de l'instance de collection, les arguments originaux non convertis sont utilisés.
Sauf si vous autorisez un nombre variable d’arguments dans un appel, une collection de paramètres équivaut précisément à un paramètre de valeur du même type.
Lors de l’exécution d’une résolution de surcharge, une méthode avec une collection de paramètres peut être applicable, sous sa forme normale ou dans sa forme développée. La forme développée d’une méthode est disponible uniquement si la forme normale de la méthode n’est pas applicable et uniquement si une méthode applicable avec la même signature que la forme développée n’est pas déjà déclarée dans le même type.
Une ambiguïté potentielle se produit entre la forme normale et la forme développée de la méthode avec un seul argument de collection de paramètres lorsqu’elle peut être utilisée comme collection de paramètres elle-même et simultanément comme élément de la collection de paramètres. Toutefois, cette ambiguïté ne pose aucun problème, car elle peut être résolue en insérant un cast ou en utilisant une expression de collection, si nécessaire.
Signatures et surcharge
Toutes les règles relatives au modificateur params
dans les signatures et à la surcharge restent inchangées.
Membre de fonction applicable
La section Membre de fonction applicable est modifiée comme suit.
Si un membre de fonction qui inclut une collection de paramètres n'est pas applicable dans sa forme normale, le membre de fonction peut être applicable dans sa forme étendue :
- Si la collection de paramètres n’est pas un tableau, une forme développée n’est pas applicable aux versions de langage C# 12 et antérieures.
- La forme étendue est construite en remplaçant la collection de paramètres dans la déclaration du membre de fonction par zéro ou plusieurs paramètres de valeur du type d’élément de la collection de paramètres de sorte que le nombre d’arguments dans la liste d’arguments
A
corresponde au nombre total de paramètres. SiA
comporte moins d’arguments que le nombre de paramètres fixes dans la déclaration du membre de fonction, la forme étendue du membre de fonction ne peut pas être construite et n’est donc pas applicable. - Sinon, la forme étendue est applicable si pour chaque argument dans
A
, l’une des conditions suivantes est satisfaite :- le mode de passage de l’argument est identique au mode de passage du paramètre correspondant, et
- pour un paramètre de valeur fixe ou un paramètre de valeur créé par l’expansion, une conversion implicite existe entre l’expression de l’argument et le type du paramètre correspondant, ou
- pour un paramètre
in
,out
ouref
, le type de l’expression d’argument est identique au type du paramètre correspondant.
- le mode de passage des paramètres de l'argument est la valeur, et le mode de passage des paramètres du paramètre correspondant est l'entrée, et une conversion implicite existe entre l'expression de l'argument et le type du paramètre correspondant.
- le mode de passage de l’argument est identique au mode de passage du paramètre correspondant, et
Meilleur membre de fonction
La section Meilleur membre de fonction est modifiée comme suit.
Étant donnée une liste d’arguments A
avec un ensemble d’expressions d’argument {E₁, E₂, ..., Eᵥ}
et deux membres de fonction applicables Mᵥ
et Mₓ
avec des types de paramètres {P₁, P₂, ..., Pᵥ}
et {Q₁, Q₂, ..., Qᵥ}
, Mᵥ
est défini comme étant un meilleur membre de fonction que Mₓ
si
- pour chaque argument, la conversion implicite de
Eᵥ
enQᵥ
n’est pas meilleure que la conversion implicite deEᵥ
enPᵥ
, et - pour au moins un argument, la conversion de
Eᵥ
enPᵥ
est meilleure que la conversion deEᵥ
enQᵥ
.
Si les séquences de types de paramètres {P₁, P₂, ..., Pᵥ}
et {Q₁, Q₂, ..., Qᵥ}
sont équivalentes (c'est-à-dire que chaque Pᵢ
possède une conversion d'identité vers le Qᵢ
correspondant), les règles de départage suivantes sont appliquées, dans l'ordre, pour déterminer le meilleur membre de la fonction.
- Si
Mᵢ
est une méthode non générique et queMₑ
est une méthode générique, alorsMᵢ
est mieux queMₑ
. - Dans le cas contraire, si
Mᵢ
est applicable sous sa forme normale et queMₑ
possède une collection de paramètres et n’est applicable que sous sa forme étendue,Mᵢ
est préférable àMₑ
. - Dans le cas contraire, si les deux méthodes possèdent des collections de paramètres et ne sont applicables que sous leurs formes étendues, et si la collection de paramètres de
Mᵢ
comporte moins d’éléments que la collections de paramètres deMₑ
,Mᵢ
est préférable àMₑ
. - Sinon, si
Mᵥ
a des types de paramètres plus spécifiques queMₓ
, alorsMᵥ
est mieux queMₓ
. Laissez{R1, R2, ..., Rn}
et{S1, S2, ..., Sn}
représenter les types de paramètres non instanciés et non développés deMᵥ
et deMₓ
. Les types de paramètresMᵥ
sont plus spécifiques que ceux lesMₓ
si, pour chaque paramètre,Rx
n’est pas moins spécifique queSx
, et, pour au moins un paramètre,Rx
est plus spécifique queSx
:- Un paramètre de type est moins spécifique qu’un paramètre non typé.
- De manière récursive, un type construit est plus spécifique qu’un autre type construit (avec le même nombre d’arguments de type) si au moins un argument de type est plus spécifique et qu’aucun argument de type n’est moins spécifique que l’argument de type correspondant dans l’autre.
- Un type de tableau est plus spécifique qu’un autre type de tableau (ayant le même nombre de dimensions) si le type élément du premier est plus spécifique que le type élément du second.
- Sinon, si un membre est un opérateur non lifté et que l'autre est un opérateur lifté, celui non lifté est privilégié.
- Si aucun membre de fonction n’a été jugé supérieur, et que tous les paramètres de
Mᵥ
ont un argument correspondant alors que des arguments par défaut doivent être substitués pour au moins un paramètre optionnel dansMₓ
, alorsMᵥ
est mieux queMₓ
. - Si pour au moins un paramètre,
Mᵥ
utilise le choix de passage de paramètres supérieur (§12.6.4.4) que le paramètre correspondant dansMₓ
et qu’aucun des paramètres dansMₓ
n’utilise le choix de passage de paramètres supérieur à celui deMᵥ
, alorsMᵥ
est mieux queMₓ
. - Sinon, si les deux méthodes ont des collections de paramètres et sont applicables uniquement dans leurs formes développées,
Mᵢ
est meilleur queMₑ
si le même ensemble d'arguments correspond aux éléments de collection pour les deux méthodes, et l'une des conditions suivantes s'applique (cela correspond à https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md) :- les deux collections de paramètres ne sont pas span_types, et il existe une conversion implicite de la collection de paramètres de
Mᵢ
en collection de paramètres deMₑ
- La collection de paramètres de
Mᵢ
estSystem.ReadOnlySpan<Eᵢ>
, et la collection de paramètres deMₑ
estSystem.Span<Eₑ>
, et il existe une conversion d'identité deEᵢ
àEₑ
- La collection de paramètres de
Mᵢ
estSystem.ReadOnlySpan<Eᵢ>
ouSystem.Span<Eᵢ>
, et la collection de paramètres deMₑ
est un array_or_array_interface__type avec un type d'élémentEₑ
, et il existe une conversion d'identité deEᵢ
àEₑ
- les deux collections de paramètres ne sont pas span_types, et il existe une conversion implicite de la collection de paramètres de
- Sinon, aucun membre de fonction n'est meilleur.
La raison pour laquelle la nouvelle règle de départage est placée à la fin de la liste est le dernier sous-élément
- les deux collections de paramètres ne sont pas span_types, et il existe une conversion implicite de la collection de paramètres de
Mᵢ
en collection de paramètres deMₑ
il est applicable aux tableaux et, par conséquent, le fait d'effectuer le tie-break plus tôt introduira un changement de comportement pour les scénarios existants.
Exemple :
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 l’une des règles de départage précédentes s’applique (y compris la règle des « meilleures conversions d’arguments »), le résultat de résolution de surcharge peut être différent en comparaison avec le cas où une expression de collection explicite est utilisée comme argument à la place.
Exemple :
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) {}
}
Cependant, notre principale préoccupation concerne les scénarios où les surcharges ne diffèrent que par le type de collection des paramètres, mais où les types de collection ont le même type d'élément. Le comportement doit être cohérent avec les expressions de collection explicites pour ces cas.
La condition « si le même ensemble d’arguments correspond aux éléments de la collection pour les deux méthodes » est cruciale pour des scénarios tels que les suivants :
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) {}
}
Il n’est pas raisonnable de « comparer » des collections créées à partir de différents éléments.
Cette section a été examinée lors de la LDM et a été approuvée.
L’un des effets de ces règles est que lorsque les params
de différents types d’éléments sont exposés, ceux-ci sont ambigus lorsqu’ils sont appelés avec une liste d’arguments vide.
Exemple :
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) {
}
}
Étant donné que nous hiérarchisons le type d’élément par rapport à tout le reste, cela semble raisonnable. Dans ce scénario, rien n’indique au langage si l’utilisateur préférerait int?
à int
.
Liaison dynamique
Les formes étendues de candidats utilisant des collections de paramètres autres que des tableaux ne seront pas considérées comme des candidats valides par le liant d'exécution C# actuel.
Si l’expression principale, primary_expression, n’a pas de type de compilation dynamic
, l’appel de la méthode subit une vérification limitée au moment de la compilation, comme décrit dans §12.6.5 Vérification de l’appel de membre dynamique au moment de la compilation.
Si un seul candidat réussit le test, l’appel du candidat est lié de manière statique lorsque toutes les conditions suivantes sont remplies :
- le candidat est une fonction locale
- soit le candidat n’est pas générique, soit ses arguments de type sont explicitement spécifiés ;
- il n’existe aucune ambiguïté entre les formes normales et étendues du candidat qui ne puissent pas être résolues au moment de la compilation.
Sinon, invocation_expression est liée dynamiquement.
Si un seul candidat a réussi le test ci-dessus :
- si ce candidat est une fonction locale, une erreur au moment de la compilation se produit ;
- si ce candidat est applicable uniquement dans la forme développée avec des collections de paramètres autres que des tableaux, une erreur au moment de la compilation se produit.
Nous devrions également envisager de rétablir/corriger la violation des spécifications qui affecte les fonctions locales aujourd’hui. Voir https://github.com/dotnet/roslyn/issues/71399.
LDM a confirmé que nous voulions corriger cette violation de la spécification.
Arbres d'expressions
Les expressions de collection ne sont pas prises en charge dans les arbres d'expression. De même, les formes développées de collections de paramètres autres que des tableaux ne sont pas prises en charge dans les arborescences d’expressions. Nous ne changerons pas la façon dont le compilateur lie des lambdas pour les arborescences d’expressions dans l’objectif d’éviter l’usage d’API utilisant des formes développées de collections de paramètres qui ne sont pas des tableaux.
Ordre d’évaluation avec des collections autres que des tableaux dans des scénarios non triviaux
Cette section a été examinée lors de la LDM et a été approuvée. Même si les scénarios de tableaux se distinguent des autres collections, la spécification du langage officiel n’a pas besoin de définir des règles différentes pour les tableaux. Les écarts peuvent simplement être traités comme un artefact d’implémentation. En même temps, nous n'avons pas l'intention de changer le comportement existant autour des tableaux.
Arguments nommés
Une instance de collection est créée et remplie après l’évaluation de l’argument lexical précédent, mais avant l’évaluation de l’argument lexical suivant.
Exemple :
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;
}
L’ordre d’évaluation est le suivant :
-
GetB
est appelée -
MyCollection
est créé et rempli,GetC
est appelé dans le processus -
GetA
est appelée -
Test
est appelée
Notez que, dans le cas du tableau de paramètres, le tableau est créé juste avant l’appel de la méthode cible, une fois que tous les arguments sont évalués dans leur ordre lexical.
Assignation composée
Une instance de collection est créée et remplie après l’évaluation de l’index lexical précédent, mais avant l’évaluation de l’index lexical suivant. L’instance est utilisée pour appeler getter et setter pour l’indexeur cible.
Exemple :
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;
}
L’ordre d’évaluation est le suivant :
-
GetA
est appelé et mis en cache -
MyCollection
est créé, rempli et mis en cache,GetC
est appelé dans le processus - Le getter de l’indexeur est appelé avec des valeurs mises en cache pour les index
- Le résultat est incrémenté
- Le setter de l'indexeur est invoqué avec les valeurs mises en cache pour les index et le résultat de l'incrémentation
Exemple avec une collection vide :
class Program
{
static void Test(Program p)
{
p[GetA()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
}
L’ordre d’évaluation est le suivant :
-
GetA
est appelé et mis en cache - Une collection
MyCollection
vide est créée et mise en cache - Le getter de l’indexeur est appelé avec des valeurs mises en cache pour les index
- Le résultat est incrémenté
- Le setter de l'indexeur est invoqué avec les valeurs mises en cache pour les index et le résultat de l'incrémentation
Initialiseur d'objets
Une instance de collection est créée et remplie après l’évaluation de l’index lexical précédent, mais avant l’évaluation de l’index lexical suivant. L’instance est utilisée pour appeler le getter de l’indexeur autant de fois que nécessaire, le cas échéant.
Exemple :
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;
}
L’ordre d’évaluation est le suivant :
-
GetA
est appelé et mis en cache -
MyCollection
est créé, rempli et mis en cache,GetC
est appelé dans le processus - Le getter de l’indexeur est appelé avec des valeurs mises en cache pour les index
-
GetF1
est évaluée et affectée au champF1
deC1
réaccordé à l'étape précédente - Le getter de l’indexeur est appelé avec des valeurs mises en cache pour les index
-
GetF2
est évaluée et affectée au champF2
deC1
réaccordé à l'étape précédente
Notez que dans le cas du tableau de paramètres, ses éléments sont évalués et mis en cache, mais une nouvelle instance d’un tableau (avec les mêmes valeurs à l’intérieur) est utilisée pour chaque appel du getter de l’indexeur à la place. Pour l’exemple ci-dessus, l’ordre d’évaluation est le suivant :
-
GetA
est appelé et mis en cache -
GetC
est appelé et mis en cache - Le getter de l'indexeur est invoqué avec
GetA
mis en cache et le nouveau tableau est rempli avecGetC
mis en cache. -
GetF1
est évaluée et affectée au champF1
deC1
réaccordé à l'étape précédente - Le getter de l'indexeur est invoqué avec
GetA
mis en cache et le nouveau tableau est rempli avecGetC
mis en cache. -
GetF2
est évaluée et affectée au champF2
deC1
réaccordé à l'étape précédente
Exemple avec une collection vide :
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;
}
L’ordre d’évaluation est le suivant :
-
GetA
est appelé et mis en cache - Une collection
MyCollection
vide est créée et mise en cache - Le getter de l’indexeur est appelé avec des valeurs mises en cache pour les index
-
GetF1
est évaluée et affectée au champF1
deC1
réaccordé à l'étape précédente - Le getter de l’indexeur est appelé avec des valeurs mises en cache pour les index
-
GetF2
est évaluée et affectée au champF2
deC1
réaccordé à l'étape précédente
Sécurité des références
La section sur la sécurité de la référence des expressions de collection s’applique à la construction de collections de paramètres lorsque les API sont appelées sous leur forme développée.
Les paramètres Params sont implicitement scoped
lorsque leur type est une structure ref. UnscopedRefAttribute peut être utilisé pour remplacer cela.
Métadonnées
Dans les métadonnées, nous pourrions marquer des paramètres params
non matriciels avec System.ParamArrayAttribute
, comme les tableaux params
sont marqués aujourd'hui.
Toutefois, il semble qu’il serait plus sûr d’utiliser un autre attribut pour les paramètres params
autres que des tableaux.
Par exemple, le compilateur VB actuel ne sera pas en mesure de les consommer ornés de ParamArrayAttribute
, ni sous forme normale, ni sous forme développée. Par conséquent, l'ajout du modificateur 'params' est susceptible de perturber les utilisateurs de VB, et très probablement les utilisateurs d'autres langages ou outils.
Par conséquent, les paramètres params
qui ne sont pas des tableaux sont marqués d'un nouveau System.Runtime.CompilerServices.ParamCollectionAttribute
.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
public sealed class ParamCollectionAttribute : Attribute
{
public ParamCollectionAttribute() { }
}
}
Cette section a été examinée lors de la LDM et a été approuvée.
Questions ouvertes
Allocations de pile
Voici une citation de https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#unresolved-questions : « Les allocations de pile pour les grandes collections peuvent faire exploser la pile. Le compilateur devrait-il disposer d'une heuristique pour placer ces données sur le tas ?
Should the language be unspecified to allow for this flexibility?
Nous devrions respecter la spécification pour params Span<T>
. » Il semble que nous devions répondre aux questions dans le contexte de cette proposition.
[Résolu] Paramètres scoped
implicites
Il a été suggéré que, lorsque params
modifie un paramètre ref struct
, il devrait être considéré comme scoped
.
L'argument avancé est que le nombre de cas où vous voulez que le paramètre soit scopé est virtuellement de 100% lorsque l'on regarde les cas BCL. Dans les quelques cas où cela est nécessaire, la valeur par défaut peut être remplacée par [UnscopedRef]
.
Toutefois, il peut être indésirable de modifier la valeur par défaut simplement en fonction de la présence du modificateur params
. En particulier, dans les scénarios de remplacement et d'implémentation, le modificateur params
n'a pas besoin de correspondre.
Résolution :
Les paramètres Params sont implicitement délimités - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements.
[Résolu] Envisagez d'imposer scoped
ou params
dans les remplacements.
Nous avons indiqué précédemment que les paramètres params
doivent être scoped
par défaut. Cependant, cela introduit un comportement étrange dans les remplacements, en raison de nos règles existantes autour de la reformulation de params
:
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!;
}
Nous avons ici une différence de comportement entre porter le params
et porter le scoped
à travers les remplacements : params
est hérité implicitement, et avec lui scoped
alors que scoped
en lui-même n'est pas hérité implicitement et doit être répété à chaque niveau.
Proposition : nous devrions faire en sorte que les remplacements de paramètres params
indiquent explicitement params
ou scoped
si la définition d’origine est un paramètre scoped
. En d’autres termes, s2
dans Derived
doit avoir params
, scoped
ou les deux.
Résolution :
Nous exigerons que scoped
ou params
soit explicitement indiqué en cas de remplacement d’un paramètre params
lorsque cela est nécessaire pour un paramètre autre que params
: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-and-scoped-across-overrides.
[Résolu] La présence de membres requis doit-elle empêcher la déclaration du paramètre params
?
Prenons l'exemple suivant :
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)
{
}
}
Résolution :
Nous validerons les membres required
par rapport au constructeur utilisé pour déterminer l'éligibilité d'un paramètre params
au niveau de la déclaration - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters.
Alternatives
Il existe une proposition alternative qui prolonge params
uniquement pour ReadOnlySpan<T>
.
Par ailleurs, on pourrait dire qu'avec les expressions de collection présentes dans le langage, il n'est pas du tout nécessaire d'étendre la prise en charge de params
. Pour n’importe quel type de collection. Pour utiliser une API avec un type de collection, un développeur doit simplement ajouter deux caractères, [
avant la liste développée d’arguments et ]
après. Dans ces conditions, étendre la prise en charge de params
pourrait s'avérer exagéré, d'autant plus que les autres langages ne prendront probablement pas en charge la consommation de paramètres params
autres que des tableaux dans un avenir proche.
Propositions connexes
- https://github.com/dotnet/csharplang/issues/1757
- https://github.com/dotnet/csharplang/blob/main/proposals/format.md#extending-params
Réunions de conception connexes
- 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