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.
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 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/8697
Déclaration
Syntaxe
class_body
: '{' class_member_declaration* '}' ';'?
| ';'
;
class_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| finalizer_declaration
| static_constructor_declaration
| type_declaration
| extension_declaration // add
;
extension_declaration // add
: 'extension' type_parameter_list? '(' receiver_parameter ')' type_parameter_constraints_clause* extension_body
;
extension_body // add
: '{' extension_member_declaration* '}' ';'?
;
extension_member_declaration // add
: method_declaration
| property_declaration
| operator_declaration
;
receiver_parameter // add
: attributes? parameter_modifiers? type identifier?
;
Les déclarations d’extension ne doivent être déclarées que dans des classes statiques non génériques et non imbriquées.
Il s’agit d’une erreur lorsqu’un type porte le nom extension.
Règles de portée
Les paramètres de type et le paramètre de récepteur d’une déclaration d’extension sont disponibles à l'intérieur du corps de la déclaration d’extension. C'est une erreur de faire référence au paramètre récepteur à partir d'un membre statique, à l'exception d'une expression nameof. Il est erroné pour les membres de déclarer des paramètres de type ou autres paramètres (et des variables locales et des fonctions locales directement dans le corps du membre) ayant le même nom qu'un paramètre de type ou de récepteur de la déclaration d’extension.
public static class E
{
extension<T>(T[] ts)
{
public bool M1(T t) => ts.Contains(t); // `T` and `ts` are in scope
public static bool M2(T t) => ts.Contains(t); // Error: Cannot refer to `ts` from static context
public void M3(int T, string ts) { } // Error: Cannot reuse names `T` and `ts`
public void M4<T, ts>(string s) { } // Error: Cannot reuse names `T` and `ts`
}
}
Il n’est pas une erreur pour les membres eux-mêmes d’avoir le même nom que les paramètres de type ou le paramètre récepteur de la déclaration d’extension englobante. Les noms de membres ne sont pas directement trouvés lors d'une recherche simple dans la déclaration d’extension ; on trouve donc le paramètre de type ou le paramètre récepteur de ce nom, plutôt que le membre.
Les membres donnent lieu à des méthodes statiques déclarées directement sur la classe statique englobante, et celles-ci peuvent être trouvées via une recherche de nom simple ; Toutefois, un paramètre de type de déclaration d’extension ou un paramètre récepteur du même nom est trouvé en premier.
public static class E
{
extension<T>(T[] ts)
{
public void T() { M(ts); } // Generated static method M<T>(T[]) is found
public void M() { T(ts); } // Error: T is a type parameter
}
}
Classes statiques en tant que conteneurs d’extension
Les extensions sont déclarées à l’intérieur de classes statiques non génériques de niveau supérieur, tout comme les méthodes d’extension aujourd’hui, et peuvent donc coexister avec les méthodes d’extension classiques et les membres statiques hors extension :
public static class Enumerable
{
// New extension declaration
extension(IEnumerable source) { ... }
// Classic extension method
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
// Non-extension member
public static IEnumerable<int> Range(int start, int count) { ... }
}
Déclarations d’extension
Une déclaration d’extension est anonyme et fournit une spécification de récepteur avec tous les paramètres et contraintes de type associés, suivie d’un ensemble de déclarations de membre d’extension. La spécification du récepteur peut être sous la forme d’un paramètre ou d’un type (si seuls les membres d’extension statique sont déclarés) :
public static class Enumerable
{
extension(IEnumerable source) // extension members for IEnumerable
{
public bool IsEmpty { get { ... } }
}
extension<TSource>(IEnumerable<TSource> source) // extension members for IEnumerable<TSource>
{
public IEnumerable<T> Where(Func<TSource, bool> predicate) { ... }
public IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) { ... }
}
extension<TElement>(IEnumerable<TElement>) // static extension members for IEnumerable<TElement>
where TElement : INumber<TElement>
{
public static IEnumerable<TElement> operator +(IEnumerable<TElement> first, IEnumerable<TElement> second) { ... }
}
}
Le type dans la spécification du récepteur est appelé type de récepteur et le nom du paramètre, le cas échéant, est appelé paramètre récepteur.
Si le paramètre de récepteur est nommé, le type de récepteur peut ne pas être statique.
Le paramètre récepteur n’est pas autorisé à avoir des modificateurs s’il n’est pas nommé, et il est uniquement autorisé à avoir les modificateurs refness répertoriés ci-dessous et scoped sinon.
Le paramètre récepteur présente les mêmes restrictions que le premier paramètre d’une méthode d’extension classique.
L’attribut [EnumeratorCancellation] est ignoré s’il est placé sur le paramètre récepteur.
Membres de l’extension
Les déclarations de membre d’extension sont syntaxiquement identiques aux membres d’instance et statiques correspondants dans les déclarations de classe et de struct (à l’exception des constructeurs). Les membres d’instance référencent le récepteur avec le nom du paramètre récepteur :
public static class Enumerable
{
extension(IEnumerable source)
{
// 'source' refers to receiver
public bool IsEmpty => !source.GetEnumerator().MoveNext();
}
}
C'est une erreur de spécifier un membre d'une extension d'instance si la déclaration d'extension qui l'entoure ne spécifie pas de paramètre destinataire.
public static class Enumerable
{
extension(IEnumerable) // No parameter name
{
public bool IsEmpty => true; // Error: instance extension member not allowed
}
}
Il s’agit d’une erreur de spécifier les modificateurs suivants sur un membre d'une déclaration d’extension : abstract, virtual, override, new, sealed, partial et protected (ainsi que les modificateurs d’accessibilité associés).
Il s’agit d’une erreur pour spécifier le readonly modificateur sur un membre d’une déclaration d’extension.
Les propriétés des déclarations d’extension ne doivent pas avoir d’accesseurs init.
Les membres de l’instance ne sont pas autorisés si le paramètre récepteur n’est pas nommé.
Tous les membres doivent avoir des noms qui diffèrent du nom de la classe englobante statique et du nom du type étendu s’il en a un.
C'est une erreur de décorer un membre d'extension avec l'attribut [ModuleInitializer].
Refness
Par défaut, le récepteur est transmis aux membres d’extension d’instance par valeur, tout comme d’autres paramètres.
Toutefois, un récepteur de déclaration d’extension sous forme de paramètre peut spécifier ref, ref readonly et in, tant que le type de récepteur est connu comme un type valeur.
Possibilité de valeurs nulles et attributs
Les types de récepteurs peuvent être ou contenir des types de référence nullables, et les spécifications de récepteur qui se trouvent sous la forme de paramètres peuvent spécifier des attributs :
public static class NullableExtensions
{
extension(string? text)
{
public string AsNotNull => text is null ? "" : text;
}
extension([NotNullWhen(false)] string? text)
{
public bool IsNullOrEmpty => text is null or [];
}
extension<T> ([NotNull] T t) where T : class?
{
public void ThrowIfNull() => ArgumentNullException.ThrowIfNull(t);
}
}
Compatibilité avec les méthodes d’extension classiques
Les méthodes d’extension d’instance génèrent des artefacts qui correspondent à ceux produits par les méthodes d’extension classiques.
Plus précisément, la méthode statique générée possède les attributs, les modificateurs et le nom de la méthode d’extension déclarée, ainsi que la liste des paramètres de type, la liste des paramètres et la liste des contraintes concaténées à partir de la déclaration d’extension et de la déclaration de méthode dans cet ordre :
public static class Enumerable
{
extension<TSource>(IEnumerable<TSource> source) // Generate compatible extension methods
{
public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
public IEnumerable<TSource> Select<TResult>(Func<TSource, TResult> selector) { ... }
}
}
Génère :
[Extension]
public static class Enumerable
{
[Extension]
public static IEnumerable<TSource> Where<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate) { ... }
[Extension]
public static IEnumerable<TSource> Select<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector) { ... }
}
Opérateurs
Bien que les opérateurs d’extension aient des types d’opérande explicites, ils doivent toujours être déclarés dans une déclaration d’extension :
public static class Enumerable
{
extension<TElement>(IEnumerable<TElement>) where TElement : INumber<TElement>
{
public static IEnumerable<TElement> operator *(IEnumerable<TElement> vector, TElement scalar) { ... }
public static IEnumerable<TElement> operator *(TElement scalar, IEnumerable<TElement> vector) { ... }
}
}
Cela permet aux paramètres de type d’être déclarés et déduits, de la même manière qu’un opérateur défini par l’utilisateur standard doit être déclaré dans l’un de ses types d’opérandes.
Vérification
Inférabilité : Pour chaque membre d’extension non méthode, tous les paramètres de type de son bloc d’extension doivent être intégrés dans l’ensemble combiné de paramètres de l'extension et du membre.
Unicité: Dans une classe statique englobante donnée, l’ensemble de déclarations membres d’extension avec le même type de récepteur (conversion d’identité modulo et substitution de nom de paramètre de type) sont traités comme un espace de déclaration unique similaire aux membres d’une déclaration de classe ou de struct, et sont soumis aux mêmes règles concernant l’unicité.
public static class MyExtensions
{
extension<T1>(IEnumerable<int>) // Error! T1 not inferrable
{
...
}
extension<T2>(IEnumerable<T2>)
{
public bool IsEmpty { get ... }
}
extension<T3>(IEnumerable<T3>?)
{
public bool IsEmpty { get ... } // Error! Duplicate declaration
}
}
L’application de cette règle d’unicité inclut des méthodes d’extension classiques dans la même classe statique.
À des fins de comparaison avec les méthodes dans les déclarations d’extension, le paramètre this est traité comme une spécification de récepteur, ainsi que tous les paramètres de type mentionnés dans ce type de récepteur. Les paramètres de type restants et les paramètres de méthode sont utilisés pour la signature de méthode :
public static class Enumerable
{
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
extension(IEnumerable source)
{
IEnumerable<TResult> Cast<TResult>() { ... } // Error! Duplicate declaration
}
}
Consommation
Lorsqu’une recherche de membre d’extension est tentée, tous les membres des déclarations d’extension dans les classes statiques qui sont importées par using contribuent en tant que candidats, quel que soit le type de récepteur. Ce n’est que dans le cadre de la résolution que les candidats ayant des types de récepteurs incompatibles sont ignorés.
Une inférence de type générique complète est tentée entre le type des arguments (y compris le récepteur réel) et tous les paramètres de type (combinant ceux dans la déclaration d’extension et dans la déclaration de membre d’extension).
Lorsque des arguments de type explicites sont fournis, ils sont utilisés pour remplacer les paramètres de type de la déclaration d’extension et la déclaration de membre d’extension.
string[] strings = ...;
var query = strings.Select(s => s.Length); // extension invocation
var query2 = strings.Select<string, int>(s => s.Length); // ... with explicit full set of type arguments
var query3 = Enumerable.Select(strings, s => s.Length); // static method invocation
var query4 = Enumerable.Where<string, int>(strings, s => s.Length); // ... with explicit full set of type arguments
public static class Enumerable
{
extension<TSource>(IEnumerable<TSource> source)
{
public IEnumerable<TResult> Select<TResult>(Func<T, TResult> predicate) { ... }
}
}
Comme pour les méthodes d’extension classiques, les méthodes d’implémentation émises peuvent être appelées statiquement.
Cela permet au compilateur de lever l’ambiguïté entre les membres d’extension portant le même nom et la même arité.
object.M(); // ambiguous
E1.M();
new object().M2(); // ambiguous
E1.M2(new object());
_ = _new object().P; // ambiguous
_ = E1.get_P(new object());
static class E1
{
extension(object)
{
public static void M() { }
public void M2() { }
public int P => 42;
}
}
static class E2
{
extension(object)
{
public static void M() { }
public void M2() { }
public int P => 42;
}
}
Les méthodes d’extension statiques sont résolues comme les méthodes d’extension d’instance (nous prenons en compte un argument supplémentaire du type de récepteur).
Les propriétés d’extension sont résolues comme les méthodes d’extension, avec un paramètre unique (paramètre de récepteur) et un seul argument (valeur de récepteur réelle).
Directives using static
Une using_static_directive rend les membres de blocs d’extension dans la déclaration de type disponible pour l’accès aux extensions.
using static N.E;
new object().M();
object.M2();
_ = new object().Property;
_ = object.Property2;
C c = null;
_ = c + c;
c += 1;
namespace N
{
static class E
{
extension(object o)
{
public void M() { }
public static void M2() { }
public int Property => 0;
public static int Property2 => 0;
}
extension(C c)
{
public static C operator +(C c1, C c2) => throw null;
public void operator +=(int i) => throw null;
}
}
}
class C { }
Comme précédemment, les membres statiques accessibles (à l’exception des méthodes d’extension) contenus directement dans la déclaration du type donné peuvent être référencés directement.
Cela signifie que les méthodes d’implémentation (sauf celles qui sont des méthodes d’extension) peuvent être utilisées directement comme méthodes statiques :
using static E;
M();
System.Console.Write(get_P());
set_P(43);
_ = op_Addition(0, 0);
_ = new object() + new object();
static class E
{
extension(object)
{
public static void M() { }
public static int P { get => 42; set { } }
public static object operator +(object o1, object o2) { return o1; }
}
}
Une using_static_directive n’importe toujours pas les méthodes d’extension directement en tant que méthodes statiques. Par conséquent, la méthode d’implémentation pour les méthodes d’extension non statiques ne peut pas être appelée directement en tant que méthode statique.
using static E;
M(1); // error: The name 'M' does not exist in the current context
static class E
{
extension(int i)
{
public void M() { }
}
}
OverloadResolutionPriorityAttribute
Les membres d’extension dans une classe statique englobante sont soumis à la hiérarchisation en fonction des valeurs ORPA. La classe statique englobante est considérée comme le « type contenant » que les règles ORPA prennent en compte.
Tout attribut ORPA présent sur une propriété d’extension est copié sur les méthodes d’implémentation des accesseurs de la propriété, afin que la hiérarchisation soit respectée lorsque ces accesseurs sont utilisés via la syntaxe de désambiguation.
Points d’entrée
Les méthodes des blocs d’extension ne sont pas éligibles en tant que candidats au point d’entrée (voir 7.1 « Démarrage de l’application »). Remarque : la méthode d’implémentation peut toujours être candidate.
Abaissement
La stratégie de réduction des déclarations d’extension n’est pas une décision au niveau du langage. Toutefois, au-delà de l’implémentation de la sémantique de langage, cette stratégie doit répondre à certaines exigences :
- Le format des types, des membres et des métadonnées générés doit être clairement spécifié dans tous les cas, afin que d’autres compilateurs puissent les utiliser et les générer.
- Les artefacts générés doivent être stables, dans le sens où des modifications raisonnables ultérieures ne doivent pas perturber les consommateurs ayant compilé avec des versions antérieures.
Ces exigences nécessitent davantage d’affinement à mesure que l’implémentation progresse, et des compromis peuvent être nécessaires dans certains cas pour permettre une approche d’implémentation raisonnable.
Métadonnées pour les déclarations
Objectifs
La conception ci-dessous permet :
- roundtripping des symboles de déclaration d’extension au moyen de métadonnées (assemblys de référence et complets)
- références stables aux membres d’extension (documents xml),
- détermination locale des noms émis (utiles pour EnC),
- suivi d’API public.
Pour les documents xml, le docID d’un membre d’extension est le docID du membre d’extension dans les métadonnées. Par exemple, le docID utilisé dans cref="Extension.extension(object).M(int)" est M:Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) et ce docID est stable lors des re-compilations et des réordonnancements des blocs d’extension. Dans l'idéal, il resterait également stable lorsque les contraintes concernant le bloc d'extension changent, mais nous n'avons pas trouvé de conception qui permette d'y parvenir sans effet néfaste sur la conception du langage en ce qui concerne les conflits entre membres.
Pour EnC, il est utile d'identifier localement (en examinant simplement un membre d'extension modifié) l'emplacement où le membre d'extension mis à jour est émis dans les métadonnées.
Pour le suivi des API publiques, les noms plus stables réduisent le bruit. Mais d'un point de vue technique, les noms des types de regroupement d'extensions ne devraient pas intervenir dans de tels scénarios. Lorsque vous examinez le membre Mde l’extension, il n’importe pas quel est le nom du type de regroupement d’extensions, ce qui importe est la signature du bloc d’extension auquel il appartient. La signature de l’API publique ne doit pas être considérée comme Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) mais plutôt comme Extension.extension(object).M(int). En d’autres termes, les membres d’extension doivent être considérés comme ayant deux ensembles de paramètres de type et deux ensembles de paramètres.
Aperçu
Les blocs d’extension sont regroupés par leur signature au niveau du Common Language Runtime (CLR). Chaque groupe d’équivalence CLR est émis en tant que type de regroupement d’extensions avec un nom basé sur le contenu. Les blocs d’extension au sein d’un groupe d’équivalence CLR sont ensuite sous-regroupés par l’équivalence C#. Chaque groupe d’équivalence C# est émis en tant que type de marqueur d’extension avec un nom basé sur le contenu, imbriqué dans son type de regroupement d’extensions correspondant. Un type de marqueur d’extension contient une méthode de marqueur d’extension unique qui encode un paramètre d’extension. La méthode de marqueur d’extension, avec le type de marqueur d’extension qu’elle comporte, encode la signature d’un bloc d’extension avec une fidélité totale. La déclaration de chaque membre d’extension est émise dans le type de regroupement d’extensions approprié, fait référence à un type de marqueur d’extension par son nom via un attribut et est accompagnée d’une méthode d’implémentation statique de niveau supérieur avec une signature modifiée.
Voici une vue d’ensemble schématisée de l’encodage des métadonnées :
[Extension]
static class EnclosingStaticClass
{
[Extension]
public sealed class ExtensionGroupingType1 // has type parameters with minimal constraints sufficient to keep extension member declarations below valid
{
public static class ExtensionMarkerType1 // has re-declared type parameters with full fidelity of C# constraints
{
public static void <Extension>$(... extension parameter ...) // extension marker method
}
... ExtensionMarkerType2, etc ...
... extension members for ExtensionGroupingType1, each points to its corresponding extension marker type ...
}
... ExtensionGroupingType2, etc ...
... implementation methods ...
}
La classe statique englobante est émise avec un [Extension] attribut.
Signature au niveau du CLR et signature au niveau C#
La signature de niveau CLR d’un bloc d’extension résulte des résultats suivants :
- normalisation des noms de paramètres de type à
T0,T1etc... - suppression d’attributs
- effacement du nom du paramètre
- effacement des modificateurs de paramètres (comme
ref, ,inscoped, ...) - effacement des noms de tuples
- effacement des annotations de nullabilité
- Effacement des contraintes
notnull
Remarque : d’autres contraintes sont conservées, telles que new(), , structclass, allows ref structunmanaged, et les contraintes de type.
Types de regroupement d’extensions
Un type de regroupement d’extensions est émis dans les métadonnées pour chaque ensemble de blocs d’extension dans la source avec une même signature au niveau CLR.
- Son nom est inexprimable et déterminé sur la base du contenu de la signature de niveau CLR. Plus d’informations ci-dessous.
- Ses paramètres de type ont des noms normalisés (
T0, ,T1...) et n’ont aucun attribut. - C’est public et scellé.
- Il est marqué avec l’indicateur
specialnameet un[Extension]attribut.
Le nom basé sur le contenu du type de regroupement d’extensions est basé sur la signature au niveau du CLR et inclut les éléments suivants :
- Nom CLR complet du type du paramètre d’extension.
- Les noms de paramètres de type référencés sont normalisés à
T0,T1etc... en fonction de l’ordre dans lequel ils apparaissent dans la déclaration de type. - Le nom complet n’inclut pas l’assemblage conteneur. Il est courant que les types soient déplacés entre les assemblies et cela ne doit pas interrompre les références de documentation XML.
- Les noms de paramètres de type référencés sont normalisés à
- Les contraintes des paramètres de type sont incluses et triées de telle sorte que la réorganisation dans le code source ne modifie pas le nom. Spécifiquement:
- Les contraintes de paramètre de type sont répertoriées dans l’ordre de déclaration. Les contraintes pour le paramètre de type Nth se produisent avant le paramètre de type Nth+1.
- Les contraintes de type sont triées en comparant les noms complets de manière ordinale.
- Les contraintes non de type sont ordonnées de manière déterministe et sont gérées de façon à éviter toute ambiguïté ou collision avec les contraintes de type.
- Étant donné que cela n’inclut pas d’attributs, il ignore intentionnellement les spécificités du C# comme les noms de tuple, la nullabilité, etc. …
Remarque : Le nom est garanti pour rester stable lors des re-compilations, des réorganisations et des modifications spécifiques au C# (c’est-à-dire qui n’affectent pas la signature au niveau du CLR).
Types de marqueurs d’extension
Le type de marqueur redéfinit les paramètres de type du type auquel il appartient (un type de regroupement d’extensions) pour obtenir une représentation fidèle de la vue C# des blocs d’extension.
Un type de marqueur d’extension est émis dans les métadonnées pour chaque ensemble de blocs d’extension dans la source avec une même signature au niveau C#.
- Son nom est innommable et déterminé en fonction du contenu de la signature au niveau C# du bloc d’extension. Plus d’informations ci-dessous.
- Il redeclare les paramètres de type pour son type de regroupement contenant pour être ceux déclarés dans la source (y compris le nom et les attributs).
- Il est public et statique.
- Il est marqué avec l’indicateur
specialname.
Le nom basé sur le contenu du type de marqueur d’extension est basé sur les éléments suivants :
- Les noms des paramètres de type seront inclus dans l’ordre dans lequel ils apparaissent dans la déclaration d’extension.
- Les attributs des paramètres de type seront inclus et triés de telle sorte que leur réorganisation dans le code source ne modifie pas le nom.
- Les contraintes des paramètres de type sont incluses et triées de telle sorte que la réorganisation dans le code source ne modifie pas le nom.
- Nom C# complètement qualifié du type étendu
- Cela inclut des éléments tels que des annotations nullables, des noms de tuples, etc...
- Le nom complètement qualifié n’inclut pas l’assembly conteneur.
- Nom du paramètre d’extension
- Modificateurs du paramètre d’extension (
ref, ,ref readonlyscoped, ...) dans un ordre déterministe - Arguments complets de nom et d’attribut pour tous les attributs appliqués au paramètre d’extension dans un ordre déterministe
Remarque : Le nom est garanti pour rester stable entre les recompilations et les réorganisations.
Remarque : les types de marqueurs d’extension et les méthodes de marqueur d’extension sont émis comme partie des assemblages de référence.
Méthode du marqueur d'extension
L’objectif de la méthode de marqueur est d’encoder le paramètre d’extension du bloc d’extension. Étant donné qu’il est membre du type de marqueur d’extension, il peut faire référence aux paramètres de type déclarés à nouveau du type de marqueur d’extension.
Chaque type de marqueur d’extension contient une méthode unique, la méthode de marqueur d’extension.
- Il est statique, non générique, void-retour, et est appelé
<Extension>$. - Son paramètre unique possède les attributs, refness, type et nom du paramètre d’extension.
Si le paramètre d’extension ne spécifie pas de nom, le nom du paramètre est vide. - Il est marqué avec l’indicateur
specialname.
L'accessibilité de la méthode de marqueur correspondra à celle qui est la moins restrictive parmi les membres d'extension déclarés correspondants ; private est utilisée si aucun n'est déclaré.
Membres de l’extension
Les déclarations de méthode/propriété dans un bloc d’extension dans la source sont représentées en tant que membres du type de regroupement d’extensions dans les métadonnées.
- Les signatures des méthodes d’origine sont conservées (y compris les attributs), mais leurs corps sont remplacés par
throw NotImplementedException(). - Ceux-ci ne doivent pas être référencés en langage intermédiaire (IL).
- Les méthodes, les propriétés et leurs accesseurs sont marqués avec
[ExtensionMarkerName("...")]pour indiquer le nom du type de marqueur d’extension correspondant au bloc d’extension de ce membre.
Méthodes d’implémentation
Les corps de méthode pour les déclarations de méthode/propriété dans un bloc d’extension dans la source sont émis en tant que méthodes d’implémentation statique dans la classe statique de niveau supérieur.
- Une méthode d’implémentation porte le même nom que la méthode d’origine.
- Il a des paramètres de type dérivés du bloc d’extension ajoutés aux paramètres de type de la méthode d’origine (y compris les attributs).
- Elle a la même accessibilité et les mêmes attributs que la méthode d’origine.
- Si elle implémente une méthode statique, elle a les mêmes paramètres et le même type de retour.
- Si elle implémente une méthode d’instance, elle a un paramètre ajouté à la signature de la méthode d’origine. Les attributs, refness, type et nom de ce paramètre sont dérivés du paramètre d’extension déclaré dans le bloc d’extension approprié.
- Les paramètres des méthodes d’implémentation font référence aux paramètres de type appartenant à la méthode d’implémentation, au lieu de ceux d’un bloc d’extension.
- Si le membre d’origine est une méthode ordinaire d’instance, la méthode d’implémentation est marquée avec un
[Extension]attribut.
L'attribut ExtensionMarkerName
Le type ExtensionMarkerNameAttribute est réservé à l'usage du compilateur - il n'est pas autorisé dans le code source. La déclaration de type est synthétisée par le compilateur si elle n'est pas déjà incluse dans la compilation.
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
public sealed class ExtensionMarkerNameAttribute : Attribute
{
public ExtensionMarkerNameAttribute(string name)
=> Name = name;
public string Name { get; }
}
Remarque : bien que certaines cibles d’attribut soient incluses à des fins de pérennité (types imbriqués d’extensions, champs d’extension, événements d’extension), AttributeTargets.Constructor n’est pas inclus, car les constructeurs d’extension ne seraient pas des constructeurs.
Exemple :
Remarque : nous utilisons des noms simplifiés basés sur le contenu pour l’exemple, pour la lisibilité. Remarque : étant donné que C# ne peut pas représenter la nouvelle déclaration de paramètre de type, le code représentant les métadonnées n’est pas un code C# valide.
Voici un exemple illustrant le fonctionnement du regroupement, sans membres :
class E
{
extension<T>(IEnumerable<T> source)
{
... member in extension<T>(IEnumerable<T> source)
}
extension<U>(ref IEnumerable<U?> p)
{
... member in extension<U>(ref IEnumerable<U?> p)
}
extension<T>(IEnumerable<U> source)
where T : IEquatable<U>
{
... member in extension<T>(IEnumerable<U> source) where T : IEquatable<U>
}
}
Est émis en tant que
[Extension]
class E
{
[Extension, SpecialName]
public sealed class <>E__ContentName_For_IEnumerable_T<T0>
{
[SpecialName]
public static class <>E__ContentName1 // note: re-declares type parameter T0 as T
{
[SpecialName]
public static void <Extension>$(IEnumerable<T> source) { }
}
[SpecialName]
public static class <>E__ContentName2 // note: re-declares type parameter T0 as U
{
[SpecialName]
public static void <Extension>$(ref IEnumerable<U?> p) { }
}
[ExtensionMarkerName("<>E__ContentName1")]
... member in extension<T>(IEnumerable<T> source)
[ExtensionMarkerName("<>E__ContentName2")]
... member in extension<U>(ref IEnumerable<U?> p)
}
[Extension, SpecialName]
public sealed class <>ContentName_For_IEnumerable_T_With_Constraint<T0>
where T0 : IEquatable<T0>
{
[SpecialName]
public static class <>E__ContentName3 // note: re-declares type parameter T0 as U
{
[SpecialName]
public static void <Extension>$(IEnumerable<U> source) { }
}
[ExtensionMarkerName("ContentName3")]
public static bool IsPresent(U value) => throw null!;
}
... implementation methods
}
Voici un exemple illustrant comment les membres sont émis :
static class IEnumerableExtensions
{
extension<T>(IEnumerable<T> source) where T : notnull
{
public void Method() { ... }
internal static int Property { get => ...; set => ...; }
public int Property2 { get => ...; set => ...; }
}
extension(IAsyncEnumerable<int> values)
{
public async Task<int> SumAsync() { ... }
}
public static void Method2() { ... }
}
Est émis en tant que
[Extension]
static class IEnumerableExtensions
{
[Extension, SpecialName]
public sealed class <>E__ContentName_For_IEnumerable_T<T0>
{
// Extension marker type is emitted as a nested type and re-declares its type parameters to include C#-isms
// In this example, the type parameter `T0` is re-declared as `T` with a `notnull` constraint:
// .class <>E__IEnumerableOfT<T>.<>E__ContentName_For_IEnumerable_T_Source
// .typeparam T
// .custom instance void NullableAttribute::.ctor(uint8) = (...)
[SpecialName]
public static class <>E__ContentName_For_IEnumerable_T_Source
{
[SpecialName]
public static <Extension>$(IEnumerable<T> source) => throw null;
}
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
public void Method() => throw null;
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
internal static int Property
{
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
get => throw null;
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
set => throw null;
}
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
public int Property2
{
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
get => throw null;
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
set => throw null;
}
}
[Extension, SpecialName]
public sealed class <>E__ContentName_For_IAsyncEnumerable_Int
{
[SpecialName]
public static class <>E__ContentName_For_IAsyncEnumerable_Int_Values
{
[SpecialName]
public static <Extension>$(IAsyncEnumerable<int> values) => throw null;
}
[ExtensionMarkerName("<>E__ContentName_For_IAsyncEnumerable_Int_Values")]
public Task<int> SumAsync() => throw null;
}
// Implementation for Method
[Extension]
public static void Method<T>(IEnumerable<T> source) { ... }
// Implementation for Property
internal static int get_Property<T>() { ... }
internal static void set_Property<T>(int value) { ... }
// Implementation for Property2
public static int get_Property2<T>(IEnumerable<T> source) { ... }
public static void set_Property2<T>(IEnumerable<T> source, int value) { ... }
// Implementation for SumAsync
[Extension]
public static int SumAsync(IAsyncEnumerable<int> values) { ... }
public static void Method2() { ... }
}
Chaque fois que les membres d’extension sont utilisés dans la source, nous les émettrons comme référence aux méthodes d’implémentation.
Par exemple : un appel de enumerableOfInt.Method() sera émis en tant qu’appel statique à IEnumerableExtensions.Method<int>(enumerableOfInt).
Documents XML
Les commentaires de documentation sur le bloc d’extension sont émis pour le type de marqueur. (Le DocID pour le bloc d’extension est E.<>E__MarkerContentName_For_ExtensionOfT'1 dans l’exemple ci-dessous.)
Ils sont autorisés à référencer le paramètre d’extension et les paramètres de type à l’aide <paramref> et <typeparamref> respectivement).
Remarque : vous ne pouvez pas documenter le paramètre d’extension ou les paramètres de type (avec <param> et <typeparam>) sur un membre d’extension.
Si deux blocs d’extension sont émis en tant que type de marqueur, leurs commentaires de documentation sont également fusionnés.
Les outils qui consomment les documents XML sont responsables de la copie de <param> et <typeparam> du bloc d'extension aux membres de l'extension si nécessaire (c'est-à-dire que les informations des paramètres ne doivent être copiées que pour les membres d'instance).
<inheritdoc> est émis sur les méthodes d’implémentation et se réfère au membre d’extension pertinent avec un cref. Par exemple, la méthode d’implémentation d’un getter fait référence à la documentation de la propriété d’extension.
Si le membre d’extension n’a pas de commentaires de documentation, <inheritdoc> est omis.
Pour les blocs d’extension et les membres d’extension, nous ne prévenons pas actuellement si :
- le paramètre d'extension est documenté, mais les paramètres sur le membre d'extension ne le sont pas
- ou vice versa
- ou dans les scénarios équivalents avec des paramètres de type non documentés
Par exemple, les commentaires de la documentation suivants :
/// <summary>Summary for E</summary>
static class E
{
/// <summary>Summary for extension block</summary>
/// <typeparam name="T">Description for T</typeparam>
/// <param name="t">Description for t</param>
extension<T>(T t)
{
/// <summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
/// <typeparam name="U">Description for U</typeparam>
/// <param name="u">Description for u</param>
public void M<U>(U u) => throw null!;
/// <summary>Summary for P</summary>
public int P => 0;
}
}
produisent le code xml suivant :
<?xml version="1.0"?>
<doc>
<assembly>
<name>Test</name>
</assembly>
<members>
<member name="T:E">
<summary>Summary for E</summary>
</member>
<member name="T:E.<>E__MarkerContentName_For_ExtensionOfT`1">
<summary>Summary for extension block</summary>
<typeparam name="T">Description for T</typeparam>
<param name="t">Description for t</param>
</member>
<member name="M:E.<>E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)">
<summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
<typeparam name="U">Description for U</typeparam>
<param name="u">Description for u</param>
</member>
<member name="P:E.<>E__MarkerContentName_For_ExtensionOfT`1.P">
<summary>Summary for P</summary>
</member>
<member name="M:E.M``2(``0,``1)">
<inheritdoc cref="M:E.<>E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)"/>
</member>
<member name="M:E.get_P``1(``0)">
<inheritdoc cref="P:E.<>E__MarkerContentName_For_ExtensionOfT`1.P"/>
</member>
</members>
</doc>
Références CREF
Nous pouvons traiter les blocs d’extension comme les types imbriqués, qui peuvent être traités par leur signature (comme s’il s’agissait d’une méthode avec un paramètre d’extension unique).
Exemple : E.extension(ref int).M().
Mais un cref ne peut pas traiter un bloc d’extension lui-même.
E.extension(int) peut faire référence à une méthode nommée « extension » dans le type E.
static class E
{
extension(ref int i)
{
void M() { } // can be addressed by cref="E.extension(ref int).M()" or cref="extension(ref int).M()" within E, but not cref="M()"
}
extension(ref int i)
{
void M(int i2) { } // can be addressed by cref="E.extension(ref int).M(int)" or cref="extension(ref int).M(int)" within E
}
}
L'outil de recherche sait examiner tous les blocs d’extension correspondants.
Comme nous interdisons les références non qualifiées aux membres d’extension, cref les interdirait également.
La syntaxe serait la suivante :
member_cref
: conversion_operator_member_cref
| extension_member_cref // added
| indexer_member_cref
| name_member_cref
| operator_member_cref
;
extension_member_cref // added
: 'extension' type_argument_list? cref_parameter_list '.' member_cref
;
qualified_cref
: type '.' member_cref
;
cref
: member_cref
| qualified_cref
| type_cref
;
C'est une erreur d'utiliser extension_member_cref au niveau supérieur (extension(int).M) ou lorsqu'il est imbriqué dans une autre extension (E.extension(int).extension(string).M).
Modifications majeures
Les types et alias peuvent ne pas être nommés « extension ».
Problèmes ouverts
Section temporaire du document relative aux problèmes ouverts, y compris la discussion sur la syntaxe non définie et les autres conceptions
- Devons-nous ajuster les critères du récepteur lors de l’accès à un élément d’extension ? (commentaire)
-
Confirmer(réponse :extensioncontreextensionscomme le mot-cléextension, LDM 2025-03-24) -
Confirmer que nous voulons interdire(réponse : oui, interdire, LDM 2025-06-11)[ModuleInitializer] -
Veuillez confirmer que nous pouvons supprimer les blocs d’extension en tant que candidats pour le point d’entrée(réponse : oui, supprimer, LDM 2025-06-11). -
Confirmez la logique LangVer (ignorez les nouvelles extensions, et signalez-les lorsqu’elles sont sélectionnées)(réponse : lier inconditionnellement et signaler une erreur LangVer à l’exception des méthodes d’extension d’instance, LDM 2025-06-11) Doit-il(réponse : les commentaires de la documentation sont fusionnés silencieusement lorsque les blocs sont fusionnés, aucunpartialêtre nécessaire pour les blocs d’extension qui fusionnent et ont leurs commentaires docs fusionnés ?partialbesoin, confirmé par e-mail 2025-09-03)Confirmez que les membres ne doivent pas porter le même nom que les types contenant ou étendus.(réponse : oui, confirmé par e-mail 2025-09-03)
Revisitez les règles de regroupement/conflit en fonction du problème de portabilité : https://github.com/dotnet/roslyn/issues/79043
(réponse : ce scénario a été résolu dans le cadre de la nouvelle conception de métadonnées avec des noms de types basés sur le contenu, il est autorisé)
La logique actuelle consiste à regrouper les blocs d’extension qui ont le même type de récepteur. Cela ne tient pas compte des contraintes. Cela provoque un problème de portabilité avec ce scénario :
static class E
{
extension<T>(ref T) where T : struct
void M()
extension<T>(T) where T : class
void M()
}
La proposition consiste à utiliser la même logique de regroupement que celle que nous prévoyons pour la conception du type de regroupement d’extensions, à savoir pour tenir compte des contraintes au niveau du CLR (c’est-à-dire ignorer notnull, les noms de tuples, les annotations de nullabilité).
La refness doit-elle être encodée dans le nom du type de regroupement ?
-
Passer en revue la proposition qui(réponse : confirmée par e-mail 2025-09-03)refn’est pas incluse dans le nom du type de regroupement d’extensions (nécessite une discussion supplémentaire après la révision des règles de regroupement/conflit, LDM 2025-06-23)
public static class E
{
extension(ref int)
{
public static void M()
}
}
Il est émis comme suit :
public static class E
{
public static class <>ExtensionTypeXYZ
{
.. marker method ...
void M()
}
}
Des références CREF tierces pour E.extension(ref int).M sont émises sous forme de M:E.<>ExtensionGroupingTypeXYZ.M(). Si ref est supprimé ou ajouté à un paramètre d'extension, nous souhaitons probablement éviter que le CREF ne s’interrompe.
Nous ne nous soucions pas beaucoup de ce scénario, car toute utilisation en tant qu'extension serait une ambiguïté :
public static class E
{
extension(ref int)
static void M()
extension(int)
static void M()
}
Mais nous nous soucions de ce scénario (pour la portabilité et l’utilité), et cela devrait fonctionner avec la conception de métadonnées proposée après l’ajustement des règles de conflit :
static class E
{
extension<T>(ref T) where T : struct
void M()
extension<T>(T) where T : class
void M()
}
La non-prise en compte de la refness présente un inconvénient, car nous perdons la portabilité dans ce scénario :
static class E
{
extension<T>(ref T)
void M()
extension<T>(T)
void M()
}
// portability issue: since we're grouping without accounting for refness, the emitted extension members conflict (not implementation members). Mitigation: keep as classic extensions or split to another static class
nomde
Devrions-nous interdire les propriétés d'extension dans l'expression nameof comme nous le faisons pour les méthodes d'extension classiques et nouvelles ?(réponse : nous aimerions utiliser 'nameof(EnclosingStaticClass.ExtensionMember). Nécessite une conception, probablement reporté à .NET 10. LDM 2025-06-11)
constructions basées sur des modèles
Méthodes
Où de nouvelles méthodes d’extension doivent-elles entrer en jeu ?(réponse : les mêmes endroits où les méthodes d’extension classiques entrent en jeu, LDM 2025-05-05)
Cela inclut les éléments suivants :
-
GetEnumerator/GetAsyncEnumeratordansforeach -
Deconstructdans la déconstruction, dans les modèles positionnels et foreach -
Adddans les initialiseurs de collection -
GetPinnableReferencedansfixed -
GetAwaiterdansawait
Cela exclut :
-
Dispose/DisposeAsyncdansusingetforeach -
MoveNext/MoveNextAsyncdansforeach -
Sliceetintdans les indexeurs implicites (et éventuellement les modèles de liste ?) -
GetResultdansawait
Propriétés et indexeurs
Où les propriétés d’extension et les indexeurs doivent-ils entrer en jeu ?(réponse : commençons par les quatre, LDM 2025-05-05)
Nous allons inclure :
- initialiseur d’objet :
new C() { ExtensionProperty = ... } - dictionnaire initialiseur :
new C() { [0] = ... } -
with:x with { ExtensionProperty = ... } - modèles de propriété :
x is { ExtensionProperty: ... }
Nous allons exclure :
-
Currentdansforeach -
IsCompleteddansawait -
Count/Lengthpropriétés et indexeurs dans le modèle de liste -
Count/Lengthpropriétés et indexeurs dans les indexeurs implicites
Propriétés renvoyant un délégué
Vérifiez que les propriétés d’extension de cette forme ne doivent entrer en jeu que dans les requêtes LINQ, pour correspondre à ce que font les propriétés d’instance.(réponse : a du sens, LDM 2025-04-06)
Modèle de liste et de propagation
- Vérifiez que les indexeurs d’extension
Index/Rangedoivent jouer dans les modèles de liste (réponse : non pertinents pour C# 14)
Revisiter où Count/Length les propriétés d’extension entrent en jeu
Expressions de collection
- L’extension
Addfonctionne - L’extension
GetEnumeratorfonctionne pour la propagation - L’extension
GetEnumeratorn’affecte pas la détermination du type d’élément (doit être une instance) - Les méthodes d’extension statique
Createne doivent pas compter comme méthode de création bénie - Les propriétés d'extension dénombrables doivent-elles affecter les expressions relatives aux collections ?
Collections params
- Les extensions
Addn’affectent pas les types autorisés avecparams
expressions de dictionnaire
- Assurez-vous que les indexeurs d'extension ne sont pas utilisés dans les expressions associées à des dictionnaires, car la présence de l'indexeur est une partie intégrante de ce qui définit un type de dictionnaire. (réponse : non pertinent pour C# 14)
extern
-
nous prévoyons d’autoriser(réponse : approuvé, LDM 2025-06-23)externpour la portabilité : https://github.com/dotnet/roslyn/issues/78572
Schéma de nommage/numérotation pour le type d’extension
Problème
Le système de numérotation actuel provoque des problèmes avec la validation des API publiques , ce qui garantit que les API publiques correspondent entre les assemblys de référence uniquement et les assemblys d’implémentation.
Devrions-nous apporter l’une des modifications suivantes ? (réponse : nous adoptons un schéma d’affectation de noms basé sur le contenu pour augmenter la stabilité de l’API publique, et les outils devront toujours être mis à jour pour tenir compte des méthodes de marqueur)
- ajuster l’outil
- utiliser un schéma d’affectation de noms basé sur le contenu (TBD)
- laissez le nom être contrôlé par le biais d’une syntaxe
La nouvelle méthode d'extension générique Cast ne fonctionne toujours pas dans LINQ
Problème
Dans les conceptions antérieures de rôles/extensions, il était possible de spécifier uniquement les arguments de type de la méthode explicitement.
Mais maintenant que nous nous concentrons sur une transition fluide à partir des méthodes d'extension classiques, tous les arguments de type doivent être donnés explicitement.
Cela ne parvient pas à résoudre un problème lié à l’utilisation de la méthode Cast d’extension dans LINQ.
Devons-nous apporter une modification à la fonctionnalité d’extensions pour prendre en charge ce scénario ? (réponse : non, cela ne nous amène pas à revoir la conception de résolution d’extension, LDM 2025-05-05)
Limitation du paramètre d’extension sur un membre d’extension
Devrions-nous autoriser ce qui suit ? (réponse : non, cela peut être ajouté ultérieurement)
static class E
{
extension<T>(T t)
{
public void M<U>(U u) where T : C<U> { } // error: 'E.extension<T>(T).M<U>(U)' does not define type parameter 'T'
}
}
public class C<T> { }
Possibilité de valeurs nulles
-
Confirmez la conception actuelle, c’est-à-dire la portabilité maximale/compatibilité(réponse : oui, LDM 2025-04-17)
extension([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool b)
{
public void AssertTrue() => throw null!;
}
extension([System.Diagnostics.CodeAnalysis.NotNullIfNotNull("o")] ref int? i)
{
public void M(object? o) => throw null!;
}
Métadonnées
Les méthodes squelettes doivent-elles lever(réponse : oui, LDM 2025-04-17)NotSupportedExceptionou une autre exception standard (pour l'instant, nous utilisonsthrow null;) ?Devons-nous accepter plusieurs paramètres dans la méthode de marqueur dans les métadonnées (dans le cas où de nouvelles versions ajoutent plus d’informations) ?(réponse : nous pouvons rester stricts, LDM 2025-04-17)Le marqueur d’extension ou les méthodes d’implémentation parlantes doivent-elles être marquées avec un nom spécial ?(réponse : la méthode de marqueur doit être marquée avec un nom spécial et nous devons la vérifier, mais pas les méthodes d’implémentation, LDM 2025-04-17)Devons-nous ajouter(réponse : oui, LDM 2025-03-10)[Extension]un attribut sur la classe statique même s’il n’existe aucune méthode d’extension d’instance à l’intérieur ?Confirmez que nous devrions également ajouter l'attribut(réponse : non, LDM 2025-03-10)[Extension]aux getters et setters d'implémentation.-
Confirmez que les types d'extension doivent être marqués avec un nom spécial et que le compilateur exigera cet indicateur dans les métadonnées (ceci représente un changement de rupture par rapport à la préversion)(réponse : approuvé, LDM 2025-06-23)
Scénario de fabrique statique
Quelles sont les règles de conflit pour les méthodes statiques ?(réponse : utiliser des règles C# existantes pour le type statique englobant, aucune relaxation, LDM 2025-03-17)
Recherche
Comment résoudre les appels de méthode d’instance maintenant que nous avons des noms d’implémentation parlants ?Nous préférons la méthode squelette à sa méthode d’implémentation correspondante.Comment résoudre les méthodes d’extension statique ?(réponse : tout comme les méthodes d’extension d’instance, LDM 2025-03-03)Comment résoudre les propriétés ?(répondu dans les grands traits LDM 2025-03-03, mais nécessite un suivi pour amélioration)-
Règles de portée et de masquage pour les paramètres d’extension et de type(réponse : dans la portée du bloc d’extension, le masquage est interdit, LDM 2025-03-10) Comment ORPA doit-il s’appliquer aux nouvelles méthodes d’extension ?(réponse : traiter les blocs d’extension comme transparents, le « type contenant » pour ORPA est la classe statique englobante, LDM 2025-04-17)
public static class Extensions
{
extension(Type1)
{
[OverloadResolutionPriority(1)]
public void Overload(...)
}
extension(Type2)
{
public void Overload(...)
}
}
ORPA devrait-il s'appliquer à de nouvelles propriétés d’extension ?(réponse : oui et ORPA doit être copié sur les méthodes d’implémentation, LDM 2025-04-23)
public static class Extensions
{
extension(int[] i)
{
public P { get => }
}
extension(ReadOnlySpan<int> r)
{
[OverloadResolutionPriority(1)]
public P { get => }
}
}
- Comment modifier rétroactivement les règles classiques de résolution d’extension ? Le faisons-nous
- mettez à jour la norme pour les méthodes d’extension classiques et utilisez-la pour décrire également les nouvelles méthodes d’extension,
- conservez le langage existant pour les méthodes d’extension classiques, utilisez-le pour décrire également les nouvelles méthodes d’extension, mais ayez un écart de spécification connu pour les deux,
- conservez le langage existant pour les méthodes d’extension classiques, mais utilisez un langage différent pour les nouvelles méthodes d’extension et n’avez qu’un écart de spécification connu pour les méthodes d’extension classiques ?
-
Vérifiez que nous voulons interdire les arguments de type explicites sur un accès aux propriétés(réponse : aucun accès aux propriétés avec des arguments de type explicites, abordé dans WG)
string s = "ran";
_ = s.P<object>; // error
static class E
{
extension<T>(T t)
{
public int P => 0;
}
}
-
Vérifiez notre volonté d’appliquer les règles de supériorité même lorsque le récepteur est un type(réponse : un paramètre d’extension de type uniquement doit être pris en compte lors de la résolution des membres d’extension statiques, LDM 2025-06-23)
int.M();
static class E1
{
extension(int)
{
public static void M() { }
}
}
static class E2
{
extension(in int i)
{
public static void M() => throw null;
}
}
-
Vérifier que nous acceptons une forme d’ambiguïté lorsque les méthodes et les propriétés sont toutes deux applicables(Réponse : nous devrions concevoir une proposition pour dépasser le statu quo, en reportant à .NET 10, LDM 2025-06-23) -
Vérifier que nous ne souhaitons pas de meilleure qualité pour tous les membres avant de déterminer le type de membre gagnant(Réponse : en reportant à .NET 10, WG 2025-07-02)
string s = null;
s.M(); // error
static class E
{
extension(string s)
{
public System.Action M => throw null;
}
extension(object o)
{
public string M() => throw null;
}
}
Avons-nous un récepteur implicite dans les déclarations d’extension ?(réponse : non, a été discuté précédemment dans LDM)
static class E
{
extension(object o)
{
public void M()
{
M2();
}
public void M2() { }
}
}
Devons-nous autoriser la recherche sur le paramètre de type ?(discussion) (réponse : non, nous allons attendre les commentaires, LDM 2025-04-16)
Accessibilité
Quelle est la signification de l’accessibilité dans une déclaration d’extension ?(réponse : les déclarations d’extension ne comptent pas comme étendue d’accessibilité, LDM 2025-03-17)Devons-nous appliquer la vérification de l'« accessibilité incohérente » sur le paramètre récepteur, même pour les membres statiques ?(réponse : oui, LDM 2025-04-17)
public static class Extensions
{
extension(PrivateType p)
{
// We report inconsistent accessibility error,
// because we generate a `public static void M(PrivateType p)` implementation in enclosing type
public void M() { }
public static void M2() { } // should we also report here, even though not technically necessary?
}
private class PrivateType { }
}
Validation de la déclaration d’extension
Devrions-nous assouplir la validation des paramètres de type (inférence : tous les paramètres de type doivent apparaître dans le type du paramètre d’extension) où il existe uniquement des méthodes ?(réponse : oui, LDM 2025-04-06) Cela permettrait de transporter 100% de méthodes d'extension classiques.
Si vous l’avezTResult M<TResult, TSource>(this TSource source), vous pouvez le porter sous forme deextension<TResult, TSource>(TSource source) { TResult M() ... }.Confirmez si les accesseurs à initialisation uniquement doivent être autorisés dans les extensions(réponse : pour l'instant, il est acceptable de les interdire, LDM 2025-04-17)La seule différence dans la réf-ness du récepteur doit-elle être autorisée(réponse : non, conserver la règle spécifiée, LDM 2025-03-24)extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }?Devrions-nous nous plaindre d’un conflit comme celui-ci(réponse : oui, conserver la règle spécifiée, LDM 2025-03-24)extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }?Devrions-nous nous plaindre des conflits entre les méthodes squelettes qui ne sont pas des conflits entre les méthodes d’implémentation ?(réponse : oui, conserver la règle spécifiée, LDM 2025-03-24)
static class E
{
extension(object)
{
public void Method() { }
public static void Method() { }
}
}
Les règles de conflit actuelles sont les suivantes : 1. Assurez-vous qu'il n'y a aucun conflit dans des extensions similaires en utilisant les règles de classe/struct, 2. vérifiez qu’il n’y a pas de conflit entre les méthodes d’implémentation pour les différentes déclarations d’extensions.
Avons-nous besoin de la première partie des règles ?(réponse : oui, nous conservons cette structure car elle facilite l'utilisation des API, LDM 2025-03-24)
Documents XML
Le paramètre de récepteur(réponse : oui, l'utilisation du paramref pour le paramètre d’extension est autorisée sur les membres de l’extension, LDM 2025-05-05)paramrefest-il pris en charge sur les membres d'extension ? Même avec des interférences statiques ? Comment est-il encodé dans la sortie ? Probablement la méthode<paramref name="..."/>standard fonctionnerait pour un humain, mais il existe un risque que certains outils existants ne soient pas heureux de ne pas le trouver parmi les paramètres de l’API.Sommes-nous censés copier des commentaires de document vers les méthodes d’implémentation avec des noms parlants ?(réponse : aucune reproduction, LDM 2025-05-05)L’élément correspondant au paramètre de récepteur doit-il(réponse : aucune reproduction, LDM 2025-05-05)<param>être copié à partir du conteneur d’extension pour les méthodes d’instance ? Faut-il copier tout autre élément du conteneur aux méthodes d'implémentation (<typeparam>etc.) ?Le paramètre d'extension(réponse : non, pour l’instant, LDM 2025-05-05)<param>doit-il être autorisé en tant que surcharge sur les membres d'extension ?- Le résumé sur les blocs d’extension apparaîtra-t-il quelque part ?
CREF
-
Confirmer la syntaxe(réponse : la proposition est bonne, LDM 2025-06-09) Doit-il être possible de faire référence à un bloc d’extension ((réponse : non, LDM 2025-06-09)E.extension(int)) ?Doit-il être possible de faire référence à un membre à l’aide d’une syntaxe non qualifiée :(réponse : oui, LDM 2025-06-09)extension(int).Member- Devons-nous utiliser des caractères différents pour un nom inexpressible, afin d’échapper le XML ? (réponse : s'en remettre à WG, LDM 2025-06-09)
Vérifiez que les deux références aux méthodes squelette et d’implémentation sont possibles :(réponse : oui, LDM 2025-06-09)E.Mvs.E.extension(int).MLes deux semblent nécessaires (propriétés d’extension et portabilité des méthodes d’extension classiques).Les noms de métadonnées d’extension sont-ils problématiques pour les documents de contrôle de version ?(réponse : oui, nous allons nous éloigner des ordinals et utiliser un schéma de nommage stable basé sur le contenu)
Ajouter la prise en charge d’autres types de membres
Nous n’avons pas besoin d’implémenter l’ensemble de cette conception d’un coup, mais nous pouvons aborder un ou plusieurs types de membres à la fois. En fonction des scénarios connus dans nos bibliothèques principales, nous devons travailler dans l’ordre suivant :
- Propriétés et méthodes (instance et statique)
- Opérateurs
- Indexeurs (instance et statique, peut être effectué de manière opportuniste à un point antérieur)
- Tout autre élément
Dans quelle mesure voulons-nous charger la conception d’autres types de membres ?
extension_member_declaration // add
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| finalizer_declaration
| static_constructor_declaration
| type_declaration
;
Types imbriqués
Dans le cas où nous choisissons de poursuivre avec les types imbriqués d’extension, voici quelques notes issues de discussions antérieures :
- Il y aurait un conflit si deux déclarations d’extension déclaraient des types d'extensions imbriqués avec les mêmes noms et les mêmes arités. Nous n’avons pas de solution pour représenter cela dans les métadonnées.
- L’approche approximative que nous avons abordée pour les métadonnées :
- nous émettrions un type imbriqué squelette avec des paramètres de type d’origine et sans membre
- nous émettrions un type imbriqué d’implémentation avec des paramètres de type prédéfinis à partir de la déclaration d’extension et toutes les implémentations membres telles qu’elles apparaissent dans la source (références modulo aux paramètres de type)
Constructeurs
Les constructeurs sont généralement décrits comme un membre d’instance en C#, puisque leur corps a accès à la valeur nouvellement créée par le mot clé this.
Cependant, cela ne fonctionne pas bien pour l’approche basée sur les paramètres des membres d’extension d’instance, puisqu’il n’y a pas de valeur préalable à transmettre en tant que paramètre.
Les constructeurs d’extension fonctionnent plutôt comme des méthodes de fabrique statiques.
Ils sont considérés comme des membres statiques dans le sens où ils ne dépendent pas du nom d’un paramètre récepteur.
Leur corps doit explicitement créer et retourner le résultat de construction.
Le membre lui-même est toujours déclaré avec la syntaxe du constructeur, mais ne peut pas avoir d’initialiseurs this ou base et ne s’appuie pas sur le type de récepteur ayant des constructeurs accessibles.
Cela signifie également que les constructeurs d’extension peuvent être déclarés pour les types qui n’ont pas de constructeurs propres, tels que les types d’interface et les types enum :
public static class Enumerable
{
extension(IEnumerable<int>)
{
public static IEnumerable(int start, int count) => Range(start, count);
}
public static IEnumerable<int> Range(int start, int count) { ... }
}
Autorise :
var range = new IEnumerable<int>(1, 100);
Formes plus courtes
La conception proposée évite la répétition des spécifications de récepteur par membre, mais elle aboutit à une imbrication à double niveau des membres d’extension dans une classe statique et une déclaration d’extension. Il est probable que les classes statiques ne contiennent qu’une seule déclaration d’extension, et que les déclarations d’extension ne contiennent qu’un seul membre. Il nous semble donc plausible d’autoriser l’abréviation syntaxique de ces cas.
Fusionner les déclarations de classe statique et d’extension :
public static class EmptyExtensions : extension(IEnumerable source)
{
public bool IsEmpty => !source.GetEnumerator().MoveNext();
}
Cela ressemble davantage à ce que nous appelons une approche « basée sur le type », où le conteneur des membres d’extension est lui-même nommé.
Fusionner la déclaration d'extension et le membre d'extension :
public static class Bits
{
extension(ref ulong bits) public bool this[int index]
{
get => (bits & Mask(index)) != 0;
set => bits = value ? bits | Mask(index) : bits & ~Mask(index);
}
static ulong Mask(int index) => 1ul << index;
}
public static class Enumerable
{
extension<TSource>(IEnumerable<TSource> source) public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
}
Cela ressemble davantage à ce que nous appelons une approche « basée sur les membres », où chaque membre d’extension contient sa propre spécification de récepteur.
C# feature specifications