Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Note
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 divergences entre la spécification de la fonctionnalité et l'implémentation réalisée. Ces différences sont capturées dans les notes de réunion de conception de langage (LDM) pertinentes .
Vous pouvez en savoir plus sur le processus d’adoption des speclets de fonctionnalités dans la norme de langage C# dans l’article sur les spécifications.
Problème de champion : https://github.com/dotnet/csharplang/issues/9856
Déclaration
Grammaire
Les indexeurs d’extension sont ajoutés à l’ensemble de membres autorisés à l’intérieur d’une déclaration d’extension en étendant la grammaire comme suit (par rapport aux propositions/csharp-14.0/extensions.md) :
extension_member_declaration
: method_declaration
| property_declaration
| indexer_declaration // new
| operator_declaration
;
Comme les indexeurs ordinaires, les indexeurs d’extension n’ont pas d’identificateur et sont identifiés par leur liste de paramètres. Les indexeurs d’extension peuvent utiliser l’ensemble complet de fonctionnalités prises en charge par les indexeurs ordinaires aujourd’hui (corps d’accesseur, membres expression-bodied, accesseurs ref-retour, paramètres, scoped attributs, etc.).
Étant donné que les indexeurs sont toujours membres d’instance, un bloc d’extension qui déclare un indexeur doit fournir un paramètre de récepteur nommé.
Les restrictions existantes sur les membres d’extension continuent à s’appliquer : les indexeurs à l’intérieur d’une déclaration d’extension ne peuvent pas spécifier , virtual, , , , partial, ( protected ou l’un des modificateurs d’accessibilité associés) ou init les accesseurs. sealednewoverrideabstract
public static class BitExtensions
{
extension(int i)
{
public bool this[int index]
{
get => ...;
}
}
}
Toutes les règles de la norme C# qui s’appliquent aux indexeurs ordinaires s’appliquent aux indexeurs d’extension, mais les membres d’extension n’ont pas d’indexeurs implicites ou explicites this.
La règle d’inférence du membre d’extension existant s’applique toujours : pour chaque membre d’extension non-méthode, tous les paramètres de type de son bloc d’extension doivent être utilisés dans l’ensemble combiné de paramètres de l’extension et du membre.
attribut IndexerName
IndexerNameAttribute peut être appliqué à un indexeur d’extension. L’attribut n’est pas émis dans les métadonnées, mais sa valeur affecte les conflits entre les membres, il détermine le nom de la propriété et des accesseurs dans les métadonnées et est utilisé lors de l’émission [DefaultMemberAttribute] (voir Métadonnées).
Consommation
Accès à l’indexeur
Les règles de l’accès Indexer sont mises à jour : si le traitement normal de l’accès de l’indexeur ne trouve aucun indexeur applicable, une tentative est effectuée pour traiter la construction en tant qu’accès à l’indexeur d’extension.
- Tentez de lier à l’aide des indexeurs d’instance déclarés (ou hérités) sur le type de récepteur. Si un candidat applicable est trouvé, la résolution de surcharge sélectionne parmi ces membres d’instance comme aujourd’hui et s’arrête.
- Tentez de lier à l’aide des indexeurs d’instance implicites déclarés (ou hérités) sur le type de récepteur. Si un candidat applicable est trouvé, la résolution de surcharge sélectionne parmi ces membres d’instance comme aujourd’hui et s’arrête.
- Tentez de lier en tant qu’accès de l’indexeur d’extension. Si ce processus trouve un candidat applicable, la résolution de surcharge sélectionne parmi ces membres d’extension, comme décrit ci-dessous et s’arrête.
- Tentative de liaison en tant qu’accès implicite d’indexeur d’extension. Si ce processus trouve un candidat applicable, la résolution de surcharge sélectionne parmi ces membres d’extension, comme décrit ci-dessous et s’arrête.
Remarque : la section d’accès à l’élément gère le cas où un argument a un type dynamic, de sorte qu’il n’est jamais traité en tant qu’accès indexeur.
Accès de l’indexeur d’extension
Les membres d’extension, y compris les indexeurs d’extension, ne sont jamais pris en compte lorsque le récepteur est une expression base_access .
Remarque : nous traitons uniquement une element_access en tant qu’accès d’indexeur si le récepteur est une variable ou une valeur. Par conséquent, les indexeurs d’extension ne sont jamais pris en compte lorsque le récepteur est un type.
Étant donné une element_accessE[A], l’objectif est d’identifier un indexeur d’extension.
Un indexeur d’extension candidat s’applique au récepteur E et à la liste A d’arguments si une signature développée, composée des paramètres de type du bloc d’extension et d’une liste de paramètres combinant le paramètre d’extension avec les paramètres de l’indexeur, s’applique à une liste d’arguments combinant le récepteur E à la liste d’arguments A.
Nous réutilisons l’étendue de la méthode d’extension : nous parcourons les mêmes étendues consultées pour l’appel de méthode d’extension, y compris les étendues lexicales actuelles et englobantes et using l’espace de noms ou using static les importations.
Compte tenu de chaque étendue à son tour :
- Les blocs d’extension dans les déclarations de classe statique non générique dans l’étendue actuelle sont pris en compte.
- Les indexeurs de ces blocs d’extension comprennent l’ensemble de candidats.
- Les candidats qui ne sont pas accessibles sont supprimés de l’ensemble.
- Les candidats qui ne sont pas applicables (tels que définis ci-dessus) sont supprimés de l’ensemble.
- Si l’ensemble obtenu d’indexeurs candidats est vide, nous procédons à l’étendue suivante ou ne parvenons pas à résoudre un accès d’indexeur d’extension si nous avons atteint la dernière étendue (nous continuerons à tenter de résoudre en tant qu’indexeur implicite d’extension dans ce cas).
- Sinon, la résolution de surcharge est appliquée à l’ensemble de candidats. Si un seul indexeur ne peut pas être identifié, l’accès à l’indexeur d’extension est ambigu et une erreur au moment de la compilation se produit.
À l’aide de cet indexeur unique identifié à l’étape précédente, l’accès de l’indexeur est ensuite traité en tant qu’appel de méthode statique.
Selon le contexte dans lequel il est utilisé, un accès indexeur provoque l’appel de l’get_accessor ou de l’set_accessor de l’indexeur.
Si l’accès de l’indexeur est la cible d’une affectation, la méthode d’implémentation statique set_accessor est appelée pour affecter une nouvelle valeur.
Dans tous les autres cas, la méthode d’implémentation statique get_accessor est appelée pour obtenir la valeur actuelle.
Dans les deux cas, l’appel utilise des arguments génériques déduits pendant la vérification de l’applicabilité et le récepteur comme premier argument.
Accès implicite à l’indexeur d’extension
Un accès implicite System.Index (ou System.Range) d’indexeur d’extension est applicable si :
- le element_access a un seul argument de type
System.Index(ouSystem.Range) et - une propriété applicable
LengthouCount(instance ou extension) se trouve sur le type de récepteur et - un indexeur applicable
this[int](ouSlice(int, int)) (instance ou extension) se trouve sur le type de récepteur.
Autres formulaires d’accès aux éléments
Toute construction qui se reporte à la liaison d’accès aux éléments (accès ou affectations d’éléments conditionnels Null, affectations d’index dans les initialiseurs d’objet, liste et modèles de propagation) participe automatiquement à la résolution de l’indexeur d’extension décrite ci-dessus.
Arbres d'expression
Les indexeurs d’extension ne peuvent pas être capturés dans les arborescences d’expressions.
Documents XML
La syntaxe CREF permet de faire référence à un indexeur d’extension et à ses accesseurs, ainsi qu’à ses méthodes d’implémentation.
Exemple :
/// <see cref="E.extension(int).this[string]"/>
/// <see cref="E.extension(int).get_Item(string)"/>
/// <see cref="E.extension(int).get_Item"/>
/// <see cref="E.extension(int).set_Item(string, int)"/>
/// <see cref="E.extension(int).set_Item"/>
/// <see cref="E.get_Item(int, string)"/>
/// <see cref="E.get_Item"/>
/// <see cref="E.set_Item(int, string, int)"/>
/// <see cref="E.set_Item"/>
public static class E
{
extension(int i)
{
/// <summary></summary>
public int this[string s]
{
get => throw null;
set => throw null;
}
}
}
Metadata
Les indexeurs d’extension suivent le même modèle de réduction que les propriétés d’extension. Pour chaque type de regroupement d’extensions de niveau CLR qui contient au moins un indexeur, le compilateur émet :
- Propriété d’extension nommée
Item(ou valeur fournie parIndexerNameAttribute) avec des corps d’accesseur quithrow NotImplementedException()et un[ExtensionMarkerName]attribut référençant le type de marqueur d’extension approprié. - Méthodes d’implémentation nommées
get_Item/set_Itemdans la classe statique englobante. Ces méthodes ont précédé le paramètre de récepteur dans la liste des paramètres et contiennent les corps définis par l’utilisateur. Ils sontstaticet participent à la résolution de surcharge de la même façon que les méthodes d’implémentation pour les propriétés d’extension.
Pour mettre en miroir le comportement des indexeurs ordinaires [DefaultMemberAttribute] , le compilateur émet également sur n’importe quel type de regroupement d’extensions qui contient un ou plusieurs indexeurs d’extension. L’attribut est égal au nom des métadonnées de l’indexeur MemberName (Item par défaut, ou à partir de la valeur de IndexerNameAttribute).
Example
Code source :
static class BitExtensions
{
extension<T>(T t)
{
public bool this[int index]
{
get => ...;
set => ...;
}
}
}
Métadonnées émises (simplifiées en syntaxe C#-like) :
[Extension]
static class BitExtensions
{
[Extension, SpecialName, DefaultMember("Item")]
public sealed class <G>$T0 // grouping type
{
[SpecialName]
public static class <M>$T_t // marker type
{
[SpecialName]
public static void <Extension>$(T t) { } // marker method
}
[ExtensionMarkerName("<M>$T_t")]
public bool this[int index] // extension indexer
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
}
// accessor implementation methods
public static bool get_Item<T>(T t, int index) => ...;
public static void set_Item<T>(T t, int index, bool value) => ...;
}
Problèmes ouverts
Section temporaire du document relatif aux questions ouvertes, y compris la discussion sur d’autres conceptions
Traiter avec params
paramsSi vous avez un indexeur d’extension avec params, par int this[int i, params string[] s] { get; set; }exemple, il existe trois façons de l’utiliser :
- indexation d’extension :
receiver[i: 0, "Alice", "Bob"] - Appel d’implémentation getter :
E.get_Item(receiver, i: 0, "Alice", "Bob") - Appel d’implémentation setter :
E.set_Item(...)
Mais quelle est la signature de la méthode d’implémentation setter ?
Il n’est logique que pour le dernier paramètre d’une signature de méthode invocable par l’utilisateur d’avoir params, de sorte qu’il n’a aucun but dans E.set_Item(... extension parameter ..., this i, params string[] s, int value).
Certaines options :
-
paramsinterdire aux indexeurs d’extension qui ont un setter - omettre l’attribut sur la
[ParamArray]méthode d’implémentation setter - ne rien faire spécial (émettre le
[ParamArray])
Je proposerais l’option 2, car elle optimise l’utilité params . Le coût n’est qu’une petite différence entre l’indexation d’extension et la syntaxe d’ambiguïté.
Décision (LDM 2026-02-02) : émettre et vérifier qu’aucun impact négatif sur les [ParamArray] outils
Impact de la valeur affectée à l’inférence de type
int i = 0;
i[42, null] = new object(); // fails inference
E.set_Item(i, 42, null, new object()); // infer `E.set_Item<object>`
public static class E
{
extension<T>(int i)
{
public T this[int j, T t] { set { } }
}
}
#nullable enable
int i = 0;
i[new object()] = null; // infer `E.extension<object!>` and warn on conversion of null literal to `object!`
E.set_Item(i, new object(), null); // infer `E.set_Item<object?>`
public static class E
{
extension<T>(int i)
{
public T this[T t] { set { } }
}
}
Décision (LDM 2026-02-02) : l’indexeur est déduit uniquement en fonction du récepteur et des arguments de la liste d’arguments (par exemple, la valeur affectée ne contribue pas).
Les propriétés d’extension Length/Count doivent-elle rendre un type countable ?
Length/Count doivent-elle rendre un type countable ?En guise de rappel, les extensions ne sont pas entrées en jeu lors de la liaison d’index implicites ou d’indexeurs de plage :
C c = new C();
_ = c[..]; // Cannot apply indexing with [] to an expression of type 'C'
class C
{
public int Length => 0;
}
static class E
{
public static C Slice(this C c, int i, int j) => null!;
}
Notre position jusqu’à présent a été que les propriétés d’extensions ne doivent pas compter comme « propriétés countables » dans les modèles de liste, les expressions de collection et les indexeurs implicites.
Si nous exposons this[Index] ou this[Range] des indexeurs d’extension dans des scénarios d’accès aux éléments, il est naturel de s’attendre à ce que le type cible fonctionne dans des modèles de liste.
Toutefois, les modèles de liste nécessitent une ou Count une Length propriété.
Les propriétés d’extension doivent-elle satisfaire à cette exigence ? (qui semblerait naturel)
C c = new C();
var x1 = c[^1];
var x2 = c[1..];
if (c is [.., var y1]) { }
if (c is [_, .. var y2]) { }
class C { }
static class E
{
extension(C c)
{
object this[System.Index] => ...;
C this[System.Range] => ...;
int Length => ...;
}
}
Mais alors, ces propriétés doivent-elles également contribuer à la secours de l’indexeur implicite (LengthSlice + Count/) utilisée lorsqu’un indexeur explicite Index/Range est manquant ?
C c = new C();
if (c is [var y1, .. var y2]) { }
class C
{
C Slice(int i, int j) => ...;
}
static class E
{
extension(C c)
{
object this[System.Index] => ...;
int Length => ...;
}
}
Décision (LDM 2026-02-02) : les extensions doivent contribuer partout, y compris les propriétés countables et le secours implicite de l’indexeur.
Vérifiez si l’accès de l’indexeur d’extension arrive avant ou après les indexeurs implicites
C c = new C();
_ = c[^1];
class C
{
public int Length => ...;
public int this[int i] => ...;
}
static class E
{
extension(C c)
{
public int this[System.Index i] => ...;
}
}
J’ai spécifié et implémenté l’accès de l’indexeur d’extension comme ayant la priorité sur les indexeurs implicites, mais maintenant pense qu’ils doivent venir après pour éviter les sauts de compatibilité inutiles.
Mise à jour (LDM 2026-02-02) : cela nécessite un examen approfondi. Oui, les extensions devraient venir après les membres non-extension, mais au-delà de cela, nous avons besoin de propositions concrètes à la lumière de la décision ci-dessus pour permettre aux extensions de contribuer à la secours implicite de l’indexeur.
Décision (LDM 2026-03-09) : l’ordre est des indexeurs d’instance réels, puis des indexeurs d’instance implicites, puis des indexeurs d’extension réels, puis des indexeurs d’extension implicites. Voir https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers
Nombre/longueur : le nom est-il prioritaire en premier, ou non extension et extension ?
Nous disposons également d’un secours existant : Length est hiérarchisé par Count rapport à la propriété.
Une extension Length doit-elle venir avant ou après une propriété non-extension Count ?
Décision (LDM 2026-03-09) : nous allons examiner l’étendue par étendue (à partir de l’étendue de l’instance et passer par des étendues d’extension), et dans chaque étendue, nous allons d’abord rechercherLength.Count
Voir https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers
Confirmer la conception proposée pour les indexeurs implicites
Nous examinons l’étendue par étendue, en commençant par l’étendue de l’instance, puis en accédant aux étendues d’extension.
Dans une étendue :
- nous recherchons un indexeur réel,
- sinon, si nous avons un seul argument du type droit, nous recherchons un indexeur implicite.
Décision (LDM 2026-03-09) : non, une fois que nous allons passer à des étendues d’extension, nous allons utiliser toute la résolution d’extension.
Confirmer la conception proposée pour les modèles de liste
Pour un modèle de liste avec une répartition, nous allons rechercher :
- a
Length/Count - un réel ou implicite
this[Index] - un réel ou implicite
this[Range]
Nous allons chercher chacun indépendamment.
Mais quand nous recherchons un implicite this[Index] ou this[Range], les deux parties doivent provenir de la même étendue :
Length/Countthis[int]/Slice(int, int)
Remarque : la gestion non négative des Length modèles est lancée lorsqu’un type peut être utilisé dans un modèle de liste (c’est-à-dire, il est countable et indexable).
Décision (LDM 2026-03-09) : non, nous allons suivre cet ordre de recherche à la place :
- Les modèles de liste sont résolus comme si nous recherchons l’indexeur longueur/nombre, l’indexeur d’indexeur d’index et l’indexeur de plage individuellement
- Pour les indexeurs d’index et de plage, procédez comme suit : a. Avec la recherche d’instance uniquement, recherchez l’index « réel » si possible b. Avec la recherche d’instance uniquement, recherchez les parties de l’indexeur implicite si possible c. Avec recherche complète (instance+extension), recherchez l’index « réel » si possible d. Avec recherche complète (instance+extension), recherchez les parties de l’indexeur implicite si possible (chacune dans les recherches individuelles)
La méthode d’extension Slice doit-elle également contribuer ?
Slice doit-elle également contribuer ?_ = c[1..^1];
static class E
{
extension(C c)
{
public int Length => 3;
}
public static C Slice(this C c, int i, int j) => ...;
}
Décision (LDM 2026-03-09) : oui, nous traitons exactement les méthodes d’extension classiques et nouvelles.
L’extension Length doit-elle contribuer à l’optimisation de la propagation ?
Length doit-elle contribuer à l’optimisation de la propagation ?C c = new C();
int[] i = [0, .. c]; // Uses Length, if available, to allocate the right size
Décision (LDM 2026-03-09) : non, il est peu probable qu’une extension puisse implémenter cela de manière performante, de sorte qu’elle n’aide pas à l’optimisation.
Les indexeurs implicites doivent-ils entrer en jeu pour les types de tableau ou de chaîne ?
Je propose que les indexeurs implicites ne contribuent pas à ces scénarios, car ils ne sont possibles qu’avec un corlib mal formé (instance Lengthmanquante ou GetSubArrayGetSubstring).
Décision (LDM/Mads par e-mail 2026-04-07) : aucun indexeur d’extension sur des chaînes ou des tableaux.
Les indexeurs d’extension doivent-ils entrer en jeu pour les types de tableau ou de chaîne ?
Les règles de référence et de spécification actuelles pour l’accès au tableau et l’accès aux chaînes signifient que les indexeurs d’extension ne fonctionnent pas sur des tableaux ou des chaînes.
Pourtant, la déclaration de ces indexeurs d’extension est autorisée.
int[] i = ...;
_ = i[new C()];
public static class E
{
extension(int[] i)
{
public int this[C c] => throw null;
}
}
string s = "...;
_ = s[new C()];
public static class E
{
extension(int[] i)
{
public int this[C c] => throw null;
}
}
Remarque : il n’existe aucune question pour l’accès aux éléments de pointeur , car un paramètre d’extension peut ne pas être un type de pointeur.
Décision (LDM/Mads par e-mail 2026-04-07) : aucun indexeur d’extension sur des chaînes ou des tableaux.
C# feature specifications