Megosztás a következőn keresztül:


Gyűjteménykifejezések

Megjegyzés:

Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.

A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.

A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.

A bajnokkal kapcsolatos kérdés: https://github.com/dotnet/csharplang/issues/8652

Összefoglalás

A gyűjteménykifejezések új terse szintaxist vezetnek be a [e1, e2, e3, etc]gyakori gyűjteményértékek létrehozásához. Ha más gyűjteményeket szeretne ezekbe az értékekbe beszűkíteni, az alábbihoz hasonló oldalpárelem ..e használatával lehetséges: [e1, ..c2, e2, ..c2].

Több gyűjteményszerű típus is létrehozható külső BCL-támogatás nélkül. Ezek a típusok a következők:

A fentiekben nem tárgyalt gyűjteményszerű típusok esetében további támogatás érhető el egy új attribútumon és API-mintán keresztül, amely közvetlenül a típuson alkalmazható.

Motiváció

  • A gyűjteményszerű értékek rendkívül jelen vannak a programozásban, az algoritmusokban és különösen a C#/.NET-ökoszisztémában. Szinte minden program ezeket az értékeket fogja használni az adatok tárolására és más összetevőktől származó adatok küldésére vagy fogadására. Jelenleg szinte minden C#-programnak számos különböző és sajnos részletes módszert kell használnia az ilyen értékek példányainak létrehozásához. Egyes megközelítések teljesítménybeli hátrányokkal is rendelkeznek. Íme néhány gyakori példa:

    • Tömbök, amelyek az értékeket igénylik new Type[] vagy new[] előtte { ... } .
    • Spans, amely használható stackalloc , és egyéb nehézkes szerkezetek.
    • A gyűjtemény inicializálói, amelyek szintaxist new List<T> igényelnek (esetleg részletes Tkövetkeztetés hiányában) az értékük előtt, és amelyek több memóriaáthelyezést is okozhatnak, mert N-hívásokat .Add használnak anélkül, hogy kezdeti kapacitást adnának meg.
    • Nem módosítható gyűjtemények, amelyek szintaxist igényelnek, például ImmutableArray.Create(...) inicializálják az értékeket, és amelyek köztes lefoglalásokat és adatmásolást okozhatnak. A hatékonyabb építési formák (például ImmutableArray.CreateBuilder) nem örvénytelenek, és továbbra is elkerülhetetlen szemetet termelnek.
  • A környező ökoszisztémát tekintve mindenhol találunk példákat arra, hogy a lista létrehozása kényelmesebb és kellemesebb használatot biztosít. A TypeScript, a Dart, a Swift, az Elm, a Python és még sok más az erre a célra szolgáló tömör szintaxist választja, széles körű használat mellett, és nagy hatást gyakorol. A kurzoros vizsgálatok nem mutattak ki lényegi problémákat ezekben az ökoszisztémákban, mivel ezek a literálok beépítettek.

  • A C# listamintákat is hozzáadott a C# 11-ben. Ez a minta lehetővé teszi a listaszerű értékek egyezését és dekonstruálását egy tiszta és intuitív szintaxis használatával. A szinte minden más mintaszerkezettel ellentétben azonban ez a megfeleltetési/dekonstruálási szintaxis nem rendelkezik a megfelelő konstrukciós szintaxisokkal.

  • Az egyes gyűjteménytípusok létrehozásának legjobb teljesítménye nehézkes lehet. Az egyszerű megoldások gyakran pazarolják a processzort és a memóriát is. A literális űrlapokkal maximális rugalmasságot biztosíthat a fordító implementációja számára, hogy a literálist úgy optimalizálja, hogy legalább olyan jó eredményt hozson létre, mint amit a felhasználó adhatna, de egyszerű kóddal. Nagyon gyakran a fordító képes lesz jobban teljesíteni, és a specifikáció célja, hogy a megvalósítás nagy mennyiségű mozgásteret biztosítson a megvalósítási stratégia tekintetében ennek biztosítása érdekében.

A C#-hoz befogadó megoldásra van szükség. A gyűjteményszerű típusok és értékek tekintetében meg kell felelnie az ügyfelek számára a casse túlnyomó többségének. Természetesnek kell lennie a nyelvben is, és tükröznie kell a mintaegyezésben végzett munkát.

Ez azt a természetes következtetést eredményezi, hogy a szintaxisnak olyannak [e1, e2, e3, e-etc] kell lennie, mint vagy [e1, ..c2, e2], amely megfelel az és [p1, p2, p3, p-etc]a minta megfelelőinek[p1, ..p2, p3].

Részletes kialakítás

A rendszer a következő nyelvtani produkciókat adja hozzá:

primary_no_array_creation_expression
  ...
+ | collection_expression
  ;

+ collection_expression
  : '[' ']'
  | '[' collection_element ( ',' collection_element )* ']'
  ;

+ collection_element
  : expression_element
  | spread_element
  ;

+ expression_element
  : expression
  ;

+ spread_element
  : '..' expression
  ;

A gyűjteménykonstansok cél típusúak.

Pontosítások a specifikációkról

  • A rövidség collection_expression kedvéért a következő szakaszokban "literálnak" nevezzük.

  • expression_element példányokat gyakran nevezik e1, e_nstb.

  • spread_element példányokat gyakran nevezik ..s1, ..s_nstb.

  • span type means or Span<T>ReadOnlySpan<T>.

  • A literálok általában úgy jelennek meg, [e1, ..s1, e2, ..s2, etc] hogy tetszőleges számú elemet adjanak át tetszőleges sorrendben. Fontos, hogy ez az űrlap az összes olyan esetet ábrázolja, mint például:

    • Üres literálok []
    • Szókonstansok, bennük nincs expression_element .
    • Szókonstansok, bennük nincs spread_element .
    • Bármilyen elemtípus tetszőleges sorrendjét tartalmazó literálok.
  • Az iterációs típus..s_n az iterációs változó típusa, amely úgy van meghatározva, mintha s_n az iteráció alatt álló kifejezésként használták volna.foreach_statement

  • A kezdő __name változók az adott helyen tárolt kiértékelési nameeredmények megjelenítésére szolgálnak, így csak egyszer lesz kiértékelve. Például __e1 a kiértékelése e1.

  • List<T>, stb IEnumerable<T>. hivatkozzon a névtér megfelelő System.Collections.Generic típusaira.

  • A specifikáció meghatározza a literál fordítását a meglévő C#-szerkezetekre. A lekérdezési kifejezés fordításához hasonlóan maga a literál csak akkor legális, ha a fordítás jogi kódot eredményezne. Ennek a szabálynak a célja, hogy ne kelljen megismétleni a vélelmezett nyelv egyéb szabályait (például a kifejezések konvertálhatóságát a tárolóhelyekhez rendelve).

  • A literálok az alábbiak szerint történő fordításához nem szükséges implementáció. Minden fordítás akkor jogszerű, ha ugyanazt az eredményt állítják elő, és nincs megfigyelhető különbség az eredmény előállításában.

    • Egy implementáció például közvetlenül lefordíthatja a literálokat [1, 2, 3] egy new int[] { 1, 2, 3 } olyan kifejezésre, amely maga süsse be a nyers adatokat a szerelvénybe, és meghatározza az egyes értékek hozzárendeléséhez szükséges __index vagy utasítások sorozatát. Fontos, hogy ez azt jelenti, ha a fordítás bármely lépése kivételt okozhat futásidőben, hogy a program állapota továbbra is a fordítás által jelzett állapotban marad.
  • A "veremfoglalásra" való hivatkozások a veremen lefoglalandó összes stratégiára vonatkoznak, nem pedig a halomra. Fontos, hogy ez nem azt jelenti vagy követeli meg, hogy a stratégia a tényleges stackalloc mechanizmuson keresztül legyen. A beágyazott tömbök használata például engedélyezett és kívánatos módszer a veremfoglalás megvalósításához, ahol elérhető. Vegye figyelembe, hogy a C# 12-ben a beágyazott tömbök nem inicializálhatók gyűjteménykifejezéssel. Ez továbbra is nyílt javaslat marad.

  • A gyűjtemények megfelelően viselkednek. Például:

    • Feltételezzük, hogy egy gyűjtemény értéke Count ugyanazt az értéket fogja eredményezni, mint az elemek számának számbavételekor.
    • A névtérben definiált specifikációban System.Collections.Generic használt típusok vélelmezhetően mellékhatásmentesek. Így a fordító optimalizálhatja azokat a forgatókönyveket, amelyekben az ilyen típusok köztes értékekként használhatók, de máskülönben nem lesznek közzétéve.
    • Feltételezzük, hogy a gyűjtemény néhány alkalmazható .AddRange(x) tagjának hívása ugyanazt a végső értéket eredményezi, mint az iterálás x , és az összes számba vett érték egyenként hozzáadódik a gyűjteményhez .Add.
    • A nem megfelelően viselkedő gyűjtemények gyűjteménykonstansainak viselkedése nincs meghatározva.

Konverziók

A gyűjteménykifejezések konvertálása lehetővé teszi, hogy a gyűjteménykifejezések típussá alakuljanak.

Implicit gyűjteménykifejezés-átalakítás létezik egy gyűjteménykifejezésből a következő típusokra:

  • Egydimenziós tömbtípusT[], amely esetben az elem típusaT
  • Egy span típus:
    • System.Span<T>
    • System.ReadOnlySpan<T>
      Ebben az esetben az elem típusaT
  • Egy megfelelő létrehozási metódussal rendelkező típus, amely esetben az elemtípus egy példánymetódusból vagy számbavételi felületből meghatározott GetEnumerator, nem bővítménymetódusból
  • Olyan szerkezet vagy osztálytípus , amely az alábbiakat valósítja meg System.Collections.IEnumerable :
    • A típus rendelkezik egy alkalmazható konstruktorral, amely argumentumok nélkül hívható meg, és a konstruktor a gyűjteménykifejezés helyén érhető el.

    • Ha a gyűjteménykifejezésnek vannak elemei, a típusnak van egy példány- vagy bővítménymetódusa Add , ahol:

      • A metódus egyetlen érték argumentummal hívható meg.
      • Ha a metódus általános, a típusargumentumok a gyűjteményből és az argumentumból következtethetők.
      • A metódus a gyűjteménykifejezés helyén érhető el.

      Ebben az esetben az elem típusa a típusiterációs típusa.

  • Felület típusa:
    • System.Collections.Generic.IEnumerable<T>
    • System.Collections.Generic.IReadOnlyCollection<T>
    • System.Collections.Generic.IReadOnlyList<T>
    • System.Collections.Generic.ICollection<T>
    • System.Collections.Generic.IList<T>
      Ebben az esetben az elem típusaT

Az implicit konverzió akkor létezik, ha a típus olyan elemtípussalT rendelkezik, amely a gyűjteménykifejezés minden eleméhezEᵢ tartozik:

  • Ha Eᵢkifejezéselemről van szó, akkor implicit átalakítás történik a helyről Eᵢ a .-ra T.
  • Ha Eᵢspread elem..Sᵢ, akkor implicit átalakítás történik az iterációs típusbólSᵢ a következőre T: .

A gyűjteménykifejezések nem konvertálnak többdimenziós tömbtípusra gyűjteménykifejezést.

A gyűjteménykifejezés érvényes céltípusai azok a típusok, amelyek esetében implicit gyűjteménykifejezés-átalakítás történik egy gyűjteménykifejezésből.

A gyűjteménykifejezésből a következő további implicit konverziók léteznek:

  • Null értékű értéktípusraT?, ahol a gyűjteménykifejezés értéktípusra Tkonvertálása történik. Az átalakítás egy gyűjteménykifejezés konvertálása , T amelyet implicit null értékű konvertálásTT?követ.

  • Olyan referenciatípusraT, amelyhez egy T van társítva, amely egy típust U és egy implicit referenciakonverziótU ad vissza.T Az átalakítás egy gyűjteménykifejezés konvertálása , U amelyet implicit referenciakonvertálásUTkövet.

  • Olyan illesztőtípusraI, amelyhez egy létrehozási metódus van társítvaI, amely egy típust V és egy implicit boxing-konverziótV ad vissza.I Az átalakítás egy gyűjteménykifejezés konvertálása , V amelyet implicit boxing konverzióVIkövet.

Metódusok létrehozása

A létrehozási metódust a [CollectionBuilder(...)] egyik attribútuma jelzi. Az attribútum a gyűjteménytípus egy példányának létrehozásához meghívandó metódus szerkesztőtípusát és metódusnevét adja meg.

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(
        AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface,
        Inherited = false,
        AllowMultiple = false)]
    public sealed class CollectionBuilderAttribute : System.Attribute
    {
        public CollectionBuilderAttribute(Type builderType, string methodName);
        public Type BuilderType { get; }
        public string MethodName { get; }
    }
}

Az attribútum alkalmazható egy class, struct, ref structvagy interface. Az attribútum nem öröklődik, bár az attribútum alkalmazható egy alapra class vagy egy abstract class.

A szerkesztő típusának nem általános class vagy struct.

Először meg kell határozni az alkalmazandó létrehozási módszerekCM készletét.
Olyan módszerekből áll, amelyek megfelelnek a következő követelményeknek:

  • A metódusnak az attribútumban megadott névvel kell rendelkeznie [CollectionBuilder(...)] .
  • A metódust közvetlenül a szerkesztőtípuson kell definiálni.
  • A metódusnak a következőnek kell lennie static: .
  • A metódusnak elérhetőnek kell lennie a gyűjteménykifejezés használatakor.
  • A metódus aritásának meg kell egyeznie a gyűjteménytípus aritásával .
  • A metódusnak egyetlen, érték szerint átadott paraméterrel System.ReadOnlySpan<E>kell rendelkeznie.
  • Identitáskonverzió, implicit referenciakonvertálás vagy boxing átalakítás történik a metódus visszatérési típusától a gyűjteménytípusig.

Az alaptípusokon vagy interfészeken deklarált metódusok figyelmen kívül lesznek hagyva, és nem részei a CM készletnek.

Ha a CM készlet üres, akkor a gyűjtemény típusa nem rendelkezik elemtípussal , és nem rendelkezik létrehozási metódussal. A következő lépések egyike sem alkalmazható.

Ha a CM készletben lévők közül csak egy metódus rendelkezik identitásátalakítássalE a gyűjteménytípus elemtípusára, akkor ez a gyűjteménytípuslétrehozási metódusa. Ellenkező esetben a gyűjtemény típusa nem rendelkezik létrehozási metódussal.

Hiba jelenik meg, ha az [CollectionBuilder] attribútum nem hivatkozik invokable metódusra a várt aláírással.

Olyan céltípusú C<S0, S1, …> esetében, ahol a típusdeklarációhozC<T0, T1, …> társított szerkesztőmetódusB.M<U0, U1, …>() tartozik, a céltípus általános típusargumentumait a rendszer a legbelső típustól a legbelsőig alkalmazza a szerkesztő metódusra.

A létrehozási módszer span paramétere explicit módon megjelölhető scoped vagy [UnscopedRef]. Ha a paraméter implicit vagy explicit, scopeda fordító a halom helyett lefoglalhatja a tárterületet a veremen.

Például egy lehetséges létrehozási módszer a következőhöz ImmutableArray<T>:

[CollectionBuilder(typeof(ImmutableArray), "Create")]
public struct ImmutableArray<T> { ... }

public static class ImmutableArray
{
    public static ImmutableArray<T> Create<T>(ReadOnlySpan<T> items) { ... }
}

A fenti ImmutableArray<int> ia = [1, 2, 3]; a következő módon lehet kibocsátani:

[InlineArray(3)] struct __InlineArray3<T> { private T _element0; }

Span<int> __tmp = new __InlineArray3<int>();
__tmp[0] = 1;
__tmp[1] = 2;
__tmp[2] = 3;
ImmutableArray<int> ia =
    ImmutableArray.Create((ReadOnlySpan<int>)__tmp);

Építés

A gyűjteménykifejezés elemeinek kiértékelése sorrendben történik, balról jobbra. Minden elem kiértékelése pontosan egyszer történik, és az elemekre mutató további hivatkozások a kezdeti értékelés eredményeire vonatkoznak.

Az oldalpár elemet a gyűjteménykifejezés későbbi elemeinek kiértékelése előtt vagy után is elemezheti.

Az építés során használt módszerek egyikéből származó kezeletlen kivétel nem merül fel, és megakadályozza az építés további lépéseit.

Length, Countés GetEnumerator feltételezzük, hogy nincsenek mellékhatásai.


Ha a céltípus egy implementálandóstruktúra vagy System.Collections.IEnumerable, és a céltípus nem rendelkezik létrehozási módszerrel, a gyűjteménypéldány felépítése a következő:

  • Az elemek kiértékelése sorrendben történik. Néhány vagy az összes elem kiértékelhető az alábbi lépések során , nem pedig a korábbiakban.

  • A fordító úgy határozhatja meg a gyűjteménykifejezés ismert hosszát , hogy megszámlálható tulajdonságokat – vagy a jól ismert felületek vagy típusok egyenértékű tulajdonságait – invokál minden egyes oldalpárelem-kifejezésre.

  • Az argumentumok nélkül alkalmazható konstruktor meghívása.

  • Az egyes elemek sorrendje:

    • Ha az elem kifejezéselem, akkor az alkalmazható Add példány vagy bővítmény metódus argumentumként az elemkifejezéssel lesz meghívva. (A klasszikus gyűjtemény inicializáló viselkedésével ellentétben az elemek kiértékelése és Add a hívások nem feltétlenül kapcsolódnak egymáshoz.)
    • Ha az elem egy spread elem , akkor a rendszer az alábbiak egyikét használja:
      • A rendszer meghív egy alkalmazható GetEnumerator példányt vagy bővítménymetódust az oldalpárelem-kifejezésen , és az enumerátor minden egyes eleméhez meghívja az alkalmazható Add példányt vagy bővítménymetódust a gyűjteménypéldányon az elem argumentumaként. Ha az enumerátor implementálva IDisposablevan, akkor Dispose a rendszer a kivételektől függetlenül meghívja az enumerálás után.
      • Egy alkalmazható AddRange példányt vagy bővítménymetódust hív meg a gyűjteménypéldány , argumentumként pedig a spread elem kifejezést .
      • Egy alkalmazható CopyTo példányt vagy bővítménymetódust hív meg az oldalpárelem-kifejezés a gyűjteménypéldány és int az index argumentumaként.
  • A fenti építési lépések során egy alkalmazható EnsureCapacity példányt vagy bővítménymetódust egy vagy több alkalommal is meghívhat a gyűjteménypéldányon kapacitásargumentummal int .


Ha a céltípus tömb, span, létrehozási metódussal vagy felülettel rendelkező típus, a gyűjteménypéldány felépítése a következő:

  • Az elemek kiértékelése sorrendben történik. Néhány vagy az összes elem kiértékelhető az alábbi lépések során , nem pedig a korábbiakban.

  • A fordító úgy határozhatja meg a gyűjteménykifejezés ismert hosszát , hogy megszámlálható tulajdonságokat – vagy a jól ismert felületek vagy típusok egyenértékű tulajdonságait – invokál minden egyes oldalpárelem-kifejezésre.

  • Az inicializálási példány a következőképpen jön létre:

    • Ha a céltípus egy tömb , és a gyűjteménykifejezés ismert hosszúságú, a rendszer a tömböt a várt hosszúsággal foglalja le.
    • Ha a céltípus egy span vagy egy létrehozási metódussal rendelkező típus, és a gyűjtemény ismert hosszúságú, akkor a rendszer egy összefüggő tárolóra hivatkozó, várt hosszúságú spant hoz létre.
    • Ellenkező esetben a rendszer közbenső tárolót foglal le.
  • Az egyes elemek sorrendje:

    • Ha az elem kifejezéselem, az inicializálási példány indexelője meghívva hozzáadja a kiértékelt kifejezést az aktuális indexhez.
    • Ha az elem egy spread elem , akkor a rendszer az alábbiak egyikét használja:
      • A program meghív egy jól ismert felület vagy típus egy tagját, hogy elemeket másoljon az oldalpárelem-kifejezésből az inicializálási példányba.
      • A rendszer meghív egy alkalmazható GetEnumerator példányt vagy bővítménymetódust az oldalpárelem-kifejezésen , és az enumerátor minden egyes eleméhez meghívja az inicializálási példány indexelőt , hogy hozzáadja az elemet az aktuális indexhez. Ha az enumerátor implementálva IDisposablevan, akkor Dispose a rendszer a kivételektől függetlenül meghívja az enumerálás után.
      • Egy alkalmazható CopyTo példányt vagy bővítménymetódust hív meg az oldalpárelem-kifejezésen az inicializálási példány és int az index argumentumként.
  • Ha a gyűjteményhez közbenső tároló van lefoglalva, a rendszer a gyűjteménypéldányt a tényleges gyűjteményhosszsal foglalja le, és az inicializálási példány értékeit átmásolja a gyűjteménypéldányba, vagy ha szükség van rá, a fordító a köztes tárból származó tényleges gyűjteményhosszt használhatja . Ellenkező esetben az inicializálási példány a gyűjteménypéldány.

  • Ha a céltípus rendelkezik létrehozási metódussal, a rendszer meghívja a létrehozási metódust a span példánysal.


Jegyzet: A fordító késleltetheti az elemek gyűjteményhez való hozzáadását – vagy késleltetheti az oldalpárelemeken keresztüli iterálást – a további elemek kiértékelése után. (Ha a későbbi oldalpárelemek olyan megszámlálható tulajdonságokkal rendelkeznek, amelyek lehetővé tennék a gyűjtemény várható hosszának kiszámítását a gyűjtemény kiosztása előtt.) Ezzel szemben előfordulhat, hogy a fordító lelkesen ad hozzá elemeket a gyűjteményhez – és lelkesen iterál az oldalpár elemein keresztül –, ha nincs előnye a késleltetésnek.

Vegye figyelembe a következő gyűjteménykifejezést:

int[] x = [a, ..b, ..c, d];

Ha az elemek b el vannak osztva, és cmegszámlálhatók, a fordító késleltetheti az elemek hozzáadását a kiértékelés után és után abc , hogy az eredményül kapott tömböt a várt hosszon oszthassa ki. Ezután a fordító lelkesen hozzáadhat elemeket ca kiértékelés delőtt.

var __tmp1 = a;
var __tmp2 = b;
var __tmp3 = c;
var __result = new int[2 + __tmp2.Length + __tmp3.Length];
int __index = 0;
__result[__index++] = __tmp1;
foreach (var __i in __tmp2) __result[__index++] = __i;
foreach (var __i in __tmp3) __result[__index++] = __i;
__result[__index++] = d;
x = __result;

Üres gyűjteménykonstans

  • Az üres literálnak [] nincs típusa. A null-literálhoz hasonlóan azonban ez a literál implicit módon konvertálható bármilyen konstruálható gyűjteménytípusra.

    Az alábbiak például nem jogszerűek, mivel nincs céltípus , és nincs más konverzió:

    var v = []; // illegal
    
  • Az üres literál terjesztése engedélyezett. Például:

    bool b = ...
    List<int> l = [x, y, .. b ? [1, 2, 3] : []];
    

    Ha hamis, b akkor nincs szükség arra, hogy bármilyen érték ténylegesen létrejönjön az üres gyűjteménykifejezéshez, mivel az azonnal nulla értékre oszlik a végső literálban.

  • Az üres gyűjteménykifejezés egy adott mező lehet, ha egy olyan végső gyűjteményérték létrehozására szolgál, amelyről ismert, hogy nem módosítható. Például:

    // Can be a singleton, like Array.Empty<int>()
    int[] x = []; 
    
    // Can be a singleton. Allowed to use Array.Empty<int>(), Enumerable.Empty<int>(),
    // or any other implementation that can not be mutated.
    IEnumerable<int> y = [];
    
    // Must not be a singleton.  Value must be allowed to mutate, and should not mutate
    // other references elsewhere.
    List<int> z = [];
    

Ref biztonság

A biztonságos környezet értékeinek definícióihoz lásd: deklarációblokk, függvénytag és hívókörnyezet.

A gyűjteménykifejezések biztonságos környezete a következő:

  • Az üres gyűjteménykifejezések [] biztonságos környezete a hívókörnyezet.

  • Ha a céltípus span típusúSystem.ReadOnlySpan<T>, és T az egyik primitív típusbool, sbyte, , byte, short, ushortcharintuintlong, ulongfloatvagy double, és a gyűjteménykifejezés csak állandó értékeket tartalmaz, a gyűjteménykifejezés biztonságos környezete a hívó-környezet.

  • Ha a céltípus egy span típusSystem.Span<T> , vagy System.ReadOnlySpan<T>ha a gyűjteménykifejezés biztonságos környezete a deklarációblokk.

  • Ha a céltípus egy létrehozási metódussal rendelkező refstruktúratípus, a gyűjteménykifejezés biztonságos környezete a létrehozási metódus meghívásának biztonságos környezete, ahol a gyűjteménykifejezés a metódus span argumentuma.

  • Ellenkező esetben a gyűjteménykifejezés biztonságos környezete a hívókörnyezet.

A deklarálási blokkok biztonságos környezetével rendelkező gyűjteménykifejezések nem kerülhetik el a beágyazási hatókört, és a fordító a gyűjteményt a halom helyett a veremben tárolhatja .

Ha lehetővé szeretné tenni, hogy egy ref struktúratípus gyűjteménykifejezése elkerülje a deklarálási blokkot, szükség lehet arra, hogy a kifejezést egy másik típusba helyezze.

static ReadOnlySpan<int> AsSpanConstants()
{
    return [1, 2, 3]; // ok: span refers to assembly data section
}

static ReadOnlySpan<T> AsSpan2<T>(T x, T y)
{
    return [x, y];    // error: span may refer to stack data
}

static ReadOnlySpan<T> AsSpan3<T>(T x, T y, T z)
{
    return (T[])[x, y, z]; // ok: span refers to T[] on heap
}

Típuskövetkeztetés

var a = AsArray([1, 2, 3]);          // AsArray<int>(int[])
var b = AsListOfArray([[4, 5], []]); // AsListOfArray<int>(List<int[]>)

static T[] AsArray<T>(T[] arg) => arg;
static List<T[]> AsListOfArray<T>(List<T[]> arg) => arg;

A típuskövetkeztetési szabályok az alábbiak szerint frissülnek.

Az első fázis meglévő szabályai egy új bemeneti típusú következtetési szakaszba lesznek kinyerve, és a rendszer hozzáad egy szabályt a bemeneti típusú következtetéshez és a kimeneti típus következtetéséhez a gyűjteménykifejezés-kifejezések esetében.

11.6.3.2 Az első fázis

Az egyes metódusargumentumok Eᵢesetében:

  • A bemeneti típus következtetésea megfelelőEᵢszármazikTᵢ.

A bemeneti típus következtetése egy kifejezésbőlE egy típusbaT a következő módon történik:

  • Ha E egy elemekkelEᵢrendelkező gyűjteménykifejezés, és TelemtípusúTₑ vagy Tnull értékűT0? típus, és T0elemtípussalTₑ rendelkezik, akkor mindegyikhez Eᵢ:
  • [meglévő szabályok az első fázisból] ...

11.6.3.7 Kimenettípus-következtetések

A kimeneti típus következtetése egy kifejezésbőlE egy típusbaT a következő módon történik:

  • Ha E egy elemekkelEᵢrendelkező gyűjteménykifejezés, és TelemtípusúTₑ vagy Tnull értékűT0? típus, és T0elemtípussalTₑ rendelkezik, akkor mindegyikhez Eᵢ:
    • Ha Eᵢkifejezéselem, akkor a kimeneti típus következtetése a következőbőlEᵢszármazikTₑ.
    • Ha Eᵢspread elem, akkor a függvény nem következtet a következőből Eᵢ: .
  • [a kimeneti típusból származó meglévő szabályok] ...

Bővítménymetelyek

A bővítménymetódus meghívási szabályai nem módosulnak .

12.8.10.3 Bővítménymetódus-meghívások

A bővítménymetódus Cᵢ.Mₑakkor jogosult , ha:

  • ...
  • Implicit identitás-, hivatkozás- vagy boxkonvertálás létezik az expr függvénytől a paraméter első paraméterének típusához Mₑ.

A gyűjteménykifejezések nem rendelkeznek természetes típussal, ezért a típusból való meglévő átalakítások nem alkalmazhatók. Ennek eredményeképpen a gyűjteménykifejezés nem használható közvetlenül a bővítménymetódus meghívásának első paramétereként.

static class Extensions
{
    public static ImmutableArray<T> AsImmutableArray<T>(this ImmutableArray<T> arg) => arg;
}

var x = [1].AsImmutableArray();           // error: collection expression has no target type
var y = [2].AsImmutableArray<int>();      // error: ...
var z = Extensions.AsImmutableArray([3]); // ok

Túlterhelés feloldás

A kifejezésből való jobb átalakítás úgy frissül, hogy bizonyos céltípusokat előnyben részesítsen a gyűjteménykifejezés-konverziókban.

A frissített szabályokban:

  • A span_type az alábbiak egyike:
    • System.Span<T>
    • System.ReadOnlySpan<T>.
  • A array_or_array_interface az alábbiak egyike:
    • tömbtípus
    • a tömbtípus által implementált alábbi felülettípusok egyike:
      • System.Collections.Generic.IEnumerable<T>
      • System.Collections.Generic.IReadOnlyCollection<T>
      • System.Collections.Generic.IReadOnlyList<T>
      • System.Collections.Generic.ICollection<T>
      • System.Collections.Generic.IList<T>

Egy kifejezést C₁ típussá E konvertáló implicit átalakítás, valamint egy kifejezést T₁ típussá C₂ konvertáló implicit átalakítás esetén, E egy T₂, mint C₁, ha az alábbiak valamelyike fennáll.

  • Egyűjteménykifejezés, és az alábbiak egyike tartalmazza:
    • T₁az System.ReadOnlySpan<E₁>, és T₂ vanSystem.Span<E₂>, és implicit konverzió létezik a E₁E₂
    • T₁elemtípussal System.ReadOnlySpan<E₁>System.Span<E₁>T₂rendelkező array_or_array_interface, és implicit konverzió létezik a E₂E₁E₂
    • T₁nem span_type, és T₂ nem span_type, és implicit konverzió létezik a T₁T₂
  • E nem gyűjteménykifejezés , és az alábbiak egyikét tartalmazza:
    • E pontosan egyezik T₁ , és E nem pontosan egyezik T₂
    • Epontosan megfelel mindkettőnek vagy egyiknekT₁, és T₂T₁jobb konverziós cél, mintT₂
  • E egy metóduscsoport, ...

Példák a tömb inicializálói és a gyűjteménykifejezések túlterhelési felbontásával kapcsolatos különbségekre:

static void Generic<T>(Span<T> value) { }
static void Generic<T>(T[] value) { }

static void SpanDerived(Span<string> value) { }
static void SpanDerived(object[] value) { }

static void ArrayDerived(Span<object> value) { }
static void ArrayDerived(string[] value) { }

// Array initializers
Generic(new[] { "" });      // string[]
SpanDerived(new[] { "" });  // ambiguous
ArrayDerived(new[] { "" }); // string[]

// Collection expressions
Generic([""]);              // Span<string>
SpanDerived([""]);          // Span<string>
ArrayDerived([""]);         // ambiguous

Span típusok

A span típusok ReadOnlySpan<T> és Span<T> mindkettő konstrukciós gyűjteménytípusok. A támogatásuk a következőhöz tartozó tervet params Span<T>követi: . Ha a paramétertömb a fordító által beállított korlátokon belül van (ha van ilyen), akkor a veremen létrejön egy T[] tömb. Ellenkező esetben a tömb le lesz foglalva a halomra.

Ha a fordító úgy dönt, hogy lefoglalja a vermet, akkor nem szükséges közvetlenül egy adott pontra lefordítani a stackalloc literálokat. Például:

foreach (var x in y)
{
    Span<int> span = [a, b, c];
    // do things with span
}

A fordító ezt a fordítást mindaddig használhatja, stackalloc amíg a Span jelentés nem változik, és a span-safety megmarad. Lefordíthatja például a fentieket a következőre:

Span<int> __buffer = stackalloc int[3];
foreach (var x in y)
{
    __buffer[0] = a
    __buffer[1] = b
    __buffer[2] = c;
    Span<int> span = __buffer;
    // do things with span
}

A fordító beágyazott tömböket is használhat, ha van ilyen, amikor a veremen való lefoglalást választja. Vegye figyelembe, hogy a C# 12-ben a beágyazott tömbök nem inicializálhatók gyűjteménykifejezéssel. Ez a funkció egy nyílt javaslat.

Ha a fordító úgy dönt, hogy lefoglalja a halomra, a fordítás Span<T> egyszerűen a következő:

T[] __array = [...]; // using existing rules
Span<T> __result = __array;

Gyűjtemény literális fordítása

A gyűjteménykifejezések ismert hosszúságúak , ha a gyűjteménykifejezés egyes oldalpárelemeinek fordítási ideje megszámlálható.

Felületi fordítás

Nem módosítható felületi fordítás

Mivel a céltípus nem tartalmaz mutációs tagokat, nevezetesen IEnumerable<T>IReadOnlyCollection<T>, és IReadOnlyList<T>megfelelő implementációra van szükség ahhoz, hogy olyan értéket állítson elő, amely megvalósítja ezt a felületet. Ha egy típus szintetizálva van, javasoljuk, hogy a szintetizált típus implementálja ezeket az interfészeket, valamint ICollection<T>IList<T>, függetlenül attól, hogy melyik felülettípust célozta meg. Ez biztosítja a maximális kompatibilitást a meglévő kódtárakkal, beleértve azokat is, amelyek az érték által implementált felületeket introspektálják a teljesítményoptimalizálás érdekében.

Emellett az értéknek implementálnia kell a nemgenerikus ICollection és IList interfészeket is. Ez lehetővé teszi, hogy a gyűjteménykifejezések támogatják a dinamikus bevezető szakaszokat olyan helyzetekben, mint az adatkötés.

A megfelelő implementációk szabadon:

  1. Használjon egy meglévő típust, amely implementálja a szükséges interfészeket.
  2. Szintetizáljon egy típust, amely megvalósítja a szükséges interfészeket.

Mindkét esetben a használt típus lehetővé teszi a szigorúan szükségesnél nagyobb interfészkészlet implementálását.

A szintetizált típusok szabadon alkalmazhatnak minden olyan stratégiát, amelyet a szükséges interfészek megfelelő implementálásához szeretnének használni. Előfordulhat például, hogy egy szintetizált típus közvetlenül magában foglalja az elemeket, elkerülve a további belső gyűjteményfoglalások szükségességét. A szintetizált típus semmilyen tárterületet sem használhat, így közvetlenül számítja ki az értékeket. Például: visszatérés index + 1 a következőhöz [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]: .

  1. Az értéknek akkor kell visszaadnia true , ha lekérdezi ICollection<T>.IsReadOnly (ha van) és nemgenerikus IList.IsReadOnly és IList.IsFixedSize. Ez biztosítja, hogy a felhasználók megfelelően jelezhetik, hogy a gyűjtemény nem mutable, annak ellenére, hogy implementálja a mutable nézeteket.
  2. Az értéknek egy mutációs metódusra (például IList<T>.Add) irányuló hívásra kell dobnia. Ez biztosítja a biztonságot, megakadályozva a nem mutálható gyűjtemény véletlen mutálását.

Mutable felület fordítása

Adott céltípus, amely mutációs tagokat tartalmaz, nevezetesen ICollection<T>IList<T>:

  1. Az értéknek a következő példánynak List<T>kell lennie: .

Ismert hosszfordítás

Az ismert hossz lehetővé teszi az eredmények hatékony felépítését, így nincs lehetőség adatok másolására, és nincs szükség felesleges tartalékidőre az eredményben.

Ha nem rendelkezik ismert hosszsal , az nem akadályozza meg, hogy bármilyen eredmény létrejönjön. Ez azonban további processzor- és memóriaköltségeket eredményezhet az adatok előállításához, majd a végső célhelyre való áthelyezéshez.

  • Ismert hosszkonstans [e1, ..s1, etc]esetén a fordítás először a következővel kezdődik:

    int __len = count_of_expression_elements +
                __s1.Count;
                ...
                __s_n.Count;
    
  • Adott egy céltípust T ehhez a literálhoz:

    • Ha T néhány T1[], akkor a literál a következőképpen lesz lefordítva:

      T1[] __result = new T1[__len];
      int __index = 0;
      
      __result[__index++] = __e1;
      foreach (T1 __t in __s1)
          __result[__index++] = __t;
      
      // further assignments of the remaining elements
      

      Az implementáció más eszközöket is használhat a tömb feltöltéséhez. Például hatékony tömeges másolási módszereket használ, például .CopyTo().

    • Ha T néhány Span<T1>, akkor a literál ugyanúgy lesz lefordítva, mint a fenti, kivéve, hogy az inicializálás a __result következőképpen lesz lefordítva:

      Span<T1> __result = new T1[__len];
      
      // same assignments as the array translation
      

      A fordítás a stackalloc T1[]span-safety fenntartása helyett new T1[] használhat beágyazott tömböt vagy beágyazott tömböt.

    • Ha T néhányReadOnlySpan<T1>, akkor a literál ugyanúgy lesz lefordítva, mint az Span<T1> eset esetében, azzal a kivételrel, hogy Span<T1> a végeredmény implicit módon konvertálva lesz .ReadOnlySpan<T1>

      A ReadOnlySpan<T1> hol T1 valamilyen primitív típus, és az összes gyűjteményelem állandó, nincs szükség az adataira a halomon vagy a veremen. Egy implementáció például közvetlenül a program adatszegmensére mutató hivatkozásként hozhatja létre ezt a skálát.

      A fenti űrlapok (tömbök és spanok esetén) a gyűjteménykifejezés alapábrázolásai, és a következő fordítási szabályokhoz használhatók:

      • Ha T van olyan, C<S0, S1, …> amelynek van egy megfelelő létrehozási metódusaB.M<U0, U1, …>(), akkor a literál a következőképpen lesz lefordítva:

        // Collection literal is passed as is as the single B.M<...>(...) argument
        C<S0, S1, …> __result = B.M<S0, S1, …>([...])
        

        Mivel a létrehozási metódusnak valamilyen példányosított ReadOnlySpan<T>argumentumtípussal kell rendelkeznie, a hatókörök fordítási szabálya akkor érvényes, amikor a gyűjteménykifejezést átadja a létrehozási metódusnak.

      • Ha T támogatja a gyűjtemény inicializálóit, akkor:

        • ha a típus T egyetlen paraméterrel int capacityrendelkező akadálymentes konstruktort tartalmaz, akkor a literál a következőképpen lesz lefordítva:

          T __result = new T(capacity: __len);
          __result.Add(__e1);
          foreach (var __t in __s1)
              __result.Add(__t);
          
          // further additions of the remaining elements
          

          Megjegyzés: a paraméter nevének meg kell lennie capacity.

          Ez az űrlap lehetővé teszi, hogy a konstans tájékoztassa az újonnan létrehozott típust az elemek számáról, hogy lehetővé tegye a belső tároló hatékony lefoglalását. Ez elkerüli az elemek hozzáadásakor a pazarló újraelosztásokat.

        • ellenkező esetben a literál a következőképpen lesz lefordítva:

          T __result = new T();
          
          __result.Add(__e1);
          foreach (var __t in __s1)
              __result.Add(__t);
          
          // further additions of the remaining elements
          

          Ez lehetővé teszi a céltípus létrehozását, bár kapacitásoptimalizálás nélkül, hogy megakadályozza a tároló belső újraelosztását.

Ismeretlen hosszfordítás

  • Egy T hosszkonstans céltípusának megadása:

    • Ha T támogatja a gyűjtemény inicializálóit, akkor a literál a következőképpen lesz lefordítva:

      T __result = new T();
      
      __result.Add(__e1);
      foreach (var __t in __s1)
          __result.Add(__t);
      
      // further additions of the remaining elements
      

      Ez lehetővé teszi bármilyen iterálási típus terjesztését, bár a lehető legkevesebb optimalizálással.

    • Ha T néhány T1[], akkor a literál ugyanazzal a szemantikával rendelkezik, mint:

      List<T1> __list = [...]; /* initialized using predefined rules */
      T1[] __result = __list.ToArray();
      

      A fentiek azonban nem hatékonyak; létrehozza a köztes listát, majd létrehoz egy másolatot a végső tömbről. Az implementációk ezt szabadon optimalizálhatja, például a következő kódokat készítik el:

      T1[] __result = <private_details>.CreateArray<T1>(
          count_of_expression_elements);
      int __index = 0;
      
      <private_details>.Add(ref __result, __index++, __e1);
      foreach (var __t in __s1)
          <private_details>.Add(ref __result, __index++, __t);
      
      // further additions of the remaining elements
      
      <private_details>.Resize(ref __result, __index);
      

      Ez minimális pazarlást és másolást tesz lehetővé, anélkül, hogy a könyvtárgyűjtemények többletterhelést jelenthetnek.

      A megadott CreateArray számok kezdőméret-tippet adnak a pazarló átméretezések megelőzéséhez.

    • Ha T valamilyen span típusú, a megvalósítás a fenti T[] stratégiát követheti, vagy bármely más, azonos szemantikával rendelkező stratégiát, de jobb teljesítményt. Ha például a tömböt a listaelemek másolataként szeretné kiosztani, CollectionsMarshal.AsSpan(__list) akkor közvetlenül is beolvasható egy skálázási érték.

Nem támogatott forgatókönyvek

Bár a gyűjteménykonstansok számos forgatókönyvhöz használhatók, van néhány, amely nem képes lecserélni őket. Ezek a következők:

  • Többdimenziós tömbök (pl. new int[5, 10] { ... }). Nincs lehetőség a dimenziók belefoglalására, és az összes gyűjteménykonstans csak lineáris vagy térképszerkezet.
  • Gyűjtemények, amelyek különleges értékeket adnak át a konstruktoroknak. Nincs lehetőség a használt konstruktor elérésére.
  • Beágyazott gyűjtemény inicializálói, például. new Widget { Children = { w1, w2, w3 } } Ennek az űrlapnak maradnia kell, mivel nagyon eltérő szemantikája van.Children = [w1, w2, w3] Az előbbi ismételten hív, .Add.Children míg az utóbbi egy új gyűjteményt rendel hozzá .Children. Fontolóra vehetjük, hogy az utóbbi űrlap visszaáll egy meglévő gyűjteményhez való hozzáadásra, ha .Children nem rendelhető hozzá, de úgy tűnik, hogy ez rendkívül zavaró lehet.

Szintaxisbeli kétértelműségek

  • Két "igaz" szintaktikai kétértelműség létezik, amelyekben a kód több jogi szintaktikai értelmezése is létezik collection_literal_expression.

    • A spread_element nem egyértelmű egy range_expression. Az egyik technikailag a következő lehet:

      Range[] ranges = [range1, ..e, range2];
      

      A probléma megoldásához a következőkre van lehetőség:

      • Ha tartományt szeretne, a felhasználóknak zárójelet (..e) kell írniuk, vagy tartalmazniuk kell egy kezdőindexet 0..e .
      • Válasszon egy másik szintaxist (például ...) az oldalpárhoz. Ez nem lenne szerencsés a szeletmintákkal való konzisztencia hiánya miatt.
  • Két olyan eset van, ahol nincs valódi kétértelműség, de a szintaxis nagyban növeli az elemzés összetettségét. Bár a mérnöki idő nem jelent problémát, ez még mindig növeli a felhasználók kognitív többletterhelését a kód megvizsgálásakor.

    • Kétértelműség az utasítások és a helyi függvények között és collection_literal_expression azok közöttattributes. Tekint:

      [X(), Y, Z()]
      

      Ez a következő lehet:

      // A list literal inside some expression statement
      [X(), Y, Z()].ForEach(() => ...);
      
      // The attributes for a statement or local function
      [X(), Y, Z()] void LocalFunc() { }
      

      Összetett lookahead nélkül lehetetlen lenne megmondani anélkül, hogy a literál teljes egészét használná.

      A következő megoldási lehetőségek közül választhat:

      • Engedélyezze ezt, és az elemzési munka segítségével állapítsa meg, hogy ezek közül melyikről van szó.
      • Tiltsa le ezt, és követelje meg, hogy a felhasználó zárójelben csomagolja be a literálist, például ([X(), Y, Z()]).ForEach(...).
      • Kétértelműség az a collection_literal_expression és conditional_expression a null_conditional_operationsközött. Tekint:
      M(x ? [a, b, c]
      

      Ez a következő lehet:

      // A ternary conditional picking between two collections
      M(x ? [a, b, c] : [d, e, f]);
      
      // A null conditional safely indexing into 'x':
      M(x ? [a, b, c]);
      

      Összetett lookahead nélkül lehetetlen lenne megmondani anélkül, hogy a literál teljes egészét használná.

      Megjegyzés: ez a probléma természetes típus nélkül is problémát jelent, mert a cél beírása a műveleten keresztül conditional_expressionstörténik.

      A többihez hasonlóan zárójeleket is megkövetelhetünk, hogy egyértelműsítsük. Más szóval, feltételezzük az értelmezést null_conditional_operation , hacsak nem így írunk: x ? ([1, 2, 3]) :. Ez azonban meglehetősen szerencsétlennek tűnik. Ez a kód nem tűnik ésszerűtlennek az íráshoz, és valószínűleg fel fogja ránni az embereket.

Hátránya

  • Ez egy újabb űrlapot vezet be a gyűjteménykifejezésekhez a már meglévő számtalan módszeren felül. Ez a nyelv további összetettsége. Ez azt is lehetővé teszi, hogy egyesítsék az egyik gyűrű szintaxisát, hogy mindegyiket szabályozzák, ami azt jelenti, hogy a meglévő kódbázisok egyszerűsíthetők, és mindenhol egységes megjelenésre helyezhetők át.
  • A ...[ használata ]... helyett{}eltávolodik a tömbökhöz és gyűjtemény inicializálókhoz általánosan használt szintaxistól. Pontosabban... ahelyett[, hogy ...]{-t használ}. Ezt azonban már megoldotta a nyelvi csapat, amikor a mintákat listáztuk. Megpróbáltuk{}... használni a listamintákat, és megoldhatatlan problémákba ütköztünk. Emiatt átköltöztünk [...] amely bár új A C# úgy érzi, természetes számos programozási nyelven, és lehetővé tette számunkra, hogy kezdjen új, nem kétértelmű. Használata [...] mint a megfelelő literális forma kiegészíti a legújabb döntéseket, és ad nekünk egy tiszta hely a munka probléma nélkül.

Ez szemöldököket vezet be a nyelvbe. A következők például jogiak, és (szerencsére) pontosan ugyanazt jelentik:

int[] x = { 1, 2, 3 };
int[] x = [ 1, 2, 3 ];

Az új konstans szintaxis által okozott szélességet és konzisztenciát figyelembe véve azonban azt javasoljuk, hogy az emberek lépjenek az új űrlapra. Az IDE-javaslatok és javítások segíthetnek ebben a tekintetben.

Alternatives

  • Milyen más terveket is figyelembe vettünk? Milyen hatása van annak, ha ezt nem teszi meg?

Megoldott kérdések

  • A fordítónak akkor kell használnia stackalloc a veremfoglalást, ha a beágyazott tömbök nem érhetők el, és az iteráció típusa egy primitív típus?

    Megoldás: Nem. A stackalloc puffer kezelése további erőfeszítést igényel egy beágyazott tömbön , hogy a rendszer ne foglalja le többször a puffert, ha a gyűjteménykifejezés cikluson belül van. A fordító és a generált kód további összetettsége meghaladja a veremfoglalás előnyeit a régebbi platformokon.

  • Milyen sorrendben értékeljük ki a literális elemeket a Hossz/Darab tulajdonság kiértékeléséhez képest? Először az összes elemet értékeljük ki, majd az összes hosszt? Vagy értékeljünk ki egy elemet, majd annak hosszát, majd a következő elemet, és így tovább?

    Megoldás: Először az összes elemet kiértékeljük, majd minden más ezt követi.

  • Létrehozhat egy ismeretlen hosszkonstans egy ismert hosszúságú gyűjteménytípust, például tömböt, spant vagy Construct(tömb/span) gyűjteményt? Ezt nehezebb lenne hatékonyan elvégezni, de ez a készletezett tömbök és/vagy építők okos használatával lehetséges.

    Megoldás: Igen, engedélyezzük egy fixes-length gyűjtemény létrehozását egy ismeretlen hosszúságú literálból. A fordító ezt a lehető leghatékonyabb módon implementálhatja.

    A témakör eredeti vitafórumának rögzítéséhez az alábbi szöveg található.

    A felhasználók mindig egy ismeretlen hosszkonstanst hozhatnak létre egy ismert hosszúságúra az alábbi kóddal:

    ImmutableArray<int> x = [a, ..unknownLength.ToArray(), b];
    

    Ez azonban nem szerencsés, mert kényszeríteni kell az ideiglenes tárolás lefoglalását. Hatékonyabbak is lehetnénk, ha szabályoznánk a kibocsátás módját.

  • Lehet céltípust collection_expression adni egy IEnumerable<T> vagy más gyűjteményfelületnek?

    Például:

    void DoWork(IEnumerable<long> values) { ... }
    // Needs to produce `longs` not `ints` for this to work.
    DoWork([1, 2, 3]);
    

    Megoldás: Igen, a konstansok bármilyen implementált felülettípusra I<T>List<T> cél típusúak lehetnek. Például: IEnumerable<long>. Ez ugyanaz, mint a célgépelés, List<long> majd az eredmény hozzárendelése a megadott felülettípushoz. A témakör eredeti vitafórumának rögzítéséhez az alábbi szöveg található.

    A nyitott kérdés itt határozza meg, hogy milyen mögöttes típust kell ténylegesen létrehozni. Az egyik lehetőség, hogy megtekintse a javaslatot.params IEnumerable<T> Itt létrehozunk egy tömböt, amely átadja az értékeket, hasonlóan ahhoz, ami történik.params T[]

  • Kibocsáthatja/kibocsáthatja Array.Empty<T>() a fordítót []? Meg kell-e köteleznünk, hogy ezt tegye, hogy lehetőség szerint elkerüljük a kiosztásokat?

    Igen. A fordítónak minden olyan esetben ki kell bocsátania Array.Empty<T>() azokat az eseteket, amikor ez jogi jellegű, és a végeredmény nem módosítható. Például célzásT[], IEnumerable<T>IReadOnlyCollection<T> vagy IReadOnlyList<T>. Nem használható Array.Empty<T> , ha a cél mutable (ICollection<T> vagy IList<T>).

  • Bővítsük a gyűjtemény inicializálóit, hogy megkeressük a nagyon gyakori AddRange módszert? A mögöttes, létrehozott típus használhatja az oldalpárelemek hatékonyabb hozzáadásához. Azt is érdemes keresni a dolgokat, mint .CopyTo is. Itt lehetnek hátrányok, mivel ezek a metódusok a lefordított kódban való közvetlen számbavétel helyett többletfoglalásokat/diszpécsereket okozhatnak.

    Igen. A implementációk más módszereket is használhatnak a gyűjteményértékek inicializálásához, azzal a feltételezéssel, hogy ezek a metódusok jól definiált szemantikával rendelkeznek, és hogy a gyűjteménytípusoknak "jól kell viselkedniük". A gyakorlatban azonban a megvalósításnak óvatosnak kell lennie, mivel az egyirányú előnyök (tömeges másolás) negatív következményekkel is járhatnak (például egy szerkezetgyűjtemény dobozolása).

    A megvalósításnak kihasználnia kell azokat az eseteket, amikor nincsenek hátrányai. Például egy .AddRange(ReadOnlySpan<T>) metódussal.

Megoldatlan kérdések

  • Engedélyeznünk kell az elemtípus következtetését, ha az iterációs típus "nem egyértelmű" (bizonyos definíció szerint)? Például:
Collection x = [1L, 2L];

// error CS1640: foreach statement cannot operate on variables of type 'Collection' because it implements multiple instantiations of 'IEnumerable<T>'; try casting to a specific interface instantiation
foreach (var x in new Collection) { }

static class Builder
{
    public Collection Create(ReadOnlySpan<long> items) => throw null;
}

[CollectionBuilder(...)]
class Collection : IEnumerable<int>, IEnumerable<string>
{
    IEnumerator<int> IEnumerable<int>.GetEnumerator() => throw null;
    IEnumerator<string> IEnumerable<string>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}
  • Legálisnak kell lennie egy gyűjteménykonstans létrehozására és azonnali indexelésére? Megjegyzés: ehhez választ kell adnia az alábbi megoldatlan kérdésre, hogy a gyűjteménykonstansok természetes típusúak-e.

  • A hatalmas gyűjtemények veremfoglalásai felrobbanthatják a vermet. A fordítónak heurisztikusnak kell lennie ahhoz, hogy ezeket az adatokat a halomra helyezze? Meg kell-e adni a nyelvet, hogy lehetővé tegye ezt a rugalmasságot? Követnünk kell a specifikációt a következőhöz params Span<T>: .

  • Meg kell céloznunk a típust spread_element? Fontolja meg például a következőt:

    Span<int> span = [a, ..b ? [c] : [d, e], f];
    

    Megjegyzés: ez általában a következő formában jelenhet meg, hogy lehetővé tegye bizonyos elemek feltételes felvételét, vagy semmi, ha a feltétel hamis:

    Span<int> span = [a, ..b ? [c, d, e] : [], f];
    

    A teljes literál kiértékeléséhez ki kell értékelnünk a benne lévő elemkifejezéseket. Ez azt jelenti , hogy képes kiértékelni b ? [c] : [d, e]. Ha azonban hiányzik egy céltípus, amely ezt a kifejezést a természetes típus kontextusában értékelné ki, és nem lenne képes meghatározni, hogy mi a teendő bármelyikkel [c] , vagy [d, e] itt.

    A probléma megoldásához azt mondhatjuk, hogy a literál spread_element kifejezésének kiértékelésekor egy implicit céltípus felelt meg magának a literálnak a céltípusával. A fentiekben tehát a következőt kell újraírni:

    int __e1 = a;
    Span<int> __s1 = b ? [c] : [d, e];
    int __e2 = f;
    
    Span<int> __result = stackalloc int[2 + __s1.Length];
    int __index = 0;
    
    __result[__index++] = a;
    foreach (int __t in __s1)
      __result[index++] = __t;
    __result[__index++] = f;
    
    Span<int> span = __result;
    

A létrehozási metódust használó konstruktív gyűjteménytípus specifikációja érzékeny az átalakítás besorolási környezetére

Az átalakítás megléte ebben az esetben a gyűjteménytípus iterációs típusának fogalmától függ. Ha létezik olyan létrehozási módszer, amely az ReadOnlySpan<T> veszi igénybe T, az átalakítás létezik. Ellenkező esetben nem.

Az iterációs típus azonban érzékeny a környezetre, amelyen foreach a végrehajtás történik. Ugyanahhoz a gyűjteménytípushoz eltérő lehet attól függően, hogy milyen bővítménymetelyek vannak a hatókörben, és meghatározatlan is lehet.

Ez úgy érzi, jó, foreach ha a típus nem úgy van kialakítva, hogy foreach-able önmagában. Ha igen, a bővítménymetelyek nem módosíthatják a típus foreach-átvételét, függetlenül attól, hogy milyen a környezet.

Ez azonban kissé furcsának tűnik ahhoz, hogy az átalakítás környezetérzékeny legyen. Az átalakítás gyakorlatilag "instabil". A kifejezetten konstruktálásra tervezett gyűjteménytípusok elhagyhatják egy nagyon fontos részlet definícióját – annak iterációs típusát. A típus "unconvertible" marad önmagában.

Íme egy példa:

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))]
class MyCollection
{
}
class MyCollectionBuilder
{
    public static MyCollection Create(ReadOnlySpan<long> items) => throw null;
    public static MyCollection Create(ReadOnlySpan<string> items) => throw null;
}

namespace Ns1
{
    static class Ext
    {
        public static IEnumerator<long> GetEnumerator(this MyCollection x) => throw null;
    }
    
    class Program
    {
        static void Main()
        {
            foreach (var l in new MyCollection())
            {
                long s = l;
            }
        
            MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
                               2];
        }
    }
}

namespace Ns2
{
    static class Ext
    {
        public static IEnumerator<string> GetEnumerator(this MyCollection x) => throw null;
    }
    
    class Program
    {
        static void Main()
        {
            foreach (var l in new MyCollection())
            {
                string s = l;
            }
        
            MyCollection x1 = ["a",
                               2]; // error CS0029: Cannot implicitly convert type 'int' to 'string'
        }
    }
}

namespace Ns3
{
    class Program
    {
        static void Main()
        {
            // error CS1579: foreach statement cannot operate on variables of type 'MyCollection' because 'MyCollection' does not contain a public instance or extension definition for 'GetEnumerator'
            foreach (var l in new MyCollection())
            {
            }
        
            MyCollection x1 = ["a", 2]; // error CS9188: 'MyCollection' has a CollectionBuilderAttribute but no element type.
        }
    }
}

Az aktuális kialakítás miatt, ha a típus nem határozza meg magát az iterációs típust , a fordító nem tudja megbízhatóan ellenőrizni egy CollectionBuilder attribútum alkalmazását. Ha nem tudjuk az iterációs típust, nem tudjuk, mi legyen a létrehozási módszer aláírása. Ha az iterációs típus környezetből származik, nincs garancia arra, hogy a típus mindig hasonló környezetben lesz használva.

A Params Collections funkcióra is hatással van ez. Furcsa érzés, hogy nem lehet megbízhatóan előrejelezni egy params paraméter elemtípusát a deklarációs ponton. A jelenlegi javaslatnak azt is biztosítania kell, hogy a létrehozási módszer legalább olyan elérhető legyen, mint a paramsgyűjtemény típusa. Ezt az ellenőrzést nem lehet megbízható módon elvégezni, hacsak a gyűjtemény típusa nem határozza meg az iteráció típusát .

Vegye figyelembe, hogy megnyitottuk https://github.com/dotnet/roslyn/issues/69676 a fordítót is, amely alapvetően ugyanazt a problémát figyeli meg, de az optimalizálás szempontjából beszél róla.

Javaslat

Egy olyan típus megkövetelése, amely CollectionBuilder az iterációs típust önmagában határozza meg. Más szóval ez azt jelenti, hogy a típusnak implementálnia IEnumarable/IEnumerable<T>kell, vagy a megfelelő aláírással rendelkező nyilvános GetEnumerator metódussal kell rendelkeznie (ez kizárja a bővítménymetódusokat).

Emellett a létrehozási módszerre is szükség van ahhoz, hogy "elérhető legyen, ahol a gyűjteménykifejezést használják". Ez az akadálymentességen alapuló környezeti függőség egy másik pontja. Ennek a módszernek a célja nagyon hasonló a felhasználó által definiált konverziós módszer céljához, és ennek nyilvánosnak kell lennie. Ezért érdemes megfontolnunk, hogy a létrehozási módszer nyilvános legyen.

Conclusion

Az LDM-2024-01-08 módosításokkal jóváhagyva

Az iterációs típus fogalmát a rendszer nem alkalmazza következetesen a konverziók során

  • Olyan szerkezetre vagy osztálytípusra , amely az alábbiakat valósítja meg System.Collections.Generic.IEnumerable<T> :
    • Minden elemEi esetében implicit átalakításTtörténik.

Úgy tűnik, hogy feltételezik, hogy T ebben az esetben szükség van a struktúra vagy az osztálytípusiterációs típusára. Ez a feltételezés azonban helytelen. Ami nagyon furcsa viselkedéshez vezethet. Például:

using System.Collections;
using System.Collections.Generic;

class MyCollection : IEnumerable<long>
{
    IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;

    public void Add(string l) => throw null;
    
    public IEnumerator<string> GetEnumerator() => throw null; 
}

class Program
{
    static void Main()
    {
        foreach (var l in new MyCollection())
        {
            string s = l; // Iteration type is string
        }
        
        MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
                           2];
        MyCollection x2 = new MyCollection() { "b" };
    }
}
  • Olyan szerkezetre vagy osztálytípusra , amely implementálja System.Collections.IEnumerable és nem valósítja megSystem.Collections.Generic.IEnumerable<T>.

Úgy tűnik, hogy az implementáció feltételezi, hogy az iteráció típusaobject, de a specifikáció nem részletezi ezt a tényt, és egyszerűen nem követeli meg, hogy minden elem bármivé alakuljon. Általában azonban nem szükséges az object. Ami az alábbi példában figyelhető meg:

using System.Collections;
using System.Collections.Generic;

class MyCollection : IEnumerable
{
    public IEnumerator<string> GetEnumerator() => throw null; 
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}

class Program
{
    static void Main()
    {
        foreach (var l in new MyCollection())
        {
            string s = l; // Iteration type is string
        }
    }
}

Az iterációs típus fogalma alapvető fontosságú a Params Collections szolgáltatásban . Ez a probléma pedig furcsa eltérést okoz a két funkció között. Például:

using System.Collections;
using System.Collections.Generic;

class MyCollection : IEnumerable<long>
{
    IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;

    public IEnumerator<string> GetEnumerator() => throw null; 

    public void Add(long l) => throw null; 
    public void Add(string l) => throw null; 
}

class Program
{
    static void Main()
    {
        Test("2"); // error CS0029: Cannot implicitly convert type 'string' to 'long'
        Test(["2"]); // error CS1503: Argument 1: cannot convert from 'collection expressions' to 'string'
        Test(3); // error CS1503: Argument 1: cannot convert from 'int' to 'string'
        Test([3]); // Ok

        MyCollection x1 = ["2"]; // error CS0029: Cannot implicitly convert type 'string' to 'long'
        MyCollection x2 = [3];
    }

    static void Test(params MyCollection a)
    {
    }
}
using System.Collections;
using System.Collections.Generic;

class MyCollection : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator() => throw null;

    public IEnumerator<string> GetEnumerator() => throw null; 
    public void Add(object l) => throw null;
}

class Program
{
    static void Main()
    {
        Test("2", 3); // error CS1503: Argument 2: cannot convert from 'int' to 'string'
        Test(["2", 3]); // Ok
    }

    static void Test(params MyCollection a)
    {
    }
}

Valószínűleg jó lesz az egyik vagy a másik igazítása.

Javaslat

Adja meg a megvalósító vagy iterációs típust használó System.Collections.Generic.IEnumerable<T> vagy System.Collections.IEnumerableosztálytípus konvertálhatóságát, és minden elem esetében Ei igényel az iterációs típusra.

Conclusion

Jóváhagyott LDM-2024-01-08

Szükség van-e a gyűjteménykifejezések átalakítására , ha az építéshez minimális API-k állnak rendelkezésre?

A konverzióknak megfelelő konstrukciós gyűjteménytípusok valójában nem konstruktorok, ami valószínűleg valamilyen váratlan túlterhelésfeloldási viselkedéshez vezet. Például:

class C1
{
    public static void M1(string x)
    {
    }
    public static void M1(char[] x)
    {
    }
    
    void Test()
    {
        M1(['a', 'b']); // error CS0121: The call is ambiguous between the following methods or properties: 'C1.M1(string)' and 'C1.M1(char[])'
    }
}

Azonban a "C1. Az M1(sztring)" nem használható jelölt, mert:

error CS1729: 'string' does not contain a constructor that takes 0 arguments
error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)

Íme egy másik példa egy felhasználó által definiált típussal és egy erősebb hibával, amely még egy érvényes jelöltet sem említ:

using System.Collections;
using System.Collections.Generic;

class C1 : IEnumerable<char>
{
    public static void M1(C1 x)
    {
    }
    public static void M1(char[] x)
    {
    }

    void Test()
    {
        M1(['a', 'b']); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
    }

    public static implicit operator char[](C1 x) => throw null;
    IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}

Úgy tűnik, hogy a helyzet nagyon hasonló ahhoz, amit a metóduscsoporttal a konverziók delegálásához használtunk. Voltak olyan forgatókönyvek, amelyekben az átalakítás létezett, de hibás volt. Úgy döntöttünk, hogy ezt úgy javítjuk, hogy ha az átalakítás hibás, akkor az nem létezik.

Vegye figyelembe, hogy a "Params Collections" funkcióval hasonló problémába ütközünk. Érdemes lehet letiltani a params módosító használatát a nem konstruálható gyűjtemények esetében. Az aktuális javaslatban azonban az ellenőrzés a konverziók szakaszon alapul. Íme egy példa:

using System.Collections;
using System.Collections.Generic;

class C1 : IEnumerable<char>
{
    public static void M1(params C1 x) // It is probably better to report an error about an invalid `params` modifier
    {
    }
    public static void M1(params ushort[] x)
    {
    }

    void Test()
    {
        M1('a', 'b'); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
        M2('a', 'b'); // Ok
    }

    public static void M2(params ushort[] x)
    {
    }

    IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}

Úgy tűnik, hogy a problémát korábban már tárgyaltuk, lásd https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-02.md#collection-expressions. Abban az időben létrejött egy argumentum, amely szerint a jelenleg megadott szabályok összhangban vannak az interpolált sztringkezelők beállításával. Íme egy idézet:

Az interpolált sztringkezelőket eredetileg így határoztuk meg, de a probléma mérlegelése után módosítottuk a specifikációt.

Bár van némi hasonlóság, érdemes megfontolni egy fontos különbséget is. Íme egy idézet a következőből https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md#interpolated-string-handler-conversion:

A típus T azt mondja, hogy applicable_interpolated_string_handler_type , ha az attribútuma System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute. Létezik implicit interpolated_string_handler_conversionT egy interpolated_string_expression, vagy egy additive_expression , amely teljes egészében _interpolated_string_expression_s és csak + operátorokat használ.

A céltípusnak rendelkeznie kell egy speciális attribútummal, amely jelzi a szerző szándékát, hogy a típus interpolált sztringkezelő legyen. Joggal feltételezhetjük, hogy az attribútum jelenléte nem véletlen. Ezzel szemben az a tény, hogy egy típus "számba vehető", nem szükséges azt jelenti, hogy a szerző szándéka volt, hogy a típus felépíthető legyen. A létrehozási módszer jelenléte azonban, amely a [CollectionBuilder(...)] egyik attribútumával van jelezve, erős jelzésnek érzi a szerző szándékát arra vonatkozóan, hogy a típus konstrukciós legyen.

Javaslat

Olyan szerkezet vagy osztálytípus esetében, amely implementálSystem.Collections.IEnumerable, és nem rendelkezik létrehozási metóduskonvertálási szakaszsal, legalább a következő API-k meglétét kell megkövetelnie:

  • Akadálymentes konstruktor, amely argumentumok nélkül használható.
  • Akadálymentes Add példány vagy bővítmény metódus, amely argumentumként iterációs típusú értékkel hívható meg.

A Params Collectons funkció szempontjából az ilyen típusok érvényes params típusok, ha ezek az API-k nyilvánosak, és példány (és bővítmény) metódusok.

Conclusion

Az LDM-2024-01-10 módosításokkal jóváhagyva