Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Annotazioni
Questo articolo è una specifica delle funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.
Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono riportate nelle note pertinenti della riunione di progettazione linguistica (LDM) .
Ulteriori dettagli sul processo di adozione delle specifiche di funzionalità nello standard del linguaggio C# sono disponibili nell'articolo sulle specifiche .
Questione prioritaria: https://github.com/dotnet/csharplang/issues/8697
Dichiarazione
Sintassi
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?
;
Le dichiarazioni di estensione devono essere dichiarate solo in classi statiche non generiche e non annidate.
Si tratta di un errore per il nome extension
di un tipo .
Regole di definizione dell'ambito
I parametri di tipo e il parametro ricevitore di una dichiarazione di estensione si trovano nell'ambito all'interno del corpo della dichiarazione di estensione. Si tratta di un errore per fare riferimento al parametro ricevitore dall'interno di un membro statico, tranne all'interno di un'espressione nameof
. È un errore per i membri dichiarare parametri o parametri di tipo (nonché variabili locali e funzioni locali direttamente all'interno del corpo del membro) con lo stesso nome di un parametro di tipo o di un parametro ricevitore della dichiarazione di estensione.
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`
}
}
Non è un errore che i membri abbiano lo stesso nome dei parametri di tipo o del parametro ricevitore della dichiarazione di estensione contenente. I nomi dei membri non vengono trovati direttamente in una ricerca di nome semplice dall'interno della dichiarazione di estensione; La ricerca troverà quindi il parametro di tipo o il parametro ricevitore di tale nome, anziché il membro.
I membri portano alla dichiarazione di metodi statici direttamente nella classe statica contenitore, e questi possono essere trovati tramite una semplice ricerca per nome; tuttavia, verrà trovato per primo un parametro di dichiarazione di estensione o un parametro ricevitore con lo stesso nome.
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
}
}
Classi statiche come contenitori di estensioni
Le estensioni vengono dichiarate all'interno di classi statiche non generiche di primo livello, proprio come i metodi di estensione oggi, e possono quindi coesistere con i metodi di estensione classici e i membri statici non di estensione:
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) { ... }
}
Dichiarazioni di estensione
Una dichiarazione di estensione è anonima e fornisce una specifica del ricevitore con tutti i parametri e i vincoli di tipo associati, seguiti da un set di dichiarazioni di membri di estensione. La specifica del ricevitore può essere sotto forma di parametro oppure, se vengono dichiarati solo membri di estensione statici, un tipo:
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) { ... }
}
}
Il tipo nella specifica del ricevitore viene definito tipo di ricevitore e il nome del parametro, se presente, viene definito parametro ricevitore.
Se il parametro del ricevitore è denominato, il tipo di ricevitore potrebbe non essere statico.
Il parametro receiver non è autorizzato ad avere modificatori se non è nominato e, in caso contrario, può avere solo i modificatori di riflessività elencati di seguito e scoped
.
Il parametro ricevitore presenta le stesse restrizioni del primo parametro di un metodo di estensione classico.
L'attributo [EnumeratorCancellation]
viene ignorato se viene inserito nel parametro del ricevitore.
Membri dell'estensione
Le dichiarazioni dei membri di estensione sono sintatticamente identiche a membri statici e di istanza corrispondenti nelle dichiarazioni di classe e struct (ad eccezione dei costruttori). I membri dell'istanza fanno riferimento al ricevitore con il nome del parametro del ricevitore:
public static class Enumerable
{
extension(IEnumerable source)
{
// 'source' refers to receiver
public bool IsEmpty => !source.GetEnumerator().MoveNext();
}
}
È un errore specificare un membro di estensione di istanza se la dichiarazione di estensione non specifica un parametro ricevitore.
public static class Enumerable
{
extension(IEnumerable) // No parameter name
{
public bool IsEmpty => true; // Error: instance extension member not allowed
}
}
È un errore specificare i seguenti modificatori per un membro di una dichiarazione di estensione: abstract
, virtual
, override
, new
, sealed
, partial
, e protected
(e i modificatori di accessibilità correlati).
Le proprietà nelle dichiarazioni di estensione potrebbero non avere init
funzioni di accesso.
I membri dell'istanza non sono consentiti se il parametro receiver è privo di nome.
È un errore decorare un membro di estensione con l'attributo [ModuleInitializer]
.
Refness
Per impostazione predefinita, il ricevitore viene passato ai membri dell'estensione dell'istanza in base al valore, proprio come gli altri parametri.
Tuttavia, un ricevitore di dichiarazione di estensione nel formato del parametro può specificare ref
, ref readonly
e in
, purché il tipo di ricevitore sia noto come tipo valore.
Se ref
viene specificato, è possibile dichiarare readonly
un membro dell'istanza o una delle relative funzioni di accesso, impedendone la modifica del ricevitore:
public static class Bits
{
extension(ref ulong bits) // receiver is passed by ref
{
public bool this[int index]
{
set => bits = value ? bits | Mask(index) : bits & ~Mask(index); // mutates receiver
readonly get => (bits & Mask(index)) != 0; // cannot mutate receiver
}
}
static ulong Mask(int index) => 1ul << index;
}
Valori Null e attributi
I tipi di ricevitore possono essere o contenere tipi riferimento nullable e specifiche del ricevitore sotto forma di parametri possono specificare attributi:
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à con i metodi di estensione classici
I metodi di estensione dell'istanza generano artefatti corrispondenti a quelli prodotti dai metodi di estensione classici.
In particolare, il metodo statico generato ha gli attributi, i modificatori e il nome del metodo di estensione dichiarato, nonché l'elenco di parametri di tipo, l'elenco di parametri e gli elenchi di vincoli concatenati dalla dichiarazione di estensione e dalla dichiarazione del metodo in tale ordine:
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) { ... }
}
}
Genera:
[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) { ... }
}
Operatori
Anche se gli operatori di estensione hanno tipi operandi espliciti, devono comunque essere dichiarati all'interno di una dichiarazione di estensione:
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) { ... }
}
}
In questo modo è possibile dichiarare e dedurre i parametri di tipo ed è analogo al modo in cui un normale operatore definito dall'utente deve essere dichiarato all'interno di uno dei relativi tipi di operando.
Controllo in corso
Inferrabilità: Per ogni membro di estensione non di metodo, tutti i parametri di tipo del relativo blocco di estensione devono essere utilizzati nell'insieme combinato di parametri tra l'estensione e il membro.
Unicità: All'interno di una determinata classe statica contenitore, il set di dichiarazioni di membri di estensione con lo stesso tipo di ricevitore (conversione di identità modulo e sostituzione del nome del parametro di tipo) viene considerato come un singolo spazio di dichiarazione simile ai membri all'interno di una dichiarazione di classe o struct e viene trattato secondo le stesse regole sull'univocità.
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'applicazione di questa regola di univocità include metodi di estensione classici all'interno della stessa classe statica.
Ai fini del confronto con i metodi all'interno delle dichiarazioni di estensione, il this
parametro viene considerato come una specifica del ricevitore insieme a qualsiasi parametro di tipo indicato in tale tipo di ricevitore e i parametri del metodo rimanenti vengono usati per la firma del metodo:
public static class Enumerable
{
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
extension(IEnumerable source)
{
IEnumerable<TResult> Cast<TResult>() { ... } // Error! Duplicate declaration
}
}
Consumo
Quando viene tentata la ricerca di un membro di estensione, tutte le dichiarazioni di estensione all'interno di using
classi statiche importate contribuiscono ai membri come candidati, indipendentemente dal tipo di ricevitore. Solo come parte della risoluzione sono candidati con tipi ricevitori incompatibili rimossi.
Viene tentata un'inferenza completa del tipo generico tra il tipo degli argomenti (incluso il ricevitore effettivo) e qualsiasi parametro di tipo (combinando quelli nella dichiarazione di estensione e nella dichiarazione del membro di estensione).
Quando vengono forniti argomenti di tipo espliciti, vengono usati per sostituire i parametri di tipo della dichiarazione di estensione e la dichiarazione del membro di estensione.
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) { ... }
}
}
Analogamente ai metodi di estensione classici, i metodi di implementazione generati possono essere richiamati in modo statico.
Ciò consente al compilatore di disambiguare tra i membri di estensione con lo stesso nome e la stessa 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;
}
}
I metodi di estensione statici verranno risolti come i metodi di estensione dell'istanza (si considererà un argomento aggiuntivo del tipo di ricevitore).
Le proprietà di estensione verranno risolte come i metodi di estensione, con un singolo parametro (il parametro ricevitore) e un singolo argomento (il valore ricevitore effettivo).
OverloadResolutionPriorityAttribute
I membri di estensione all'interno di una classe statica di inclusione sono soggetti alla definizione delle priorità in base ai valori ORPA. La classe statica di contenimento è considerata il "tipo contenitore" preso in considerazione dalle regole ORPA.
Qualsiasi attributo ORPA presente in una proprietà di estensione viene copiato nei metodi di implementazione per le funzioni di accesso della proprietà, in modo che la definizione delle priorità venga rispettata quando tali funzioni di accesso vengono usate tramite sintassi di ambiguità.
Punti di ingresso
I metodi dei blocchi di estensione non sono idonei come candidati al punto di ingresso (vedere "7.1 Avvio dell'applicazione"). Nota: il metodo potrebbe comunque essere ancora candidato all'implementazione.
Abbassamento
La strategia di riduzione per le dichiarazioni di estensione non è una decisione a livello di linguaggio. Tuttavia, oltre all'implementazione della semantica del linguaggio, deve soddisfare determinati requisiti:
- Il formato di tipi generati, membri e metadati deve essere specificato chiaramente in tutti i casi in modo che altri compilatori possano utilizzarli e generarlo.
- Gli artefatti generati devono essere stabili, nel senso che le modifiche successive ragionevoli non devono interrompere i consumer che sono stati compilati in base alle versioni precedenti.
Questi requisiti devono essere più perfezionati man mano che l'implementazione avanza e potrebbero essere compromessi in casi di angolo per consentire un approccio di implementazione ragionevole.
Metadati per le dichiarazioni
Ogni dichiarazione di estensione viene emessa come tipo di estensione con un metodo marcatore e membri di estensione.
Ogni membro dell'estensione è accompagnato da un metodo di implementazione statico di primo livello con una firma modificata.
La classe statica contenitore per una dichiarazione di estensione è contrassegnata con un [Extension]
attributo .
Tipi di estensione
Ogni dichiarazione di estensione nell'origine viene generata come dichiarazione di estensione nei metadati (talvolta denominata skeleton type).
- Il suo nome è indicibile e determinato in base all'ordine lessicale nel programma.
Il nome non è garantito che rimanga stabile durante la ri-compilazione. Di seguito viene usato<>E__
un indice. Ad esempio:<>E__2
. - I parametri di tipo sono quelli dichiarati nell'origine (inclusi gli attributi).
- La sua accessibilità è pubblica.
- È contrassegnato con il
specialname
flag .
Le dichiarazioni di metodo/proprietà in una dichiarazione di estensione nell'origine vengono rappresentate come membri del tipo di estensione nei metadati.
Le firme dei metodi originali vengono mantenute (inclusi gli attributi), ma i relativi corpi vengono sostituiti con throw null
.
Tali riferimenti non devono essere referenziati in IL.
Nota: si tratta di assembly simili a ref. Il motivo dell'uso throw null
di corpi (anziché di nessun corpo) è in modo che la verifica IL possa essere eseguita e superata (convalidando così la completezza dei metadati).
Il metodo dell'indicatore di estensione codifica il parametro ricevitore.
- È privato e statico e viene chiamato
<Extension>$
. - Ha gli attributi, il refness, il tipo e il nome del parametro receiver nella dichiarazione di estensione.
- Se il parametro receiver non specifica un nome, il nome del parametro è vuoto.
Nota: in questo modo è possibile realizzare il roundtripping dei simboli di dichiarazione di estensione tramite metadati (assembly completi e di riferimento).
Nota: è possibile scegliere di generare un solo tipo di estensione nei metadati quando si trovano dichiarazioni di estensione duplicate nell'origine.
Implementazioni
I corpi del metodo per le dichiarazioni di metodo/proprietà in una dichiarazione di estensione nell'origine vengono generati come metodi di implementazione statici nella classe statica di primo livello.
- Un metodo di implementazione ha lo stesso nome del metodo originale.
- Ha parametri di tipo derivati dalla dichiarazione di estensione, preceduti ai parametri di tipo del metodo originale (inclusi gli attributi).
- Ha gli stessi attributi e accessibilità del metodo originale.
- Se implementa un metodo statico, ha gli stessi parametri e il tipo restituito.
- Se implementa un metodo di istanza, ha un parametro anteporto alla firma del metodo originale. Gli attributi, il riferimento, il tipo e il nome di questo parametro derivano dal parametro ricevitore dichiarato nella dichiarazione di estensione pertinente.
- I parametri nei metodi di implementazione fanno riferimento ai parametri di tipo di proprietà del metodo di implementazione, anziché a quelli di una dichiarazione di estensione.
- Se il membro originale è un metodo ordinario dell'istanza, il metodo di implementazione viene contrassegnato con un
[Extension]
attributo .
Per esempio:
static class IEnumerableExtensions
{
extension<T>(IEnumerable<T> source)
{
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() { ... }
}
viene generato come
[Extension]
static class IEnumerableExtensions
{
public class <>E__1<T>
{
private static <Extension>$(IEnumerable<T> source) => throw null;
public void Method() => throw null;
internal static int Property { get => throw null; set => throw null; }
public int Property2 { get => throw null; set => throw null; }
}
public class <>E__2
{
private static <Extension>$(IAsyncEnumerable<int> values) => throw null;
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() { ... }
}
Ogni volta che i membri dell'estensione vengono usati nell'origine, verranno generati come riferimento ai metodi di implementazione.
Ad esempio, una chiamata di enumerableOfInt.Method()
viene generata come chiamata statica a IEnumerableExtensions.Method<int>(enumerableOfInt)
.
Documentazione XML
I commenti doc sul blocco di estensione vengono emessi per il tipo denominato non indicibile (il DocID per il blocco di estensione è <>E__0'1
nell'esempio seguente).
È consentito fare riferimento rispettivamente al parametro di estensione e ai parametri di tipo usando <paramref>
e <typeparamref>
.
Nota: non è possibile documentare il parametro di estensione o i parametri di tipo (con <param>
e <typeparam>
) in un membro di estensione.
Gli strumenti che usano i documenti xml sono responsabili di copiare il <param>
e <typeparam>
dal blocco di estensione ai membri dell'estensione come appropriato (ad esempio, le informazioni sui parametri devono essere copiate solo per i membri dell'istanza).
Un <inheritdoc>
viene emesso sui metodi di implementazione e si riferisce al membro di estensione pertinente con un cref
. Ad esempio, il metodo di implementazione per un getter fa riferimento alla documentazione della proprietà di estensione.
Se il membro dell'estensione non ha commenti doc, l'oggetto <inheritdoc>
viene omesso.
Per i blocchi di estensione e i membri dell'estensione, non viene attualmente visualizzato un avviso se:
- il parametro di estensione è documentato, ma i parametri del membro dell'estensione non lo sono.
- o viceversa
- o negli scenari equivalenti con parametri di tipo non documentati
Ad esempio, i commenti del documento seguenti:
/// <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;
}
}
produrre il codice XML seguente:
<?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__0`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__0`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__0`1.P">
<summary>Summary for P</summary>
</member>
<member name="M:E.M``2(``0,``1)">
<inheritdoc cref="M:E.<>E__0`1.M``1(``0)"/>
</member>
<member name="M:E.get_P``1(``0)">
<inheritdoc cref="P:E.<>E__0`1.P"/>
</member>
</members>
</doc>
Riferimenti CREF
È possibile trattare blocchi di estensione come tipi annidati, che possono essere indirizzi in base alla firma (come se fosse un metodo con un singolo parametro di estensione).
Esempio: E.extension(ref int).M()
.
static class E
{
extension(ref int i)
{
void M() { } // can be addressed by cref="E.extension(ref int).M()"
}
extension(ref int i)
{
void M(int i2) { } // can be addressed by cref="E.extension(ref int).M(int)"
}
}
La ricerca sa come esaminare tutti i blocchi di estensione corrispondenti.
Poiché non consentiamo riferimenti non qualificati ai membri dell'estensione, anche cref non li consentirebbe.
La sintassi sarà:
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
;
È un errore utilizzare extension_member_cref
a livello superiore (extension(int).M
) o incapsulato in un'altra estensione (E.extension(int).extension(string).M
).
Nota: questo non consente cref nel blocco di estensione, poiché E.extension(int)
si riferisce a un metodo chiamato "extension" nel tipo E
.
Modifiche radicali
I tipi e gli alias non possono essere denominati "estensione".
Problemi aperti
-
Confermare(risposta:extension
oextensions
come parola chiaveextension
, LDM 2025-03-24) -
Confermare che vogliamo vietare(risposta: sì, vietare, LDM 2025-06-11)[ModuleInitializer]
-
Confermare che è possibile scartare i blocchi di estensione come candidati al punto di ingresso(risposta: sì, scartare, LDM 2025-06-11) -
Confermare la logica di LangVer (ignorare le nuove estensioni oppure prendere in considerazione e segnalarle quando selezionate)(risposta: crea un binding incondizionato e segnala l'errore di LangVer, fatta eccezione per i metodi di estensione dell'istanza, LDM 2025-06-11) - Quando si accede a un membro dell'estensione, è necessario modificare i requisiti del ricevitore? (commento) Ad esempio,
new Struct() { Property = 42 }
. - Rivedere le regole di raggruppamento/conflitto alla luce del problema di portabilità: https://github.com/dotnet/roslyn/issues/79043
nome di
Dovremmo vietare le proprietà di estensione in nameof, così come facciamo con i metodi di estensione classici e nuovi?(risposta: vorremmo usare `nameof(EnclosingStaticClass.ExtensionMember). Richiede progettazione, probabilmente rimandato da .NET 10. LDM 2025-06-11)
costrutti basati su modelli
Metodi
Dove devono entrare in gioco nuovi metodi di estensione?(risposta: stessi luoghi in cui entra in gioco i metodi di estensione classici, LDM 2025-05-05) Sono inclusi:-
GetEnumerator
/GetAsyncEnumerator
inforeach
-
Deconstruct
in decostruzione, nel modello posizionale e in foreach -
Add
negli inizializzatori di raccolta -
GetPinnableReference
infixed
-
GetAwaiter
inawait
Ciò esclude:
-
Dispose
/DisposeAsync
inusing
eforeach
-
MoveNext
/MoveNextAsync
inforeach
-
Slice
eint
indicizzatori negli indicizzatori impliciti (ed eventualmente list-patterns?) -
GetResult
inawait
Proprietà e indicizzatori
Dove devono entrare in gioco le proprietà di estensione e gli indicizzatori?(risposta: iniziamo con i quattro, LDM 2025-05-05)
Includeremo:
- inizializzatore di oggetti:
new C() { ExtensionProperty = ... }
- intializer dizionario:
new C() { [0] = ... }
-
with
:x with { ExtensionProperty = ... }
- modelli di proprietà:
x is { ExtensionProperty: ... }
Escluderemmo:
-
Current
inforeach
-
IsCompleted
inawait
-
Count
/Length
proprietà e indicizzatori nel modello di elenco -
Count
/Length
proprietà e indicizzatori negli indicizzatori impliciti
Proprietà che restituiscono delegati
Verificare che le proprietà di estensione di questa forma vengano eseguite solo nelle query LINQ, in modo che corrispondano alle proprietà dell'istanza.(risposta: ha senso, LDM 2025-04-06)
Modello di elenco e diffusione
- Confermare che gli indicizzatori di estensione
Index
/Range
debbano svolgere un ruolo negli schemi di lista
Rivedere la posizione in cui Count
/Length
vengono in gioco le proprietà dell'estensione
Espressioni di raccolta
- L'estensione
Add
funziona - L'estensione
GetEnumerator
funziona per la diffusione - L'estensione non influisce
GetEnumerator
sulla determinazione del tipo di elemento (deve essere un'istanza) - I metodi di estensione statici
Create
non devono essere conteggiati come metodo di creazione benedetto - Le proprietà conteggiabili delle estensioni devono influire sulle espressioni della raccolta?
params
raccolte
- Le estensioni
Add
non influiscono sui tipi consentiti conparams
espressioni da dizionario
- Verificare che gli indicizzatori di estensione non vengano utilizzati nelle espressioni di dizionario, poiché la presenza dell'indicizzatore è parte integrante di ciò che caratterizza un tipo di dizionario.
extern
-
stiamo pianificando di consentire(risposta: approvato, LDM 2025-06-23)extern
la portabilità: https://github.com/dotnet/roslyn/issues/78572
Schema di denominazione/numerazione per il tipo di estensione
Problema
Il sistema di numerazione corrente causa problemi con la convalida delle API pubbliche che garantisce che le API pubbliche corrispondano tra assembly di sola riferimento e assembly di implementazione.
È necessario apportare una delle modifiche seguenti? (risposta: lo strumento verrà adattato e l'implementazione della numerazione perfezionata, LDM 2025-05-05)
- regolare lo strumento
- usare uno schema di denominazione basato sul contenuto (TBD)
- lasciare che il nome venga controllato tramite una sintassi
Il nuovo metodo Cast di estensione generico non può ancora funzionare in LINQ
Problema
Nelle progettazioni precedenti di ruoli/estensioni, è possibile specificare solo gli argomenti di tipo del metodo in modo esplicito.
Ma ora che ci concentriamo sulla transizione apparentemente inutile dai metodi di estensione classici, tutti gli argomenti di tipo devono essere forniti in modo esplicito.
Questo non riesce ad affrontare un problema relativo all'utilizzo del metodo Cast dell'estensione in LINQ.
È necessario apportare una modifica alla funzionalità delle estensioni per supportare questo scenario? (risposta: no, questo non ci fa rivedere la progettazione della risoluzione dell'estensione, LDM 2025-05-05)
Vincolare il parametro di estensione in un membro dell'estensione
È consigliabile consentire quanto segue? (risposta: no, potrebbe essere aggiunto in un secondo momento)
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> { }
Nullabilità
-
Confermare la progettazione corrente, ad esempio massima portabilità/compatibilità(risposta: sì, 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!;
}
Metadati
I metodi skeleton dovrebbero generare(risposta: sì, LDM 2025-04-17)NotSupportedException
o un'altra eccezione standard (attualmente facciamothrow null;
)?È consigliabile accettare più parametri nel metodo marcatore nei metadati (nel caso in cui le nuove versioni aggiungono altre informazioni)?(risposta: possiamo rimanere rigidi, LDM 2025-04-17)Il marcatore di estensione o i metodi di implementazione parlabili devono essere contrassegnati con un nome speciale?(risposta: il metodo marcatore deve essere contrassegnato con un nome speciale e dobbiamo controllarlo, ma non i metodi di implementazione, LDM 2025-04-17)È necessario aggiungere(risposta: sì, LDM 2025-03-10)[Extension]
attributo alla classe statica anche quando non è presente alcun metodo di estensione dell'istanza all'interno?Verificare che sia necessario aggiungere(risposta: no, LDM 2025-03-10)[Extension]
l'attributo anche ai getter e ai setter di implementazione.-
Verificare che i tipi di estensione debbano essere contrassegnati con un nome speciale e che il compilatore richieda questo flag nei metadati (si tratta di una modifica che causa un'interruzione dall'anteprima)(risposta: approvato, LDM 2025-06-23) - Confermare che
ref
non deve essere incluso nel nome del tipo di estensione (richiede ulteriore discussione dopo che il WG rivede le regole di raggruppamento/conflitti, LDM 2025-06-23)
public static class E
{
extension(ref int)
{
public static void M()
}
}
Viene emesso come:
public static class E
{
public static class <>ExtensionTypeXYZ
{
.. marker method ...
void M()
}
}
I riferimenti CREF di terze parti a E.extension(ref int).M
vengono emessi come M:E.<>ExtensionTypeXYZ.M()
Se ref
viene rimosso o aggiunto a un parametro di estensione, probabilmente non vogliamo che il CREF si rompa.
scenario di fabbrica statica
Quali sono le regole di conflitto per i metodi statici?(risposta: usare le regole C# esistenti per il tipo statico circostante, nessun rilassamento, LDM 2025-03-17)
Ricerca
Come risolvere le chiamate al metodo di istanza ora che sono disponibili nomi di implementazione parlabili?Preferiamo il metodo skeleton al metodo di implementazione corrispondente.Come risolvere i metodi di estensione statici?(risposta: proprio come i metodi di estensione di un'istanza, LDM 2025-03-03)Come risolvere le proprietà?(risposto in tratti ampi LDM 2025-03-03, ma ha bisogno di ulteriore verifica per ottenere un miglioramento)-
Regole di ambito e shadowing per parametri di estensione e parametri di tipo(risposta: nell'ambito del blocco di estensione, shadowing non consentito, LDM 2025-03-10) In che modo ORPA deve essere applicato ai nuovi metodi di estensione?(risposta: considerare i blocchi di estensione come trasparenti, il "tipo contenitore" per ORPA è la classe statica delimitante, LDM 2025-04-17)
public static class Extensions
{
extension(Type1)
{
[OverloadResolutionPriority(1)]
public void Overload(...)
}
extension(Type2)
{
public void Overload(...)
}
}
ORPA deve essere applicato alle nuove proprietà di estensione?(risposta: sì e ORPA deve essere copiato nei metodi di implementazione, LDM 2025-04-23)
public static class Extensions
{
extension(int[] i)
{
public P { get => }
}
extension(ReadOnlySpan<int> r)
{
[OverloadResolutionPriority(1)]
public P { get => }
}
}
- Come retconizzare le regole di risoluzione delle estensioni classiche? Facciamo noi?
- aggiornare lo standard per i metodi di estensione classici e usarlo per descrivere anche i nuovi metodi di estensione,
- mantenere il linguaggio esistente per i metodi di estensione classici, usarlo anche per descrivere nuovi metodi di estensione, ma hanno una deviazione specifica nota per entrambi,
- mantenere il linguaggio esistente per i metodi di estensione classici, ma usare un linguaggio diverso per i nuovi metodi di estensione e avere solo una deviazione specifica nota per i metodi di estensione classici?
-
Confermare che vogliamo vietare argomenti di tipo esplicito su un accesso a una proprietà(risposta: nessun accesso a proprietà con argomenti di tipo esplicito, discusso nel WG)
string s = "ran";
_ = s.P<object>; // error
static class E
{
extension<T>(T t)
{
public int P => 0;
}
}
-
Verificare che si vogliano applicare regole di miglioramento anche quando il ricevitore è un tipo(risposta: è consigliabile prendere in considerazione un parametro di estensione solo tipo durante la risoluzione dei membri di estensione statici, 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;
}
}
- Confermare che siamo d'accordo col tollerare un'ambiguità quando sia i metodi sia le proprietà sono applicabili (risposta: dovremmo progettare una proposta per migliorare lo status quo, senza bloccare il rilascio di .NET 10, LDM 2025-06-23)
- Confermare che non vogliamo alcun miglioramento per tutti i membri prima di determinare il tipo di membro vincente.
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;
}
}
All'interno delle dichiarazioni di estensione è presente un ricevitore implicito?(risposta: no, è stato discusso in precedenza in LDM)
static class E
{
extension(object o)
{
public void M()
{
M2();
}
public void M2() { }
}
}
È consigliabile consentire la ricerca nel parametro di tipo?(discussione) (risposta: no, attenderemo commenti e suggerimenti, LDM 2025-04-16)
Accessibilità
Qual è il significato dell'accessibilità all'interno di una dichiarazione di estensione?(risposta: le dichiarazioni di estensione non sono conteggiati come ambito di accessibilità, LDM 2025-03-17)È necessario applicare il controllo "accessibilità incoerente" sul parametro ricevitore anche per i membri statici?(risposta: sì, 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 { }
}
Convalida della dichiarazione di estensione
È consigliabile ridurre la convalida dei parametri di tipo (inferrità: tutti i parametri di tipo devono essere visualizzati nel tipo del parametro di estensione) in cui sono presenti solo metodi?(risposta: sì, LDM 2025-04-06) Ciò consentirebbe la conversione di 100% dei metodi di estensione classici.
Se si dispone diTResult M<TResult, TSource>(this TSource source)
, è possibile convertirlo comeextension<TResult, TSource>(TSource source) { TResult M() ... }
.Verificare se le funzioni di accesso init-only devono essere consentite nelle estensioni(risposta: ok per non consentire per il momento, LDM 2025-04-17)La sola differenza nella ref-ness del ricevitore dovrebbe essere consentita(risposta: no, mantieni la regola specificata, LDM 2025-03-24)extension(int receiver) { public void M2() {} }
extension(ref int receiver) { public void M2() {} }
?Dobbiamo lamentarci di un conflitto come questo(risposta: sì, mantieni la regola specificata, LDM 2025-03-24)extension(object receiver) { public int P1 => 1; }
extension(object receiver) { public int P1 {set{}} }
?È consigliabile lamentarsi dei conflitti tra i metodi di struttura che non sono conflitti tra i metodi di implementazione?(risposta: sì, mantieni la regola specificata, LDM 2025-03-24)
static class E
{
extension(object)
{
public void Method() { }
public static void Method() { }
}
}
Le regole di conflitto correnti sono: 1. verificare l'assenza di conflitti all'interno di estensioni simili utilizzando le regole di classe/struttura, 2. Controlla che non ci siano conflitti tra i metodi di implementazione attraverso varie dichiarazioni di estensione.
Abbiamo bisogno della prima parte delle regole?(risposta: sì, stiamo mantenendo questa struttura perché aiuta a usare le API, LDM 2025-03-24)
Documentazione XML
Il parametro ricevitore è(risposta: sì, paramref al parametro di estensione è consentito per i membri dell'estensione, LDM 2025-05-05)paramref
supportato nei membri dell'estensione? Anche quando è statico? Come viene codificato nell'output? Probabilmente un modo<paramref name="..."/>
standard funziona per un essere umano, ma c'è il rischio che alcuni strumenti esistenti non siano felici di non trovarlo tra i parametri nell'API.Dovremmo copiare i commenti della documentazione nei metodi di implementazione con nomi leggibili?(risposta: nessuna copia, LDM 2025-05-05)Deve l'elemento corrispondente al parametro ricevitore essere copiato dal contenitore di estensione per i metodi di istanza? È necessario copiare altri elementi dal contenitore ai metodi di implementazione ((risposta: nessuna copia, LDM 2025-05-05)<typeparam>
e così via) ?Il parametro di estensione(risposta: no, per ora, LDM 2025-05-05)<param>
dovrebbe essere consentito sui membri dell'estensione come override?- Verrà visualizzato il riepilogo dei blocchi di estensione ovunque?
CREF
-
Conferma sintassi(risposta: la proposta è buona, LDM 2025-06-09) Dovrebbe essere possibile fare riferimento a un blocco di estensione ((risposta: no, LDM 2025-06-09)E.extension(int)
)?È possibile fare riferimento a un membro usando una sintassi non qualificata:(risposta: sì, LDM 2025-06-09)extension(int).Member
?- È opportuno usare caratteri diversi per il nome impronunciabile, per evitare l'escape XML? (risposta: rinvio a WG, LDM 2025-06-09)
Verificare che sia corretto che entrambi i riferimenti a uno scheletro e ai metodi di implementazione siano possibili:(risposta: sì, LDM 2025-06-09)E.M
eE.extension(int).M
. Entrambi sembrano necessari (proprietà di estensione e portabilità dei metodi di estensione classici).- I nomi dei metadati di estensione sono problematici per il controllo delle versioni della documentazione?
Aggiunta del supporto per altri tipi di membri
Non è necessario implementare contemporaneamente tutto questo progetto, ma è possibile avvicinarlo a uno o a pochi tipi di membri alla volta. In base agli scenari noti nelle librerie principali, è consigliabile usare l'ordine seguente:
- Proprietà e metodi (istanza e statica)
- Operatori
- Gli indicizzatori (istanza e statica, possono essere eseguiti opportunisticamente in un punto precedente)
- Altro
Quanto si vuole far fronte al carico della progettazione per altri tipi di membri?
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
;
Tipi annidati
Se si sceglie di procedere con i tipi annidati di estensione, ecco alcune note delle discussioni precedenti:
- Ci sarebbe un conflitto se due dichiarazioni di estensione dichiarassero tipi di estensione annidati con gli stessi nomi e arità. Non è disponibile una soluzione per la rappresentazione nei metadati.
- L'approccio approssimativo descritto per i metadati:
- generaremo uno scheletro di tipo annidato con parametri di tipo originali e nessun membro
- verrà generato un tipo annidato di implementazione con parametri di tipo anteporti dalla dichiarazione di estensione e tutte le implementazioni dei membri così come appaiono nell'origine (riferimenti modulo ai parametri di tipo)
Costruttori
I costruttori vengono in genere descritti come membro di istanza in C#, poiché il corpo ha accesso al valore appena creato tramite la this
parola chiave .
Ciò non funziona correttamente per l'approccio basato su parametri ai membri dell'estensione dell'istanza, anche se non esiste alcun valore precedente da passare come parametro.
I costruttori di estensione funzionano invece in modo più simile ai metodi factory statici.
Sono considerati membri statici nel senso che non dipendono da un nome di parametro ricevitore.
I loro corpi devono creare e restituire in modo esplicito il risultato della costruzione.
Il membro stesso è ancora dichiarato con la sintassi del costruttore, ma non può avere this
o base
inizializzatori e non si basa sul tipo di ricevitore con costruttori accessibili.
Ciò significa anche che i costruttori di estensione possono essere dichiarati per i tipi che non dispongono di costruttori propri, ad esempio interfacce ed enumerazioni:
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) { ... }
}
Permette:
var range = new IEnumerable<int>(1, 100);
Forme più brevi
La progettazione proposta evita la ripetizione per membro delle specifiche del ricevitore, ma alla fine i membri di estensione risultano annidati su due livelli in una classe statica e in una dichiarazione di estensione. È probabile che le classi statiche contengano una sola dichiarazione di estensione o che le dichiarazioni di estensione contengano un solo membro e sembra plausibile consentire l'abbreviazione sintattica di tali casi.
Unire dichiarazioni di estensione e classi statiche:
public static class EmptyExtensions : extension(IEnumerable source)
{
public bool IsEmpty => !source.GetEnumerator().MoveNext();
}
Questo finisce per essere più simile a quello che è stato chiamato un approccio "basato sui tipi", in cui il contenitore per i membri dell'estensione è denominato.
Dichiarazione di estensione di merge e membro dell'estensione:
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) { ... }
}
Questo finisce per essere più simile a quello che abbiamo chiamato un approccio "basato sui membri", in cui ogni membro di estensione contiene la propria specifica ricevitore.
C# feature specifications