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


Rekordstruktúrák

Jegyzet

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.

Bajnoki probléma: https://github.com/dotnet/csharplang/issues/4334

A rekordstruktúra szintaxisa a következő:

record_struct_declaration
    : attributes? struct_modifier* 'partial'? 'record' 'struct' identifier type_parameter_list?
      parameter_list? struct_interfaces? type_parameter_constraints_clause* record_struct_body
    ;

record_struct_body
    : struct_body
    | ';'
    ;

A rekordstruktúra-típusok értéktípusok, például más struktúratípusok. Implicit módon öröklik az osztály System.ValueType. A rekordstruktúra módosítóira és tagjaira ugyanazok a korlátozások vonatkoznak, mint a szerkezetekre (kisegítő lehetőségek a típusra, módosítók a tagokra, base(...) példánykonstruktor inicializálók, határozott hozzárendelés this konstruktorban, destruktorok, ...). A rekordstruktúra ugyanazokat a szabályokat fogja követni, mint a paraméter nélküli példány-konstruktorok és mező inicializálók esetében, de ez a dokumentum feltételezi, hogy a szerkezetekre vonatkozó korlátozásokat általában feloldjuk.

Lásd §16.4.9 Lásd: paraméter nélküli szerkezetkonstruktorok specifikáció.

A rekordstruktúra nem használhat ref módosítót.

A részleges rekordstruktúra legfeljebb egy részleges típusdeklarációja megadhatja a parameter_list-t. Lehet, hogy a parameter_list üres.

A rekordstruktúra paraméterei nem használhatnak ref, out vagy this módosítókat (de in és params engedélyezettek).

Rekordstruktúra tagjai

A rekordstruktúra törzsében deklarált tagok mellett a rekordstruktúratípus további szintetizált tagokkal is rendelkezik. A tagok szintetizálása csak akkor történik, ha egy "egyező" aláírással rendelkező tagot a rekordstruktúra törzsében deklarálnak, vagy egy akadálymentes, "egyező" aláírással rendelkező, nem virtuális tag öröklődik. Két tag akkor tekinthető egyezőnek, ha ugyanazzal az aláírással rendelkezik, vagy "elrejtésnek" minősül egy öröklési forgatókönyvben. Lásd: Aláírások és túlterhelés §7.6. Hiba, hogy egy rekordstruktúra egy tagja "Clone" néven szerepel.

Hiba, ha egy rekordstruktúra példánymezője nem biztonságos típusú.

A rekordstruktúra nem deklarálhat destruktort.

A szintetizált tagok a következők:

Egyenlőségi tagok

A szintetizált egyenlőség tagjai hasonlóak, mint egy rekordosztályban (Equals ehhez a típushoz, Equalsobject típushoz, == és != operátorok ehhez a típushoz).
kivéve EqualityContracthiányát, a null értékű ellenőrzések vagy az öröklés hiányát.

A rekordstruktúra implementálja a System.IEquatable<R>-t, és tartalmaz egy erős típusú, szintetizált túlterhelést a Equals(R other) esetében, ahol a R a rekordstruktúra. A módszer public. A metódus explicit módon deklarálható. Hiba, ha a kifejezett deklaráció nem felel meg a várt aláírásnak vagy akadálymentességnek.

Ha Equals(R other) felhasználó által definiált (nem szintetizált), de GetHashCode nem, figyelmeztetés jön létre.

public readonly bool Equals(R other);

A szintetizált Equals(R) akkor és csakis akkor ad vissza true értéket, ha a rekord struktúrában minden fieldN mező esetében a System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN) értéke, ahol TN a mezőtípus, true.

A rekordstruktúra a következő deklarált operátorokkal egyenértékű szintetizált == és != operátorokat tartalmazza:

public static bool operator==(R r1, R r2)
    => r1.Equals(r2);
public static bool operator!=(R r1, R r2)
    => !(r1 == r2);

A Equals operátor által hívott == metódus a fent megadott Equals(R other) metódus. A != operátor a == operátorhoz delegál. Hiba, ha az operátorok explicit módon vannak deklarálva.

A rekordstruktúra a következőképpen deklarált metódussal egyenértékű szintetizált felülbírálást tartalmaz:

public override readonly bool Equals(object? obj);

Hiba, ha a felülbírálás explicit módon van deklarálva. A szintetizált felülbírálás a other is R temp && Equals(temp)-t adja vissza, ahol a R a rekordstruktúra.

A rekordstruktúra a következőképpen deklarált metódussal egyenértékű szintetizált felülbírálást tartalmaz:

public override readonly int GetHashCode();

A metódus explicit módon deklarálható.

A rendszer figyelmeztetést ad, ha az egyik (Equals(R) vagy GetHashCode()) explicit módon van deklarálva, de a másik nem explicit.

A GetHashCode() szintetizált felülbírálása visszaad egy int eredményt, amely az egyes példánymezők System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)-hez tartozó fieldN értékek kombinációja, ahol a TN a fieldNtípusa.

Vegyük például a következő rekordszerkezetet:

record struct R1(T1 P1, T2 P2);

Ebben a rekordstruktúra esetében a szintetizált egyenlőségi tagok a következőhöz hasonlóak:

struct R1 : IEquatable<R1>
{
    public T1 P1 { get; set; }
    public T2 P2 { get; set; }
    public override bool Equals(object? obj) => obj is R1 temp && Equals(temp);
    public bool Equals(R1 other)
    {
        return
            EqualityComparer<T1>.Default.Equals(P1, other.P1) &&
            EqualityComparer<T2>.Default.Equals(P2, other.P2);
    }
    public static bool operator==(R1 r1, R1 r2)
        => r1.Equals(r2);
    public static bool operator!=(R1 r1, R1 r2)
        => !(r1 == r2);    
    public override int GetHashCode()
    {
        return Combine(
            EqualityComparer<T1>.Default.GetHashCode(P1),
            EqualityComparer<T2>.Default.GetHashCode(P2));
    }
}

Nyomtatási tagok: PrintMembers és ToString függvények

A rekordstruktúra a következőképpen deklarált metódussal egyenértékű szintetizált metódust tartalmaz:

private bool PrintMembers(System.Text.StringBuilder builder);

A metódus a következőket teszi:

  1. a rekordstruktúra minden nyomtatható tagjára (nem statikus nyilvános mezőre és olvasható tulajdonságtagokra) fűzi hozzá a tag nevét, majd a " = " elemet, majd a tag értékét a következővel elválasztva: ", ",
  2. igaz értéket ad vissza, ha a rekordstruktúra nyomtatható tagokat tartalmaz.

Az értéktípussal rendelkező tagok esetében az értékét sztring-ábrázolássá alakítjuk a célplatform számára elérhető leghatékonyabb módszer használatával. Jelenleg ez azt jelenti, hogy meghívja ToString, mielőtt átmegy StringBuilder.Append.

Ha a rekord nyomtatható tagjai nem tartalmaznak olyan olvasható tulajdonságot, amely nem rendelkezikreadonlyget hozzáférővel, akkor a szintetizált PrintMembersreadonly. Nincs követelmény, hogy a rekord mezőinek readonly-nek kell lenniük ahhoz, hogy a PrintMembers metódus readonlylegyen.

A PrintMembers metódus explicit módon deklarálható. Hiba, ha a kifejezett deklaráció nem felel meg a várt aláírásnak vagy akadálymentességnek.

A rekordstruktúra a következőképpen deklarált metódussal egyenértékű szintetizált metódust tartalmaz:

public override string ToString();

Ha a rekordstruktúra PrintMembers metódusa readonly, akkor a szintetizált ToString() metódus readonly.

A metódus explicit módon deklarálható. Hiba, ha a kifejezett deklaráció nem felel meg a várt aláírásnak vagy akadálymentességnek.

A szintetizált módszer:

  1. létrehoz egy StringBuilder példányt,
  2. hozzáfűzi a rekord struktúra nevét az építőhöz, majd a " { ",
  3. meghívja a rekordstruktúra PrintMembers metódusát, amely a szerkesztőt adja meg, majd a " ", ha igaz értéket ad vissza,
  4. hozzáfűzi a "}" elemet,
  5. visszaadja a szerkesztő tartalmát builder.ToString().

Vegyük például a következő rekordszerkezetet:

record struct R1(T1 P1, T2 P2);

Ebben a rekordstruktúra esetében a szintetizált nyomtatási tagok a következőhöz hasonlóak lehetnek:

struct R1 : IEquatable<R1>
{
    public T1 P1 { get; set; }
    public T2 P2 { get; set; }

    private bool PrintMembers(StringBuilder builder)
    {
        builder.Append(nameof(P1));
        builder.Append(" = ");
        builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if P1 has a value type
        builder.Append(", ");

        builder.Append(nameof(P2));
        builder.Append(" = ");
        builder.Append(this.P2); // or builder.Append(this.P2.ToString()); if P2 has a value type

        return true;
    }

    public override string ToString()
    {
        var builder = new StringBuilder();
        builder.Append(nameof(R1));
        builder.Append(" { ");

        if (PrintMembers(builder))
            builder.Append(" ");

        builder.Append("}");
        return builder.ToString();
    }
}

Beosztási rekord strukturálja a tagokat

A fenti tagok mellett a paraméterlistával ("pozíciórekordok") rendelkező rekordstruktúra további tagokat is szintetizál a fenti tagokkal azonos feltételekkel.

Elsődleges konstruktor

A rekordstruktúra nyilvános konstruktorsal rendelkezik, amelynek aláírása megfelel a típusdeklaráció értékparamétereinek. Ezt nevezik a típus elsődleges konstruktorának. Hiba, ha egy elsődleges konstruktor és egy konstruktor ugyanazzal az aláírással rendelkezik, amely már megtalálható a szerkezetben. Ha a típusdeklaráció nem tartalmaz paraméterlistát, a rendszer nem hoz létre elsődleges konstruktort.

record struct R1
{
    public R1() { } // ok
}

record struct R2()
{
    public R2() { } // error: 'R2' already defines constructor with same parameter types
}

A rekordstruktúra példánymező-deklarációi tartalmazhatnak változó inicializálókat. Ha nincs elsődleges konstruktor, a példány inicializálói a paraméter nélküli konstruktor részeként futnak. Ellenkező esetben futásidőben az elsődleges konstruktor végrehajtja a rekordstruktúra törzsében található példányinicializálókat.

Ha egy rekordstruktúra elsődleges konstruktorsal rendelkezik, minden felhasználó által definiált konstruktornak explicit this konstruktor inicializálóval kell rendelkeznie, amely meghívja az elsődleges konstruktort vagy egy explicit módon deklarált konstruktort.

Az elsődleges konstruktor paraméterei és a rekordstruktúra tagjai hatóköre a példánymezők vagy tulajdonságok inicializálóiban található. A példánytagok hibásak lennének ezeken a helyeken (hasonlóan ahhoz, ahogyan a példánytagok most is a szokásos konstruktor-inicializálók hatókörében vannak, de használatuk hibás lenne), viszont az elsődleges konstruktor paraméterei hatókörben és használhatók lennének, és felülírnák a tagokat. A statikus tagok is használhatók.

Figyelmeztetés jön létre, ha az elsődleges konstruktor egyik paramétere nem olvasható.

A szerkezetpéldány-konstruktorokra vonatkozó határozott hozzárendelési szabályok a rekordstruktúra elsődleges konstruktorára vonatkoznak. Például a következő hibaüzenet jelenik meg:

record struct Pos(int X) // definite assignment error in primary constructor
{
    private int x;
    public int X { get { return x; } set { x = value; } } = X;
}

Tulajdonságok

A rekordstruktúra-deklaráció minden rekordstruktúra-paraméteréhez tartozik egy megfelelő köztulajdon-tag, akinek a neve és típusa az értékparaméter-deklarációból származik.

Rekordstruktúra esetén:

  • Nyilvános get és init automatikus tulajdonság jön létre, ha a rekordstruktúrának readonly módosítója van; máskülönben get és set. Mindkét készletkiegészítő (set és init) "egyezőnek" minősül. A felhasználó tehát csak init-only tulajdonságot deklarálhat egy szintetizált mutable helyett. Egy egyező típusú öröklött abstract tulajdonság felülíródik. Nem jön létre automatikus tulajdonság, ha a rekordstruktúra egy várt névvel és típussal rendelkező példánymezővel rendelkezik. Hiba történt, ha az örökölt tulajdonságnak nincsenek publicget és set/init hozzáférői. Hiba, ha az örökölt tulajdonság vagy mező rejtett.
    Az automatikus tulajdonság inicializálása a megfelelő elsődleges konstruktorparaméter értékére történik. Az attribútumok a szintetizált automatikus tulajdonságra és annak háttérmezőjére alkalmazhatók a megfelelő rekordstruktúraparaméterre alkalmazott attribútumok property: vagy field: céljainak használatával.

Dekonstruktúra

A helymeghatározó rekordstruktúra, amelynek legalább egy paramétere van, egy nyilvános, void típusú példánymetódust szintetizál Deconstruct néven, mely az elsődleges konstruktor deklaráció minden paraméteréhez egy-egy out paraméterdeklarációt állít elő. A Deconstruct metódus minden paramétere ugyanazzal a típussal rendelkezik, mint az elsődleges konstruktor-deklaráció megfelelő paramétere. A metódus törzse hozzárendeli a Deconstruct metódus minden paraméterét ahhoz az értékhez, amely egy példánytag hozzáféréséből származik egy azonos nevű taghoz. Ha a törzsben elérhető példánytagok nem tartalmaznak nemreadonlyget tartozékot tartalmazó tulajdonságot, akkor a szintetizált Deconstruct metódus readonly. A metódus explicit módon deklarálható. Hiba, ha az explicit deklaráció nem felel meg a várt aláírásnak vagy akadálymentességnek, vagy statikus.

with kifejezés engedélyezése a szerkezeteken

Most már érvényes, hogy a fogadó egy with kifejezésben struktúratípussal rendelkezik.

A with kifejezés jobb oldalán található egy member_initializer_list, amely azonosítóhozzárendeléseinek sorozatával rendelkezik, amelynek a fogadó típusának elérhető példánymezőjének vagy tulajdonságának kell lennie.

A szerkezettípusú fogadók esetében a rendszer először másolja a fogadót, majd minden member_initializer ugyanúgy dolgozza fel, mint az átalakítás eredményének egy mezőhöz vagy tulajdonsághoz való hozzáféréséhez való hozzárendelést. A hozzárendelések lexikális sorrendben vannak feldolgozva.

Rekordok fejlesztései

record class engedélyezése

A rekordtípusok meglévő szintaxisa lehetővé teszi, hogy record class ugyanazt a jelentést hordozza, mint record.

record_declaration
    : attributes? class_modifier* 'partial'? 'record' 'class'? identifier type_parameter_list?
      parameter_list? record_base? type_parameter_constraints_clause* record_body
    ;

A felhasználó által definiált pozíciótagok mezőinek engedélyezése

Lásd: https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-05.md#changing-the-member-type-of-a-primary-constructor-parameter

Nem jön létre automatikus tulajdonság, ha a rekordnak vagy egy példánymezője van a várt névvel és típussal, vagy ilyet örököl.

Paraméter nélküli konstruktorok és tag inicializálók engedélyezése a szerkezetekben

Lásd paraméter nélküli szerkezetkonstruktorokat specifikációt.

Kérdések megnyitása

  • hogyan ismerhető fel a rekordstruktúra a metaadatokban? (nincs nem létező klónozási módszerünk kihasználásra...)

Válaszolt

  • Ellenőrizze, hogy meg szeretnénk-e tartani a PrintMembers kialakítását (külön metódus, amely visszaadja a boolértéket) (válasz: igen).
  • Ellenőrizzük, hogy nem engedélyezzük a record ref struct (IEquatable<RefStruct> és ref mezőkkel kapcsolatos probléma) (válasz: igen)
  • az egyenlőségi tagok implementálásának megerősítése. Az alternatíva az, hogy a szintetizált bool Equals(R other), bool Equals(object? other) és az operátorok mind a ValueType.Equals-re delegálnak. (válasz: igen)
  • Ellenőrizze, hogy szeretnénk-e engedélyezni a mezőinicializálókat, ha van elsődleges konstruktor. Szeretnénk engedélyezni a paraméter nélküli szerkezetkonstruktorokat is, amíg ott vagyunk (az Activator-probléma nyilvánvalóan ki lett javítva)? (válasz: igen, a frissített specifikációt felül kell vizsgálni az LDM-ben)
  • mennyit szeretnénk mondani Combine módszerről? (válasz: a lehető legkevesebb)
  • tiltsunk le egy felhasználó által definiált konstruktort másolatkonstruktor-aláírással? (válasz: nem, nincs fogalma a másoló konstruktornak a rekordstruktúrák leírásában)
  • erősítse meg, hogy le szeretnénk tiltani a "Clone" nevű tagokat. (válasz: helyes)
  • Ellenőrizze, hogy a szintetizált Equals logika funkcionálisan egyenértékű-e a futásidejű implementációval (például float.NaN). (Válasz: LDM-ben megerősítve)
  • elhelyezhetők-e mező- vagy tulajdonságcélzási attribútumok a pozícióparaméterek listájában? (válasz: igen, ugyanaz, mint a rekordosztály esetében)
  • with a generikumokról? (válasz: a C# 10 hatókörén kívül)
  • GetHashCode-nek tartalmaznia kellene magának a típusnak a hash-ét, hogy különböző értékeket eredményezzen record struct S1; és record struct S2;között? (válasz: nem)