Udostępnij za pośrednictwem


Członkowie instancji tylko do odczytu

Notatka

Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.

Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są zawarte w stosownych notatkach ze spotkania projektowania języka (LDM) .

Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .

Problem z mistrzem: <https://github.com/dotnet/csharplang/issues/1710>

Streszczenie

Podaj sposób określania poszczególnych elementów składowych wystąpienia w strukturze, które nie modyfikują stanu, w taki sam sposób, jak readonly struct określa, że żadne elementy członkowskie wystąpienia nie modyfikują stanu.

Warto zauważyć, że != czystego elementu członkowskiego wystąpienia. czysta składowa instancji gwarantuje, że żaden stan nie zostanie zmodyfikowany. Członek instancji readonly gwarantuje, że stan instancji nie zostanie zmodyfikowany.

Wszystkie elementy członkowskie wystąpień w można uznać za niejawnie jawnych zadeklarowanych na nieczytnych strukturach zachowywałyby się w ten sam sposób. Na przykład nadal będą tworzyć ukryte kopie, jeśli wywołano element członkowski wystąpienia (w bieżącym wystąpieniu lub w polu wystąpienia), który sam był nie do odczytu.

Motywacja

Przed C# 8.0 użytkownicy mieli możliwość tworzenia typów readonly struct, które kompilator wymusza, aby wszystkie pola były tylko do odczytu (i co z tego wynika, że żaden z członków instancji nie modyfikuje stanu). Istnieją jednak pewne scenariusze, w których masz istniejące API, które udostępnia dostępne pola lub ma kombinację mutujących i niemutujących członków. W tych okolicznościach nie wolno oznaczyć typu jako readonly (byłaby to zmiana zakłócająca kompatybilność).

Zwykle nie ma to dużego wpływu, z wyjątkiem parametrów in. W przypadku parametrów in dla nieczytanych struktur kompilator utworzy kopię parametru dla wywołania każdego wystąpienia członkowskiego, ponieważ nie może zagwarantować, że wywołanie nie modyfikuje stanu wewnętrznego. Może to prowadzić do wielu kopii i gorszej ogólnej wydajności niż w przypadku bezpośredniego przekazania struktury przez wartość. Aby zapoznać się z przykładem, zobacz ten kod na sharplab

Niektóre inne scenariusze, w których mogą wystąpić ukryte kopie, obejmują pola static readonly oraz literały . Jeśli będą one obsługiwane w przyszłości, stałe blittable znajdą się w tej samej sytuacji; oznacza to, że wszystkie obecnie wymagają pełnej kopii podczas wywoływania członka instancji, jeśli struktura nie jest oznaczona readonly.

Projekt

Pozwól użytkownikowi określić, że członek instancji sam w sobie jest readonly i nie modyfikuje stanu instancji (oczywiście przy zachowaniu wszystkich odpowiednich weryfikacji przez kompilator). Na przykład:

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 można zastosować do akcesorów właściwości, aby pokazać, że this nie zostanie zmodyfikowany w akcesorze. W poniższych przykładach przedstawiono settery tylko do odczytu, ponieważ te akcesory modyfikują stan pola składowego, ale nie modyfikują wartości tego pola składowego.

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

Po zastosowaniu readonly do składni właściwości oznacza to, że wszystkie metody dostępu są readonly.

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

Readonly można stosować tylko do metod dostępu, które nie mutują typu zawierającego.

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

Funkcja Readonly może być stosowana do niektórych właściwości implementowanych automatycznie, ale nie będzie miała znaczącego wpływu. Kompilator traktuje wszystkie automatycznie zaimplementowane metody pobierania jako readonly, niezależnie od tego, czy słowo kluczowe readonly jest obecne.

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

Modyfikator tylko do odczytu można zastosować do zdarzeń implementowanych ręcznie, ale nie do zdarzeń przypominających pola. Nie można zastosować modyfikatora readonly do poszczególnych akcesorów zdarzeń (add/remove).

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

Kilka innych przykładów składni:

  • Elementy z wyrażeniowym ciałem funkcji: public readonly float ExpressionBodiedMember => (x * x) + (y * y);
  • Ograniczenia ogólne: public readonly void GenericMethod<T>(T value) where T : struct { }

Kompilator emituje element członkowski wystąpienia w zwykły sposób i dodatkowo emituje rozpoznany atrybut kompilatora wskazujący, że element członkowski wystąpienia nie modyfikuje stanu. Dzięki temu ukryty parametr this staje się in T zamiast ref T.

Pozwoliłoby to użytkownikowi bezpiecznie wywołać metodę instancji bez konieczności tworzenia kopii przez kompilator.

Ograniczenia obejmowałyby:

  • Modyfikator readonly nie może być stosowany do metod statycznych, konstruktorów lub destruktorów.
  • Modyfikator readonly nie może być stosowany do delegatów.
  • Modyfikator readonly nie może być stosowany do składowych klasy lub interfejsu.

Wady i niedociągnięcia

Te same wady, jakie istnieją w przypadku metod readonly struct dzisiaj. Niektóre kody mogą nadal powodować ukryte kopie.

Notatki

Użycie atrybutu lub innego słowa kluczowego może być również możliwe.

Wniosek ten jest nieco związany z functional purity i/lub constant expressions, ale raczej stanowi ich podzbiór. Obie te propozycje miały wcześniejsze propozycje.