Delen via


Alleen-lezen instantieleden

Notitie

Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.

Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante Language Design Meeting (LDM)-notities .

Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.

Probleem met kampioen: https://github.com/dotnet/csharplang/issues/1710

Samenvatting

Geef een manier om afzonderlijke exemplaarleden in een struct op te geven die de status niet wijzigen, net zoals readonly struct specificeert dat geen exemplaarleden de status wijzigen.

Het is de moeite waard om te vermelden dat readonly-instantielid != puur instantielid. Een pure instantielid garandeert dat er geen enkele toestand wordt gewijzigd. Een readonly exemplaarlid garandeert alleen dat de instantiestatus niet wordt gewijzigd.

Alle exemplaarleden op een readonly struct kunnen impliciet worden beschouwd als alleen-lezen exemplaarleden Expliciete alleen-lezen leden gedeclareerd op niet-lezen structs zouden zich op dezelfde manier gedragen. Ze zouden bijvoorbeeld nog steeds verborgen kopieën maken als u een exemplaarlid hebt aangeroepen (op het huidige exemplaar of op een veld van het exemplaar) dat niet-lezen was.

Motivatie

Vóór C# 8.0 hebben gebruikers de mogelijkheid om readonly struct typen te maken die de compiler afdwingt dat alle velden alleen-lezen zijn (en door uitbreiding, dat er geen exemplaarleden de status wijzigen). Er zijn echter enkele scenario's waarin u een bestaande API hebt die toegankelijke velden beschikbaar maakt of die een combinatie van mutatie- en niet-mutatieleden bevat. Onder deze omstandigheden kunt u het type niet markeren als readonly (dit zou een belangrijke wijziging zijn).

Dit heeft normaal gesproken niet veel invloed, behalve in het geval van in parameters. Met in-parameters voor niet-lezen structuren maakt de compiler een kopie van de parameter voor elke aanroep van lid van de instantie, omdat het niet kan garanderen dat de aanroep de interne status niet wijzigt. Dit kan leiden tot een veelheid aan kopieën en slechtere algehele prestaties dan wanneer u de struct direct als waarde had doorgegeven. Bijvoorbeeld, zie deze code op sharplab

Enkele andere scenario's waarin verborgen kopieën kunnen optreden, zijn static readonly velden en letterlijke waarden. Als ze in de toekomst worden ondersteund, zouden blittable constanten in dezelfde boot terechtkomen; met andere woorden, op dit moment vereisen ze allemaal een volledige kopie bij het aanroepen van instantieleden als de struct niet is gemarkeerd met readonly.

Ontwerpen

Sta een gebruiker toe om te specificeren dat een instantie-lid zelf readonly is en de toestand van de instantie niet wijzigt (met alle juiste verificatie die natuurlijk door de compiler wordt uitgevoerd). Bijvoorbeeld:

public struct Vector2
{
    public float x;
    public float y;

    public readonly float GetLengthReadonly()
    {
        return MathF.Sqrt(LengthSquared);
    }

    public float GetLength()
    {
        return MathF.Sqrt(LengthSquared);
    }

    public readonly float GetLengthIllegal()
    {
        var tmp = MathF.Sqrt(LengthSquared);

        x = tmp;    // Compiler error, cannot write x
        y = tmp;    // Compiler error, cannot write y

        return tmp;
    }

    public readonly float LengthSquared
    {
        get
        {
            return (x * x) +
                   (y * y);
        }
    }
}

public static class MyClass
{
    public static float ExistingBehavior(in Vector2 vector)
    {
        // This code causes a hidden copy, the compiler effectively emits:
        //    var tmpVector = vector;
        //    return tmpVector.GetLength();
        //
        // This is done because the compiler doesn't know that `GetLength()`
        // won't mutate `vector`.

        return vector.GetLength();
    }

    public static float ReadonlyBehavior(in Vector2 vector)
    {
        // This code is emitted exactly as listed. There are no hidden
        // copies as the `readonly` modifier indicates that the method
        // won't mutate `vector`.

        return vector.GetLengthReadonly();
    }
}

Alleen lezen kan worden toegepast op eigenschapstoegangers om aan te geven dat this niet wordt veranderd in de accessor. De volgende voorbeelden hebben alleen-lezen-setters omdat deze accessors de status van het lidveld wijzigen, maar niet de waarde van dat lidveld wijzigen.

public readonly int Prop1
{
    get
    {
        return this._store["Prop1"];
    }
    set
    {
        this._store["Prop1"] = value;
    }
}

Wanneer readonly wordt toegepast op de syntaxis van de eigenschap, betekent dit dat alle accessors readonlyzijn.

public readonly int Prop2
{
    get
    {
        return this._store["Prop2"];
    }
    set
    {
        this._store["Prop2"] = value;
    }
}

Readonly kan uitsluitend worden gebruikt voor accessors die het omvattende type niet wijzigen.

public int Prop3
{
    readonly get
    {
        return this._prop3;
    }
    set
    {
        this._prop3 = value;
    }
}

Alleen-lezen kan worden toegepast op sommige automatisch geïmplementeerde eigenschappen, maar dit heeft geen zinvol effect. De compiler behandelt alle automatisch geïmplementeerde getters als alleen-lezen, ongeacht of het readonly sleutelwoord aanwezig is.

// Allowed
public readonly int Prop4 { get; }
public int Prop5 { readonly get; set; }

// Not allowed
public int Prop6 { readonly get; }
public readonly int Prop7 { get; set; }
public int Prop8 { get; readonly set; }

Read-only kan worden toegepast op handmatig geïmplementeerde gebeurtenissen, maar niet op veld-achtige gebeurtenissen. Alleen-lezen kan niet worden toegepast op afzonderlijke gebeurtenistoegangsors (toevoegen/verwijderen).

// Allowed
public readonly event Action<EventArgs> Event1
{
    add { }
    remove { }
}

// Not allowed
public readonly event Action<EventArgs> Event2;
public event Action<EventArgs> Event3
{
    readonly add { }
    readonly remove { }
}
public static readonly event Event4
{
    add { }
    remove { }
}

Enkele andere syntaxisvoorbeelden:

  • Uitdrukking-lichamen leden: public readonly float ExpressionBodiedMember => (x * x) + (y * y);
  • Algemene beperkingen: public readonly void GenericMethod<T>(T value) where T : struct { }

De compiler verzendt het exemplaarlid, zoals gebruikelijk, en verzendt bovendien een herkend compilerkenmerk dat aangeeft dat het exemplaarlid de status niet wijzigt. Hierdoor wordt de verborgen this parameter in T in plaats van ref T.

Hierdoor kan de gebruiker de genoemde exemplaarmethode veilig aanroepen zonder dat de compiler een kopie hoeft te maken.

De beperkingen zijn onder andere:

  • De readonly-modifier kan niet worden toegepast op statische methoden, constructors of destructors.
  • De readonly wijzigingsfunctie kan niet worden toegepast op gemachtigden.
  • De readonly modifier kan niet worden toegepast op leden van klasse of interface.

Nadelen

Dezelfde nadelen als die van readonly struct methoden. Bepaalde code kan nog steeds verborgen kopieën veroorzaken.

Notities

Het gebruik van een kenmerk of een ander trefwoord is mogelijk.

Dit voorstel is enigszins gerelateerd aan (maar is meer een subset van) functional purity en/of constant expressions, die beide bestaande voorstellen hebben gehad.