Gyűjteménykifejezések – C# nyelvi referencia

Gyűjteménykifejezés használata a gyakori gyűjteményértékek létrehozásához. A gyűjteménykifejezések olyan terse szintaxisok, amelyeket számos különböző gyűjteménytípushoz rendelhet. A gyűjteménykifejezések elemek sorozatát tartalmazzák a zárójelek és [ a zárójelek között].

A C# nyelv referenciadokumentuma a C# nyelv legújabb kiadású verzióját ismerteti. Emellett a közelgő nyelvi kiadás nyilvános előzetes verziójú funkcióinak kezdeti dokumentációját is tartalmazza.

A dokumentáció azonosítja azokat a funkciókat, amelyeket először a nyelv utolsó három verziójában vagy az aktuális nyilvános előzetes verziókban vezetnek be.

Jótanács

Ha meg szeretné tudni, hogy mikor jelent meg először egy funkció a C#-ban, tekintse meg a C# nyelvi verzióelőzményeiről szóló cikket.

Az alábbi példa egy elemeket System.Span<T>string deklarál, és inicializálja őket a hét napjaira:

Span<string> weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
foreach (var day in weekDays)
{
    Console.WriteLine(day);
}

A gyűjteménykifejezéseket számos különböző gyűjteménytípusra konvertálhatja. Az első példa bemutatja, hogyan inicializálhat egy változót gyűjteménykifejezéssel. Az alábbi kód számos más helyet mutat be, ahol gyűjteménykifejezést használhat:

// Initialize private field:
private static readonly ImmutableArray<string> _months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

// property with expression body:
public IEnumerable<int> MaxDays =>
    [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

public int Sum(IEnumerable<int> values) =>
    values.Sum();

public void Example()
{
    // As a parameter:
    int sum = Sum([1, 2, 3, 4, 5]);
}

Nem használhat olyan gyűjteménykifejezést, amelyben fordítási időállandót várnak, például állandó inicializálásakor vagy egy metódusargumentum alapértelmezett értékeként.

Mindkét előző példa konstansokat használt egy gyűjteménykifejezés elemeiként. Az elemekhez változókat is használhat, ahogy az a következő példában is látható:

string hydrogen = "H";
string helium = "He";
string lithium = "Li";
string beryllium = "Be";
string boron = "B";
string carbon = "C";
string nitrogen = "N";
string oxygen = "O";
string fluorine = "F";
string neon = "Ne";
string[] elements = [hydrogen, helium, lithium, beryllium, boron, carbon, nitrogen, oxygen, fluorine, neon];
foreach (var element in elements)
{
    Console.WriteLine(element);
}

Oldalpár elem

A gyűjteménykifejezések beágyazott gyűjteményértékeivel oldalpár elemet.. használhat. Az alábbi példa létrehoz egy gyűjteményt a teljes ábécé számára a magánhangzók gyűjteményének, a mássalhangzók gyűjteményének és az "y" betűnek a kombinálásával, amely a következő lehet:

string[] vowels = ["a", "e", "i", "o", "u"];
string[] consonants = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
                       "n", "p", "q", "r", "s", "t", "v", "w", "x", "z"];
string[] alphabet = [.. vowels, .. consonants, "y"];

Az oldalpárelem ..vowelskiértékelésekor öt elemet hoz létre: "a", "e", "i", "o"és "u". Az oldalpár ..consonants 20 elemet állít elő, a tömbben lévő consonants számot. Az oldalpár elemben lévő kifejezésnek egy utasítással foreach enumerálhatónak kell lennie. Ahogy az előző példában is látható, az oldalpár elemeit egyesítheti a gyűjteménykifejezések egyes elemeivel.

Konverziók

A gyűjteménykifejezéseket különböző gyűjteménytípusokká alakíthatja át, például:

Megjegyzés:

A gyűjteménykifejezések nem használhatók beágyazott tömbök inicializálására. A beágyazott tömbök eltérő inicializálási szintaxist igényelnek.

Fontos

A gyűjteménykifejezések mindig létrehoznak egy gyűjteményt, amely a gyűjteménykifejezés összes elemét tartalmazza, függetlenül az átalakítás céltípusától. Ha például az átalakítás System.Collections.Generic.IEnumerable<T>célja, a generált kód kiértékeli a gyűjteménykifejezést, és egy memórián belüli gyűjteményben tárolja az eredményeket.

Ez a viselkedés különbözik a LINQ-tól, ahol előfordulhat, hogy a sorozat nem lesz példányosítva, amíg meg nem történik a számbavétele. Gyűjteménykifejezésekkel nem hozhat létre olyan végtelen sorozatot, amely nem lesz számba vehető.

A fordító statikus elemzéssel határozza meg a gyűjtemény gyűjteménykifejezéssel deklarált létrehozásának leghatékonyabb módját. Az üres gyűjteménykifejezés például úgy valósítható meg, []mintha Array.Empty<T>() a cél nem módosul az inicializálás után. Ha a cél egy System.Span<T> vagy System.ReadOnlySpan<T>, a tárolót lehet lefoglalni. A gyűjteménykifejezések funkciós specifikációja meghatározza, hogy a fordítónak mely szabályokat kell követnie.

Számos API paraméterként több gyűjteménytípussal van túlterhelve. Mivel a gyűjteménykifejezések számos különböző kifejezéstípusra konvertálhatók, előfordulhat, hogy ezek az API-k megkövetelik a gyűjteménykifejezésen a megfelelő átalakítás megadását. Az alábbi konverziós szabályok feloldanak néhány kétértelműséget:

  • Jobb elemkonverziót előnyben részesítünk a jobb gyűjteménytípus-átalakítással szemben. Más szóval a gyűjteménykifejezés elemeinek típusa nagyobb jelentőséggel bír, mint a gyűjtemény típusa. Ezeket a szabályokat a funkció specifikációja ismerteti a gyűjteménykifejezésből való jobb átalakítás érdekében.
  • Span<T>Az átalakítás , ReadOnlySpan<T>vagy egy másik ref struct típus jobb, mint egy nem átstrukturálási típusra való átalakítás.
  • A nem betűs típusra való átalakítás jobb, mint egy felülettípusra való átalakítás.

Ha gyűjteménykifejezést alakít át egy Span vagy ReadOnlySpantöbbre, a span objektum biztonságos környezete a spanban található összes elem biztonságos környezetéből származik. Részletes szabályokért tekintse meg a Gyűjtemény kifejezés specifikációját.

Gyűjteményszerkesztő

A gyűjteménykifejezések bármilyen jól működő gyűjteménytípussal működnek. A jól viselkedő gyűjtemények jellemzői a következők:

  • Egy megszámlálhatóCountLength ugyanazt az értéket eredményezi, mint az elemek számának számbavétele.
  • A névtér típusok System.Collections.Generic mellékhatásmentesek. A fordító képes optimalizálni azokat a forgatókönyveket, amelyekben ezek a típusok köztes értékekként használhatók, máskülönben azonban nem teszi elérhetővé őket.
  • A gyűjtemény egy megfelelő .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 adott érték egyenként hozzáadva a gyűjteményhez a használatával .Add.

A .NET-futtatókörnyezet összes gyűjteménytípusa megfelelően működik.

Figyelmeztetés

Ha egy egyéni gyűjteménytípus nem megfelelően viselkedik, a rendszer nem definiálja a viselkedést, ha ezt a gyűjteménytípust gyűjtési kifejezésekkel használja.

A típusok egy Create() metódus megírásával és az attribútumnak a System.Runtime.CompilerServices.CollectionBuilderAttribute gyűjteménytípusra való alkalmazásával támogatják a gyűjteménykifejezések támogatását, hogy jelezzék a szerkesztőmetódust. Vegyük például egy olyan alkalmazást, amely rögzített hosszúságú puffereket használ 80 karakterből. Az osztály a következő kódhoz hasonlóan nézhet ki:

public class LineBuffer : IEnumerable<char>
{
    private readonly char[] _buffer;
    private readonly int _count;

    public LineBuffer(ReadOnlySpan<char> buffer)
    {
        _buffer = new char[buffer.Length];
        _count = buffer.Length;
        for (int i = 0; i < _count; i++)
        {
            _buffer[i] = buffer[i];
        }
    }

    public int Count => _count;
    
    public char this[int index]
    {
        get
        {
            if (index >= _count)
                throw new IndexOutOfRangeException();
            return _buffer[index];
        }
    }

    public IEnumerator<char> GetEnumerator()
    {
        for (int i = 0; i < _count; i++)
        {
            yield return _buffer[i];
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    // etc
}

Gyűjteménykifejezésekkel szeretné használni az alábbi példában látható módon:

LineBuffer line = ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'];

A LineBuffer típus implementálható IEnumerable<char>, így a fordító elemgyűjteményként char ismeri fel. A implementált System.Collections.Generic.IEnumerable<T> felület típusparamétere az elem típusát jelzi. Két kiegészítést kell hozzáadnia az alkalmazáshoz, hogy gyűjteménykifejezéseket rendelhessen egy LineBuffer objektumhoz. Először létre kell hoznia egy metódust tartalmazó osztályt Create :

internal static class LineBufferBuilder
{
    internal static LineBuffer Create(ReadOnlySpan<char> values) => new LineBuffer(values);
}

A Create metódusnak egy objektumot LineBuffer kell visszaadnia, és a típus ReadOnlySpan<char>utolsó paraméterét kell megadnia. A típusparaméternek ReadOnlySpan meg kell egyeznie a gyűjtemény elemtípusával. Az általános gyűjteményt visszaadó szerkesztőmetódus paramétere az általános ReadOnlySpan<T> . A metódusnak elérhetőnek kell lennie, és static.

A C# 15-től kezdődően a Create metódus további paraméterekkel rendelkezhet a ReadOnlySpan<T> paraméter előtt. Ezeket a paramétereket a gyűjteménykifejezés egy with(...) elemével adhatja át. Részletekért tekintse meg a Gyűjteményszerkesztő argumentumait .

Végül hozzá kell adnia a következőt az CollectionBuilderAttributeLineBuffer osztálydeklarációhoz:

[CollectionBuilder(typeof(LineBufferBuilder), "Create")]

Az első paraméter a Builder osztály nevét adja meg. A második attribútum a szerkesztő metódus nevét adja meg.

Gyűjteménykifejezés argumentumai

A C# 15-től kezdve argumentumokat adhat át a mögöttes gyűjtemény konstruktorának vagy gyári metódusának úgy, hogy egy with(...) elemet használ egy gyűjteménykifejezés első elemeként. Ez a funkció lehetővé teszi kapacitások, összehasonlítók vagy más konstruktorparaméterek megadását közvetlenül a gyűjteménykifejezés szintaxisában. További információkért tekintse meg a gyűjteménykifejezés argumentumainak funkciós specifikációját.

Az with(...) elemnek kell lennie a gyűjteménykifejezés első elemének. Az elemben deklarált argumentumokat a with(...) rendszer átadja a megfelelő konstruktornak, vagy a céltípus alapján hozza létre a metódust. Az elem argumentumaihoz with bármilyen érvényes kifejezést használhat.

Konstruktor argumentumai

Ha a céltípus egy implementált System.Collections.IEnumerableosztály vagy szerkezet, a rendszer kiértékeli a benne lévő with(...) argumentumokat, és az eredményeket átadja a konstruktornak. A fordító túlterhelésfelbontással választja ki a legjobban megfelelő konstruktort:

public void CollectionArgumentsExamples()
{
    string[] values = ["one", "two", "three"];

    // Pass capacity argument to List<T> constructor
    List<string> names = [with(capacity: values.Length * 2), .. values];

    // Pass comparer argument to HashSet<T> constructor
    HashSet<string> set = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO", "hello"];
    // set contains only one element because all strings are equal with OrdinalIgnoreCase

    // Pass capacity to IList<T> (uses List<T> constructor)
    IList<int> numbers = [with(capacity: 100), 1, 2, 3];
}

Az előző példában:

  • A List<string> paraméterrel capacity rendelkező konstruktor meghívása a következővel történik values.Length * 2: .
  • A HashSet<string> paraméterrel System.Collections.Generic.IEqualityComparer<T> rendelkező konstruktor neve a következővel StringComparer.OrdinalIgnoreCasetörténik: .
  • Az olyan felületi céltípusok esetében, mint például System.Collections.Generic.IList<T>a fordító létrehoz egy List<T> adott kapacitást.

Gyűjteményszerkesztő argumentumai

Az a System.Runtime.CompilerServices.CollectionBuilderAttributetípusú típusok esetében a rendszer kiértékeli az with(...) elemben deklarált argumentumokat, és az eredményeket a paraméter előtt átadja a létrehozási metódusnak ReadOnlySpan<T> . Ez a funkció lehetővé teszi a konfigurációs paraméterek elfogadására használható metódusok létrehozását:

internal static class MySetBuilder
{
    internal static MySet<T> Create<T>(ReadOnlySpan<T> items) => new MySet<T>(items);
    internal static MySet<T> Create<T>(IEqualityComparer<T> comparer, ReadOnlySpan<T> items) => 
        new MySet<T>(items, comparer);
}

Ezután az with(...) elem használatával átadhatja a összehasonlítót:

public void CollectionBuilderArgumentsExample()
{
    // Pass comparer to a type with CollectionBuilder attribute
    // The comparer argument is passed before the ReadOnlySpan<T> parameter
    MySet<string> mySet = [with(StringComparer.OrdinalIgnoreCase), "A", "a", "B"];
    // mySet contains only two elements: "A" and "B"
}

A létrehozási módszer a megadott argumentumok alapján túlterhelésfeloldás használatával van kiválasztva. A ReadOnlySpan<T> gyűjteményelemeket tartalmazó elem mindig az utolsó paraméter.

Felületi céltípusok

Számos felületi céltípus támogatja a gyűjteménykifejezés argumentumait. Az alábbi táblázat a támogatott felületeket és az alkalmazható konstruktor-aláírásokat mutatja be:

Interfész with Támogatott elemek
\, \, \ () (csak üres)
ICollection<T>, IList<T> (), (int capacity)

IList<T>A ICollection<T> fordító és a fordító a megadott konstruktort System.Collections.Generic.List<T> használja.

Restrictions

Az with(...) elemre a következő korlátozások vonatkoznak:

  • A gyűjteménykifejezés első elemének kell lennie.
  • Az argumentumok nem tartalmazhatnak dynamic típust.
  • Tömbök és spantípusok (Span<T>, ReadOnlySpan<T>) esetén nem támogatott.