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


Nyilvántartások

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/39

Ez a javaslat nyomon követi a C# 9 rekordok funkció specifikációját, a C# nyelvtervező csapatának egyetértésével.

Egy rekord szintaxisa a következő:

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

record_base
    : ':' class_type argument_list?
    | ':' interface_type_list
    | ':' class_type argument_list? ',' interface_type_list
    ;

record_body
    : '{' class_member_declaration* '}' ';'?
    | ';'
    ;

A rekordtípusok az osztálydeklarációhoz hasonló referenciatípusok. Hiba, ha egy rekord record_baseargument_list ad meg, ha a record_declaration nem tartalmaz parameter_list. Legfeljebb egy részleges típusdeklaráció adhat meg egy részleges rekordnál parameter_list-t.

A rekordparaméterek nem használhatnak ref, out vagy this módosítókat (de in és params engedélyezettek).

Örökség

A rekordok nem örökölhetők az osztályoktól, kivéve, ha az osztály object, és az osztályok nem örökölhetők a rekordokból. A rekordok más rekordoktól örökölhetnek.

Rekordtípus tagjai

A rekordtörzsben deklarált tagokon kívül egy rekordtípus további szintetizált tagokkal is rendelkezik. A tagok szintetizálása csak akkor történik, ha a rekord törzsében "egyező" aláírással rendelkező tag van deklarálva, vagy ha egy "egyező" aláírással rendelkező, akadálymentes konkrét, nem virtuális tag öröklődik. Az egyező tag megakadályozza, hogy a fordító ezt a tagot hozza létre, nem pedig más szintetizált tagokat. 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. Hiba, ha egy rekord tagját "Clone"-nak nevezik. Hiba forrása lehet, ha egy rekord példánymezője felső szintű mutató típusú. Beágyazott mutatótípus( például mutatótömb) használata engedélyezett.

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

Egyenlőségi tagok

Ha a rekord objectszármazik, a rekordtípus tartalmaz egy szintetizált, olvasható tulajdonságot, amely a következőképpen deklarált tulajdonságnak felel meg:

Type EqualityContract { get; }

A tulajdonság private, ha a rekordtípus sealed. Ellenkező esetben a tulajdonság virtual és protected. A tulajdonság explicit módon deklarálható. Hiba, ha az explicit deklaráció nem felel meg a várt szignatúrának vagy hozzáférhetőségnek, vagy ha az explicit deklaráció nem teszi lehetővé, hogy azt egy származtatott típus felülírja, és a rekordtípus nem sealed.

Ha a rekordtípus egy alaprekordtípusból származik, Base, a rekordtípus tartalmaz egy szintetizált írásvédett tulajdonságot, amely a következőképpen deklarált tulajdonságnak felel meg:

protected override Type EqualityContract { get; }

A tulajdonság explicit módon deklarálható. Hiba, ha az explicit deklaráció nem felel meg a várt szignatúrának vagy hozzáférhetőségnek, vagy ha az explicit deklaráció nem teszi lehetővé, hogy azt egy származtatott típus felülírja, és a rekordtípus nem sealed. Hiba, ha a szintetizált vagy explicit módon deklarált tulajdonság nem felülbírál egy ilyen aláírással rendelkező tulajdonságot az Base rekordtípusban (például ha a tulajdonság hiányzik a Base, vagy lezárt vagy nem virtuális stb.). A szintetizált tulajdonság visszaadja typeof(R), ahol R a rekordtípus.

A rekordtípus implementálja a System.IEquatable<R>-t, és magában foglal egy szintetikus, erősen típusos túlterhelést a Equals(R? other) számára, ahol R a rekordtípus. A metódus public, és a metódus virtual, kivéve, ha a rekordtípus sealed. A metódus explicit módon deklarálható. Hiba, ha az explicit deklaráció nem egyezik a várt aláírással vagy hozzáférhetőséggel, vagy az explicit deklaráció nem teszi lehetővé annak felülírását egy származtatott típusban, és a rekordtípus nem sealed.

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

public virtual bool Equals(R? other);

A szintetizált Equals(R?) akkor és csak akkor ad vissza true, ha az alábbiak mindegyike true:

  • other nem null, és
  • A nem örökölt rekordtípus minden példánymezője esetében fieldN, melyeknél a mezőtípus System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN), az TN értéket kap.
  • Ha van alaprekordtípus, akkor a base.Equals(other) értéke (nem virtuális hívás a public virtual bool Equals(Base? other)-re); egyébként a EqualityContract == other.EqualityContractértéke.

A rekordtípus a következő módon deklarált operátorokkal egyenértékű szintetizált == és != operátorokat tartalmazza:

public static bool operator==(R? left, R? right)
    => (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R? left, R? right)
    => !(left == right);

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.

Ha a rekordtípus egy Basealaprekordtípusból származik, a rekordtípus a következő módon deklarált metódussal egyenértékű szintetizált felülbírálást tartalmaz:

public sealed override bool Equals(Base? other);

Hiba, ha a felülbírálás explicit módon van deklarálva. Hiba történik, ha a metódus nem bírál felül egy azonos aláírással rendelkező metódust a rekordtípusú Base-ban (például, ha a metódus hiányzik a Base-ből, le van zárva, vagy nem virtuális, stb.). A szintetizált felülbírálás visszaadja a Equals((object?)other)értéket.

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

public override bool Equals(object? obj);

Hiba, ha a felülbírálás explicit módon van deklarálva. Ez hiba, ha az eljárás nem írja felül a object.Equals(object? obj)-t (például köztes alaptípusok esetén fellépő árnyékolás miatt, stb.). A szintetizált felülbírálás a(z) Equals(other as R)-t adja vissza, ahol R a rekordtípus.

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

public override int GetHashCode();

A metódus explicit módon deklarálható. Ez akkor jelent hibát, ha az explicit deklaráció nem teszi lehetővé annak felülírását egy származtatott típusban, és a rekordtípus nem sealed. Hiba, ha a szintetizált vagy explicit módon deklarált metódus nem bírálja felül a object.GetHashCode() (például a köztes alaptípusok árnyékolása miatt stb.).

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

A GetHashCode() szintetizált felülbírálása a következő értékek kombinálásának int eredményét adja vissza:

  • A nem örökölt rekordtípus minden példánymezője esetében fieldN, melyeknél a mezőtípus System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN), az TN értéket kap.
  • Alaprekordtípus esetén a base.GetHashCode()értéke; egyéb esetben a System.Collections.Generic.EqualityComparer<System.Type>.Default.GetHashCode(EqualityContract)értéke.

Vegyük például a következő rekordtípusokat:

record R1(T1 P1);
record R2(T1 P1, T2 P2) : R1(P1);
record R3(T1 P1, T2 P2, T3 P3) : R2(P1, P2);

Ezekben a rekordtípusokban a szintetizált egyenlőségi tagok a következőhöz hasonlóak lehetnek:

class R1 : IEquatable<R1>
{
    public T1 P1 { get; init; }
    protected virtual Type EqualityContract => typeof(R1);
    public override bool Equals(object? obj) => Equals(obj as R1);
    public virtual bool Equals(R1? other)
    {
        return !(other is null) &&
            EqualityContract == other.EqualityContract &&
            EqualityComparer<T1>.Default.Equals(P1, other.P1);
    }
    public static bool operator==(R1? left, R1? right)
        => (object)left == right || (left?.Equals(right) ?? false);
    public static bool operator!=(R1? left, R1? right)
        => !(left == right);
    public override int GetHashCode()
    {
        return HashCode.Combine(EqualityComparer<Type>.Default.GetHashCode(EqualityContract),
            EqualityComparer<T1>.Default.GetHashCode(P1));
    }
}

class R2 : R1, IEquatable<R2>
{
    public T2 P2 { get; init; }
    protected override Type EqualityContract => typeof(R2);
    public override bool Equals(object? obj) => Equals(obj as R2);
    public sealed override bool Equals(R1? other) => Equals((object?)other);
    public virtual bool Equals(R2? other)
    {
        return base.Equals((R1?)other) &&
            EqualityComparer<T2>.Default.Equals(P2, other.P2);
    }
    public static bool operator==(R2? left, R2? right)
        => (object)left == right || (left?.Equals(right) ?? false);
    public static bool operator!=(R2? left, R2? right)
        => !(left == right);
    public override int GetHashCode()
    {
        return HashCode.Combine(base.GetHashCode(),
            EqualityComparer<T2>.Default.GetHashCode(P2));
    }
}

class R3 : R2, IEquatable<R3>
{
    public T3 P3 { get; init; }
    protected override Type EqualityContract => typeof(R3);
    public override bool Equals(object? obj) => Equals(obj as R3);
    public sealed override bool Equals(R2? other) => Equals((object?)other);
    public virtual bool Equals(R3? other)
    {
        return base.Equals((R2?)other) &&
            EqualityComparer<T3>.Default.Equals(P3, other.P3);
    }
    public static bool operator==(R3? left, R3? right)
        => (object)left == right || (left?.Equals(right) ?? false);
    public static bool operator!=(R3? left, R3? right)
        => !(left == right);
    public override int GetHashCode()
    {
        return HashCode.Combine(base.GetHashCode(),
            EqualityComparer<T3>.Default.GetHashCode(P3));
    }
}

Tagok másolása és klónozása

A rekordtípus két másolási tagot tartalmaz:

  • A rekordtípus egyetlen argumentumát használó konstruktor. Ezt nevezik "másolási konstruktornak".
  • Nyilvános, paraméter nélküli példányosított "clone" metódus fordító által lefoglalt névvel

A másolási konstruktor célja az állapot másolása a paraméterből az újonnan létrehozott példányba. Ez a konstruktor nem futtatja a rekorddeklarációban található példánymező-/tulajdonság-inicializálókat. Ha a konstruktor nincs explicit módon deklarálva, a fordító szintetizálja a konstruktort. Ha a rekord le van zárva, a konstruktor privát lesz, ellenkező esetben védett lesz. A kifejezetten deklarált példánykonstruktornak nyilvánosnak vagy védettnek kell lennie, kivéve, ha a rekordot lezárták. Az első dolog, amit a konstruktornak tennie kell, hogy meghívja az alap egy példánykonstruktorát, vagy egy paraméter nélküli objektumkonstruktort, ha a rekord örökli az objektumot. Hiba jelenik meg, ha egy felhasználó által definiált példánykonstruktor implicit vagy explicit konstruktor inicializálót használ, amely nem felel meg ennek a követelménynek. Az alappéldány-konstruktor meghívása után a szintetizált példánykonstruktor a rekordtípuson belül implicit módon vagy explicit módon deklarált összes példánymező értékeit másolja át. A példánykonstruktor kizárólagos jelenléte , akár explicit, akár implicit, nem akadályozza meg az alapértelmezett példánykonstruktor automatikus hozzáadását.

Ha egy virtuális "klón" metódus található az alaprekordban, a szintetizált "klón" metódus felülbírálja azt, és a metódus visszatérési típusa az aktuális tartalomtípus. Hiba keletkezik, ha az alaprekord klónozási módszere lezárva van. Ha az alaprekordban nem szerepel virtuális "klón" metódus, a klónozási módszer visszatérési típusa a tartalmazó típus, a metódus pedig virtuális, kivéve, ha a rekord lezárt vagy absztrakt. Ha a rekord absztrakciós, akkor a szintetizált klónozási módszer is absztrakt. Ha a "klónozás" metódus nem absztrakt, akkor egy másolatkonstruktor meghívásának eredményét adja vissza.

Nyomtatási tagok: PrintMembers és ToString metódusok

Ha a rekord objectszármazik, a rekord egy, a következőképpen deklarált metódussal egyenértékű szintetizált metódust tartalmaz:

bool PrintMembers(System.Text.StringBuilder builder);

A módszer private, ha a rekordtípus sealed. Ellenkező esetben a metódus virtual és protected.

A módszer:

  1. meghívja a metódust System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack(), ha a metódus jelen van, és a rekord nyomtatható tagokkal rendelkezik.
  2. a rekord 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: ", ",
  3. igaz értéket ad vissza, ha a rekord 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 fel kell hívnia a ToString-t, mielőtt átmegy a StringBuilder.Append-re.

Ha a rekordtípus egy alaprekordból származik Base, a rekord tartalmaz egy szintetizált felülbírálást, amely egyenértékű a következő deklarált metódussal:

protected override bool PrintMembers(StringBuilder builder);

Ha a rekordnak nincsenek nyomtatható tagjai, a metódus meghívja az alap PrintMembers metódust egy argumentummal (annak builder paraméterével), és visszaadja az eredményt.

Ellenkező esetben a metódus:

  1. meghívja az alap PrintMembers metódust egy argumentummal (annak builder paraméterével),
  2. ha a PrintMembers metódus igaz értéket ad vissza, fűzze hozzá a ", " elemet a szerkesztőhöz,
  3. a rekord minden nyomtatható tagjára vonatkozóan hozzáfűzi a tag nevét, majd az egyenlőségjel (" = ") következik, majd a tag értéke: this.member (vagy értéktípusok esetén this.member.ToString()), az elemeket pedig ", " választja el egymástól.
  4. igaz értéket ad vissza.

A PrintMembers metódus explicit módon deklarálható. Hiba, ha az explicit deklaráció nem felel meg a várt szignatúrának vagy hozzáférhetőségnek, vagy ha az explicit deklaráció nem teszi lehetővé, hogy azt egy származtatott típus felülírja, és a rekordtípus nem sealed.

A rekord egy, a következőképpen deklarált metódusnak megfelelő szintetizált metódust tartalmaz:

public override string ToString();

A metódus explicit módon deklarálható. Hiba, ha az explicit deklaráció nem felel meg a várt szignatúrának vagy hozzáférhetőségnek, vagy ha az explicit deklaráció nem teszi lehetővé, hogy azt egy származtatott típus felülírja, és a rekordtípus nem sealed. Hiba, ha a szintetizált vagy explicit módon deklarált metódus nem bírálja felül a object.ToString() (például a köztes alaptípusok árnyékolása miatt stb.).

A szintetizált módszer:

  1. létrehoz egy StringBuilder példányt,
  2. hozzáfűzi a rekord nevét az építőhöz, majd a " { ",
  3. meghívja a rekord PrintMembers függvényét, amely az építőt adja meg, majd " " következik, 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ő rekordtípusokat:

record R1(T1 P1);
record R2(T1 P1, T2 P2, T3 P3) : R1(P1);

Azoknál a rekordtípusoknál a szintetizált nyomtatási elemek a következőképpen néznek ki:

class R1 : IEquatable<R1>
{
    public T1 P1 { get; init; }
    
    protected virtual bool PrintMembers(StringBuilder builder)
    {
        builder.Append(nameof(P1));
        builder.Append(" = ");
        builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if T1 is 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();
    }
}

class R2 : R1, IEquatable<R2>
{
    public T2 P2 { get; init; }
    public T3 P3 { get; init; }
    
    protected override bool PrintMembers(StringBuilder builder)
    {
        if (base.PrintMembers(builder))
            builder.Append(", ");
            
        builder.Append(nameof(P2));
        builder.Append(" = ");
        builder.Append(this.P2); // or builder.Append(this.P2); if T2 is a value type
        
        builder.Append(", ");
        
        builder.Append(nameof(P3));
        builder.Append(" = ");
        builder.Append(this.P3); // or builder.Append(this.P3); if T3 is a value type
        
        return true;
    }
    
    public override string ToString()
    {
        var builder = new StringBuilder();
        builder.Append(nameof(R2));
        builder.Append(" { ");

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

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

Pozíciórekordtagok

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

Elsődleges konstruktor

Egy rekordtípushoz tartozik egy nyilvános konstruktor, amelynek aláírása megfelel a típusdeklaráció értékparamétereinek. Ezt a típus elsődleges konstruktorának nevezzük, és ha van ilyen, az implicit módon deklarált alapértelmezett osztálykonstruktor el lesz tiltva. Hiba, ha egy elsődleges konstruktor és egy ugyanolyan aláírású konstruktor már megtalálható az osztályban.

Végrehajtáskor az elsődleges konstruktor

  1. végrehajtja az osztály törzsében megjelenő példány-inicializálókat

  2. meghívja az alaposztály-konstruktort a record_base záradékban megadott argumentumokkal, ha vannak ilyenek

Ha egy rekord elsődleges konstruktorsal rendelkezik, a felhasználó által definiált konstruktoroknak a "másolási konstruktor" kivételével explicit this konstruktor inicializálóval kell rendelkezniük.

Az elsődleges konstruktor paraméterei és a rekord tagjai hatóköre a argument_list záradék record_base, valamint a példánymezők vagy tulajdonságok inicializálóiban található. A példánytagok ezeken a helyeken hibának minősülnének (hasonlóan ahhoz, ahogyan a példánytagok jelenleg az általános konstruktor inicializálók hatókörében szerepelnek, de hibának számít a használatuk), de 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 lennének, hasonlóan ahhoz, ahogyan az alaphívások és inicializálók működnek a hagyományos konstruktorokban.

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

A argument_list deklarált kifejezésváltozók a argument_listhatókörében vannak. Ugyanazok az árnyékolási szabályok érvényesek, mint egy normál konstruktor inicializáló argumentumlistájában.

Tulajdonságok

A rekordtípus-deklaráció minden rekordparaméteréhez tartozik egy megfelelő nyilvánostulajdon-tag, akinek a neve és típusa az értékparaméter-deklarációból származik.

Egy rekord érdekében:

  • Létrejön egy nyilvános get és init automatikus tulajdonság (lásd külön init tartozék specifikációját). Egy egyező típusú öröklött abstract tulajdonságot felülbírálják. Hiba, ha az örökölt tulajdonság public felülírható get és init kiegészítőkkel nem rendelkezik. Hiba, ha az örökölt tulajdonság 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ő rekordparaméterre szintaktikailag alkalmazott attribútumok property: vagy field: céljainak használatával.

Dekonstruktúra

Egy legalább egy paraméterrel rendelkező pozíciórekord szintetizálja a dekonstruálás nevű nyilvános érvénytelenítő példánymetódust az elsődleges konstruktor-deklaráció minden paraméteréhez tartozó kimenő paraméterdeklarációval. 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 a Deconstruct metódus minden paraméteréhez hozzárendeli az azonos nevű példánytulajdonság értékét. 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.

Az alábbi példa egy R pozíciórekordot mutat be a fordító által szintetizált Deconstruct metódussal, és annak használatát:

public record R(int P1, string P2 = "xyz")
{
    public void Deconstruct(out int P1, out string P2)
    {
        P1 = this.P1;
        P2 = this.P2;
    }
}

class Program
{
    static void Main()
    {
        R r = new R(12);
        (int p1, string p2) = r;
        Console.WriteLine($"p1: {p1}, p2: {p2}");
    }
}

with kifejezés

A with kifejezés az alábbi szintaxist használó új kifejezés.

with_expression
    : switch_expression
    | switch_expression 'with' '{' member_initializer_list? '}'
    ;

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : identifier '=' expression
    ;

A with kifejezés nem engedélyezett utasításként.

A with kifejezés lehetővé teszi a "nem destruktív mutációt", amely a fogadó kifejezés másolatának előállítására szolgál a member_initializer_listhozzárendeléseinek módosításával.

Egy érvényes with kifejezés fogadója nem üres típusú. A fogadó típusának rekordnak kell lennie.

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.

Először a rendszer meghívja a fogadó "klónozási" metódusát (fent megadott), és az eredmény a fogadó típusára lesz konvertálva. Ezután minden member_initializer ugyanúgy kerül feldolgozásra, mint az átalakítás eredményéhez tartozó mező vagy tulajdonság elérésére történő hozzárendelés. A hozzárendelések lexikális sorrendben vannak feldolgozva.