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.
Questione prioritaria: https://github.com/dotnet/csharplang/issues/8887
Motivazione
La funzionalità di espressione del dizionario ha identificato la necessità di passare le espressioni di raccolta ai dati specificati dall'utente per configurare il comportamento della raccolta finale. In particolare, i dizionari consentono agli utenti di personalizzare il modo in cui le chiavi confrontano, usandole per definire l'uguaglianza tra le chiavi e l'ordinamento o l'hashing (rispettivamente nel caso di raccolte ordinate o con hash). Questa esigenza si applica quando si crea qualsiasi tipo di dizionario (ad esempio D d = new D(...), D d = D.CreateRange(...) e anche IDictionary<...> d = <synthesized dict>)
Per supportare questa operazione, un nuovo with(...arguments...) elemento viene proposto come primo elemento di un'espressione di raccolta, come illustrato di seguito:
Dictionary<string, int> nameToAge = [with(comparer), .. d1, .. d2, .. d3];
- Quando si esegue la conversione in una
new CollectionType(...)chiamata, questi...arguments...vengono usati per determinare il costruttore appropriato e vengono passati di conseguenza. - Quando si esegue la conversione in una
CollectionFactory.Createchiamata, questi...arguments...vengono passati prima con l'argomentoReadOnlySpan<ElementType>elementi, tutti usati per determinare l'overload appropriatoCreatee vengono passati di conseguenza. - Quando si esegue la conversione in un'interfaccia (ad esempio
IDictionary<,>) è consentito un solo argomento. Implementa una delle interfacce note dell'operatore di confronto BCL e verrà usata per controllare la semantica di confronto delle chiavi dell'istanza finale.
Questa sintassi è stata scelta come segue:
- Mantiene tutte le informazioni all'interno della
[...]sintassi. Verificando che il codice indichi ancora chiaramente una raccolta in fase di creazione. - Non implica la chiamata a un
newcostruttore (quando non è così che vengono create tutte le raccolte). - Non implica la creazione o la copia dei valori della raccolta più volte ,ad esempio un suffisso
with { ... }. - Non modifica l'ordine delle operazioni, in particolare con la semantica di ordinamento coerente delle espressioni da sinistra a destra di C#. Ad esempio, non valuta gli argomenti utilizzati per costruire una raccolta dopo la valutazione delle espressioni utilizzate per popolare la raccolta.
- Non forza un utente a leggere fino alla fine di un'espressione di raccolta (potenzialmente di grandi dimensioni) per determinare la semantica comportamentale principale. Ad esempio, la necessità di vedere fino alla fine di un dizionario di centinaia di righe, solo per trovare che, sì, usava l'operatore di confronto chiave corretto.
- Non è sia sottile, anche se non è eccessivamente dettagliato. Ad esempio, l'uso
;di,anziché per indicare gli argomenti è un elemento di sintassi molto semplice da perdere.with()aggiunge solo 6 caratteri e si distingue facilmente, soprattutto con la colorazione della sintassi dellawithparola chiave. - Legge bene. "Si tratta di un'espressione di raccolta "con" questi argomenti, costituiti da questi elementi".
- Risolve la necessità di confrontare i dizionari e i set.
- Assicura che qualsiasi utente abbia bisogno di passare argomenti o qualsiasi esigenza che noi stessi abbiamo oltre i comparer in futuro sono già gestiti.
- Non è in conflitto con alcun codice esistente (utilizzando https://grep.app/ per la ricerca).
Filosofia di progettazione
La sezione seguente illustra le discussioni precedenti sulla filosofia di progettazione. Compreso il motivo per cui alcune forme sono state rifiutate.
Per fornire questi dati definiti dall'utente, è possibile procedere in due direzioni principali. Il primo consiste nell'usare solo valori speciali nello spazio dell'operatore di confronto , che vengono definiti come tipi che ereditano dai tipi o IEqualityComparer<T> dell'elenco IComparer<T> di controllo di base. Il secondo consiste nel fornire un meccanismo generalizzato per fornire argomenti arbitrari all'API richiamata finale durante la creazione di espressioni di raccolta. La specifica dell'espressione del dizionario principale mostra come eseguire la prima operazione, mentre questa specifica cerca di eseguire quest'ultima.
Gli esami delle soluzioni per il semplice passaggio di comparer hanno rivelato punti deboli nel loro approccio se vogliamo espanderli a argomenti arbitrari. Per esempio:
Riutilizzo della sintassi degli elementi , come nel formato :
[StringComparer.OrdinalIgnoreCase, "mads": 21]. Questo funziona bene in uno spazio in cuiKeyValuePair<,>e gli strumenti di confronto non ereditano dai tipi comuni. Ma si suddivide in un mondo in cui si potrebbe fare:HashSet<object> h = [StringComparer.OrdinalIgnoreCase, "v"]. Questo passaggio è un operatore di confronto? Oppure se si tenta di inserire due valori di oggetto nel set?Separazione degli argomenti rispetto agli elementi con sintassi sottile , ad esempio usando un punto e virgola anziché una virgola per separarli in
[comparer; v1]. Ciò comporta molto confusione nelle situazioni in cui un utente scrive[1; 2]accidentalmente (e ottiene una raccolta che passa '1' come, ad esempio, l'argomento 'capacity' per unList<>oggetto e contiene solo il valore singolo '2'), quando intende[1, 2](una raccolta con due elementi).
Per questo motivo, per supportare argomenti arbitrari, riteniamo che sia necessaria una sintassi più ovvia per demarcare più chiaramente questi valori. Molti altri problemi di progettazione hanno anche in questo spazio. In nessun ordine particolare, questi sono:
Che la soluzione non sia ambigua e causi interruzioni di codice che gli utenti usano con espressioni di raccolta oggi. Per esempio:
List<Widget> c = [new(...), w1, w2, w3];Questo è valido oggi, con l'espressione
new(...)"creazione implicita di oggetti" che crea un nuovo widget. Non è possibile riconfigurare questa operazione per passare gli argomenti alList<>costruttore del costruttore perché interromperà sicuramente il codice esistente.Che la sintassi non si estende all'esterno del
[...]costrutto. Per esempio:HashSet<string> s = [...] with ...;Queste sintassi possono essere interpretate per indicare che la raccolta viene creata per prima e quindi ricreata in una forma diversa, implicando più trasformazioni dei dati e costi potenzialmente più elevati (anche se non è ciò che viene generato).
Che
newcome parola chiave potenziale da usare in questo spazio è indesirribilmente confuso. Sia perché[...]indica già che viene creato un nuovo oggetto e poiché le traduzioni dell'espressione di raccolta possono passare attraverso API non costruttore, ad esempio il modello di metodo Create .Che la soluzione non sia eccessivamente dettagliata. Una proposta di valore di base delle espressioni di raccolta è brevità. Pertanto, se il form aggiunge una grande quantità di scaffolding sintattico, si sente come un passo indietro e sottocuterà la proposta di valore dell'uso di espressioni di raccolta, anziché chiamare nelle API esistenti per creare la raccolta.
Si noti che una sintassi come new([...], ...) viene eseguita in precedenza sia '2' che '3'. Viene visualizzato come se si chiamasse in un costruttore (quando non si potrebbe essere) e implica che un'espressione di raccolta creata viene passata a tale costruttore, che non è sicuramente.
In base a tutte le opzioni precedenti, si è verificato un piccolo numero di opzioni che si ritiene di risolvere le esigenze di passare argomenti, senza uscire dai limiti degli obiettivi delle espressioni di raccolta.
[with(...arguments...)] Design
Syntax:
collection_element
: expression_element
| spread_element
+ | with_element
;
+with_element
+ : 'with' argument_list
+ ;
Esiste un'ambiguità sintattica immediatamente introdotta con questa produzione grammaticale. Analogamente all'ambiguità tra spread_element e expression_element (spiegato qui, esiste un'ambiguità sintattica immediata tra with_element e expression_element.
In particolare with(<arguments>) , è esattamente il corpo di produzione per with_elemented è raggiungibile anche tramite expression_element -> expression -> ... -> invocation_expression. Esiste una semplice regola generale per collection_elements. In particolare, se l'elemento inizia in modo lessicale con la sequenza with( di token, viene sempre considerato come .with_element
Questo è utile in due modi. Prima di tutto, un'implementazione del compilatore deve esaminare solo i token immediatamente seguenti visualizzati per determinare quale tipo di elemento analizzare. In secondo luogo, un utente può facilmente comprendere quale tipo di elemento ha senza dover analizzare mentalmente ciò che segue per vedere se deve considerarlo come o with_elementexpression_element.
Esempi
Esempi di come l'aspetto sarebbe:
// With an existing type:
// Initialize to twice the capacity since we'll have to add
// more values later.
List<string> names = [with(capacity: values.Count * 2), .. values];
Queste forme sembrano "leggere" ragionevolmente bene. In tutti questi casi, il codice è "creazione di un'espressione di raccolta, "con" gli argomenti seguenti da passare per controllare l'istanza finale e quindi gli elementi successivi usati per popolarlo. Ad esempio, la prima riga "crea un elenco di stringhe 'with' una capacità di due volte il conteggio dei valori da distribuire in esso"
Importante, questo codice ha poca probabilità di essere trascurato come con i moduli come : [arg; element], aggiungendo anche un livello minimo di dettaglio, con una grande flessibilità per passare gli argomenti desiderati.
Tecnicamente si tratta di una modifica che causa un'interruzione, come with(...)potrebbe essere stata una chiamata a un metodo preesistente denominato with. Tuttavia, a differenza new(...) di quale sia un modo noto e consigliato per creare valori tipizzati in modo implicito, with(...) è molto meno probabile come nome di metodo, eseguendo afoul di denominazione .NET per i metodi. Nel caso improbabile che un utente disponga di un metodo di questo tipo, sarebbe certamente in grado di continuare a chiamare nel metodo esistente usando @with(...).
L'elemento verrà convertito in questo with(...) modo:
List<string> names = [with(/*capacity*/10), ...]; // translates to:
// argument_list *becomes* the argument list for the
// constructor call.
__result = new List<string>(10); // followed by normal initialization
// or
IList<string> names2 = [with(capacity: 20), ...]; // translates to:
__result = new List<string>(20);
In altre parole, gli argomenti argument_list verrebbero passati al costruttore appropriato se si chiama un costruttore o al metodo 'create' appropriato se si chiama tale metodo. È anche possibile specificare un singolo argomento che eredita dai tipi di operatore di confronto BCL quando si crea un'istanza di uno dei tipi di interfaccia del dizionario di destinazione per controllarne il comportamento.
Conversions
La sezione conversioni per le espressioni di raccolta viene aggiornata nel modo seguente:
> A struct or class type that implements System.Collections.IEnumerable where:
- * The type has an applicable constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression.
+ a. the collection expression has no `with_element` and the type has an applicable constructor
+ that can be invoked with no arguments, accessible at the location of the collection expression. or
+ b. the collection expression has a `with_element` and the type has at least one constructor
+ accessible at the location of the collection expression.
Si noti che gli argomenti effettivi all'interno argument_list di di with_element non influiscono se la conversione esiste o meno. Solo la presenza o l'assenza with_element della stessa. L'intuizione qui è semplicemente che se l'espressione di raccolta viene scritta senza uno (ad esempio [x, y, z]) deve essere in grado di chiamare il costruttore senza argomenti. Anche se è presente [with(...), x, y, z] , potrebbe chiamare il costruttore appropriato. Ciò significa anche che i tipi che non possono essere richiamati con un costruttore no-argument possono essere utilizzati con un'espressione di raccolta, ma solo se tale espressione di raccolta che contiene un oggetto with_element.
La determinazione effettiva del modo in cui un with_element influirà sulla costruzione è indicato di seguito.
Costruzione
La costruzione viene aggiornata come indicato di seguito.
Gli elementi di un'espressione di raccolta vengono valutati in ordine, da sinistra a destra. All'interno degli argomenti della raccolta, gli argomenti vengono valutati in ordine, da sinistra a destra. Ogni elemento o argomento viene valutato esattamente una volta e tutti gli altri riferimenti fanno riferimento ai risultati di questa valutazione iniziale.
Se collection_arguments è incluso e non è il primo elemento nell'espressione di raccolta, viene segnalato un errore in fase di compilazione.
Se l'elenco di argomenti contiene valori con tipo dinamico , viene segnalato un errore in fase di compilazione (LDM-2025-01-22).
Costruttori
Se il tipo di destinazione è uno struct o un tipo di classe che implementa System.Collections.IEnumerablee il tipo di destinazione non dispone di un metodo create e il tipo di destinazione non è un tipo di parametro generico , quindi:
- La risoluzione dell'overload viene usata per determinare il costruttore di istanza migliore dai candidati.
- Il set di costruttori candidati è tutti i costruttori di istanza accessibili dichiarati nel tipo di destinazione applicabili rispetto all'elenco di argomenti come definito nel membro della funzione applicabile.
- Se viene trovato un costruttore di istanza migliore, il costruttore viene richiamato con l'elenco di argomenti.
- Se il costruttore ha un
paramsparametro, la chiamata può essere espansa.
- Se il costruttore ha un
- In caso contrario, viene segnalato un errore di associazione.
// List<T> candidates:
// List<T>()
// List<T>(IEnumerable<T> collection)
// List<T>(int capacity)
List<int> l;
l = [with(capacity: 3), 1, 2]; // new List<int>(capacity: 3)
l = [with([1, 2]), 3]; // new List<int>(IEnumerable<int> collection)
l = [with(default)]; // error: ambiguous constructor
Metodi CollectionBuilderAttribute
Se il tipo di destinazione è un tipo con un metodo create, allora:
- La risoluzione dell'overload viene usata per determinare il metodo di creazione migliore dai candidati.
- Per ogni metodo di creazione per il tipo di destinazione, definiamo un metodo di proiezione con una firma identica al metodo create, ma senza l'ultimo parametro.
- Il set di metodi di proiezione candidati è i metodi di proiezione applicabili rispetto all'elenco di argomenti , come definito nel membro della funzione applicabile.
- Se viene trovato un metodo di proiezione ottimale, viene richiamato il metodo create corrispondente con l'elenco di argomenti aggiunto con un
ReadOnlySpan<T>oggetto contenente gli elementi. - In caso contrario, viene segnalato un errore di associazione.
[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> { ... }
class MyBuilder
{
public static MyCollection<T> Create<T>(ReadOnlySpan<T> elements);
public static MyCollection<T> Create<T>(IEqualityComparer<T> comparer, ReadOnlySpan<T> elements);
}
MyCollection<string> c1 = [with(GetComparer()), "1", "2"];
// IEqualityComparer<string> _tmp1 = GetComparer();
// ReadOnlySpan<string> _tmp2 = ["1", "2"];
// c1 = MyBuilder.Create<string>(_tmp1, _tmp2);
MyCollection<string> c2 = [with(), "1", "2"];
// ReadOnlySpan<string> _tmp3 = ["1", "2"];
// c2 = MyBuilder.Create<string>(_tmp3);
CollectionBuilderAttribute: Metodi di creazione
Per un'espressione di raccolta in cui la definizione del tipo di destinazione ha un [CollectionBuilder] attributo, i metodi create sono i seguenti, aggiornati dalle espressioni di raccolta: create methods.
Un
[CollectionBuilder(...)]attributo specifica il tipo di generatore e il nome del metodo di un metodo da richiamare per costruire un'istanza del tipo di raccolta.Il tipo di generatore deve essere un tipo non generico
classostruct.In primo luogo, viene determinato il set di metodi
CMdi creazione applicabili. È costituito da metodi che soddisfano i requisiti seguenti:
- Il metodo deve avere il nome specificato nell'attributo
[CollectionBuilder(...)].- Il metodo deve essere definito direttamente nel tipo di generatore .
- Il metodo deve essere
static.- Il metodo deve essere accessibile in cui viene utilizzata l'espressione di raccolta.
- L'arità del metodo deve corrispondere all'arità del tipo di raccolta.
- Il metodo deve avere un ultimo parametro di tipo
System.ReadOnlySpan<E>, passato per valore.- Esiste una conversione di identità, una conversione di riferimento implicita o una conversione boxing dal tipo restituito dal metodo al tipo di raccolta.
I metodi dichiarati su tipi o interfacce di base vengono ignorati e non fanno parte del
CMset.
Per un'espressione di raccolta con un tipo di destinazione in cui la
C<S0, S1, …>diC<T0, T1, …>tipo ha un metodoB.M<U0, U1, …>()builder associato, gli argomenti di tipo generico del tipo di destinazione vengono applicati in ordine , e dal tipo contenitore più esterno all'interno, al metodo builder.
Le differenze principali rispetto all'algoritmo precedente sono:
- I metodi Create possono avere parametri aggiuntivi prima del
ReadOnlySpan<E>parametro . - Sono supportati più metodi di creazione.
Tipo di destinazione dell'interfaccia
Se il tipo di destinazione è un tipo di interfaccia, procedere come illustrato di seguito:
La risoluzione dell'overload viene usata per determinare la firma del metodo candidato migliore.
Il set di firme candidate è rappresentato dalle firme seguenti per l'interfaccia di destinazione applicabili rispetto all'elenco di argomenti come definito nel membro della funzione applicabile.
Interfaces Firme candidate IEnumerable<E>IReadOnlyCollection<E>IReadOnlyList<E>()(nessun parametro)ICollection<E>IList<E>List<E>()List<E>(int)
Se viene trovata una firma del metodo migliore, la semantica è la seguente:
- La firma candidata per
IEnumerable<E>IReadOnlyCollection<E>eIReadOnlyList<E>è semplicemente()e ha lo stesso significato di non avere affatto l'elementowith(). - Le firme candidate per
IList<T>eICollection<T>sono le firme deiList<T>()costruttori eList<T>(int). Quando si costruisce il valore (vedere Conversione interfaccia modificabile), verrà richiamato il rispettivoList<T>costruttore. - In caso contrario, viene segnalato un errore di associazione.
Dictionary-Interface tipo di destinazione
Questa opzione viene specificata qui come parte della funzionalità definita in https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md.
L'elenco precedente è aumentato per avere gli elementi seguenti:
| Interfaces | Firme candidate |
|---|---|
IReadOnlyDictionary<K, V> |
() (nessun parametro)(IEqualityComparer<K>? comparer) |
IDictionary<K, V> |
Dictionary<K, V>()Dictionary<K, V>(int)Dictionary<K, V>(IEqualityComparer<K>)Dictionary<K, V>(int, IEqualityComparer<K>) |
Se viene trovata una firma del metodo migliore, la semantica è la seguente:
- Le firme candidate per
IReadOnlyDictionary<K, V>sono()(che hanno lo stesso significato di non avere affatto l'elementowith()) e(IEqualityComparer<K>). Questo operatore di confronto verrà usato per eseguire in modo appropriato l'hashing e confrontare le chiavi nel dizionario di destinazione scelto dal compilatore per creare (vedere Conversione interfaccia non modificabile). - Le firme candidate per
IDictionary<T>sono le firme deiDictionary<K, V>()costruttori ,Dictionary<K, V>(IEqualityComparer<K>)Dictionary<K, V>(int)eDictionary<K, V>(int, IEqualityComparer<K>). Quando si costruisce il valore (vedere Conversione interfaccia modificabile), verrà richiamato il rispettivoDictionary<K, V>costruttore. - In caso contrario, viene segnalato un errore di associazione.
IDictionary<string, int> d;
IReadOnlyDictionary<string, int> r;
d = [with(StringComparer.Ordinal)]; // new Dictionary<string, int>(StringComparer.Ordinal)
r = [with(StringComparer.Ordinal)]; // new $PrivateImpl<string, int>(StringComparer.Ordinal)
d = [with(capacity: 2)]; // new Dictionary<string, int>(capacity: 2)
r = [with(capacity: 2)]; // error: 'capacity' parameter not recognized
d = [with()]; // Legal: empty arguments supported for interfaces
Altri tipi di destinazione
Se il tipo di destinazione è qualsiasi altro tipo, viene segnalato un errore di associazione per l'elenco di argomenti, anche se vuoto.
Span<int> a = [with(), 1, 2, 3]; // error: arguments not supported
Span<int> b = [with([1, 2]), 3]; // error: arguments not supported
int[] a = [with(), 1, 2, 3]; // error: arguments not supported
int[] b = [with(length: 1), 3]; // error: arguments not supported
Sicurezza dell'arbitro
Le regole collection-expressions.md#ref-safety vengono modificate in modo da tenere conto dell'elemento with() .
Vedere anche vincolo di contesto sicuro .16.4.15.
Creare metodi
Questa sezione si applica alle espressioni di raccolta il cui tipo di destinazione soddisfa i vincoli definiti nei metodi CollectionBuilderAttribute.
Il contesto sicuro è determinato modificando una clausola da collection-expressions.md#ref-safety (modifiche in grassetto):
- Se il tipo di destinazione è un tipo di struct ref con un metodo create, il contesto sicuro dell'espressione di raccolta è il contesto sicuro di una chiamata del metodo create in cui gli argomenti sono
with()gli argomenti dell'elemento seguiti dall'espressione di raccolta come argomento per l'ultimo parametro (ilReadOnlySpan<E>parametro ).
Gli argomenti del metodo devono corrispondere al vincolo si applica all'espressione di raccolta. Analogamente alla determinazione del contesto sicuro precedente, gli argomenti del metodo devono corrispondere al vincolo viene applicato considerando l'espressione di raccolta come chiamata del metodo create, dove gli argomenti sono with() gli argomenti dell'elemento seguiti dall'espressione di raccolta come argomento per l'ultimo parametro.
Chiamate al costruttore
Questa sezione si applica alle espressioni di raccolta il cui tipo di destinazione soddisfa i vincoli definiti in Costruttori.
Per un'espressione di raccolta di un tipo di struct ref nel formato seguente:
[with(a₁, a₂, ..., aₙ), e₁, e₂, ..., eₙ]
Il contesto sicuro dell'espressione di raccolta è il più piccolo dei contesti sicuri delle espressioni seguenti:
- Espressione di creazione
new C(a₁, a₂, ..., aₙ)di oggetti , doveCè il tipo di destinazione - Espressioni di elemento
e₁, e₂, ..., eₙ,ovvero le espressioni stesse o il valore di diffusione nel caso di un elemento spread.
Gli argomenti del metodo devono corrispondere al vincolo si applica all'espressione di raccolta. Il vincolo viene applicato trattando l'espressione di raccolta come creazione di oggetti del form new C(a₁, a₂, ..., aₙ) { e₁, e₂, ..., eₙ } per ogni struct-struct-improvements.md#rules-for-object-initializers.
- Gli elementi di espressione vengono considerati come se fossero inizializzatori di elementi di raccolta.
- Gli elementi distribuiti vengono trattati in modo analogo, presupponendo temporaneamente che
Cabbia unAdd(SpreadType spread)metodo, doveSpreadTypeè il tipo del valore di distribuzione.
Domande a cui si è risposto
Argomenti dynamic
Gli argomenti con dynamic tipo devono essere consentiti? Ciò potrebbe richiedere l'uso del gestore di associazione di runtime per la risoluzione dell'overload, che renderebbe difficile limitare il set di candidati, ad esempio per i casi del generatore di raccolte.
Risoluzione: Consentita. LDM-2025-01-22
with() modifica che causa un'interruzione
L'elemento proposto with() è una modifica che causa un'interruzione.
object x, y, z = ...;
object[] items = [with(x, y), z]; // C#13: ok; C#14: error args not supported for object[]
object with(object x, object y) { ... }
Verificare che la modifica di rilievo sia accettabile e se la modifica che causa un'interruzione deve essere associata alla versione della lingua.
Risoluzione: Mantenere il comportamento precedente (nessuna modifica di rilievo) durante la compilazione con la versione precedente del linguaggio. LDM-2025-03-17
Gli argomenti devono influire sulla conversione delle espressioni di raccolta?
Gli argomenti della raccolta e i metodi applicabili influiscono sulla convertibilità dell'espressione di raccolta?
Print([with(comparer: null), 1, 2, 3]); // ambiguous or Print<int>(HashSet<int>)?
static void Print<T>(List<T> list) { ... }
static void Print<T>(HashSet<T> set) { ... }
Se gli argomenti influiscono sulla convertibilità in base ai metodi applicabili, è probabile che anche gli argomenti influiscano sull'inferenza del tipo.
Print([with(comparer: StringComparer.Ordinal)]); // Print<string>(HashSet<string>)?
Per riferimento, casi simili con tipizzato new() di destinazione generano errori.
Print<int>(new(comparer: null)); // error: ambiguous
Print(new(comparer: StringComparer.Ordinal)); // error: type arguments cannot be inferred
Risoluzione: Gli argomenti della raccolta devono essere ignorati nelle conversioni e nell'inferenza dei tipi. LDM-2025-03-17
Ordine dei parametri del metodo del generatore di raccolte
Per i metodi del generatore di raccolte , il parametro span deve essere prima o dopo eventuali parametri per gli argomenti della raccolta?
Gli elementi consentono prima di tutto di dichiarare gli argomenti come facoltativi.
class MySetBuilder
{
public static MySet<T> Create<T>(ReadOnlySpan<T> items, IEqualityComparer<T> comparer = null) { ... }
}
Gli argomenti consentono prima di tutto l'intervallo di essere un params parametro, per supportare la chiamata direttamente in formato espanso.
var s = MySetBuilder.Create(StringComparer.Ordinal, x, y, z);
class MySetBuilder
{
public static MySet<T> Create<T>(IEqualityComparer<T> comparer, params ReadOnlySpan<T> items) { ... }
}
Risoluzione: Il parametro span per gli elementi deve essere l'ultimo parametro. LDM-2025-03-12
Argomenti con la versione precedente della lingua
Viene segnalato un errore per with() durante la compilazione con una versione del linguaggio precedente o viene with associato a un altro simbolo nell'ambito?
Risoluzione: Nessuna modifica di rilievo per with all'interno di un'espressione di raccolta durante la compilazione con versioni precedenti del linguaggio.
LDM-2025-03-17
Tipi di destinazione in cui sono necessari argomenti
Le conversioni di espressioni di raccolta devono essere supportate nei tipi di destinazione in cui è necessario specificare gli argomenti perché tutti i costruttori o i metodi factory richiedono almeno un argomento?
Tali tipi possono essere usati con espressioni di raccolta che includono argomenti espliciti with() , ma non è possibile usare i tipi per params i parametri.
Si consideri ad esempio il tipo seguente costruito da un metodo factory:
MyCollection<object> c;
c = []; // error: no arguments
c = [with(capacity: 1)]; // ok
[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> : IEnumerable<T> { ... }
class MyBuilder
{
public static MyCollection<T> Create<T>(ReadOnlySpan<T> items, int capacity) { ... }
}
La stessa domanda si applica quando il costruttore viene chiamato direttamente come nell'esempio seguente.
Tuttavia, per i tipi di destinazione in cui viene chiamato direttamente il costruttore, la conversione dell'espressione di raccolta richiede attualmente un costruttore chiamabile senza argomenti, ma gli argomenti della raccolta vengono ignorati durante la determinazione della convertibilità.
c = []; // error: no arguments
c = [with(capacity: 1)]; // error: no constructor callable with no arguments?
class MyCollection<T> : IEnumerable<T>
{
public MyCollection(int capacity) { ... }
public void Add(T t) { ... }
// ...
}
Risoluzione: Supportare le conversioni in tipi di destinazione in cui tutti i costruttori o i metodi factory richiedono argomenti e richiedono with() la conversione.
LDM-2025-03-05
__arglist
Deve __arglist essere supportato negli with() elementi?
class MyCollection : IEnumerable
{
public MyCollection(__arglist) { ... }
public void Add(object o) { }
}
MyCollection c;
c = [with(__arglist())]; // ok
c = [with(__arglist(x, y)]; // ok
Risoluzione: Nessun supporto per __arglist negli argomenti della raccolta, a meno che non sia gratuito.
LDM-2025-03-05
Argomenti per i tipi di interfaccia
Gli argomenti devono essere supportati per i tipi di destinazione dell'interfaccia?
ICollection<int> c = [with(capacity: 4)];
IReadOnlyDictionary<string, int> d = [with(comparer: StringComparer.Ordinal), ..values];
Per i tipi di interfaccia modificabili , le opzioni sono:
- Usare i costruttori accessibili dal tipo noto necessario per la creazione di istanze:
List<T>oDictionary<K, V>. - Usare firme indipendenti da un tipo specifico, ad esempio usando
new()e perICollection<T>enew(int capacity)IList<T>(vedere Costruzione di firme potenziali per ogni interfaccia).
L'uso dei costruttori accessibili da un tipo noto ha le implicazioni seguenti:
- I nomi dei parametri, facoltativa-ness,
params, vengono ricavati direttamente dai parametri. - Sono inclusi tutti i costruttori accessibili, anche se ciò potrebbe non essere utile per le espressioni di raccolta, ad esempio
List(IEnumerable<T>)il che consentirebbeIList<int> list = [with(1, 2, 3)];. - Il set di costruttori può dipendere dalla versione BCL.
Ricomendation: usare i costruttori accessibili dai tipi noti. Abbiamo garantito che si userebbero questi tipi, quindi questo è semplicemente "fall out" ed è il percorso più chiaro e più semplice per costruire questi valori.
Per i tipi di interfaccia non modificabili , le opzioni sono simili:
- Non eseguire alcuna operazione. Questo
- Usare firme indipendenti da un tipo specifico, anche se l'unico scenario può essere
new(IEqualityComparer<K> comparer)perIReadOnlyDictionary<K, V>C#14.
L'uso di costruttori accessibili da un tipo noto (la strategia per i tipi di interfaccia modificabile) non è fattibile perché non esiste alcuna relazione con un particolare tipo esistente e il tipo finale che è possibile usare e/o sintetizzare. Di conseguenza, dovrebbero esserci nuovi requisiti dispari che il compilatore sia in grado di eseguire il mapping di qualsiasi costruttore esistente di tale tipo (anche man mano che si evolve) all'istanza non modificabile generata effettivamente.
Ricomendation: usare firme indipendenti da un tipo specifico. Inoltre, per C# 14, solo il supporto new(IEqualityComparer<K> comparer) per IReadOnlyDictionary<K, V> perché è l'unica interfaccia non modificabile in cui riteniamo che sia fondamentale per l'usabilità/semantica per consentire agli utenti di fornire questo tipo di dati. Le versioni future di C# possono prendere in considerazione l'espansione di questo set in base a motivazioni solide fornite.
Risoluzione:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md
Gli argomenti sono supportati per i tipi di destinazione dell'interfaccia. Per entrambe le interfacce modificabili e non modificabili, il set di argomenti verrà curato.
L'elenco previsto (che deve ancora essere ratificato dall'LDM) è il tipo di destinazione dell'interfaccia
Elenchi di argomenti vuoti
È consigliabile consentire elenchi di argomenti vuoti per alcuni o tutti i tipi di destinazione?
Un vuoto with() sarebbe equivalente a nessun oggetto with(). Potrebbe fornire una certa coerenza con casi non vuoti, ma non aggiungerebbe alcuna nuova funzionalità.
List<int> l = [with()]; // ok? new List<int>()
ImmutableArray<int> m = [with()]; // ok? ImmutableArray.Create<int>()
IList<int> i = [with()]; // ok? new List<int>() or equivalent
IEnumerable<int> e = [with()]; // ok?
int[] a = [with()]; // ok?
Span<int> s = [with()]; // ok?
Risoluzione:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-12.md#empty-argument-lists
Verranno consentiti with() per i tipi di costruttori e i tipi di generatore che possono essere chiamati senza argomenti e verranno aggiunte firme del costruttore vuote per i tipi di interfaccia (modificabili e readonly). Le matrici e gli intervalli non consentono with(), perché non ci sono firme che le adatterebbero.
Domande aperte
Finalizzazione di un problema aperto da https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion
with(...) è una modifica che causa un'interruzione nella lingua con [with(...)]. Prima di questa funzionalità, significa un'espressione di raccolta con un elemento, che è il risultato della chiamata di with-invocation-expression. Dopo questa funzionalità, si tratta di una raccolta, che contiene argomenti passati.
Si vuole che questa interruzione si verifichi solo quando un utente sceglie una versione specifica della lingua (ad esempio C#-14/15?). In altre parole, se si trovano in un linguaggio precedente, ottengono la logica di analisi precedente, ma nella versione più recente ottengono la logica di analisi più recente. In O si vuole avere sempre la logica di analisi più recente, anche in un linguaggio meno recente?
Abbiamo un'arte precedente per entrambe le strategie.
required, ad esempio, viene sempre analizzato con la nuova logica, indipendentemente da langversion.
record/field Mentre e altri modificano la logica di analisi a seconda della versione del linguaggio.
Infine, questo ha sovrapposizione e impatto con Dictionary Expressions, che introduce la key:value sintassi per gli elementi KVP. Si vuole stabilire il comportamento che si vuole per qualsiasi versione lang e per [with(...)] per autonomamente e cose come [with(...) : expr] o [expr : with(...)].
C# feature specifications