Поделиться через


Члены экземпляра только для чтения

Заметка

Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.

Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия фиксируются в соответствующих собраниях по проектированию языка (LDM).

Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .

Проблема чемпиона: <https://github.com/dotnet/csharplang/issues/1710>

Сводка

Укажите способ, позволяющий обозначить отдельные члены экземпляра в структуре, которые не изменяют состояние, таким же образом, как readonly struct указывает, что члены экземпляра не изменяют состояние.

Следует отметить, что член экземпляра только для чтения != простой член экземпляра. чистый элемент экземпляра гарантирует, что состояние не будет изменено. Элемент экземпляра readonly гарантирует, что состояние экземпляра не будет изменено.

Все члены экземпляра в readonly struct могут считаться неявно члены экземпляра чтения явные члены экземпляра, объявленные нечитаемым образом. Например, они по-прежнему будут создавать скрытые копии, если вы вызвали член экземпляра (в текущем экземпляре или в поле экземпляра), который сам не был прочитан.

Мотивация

До C# 8.0 пользователи имели возможность создавать readonly struct типы, для которых компилятор обеспечивает, что все поля только для чтения, и, следовательно, члены экземпляра не изменяют состояние. Однако есть сценарии, где имеется существующий API, предоставляющий доступные поля, или API, имеющий сочетание изменяющих и неизменяющих членов. В этих обстоятельствах тип нельзя пометить как readonly (это было бы критическое изменение).

Обычно это не оказывает большого влияния, за исключением параметров in. При использовании in параметров для нечитаемых структур компилятор сделает копию параметра для каждого вызова члена экземпляра, так как он не может гарантировать, что вызов не изменяет внутреннее состояние. Это может привести к множеству копий и снижению общей производительности, чем если бы вы просто передали структуру непосредственно по значению. Пример см. в этом коде sharplab

Некоторые другие сценарии, в которых могут возникать скрытые копии, включают static readonly поля и литералы. Если в будущем они будут поддерживаться, константы в конечном итоге окажутся в аналогичной ситуации. То есть в настоящее время все они требуют полного копирования при вызове члена экземпляра, если структура не помечена как readonly.

Дизайн

Разрешить пользователю указать, что член экземпляра сам является readonly и не изменяет состояние экземпляра (при обеспечении всех необходимых проверок, выполняемых компилятором, конечно). Например:

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 можно применить к аксессорам свойств, чтобы указать, что this не будет изменяться в аксессоре. В следующих примерах используются сеттеры только для чтения, так как эти аксессоры изменяют состояние поля, но не изменяют его значения.

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

Если readonly применяется к синтаксису свойств, это означает, что все методы доступа readonly.

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

ReadOnly можно применять только к аксессорам, которые не изменяют содержащий тип.

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

Только для чтения можно применить к некоторым автоматически реализованным свойствам, но это не окажет значительного эффекта. Компилятор будет обрабатывать все автоматически реализованные геттеры как только для чтения, независимо от того, присутствует ли ключевое слово readonly.

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

Атрибут "только для чтения" можно применять к событиям, реализованным вручную, но не к событиям, похожим на поля. Только для чтения не может быть применено к отдельным акцессорам событий (добавление и удаление).

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

Некоторые другие примеры синтаксиса:

  • Элементы выражения: public readonly float ExpressionBodiedMember => (x * x) + (y * y);
  • Универсальные ограничения: public readonly void GenericMethod<T>(T value) where T : struct { }

Компилятор генерирует член экземпляра, как обычно, и дополнительно создает распознаваемый компилятором атрибут, указывающий на то, что член экземпляра не вносит изменения в состояние. Это приводит к тому, что скрытый параметр this становится in T вместо ref T.

Это позволит пользователю безопасно вызывать указанный метод экземпляра без необходимости для компилятора создавать копию.

К ограничениям относятся следующие:

  • Модификатор readonly нельзя применять к статическим методам, конструкторам или деструкторам.
  • Модификатор readonly нельзя применить к делегатам.
  • Модификатор readonly нельзя применить к членам класса или интерфейса.

Недостатки

Те же недостатки, что и существующие с readonly struct методами сегодня. Некоторый код по-прежнему может создавать скрытые копии.

Примечания

Использование атрибута или другого ключевого слова также может быть возможным.

Это предложение в некоторой степени связано с (но больше относится к подмножеству) functional purity и/или constant expressions, оба из которых уже имели некоторые предложения.