Dela via


Skrivskyddade instansmedlemmar

Note

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningar (Language Design Meeting).

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-problem: <https://github.com/dotnet/csharplang/issues/1710>

Sammanfattning

Ange ett sätt att ange enskilda instansmedlemmar i en struct ändrar inte tillstånd, på samma sätt som readonly struct anger att inga instansmedlemmar ändrar tillstånd.

Det är värt att notera att readonly-instansmedlem != ren instansmedlem. En ren instansmedlem garanterar att ingen stat kommer att ändras. En readonly-instansmedlem garanterar endast att instanstillståndet inte ändras.

Alla instansmedlemmar på en readonly struct kan betraktas som implicit skrivskyddade instansmedlemmar. Explicit skrivskyddade instansmedlemmar som deklareras på icke-skrivskyddade strukturer skulle bete sig på samma sätt. De skulle till exempel fortfarande skapa dolda kopior om du anropade en instansmedlem (på den aktuella instansen eller i ett fält i instansen) som i sig inte var skrivskyddat.

Motivation

Innan C# 8.0 hade användarna möjlighet att skapa readonly struct-typer som kompilatorn ser till att alla fält är skrivskyddade (och därmed ser till att inga instansmedlemmar kan förändra tillståndet). Det finns dock vissa scenarier där du har ett befintligt API som exponerar tillgängliga fält eller som har en blandning av muterande och icke-muterande medlemmar. Under dessa omständigheter kan du inte markera typen som readonly (det skulle vara en icke-bakåtkompatibel ändring).

Detta har normalt inte någon större inverkan, förutom när det gäller in parametrar. Med parametrar med in för icke-skrivskyddade strukturer gör kompilatorn en kopia av parametern för varje instansmedlemsanrop, eftersom den inte kan garantera att anropet inte ändrar det interna tillståndet. Detta kan leda till en mängd kopior och sämre övergripande prestanda än om du bara hade skickat strukturen direkt som värde. Ett exempel finns i den här koden på sharplab

Några andra scenarier där dolda kopior kan förekomma inkluderar static readonly fält och literaler. Om de stöds i framtiden skulle blittable-konstanterna hamna i samma situation, det vill säga de kräver för närvarande alla en fullständig kopia (vid anrop av instansmedlem) om strukturen inte är markerad readonly.

Design

Tillåt att en användare anger att en instansmedlem i sig är readonly och inte ändrar tillståndet för instansen (med all lämplig verifiering som görs av kompilatorn, naturligtvis). Till exempel:

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();
    }
}

Readonly kan tillämpas på egenskapsaccessorer för att indikera att this inte muteras i accessorn. Följande exempel har readonly setters eftersom dessa accessorer ändrar medlemsfältets tillstånd, men ändrar inte värdet för det medlemsfältet.

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

När readonly tillämpas på egenskapens syntax betyder det att alla accessorer är readonly.

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

Skrivskyddad kan endast tillämpas på åtkomster som inte muterar den innehållande typen.

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

Readonly kan tillämpas på vissa automatiskt implementerade egenskaper, men det har ingen meningsfull effekt. Kompilatorn behandlar alla autoimplementerade metoder som endast läsbara, oavsett om nyckelordet readonly finns eller inte.

// 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; }

Readonly kan tillämpas på manuellt implementerade händelser, men inte fältliknande händelser. Readonly kan inte tillämpas på enskilda händelseåtkomster (lägg till/ta bort).

// 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 { }
}

Några andra syntaxexempel:

  • Uttryckskroppsmedlemmar: public readonly float ExpressionBodiedMember => (x * x) + (y * y);
  • Allmänna begränsningar: public readonly void GenericMethod<T>(T value) where T : struct { }

Kompilatorn genererar som vanligt instansmedlemmen och genererar dessutom ett kompilatorattribut som anger att instansmedlemmen inte ändrar tillstånd. Detta gör att den dolda this-parametern blir in T i stället för ref T.

Detta skulle göra det möjligt för användaren att på ett säkert sätt anropa instansmetoden utan att kompilatorn behöver göra en kopia.

Begränsningarna skulle omfatta:

  • Den readonly modifieraren kan inte tillämpas på statiska metoder, konstruktorer eller destruktorer.
  • Det går inte att tillämpa readonly-modifieraren på ombud.
  • Den readonly modifieraren kan inte tillämpas på medlemmar i klassen eller gränssnittet.

Nackdelar

Samma nackdelar som finns med readonly struct metoder idag. Viss kod kan fortfarande orsaka dolda kopior.

Anteckningar

Det kan också vara möjligt att använda ett attribut eller ett annat nyckelord.

Detta förslag är något relaterat till (men är mer en delmängd av) functional purity och/eller constant expressions, som båda har haft några befintliga förslag.