只读实例成员Readonly Instance Members

倡导问题: https://github.com/dotnet/csharplang/issues/1710Championed Issue: https://github.com/dotnet/csharplang/issues/1710

摘要Summary

提供一种方法来指定结构上的单个实例成员不修改状态,其方式与 readonly struct 指定无实例成员修改状态的方式相同。Provide a way to specify individual instance members on a struct do not modify state, in the same way that readonly struct specifies no instance members modify state.

值得注意的是 readonly instance member ! = pure instance memberIt is worth noting that readonly instance member != pure instance member. pure实例成员保证不会修改任何状态。A pure instance member guarantees no state will be modified. readonly实例成员只保证不会修改实例状态。A readonly instance member only guarantees that instance state will not be modified.

上的所有实例成员均可 readonly struct 被视为隐式 readonly instance membersAll instance members on a readonly struct could be considered implicitly readonly instance members. readonly instance members 非只读结构中显式声明的行为方式相同。Explicit readonly instance members declared on non-readonly structs would behave in the same manner. 例如,如果您在当前实例上或在实例的某个字段上 (调用了实例成员,则它们仍会创建隐藏的副本) 该实例本身不是只读的。For example, they would still create hidden copies if you called an instance member (on the current instance or on a field of the instance) which was itself not-readonly.

动机Motivation

现在,用户可以创建 readonly struct 类型,编译器会强制所有字段都是 readonly (并且通过扩展,而不是实例成员修改状态) 。Today, users have the ability to create readonly struct types which the compiler enforces that all fields are readonly (and by extension, that no instance members modify the state). 但是,在某些情况下,你有一个公开可访问字段或混合了转变和非转变成员的现有 API。However, there are some scenarios where you have an existing API that exposes accessible fields or that has a mix of mutating and non-mutating members. 在这些情况下,不能将类型标记为 readonly (它会成为重大更改) 。Under these circumstances, you cannot mark the type as readonly (it would be a breaking change).

这通常不会产生很大的影响,但在参数情况下除外 inThis normally doesn't have much impact, except in the case of in parameters. 对于 in 非只读结构的参数,编译器将为每个实例成员调用创建参数的副本,因为它无法保证调用不会修改内部状态。With in parameters for non-readonly structs, the compiler will make a copy of the parameter for each instance member invocation, since it cannot guarantee that the invocation does not modify internal state. 与刚刚通过值直接传递结构相比,这可能会导致大量的副本和总体性能更差。This can lead to a multitude of copies and worse overall performance than if you had just passed the struct directly by value. 有关示例,请参阅sharplab上的此代码For an example, see this code on sharplab

可能发生隐藏副本的其他情况包括 static readonly fieldsliteralsSome other scenarios where hidden copies can occur include static readonly fields and literals. 如果以后支持这些支持, blittable constants 最终会出现在同一船上; 这就是所有当前都需要在实例成员调用 (完全复制) 如果未标记该结构 readonlyIf they are supported in the future, blittable constants would end up in the same boat; that is they all currently necessitate a full copy (on instance member invocation) if the struct is not marked readonly.

设计Design

通过,用户可以指定实例成员本身, readonly 而不会修改该实例的状态, (通过编译器完成的所有适当验证(当然) )。Allow a user to specify that an instance member is, itself, readonly and does not modify the state of the instance (with all the appropriate verification done by the compiler, of course). 例如:For example:

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 将不会在访问器中改变。Readonly can be applied to property accessors to indicate that this will not be mutated in the accessor. 下面的示例具有 readonly 资源库,因为这些访问器修改了成员字段的状态,但不修改该成员字段的值。The following examples have readonly setters because those accessors modify the state of member field, but do not modify the value of that member field.

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

readonly 应用于属性语法时,这意味着所有访问器都是 readonlyWhen readonly is applied to the property syntax, it means that all accessors are readonly.

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

Readonly 只能应用于不会改变包含类型的访问器。Readonly can only be applied to accessors which do not mutate the containing type.

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

Readonly 可以应用于某些自动实现的属性,但它不会产生有意义的效果。Readonly can be applied to some auto-implemented properties, but it won't have a meaningful effect. 无论是否存在关键字,编译器都将所有自动实现的 getter 视为只读 readonlyThe compiler will treat all auto-implemented getters as readonly whether or not the readonly keyword is present.

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

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

Readonly 可应用于手动实现的事件,但不能应用于类似于字段的事件。Readonly can be applied to manually-implemented events, but not field-like events. Readonly 不能应用于单个事件访问器 (添加/移除) 。Readonly cannot be applied to individual event accessors (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 { }
}

其他一些语法示例:Some other syntax examples:

  • Expression expression-bodied 成员: public readonly float ExpressionBodiedMember => (x * x) + (y * y);Expression bodied members: public readonly float ExpressionBodiedMember => (x * x) + (y * y);
  • 泛型约束: public readonly void GenericMethod<T>(T value) where T : struct { }Generic constraints: public readonly void GenericMethod<T>(T value) where T : struct { }

通常情况下,编译器将发出实例成员,并且还会发出一个编译器可识别的属性,指示该实例成员不会修改状态。The compiler would emit the instance member, as usual, and would additionally emit a compiler recognized attribute indicating that the instance member does not modify state. 这实际上会导致隐藏 this 参数 in T 而不是 ref TThis effectively causes the hidden this parameter to become in T instead of ref T.

这样,用户就可以安全地调用所说的实例方法,无需编译器创建副本。This would allow the user to safely call said instance method without the compiler needing to make a copy.

这些限制包括:The restrictions would include:

  • readonly修饰符不能应用于静态方法、构造函数或析构函数。The readonly modifier cannot be applied to static methods, constructors or destructors.
  • readonly修饰符不能应用于委托。The readonly modifier cannot be applied to delegates.
  • readonly修饰符不能应用于类或接口的成员。The readonly modifier cannot be applied to members of class or interface.

缺点Drawbacks

目前的方法存在相同的缺点 readonly structSame drawbacks as exist with readonly struct methods today. 某些代码可能仍会导致隐藏的副本。Certain code may still cause hidden copies.

备注Notes

还可以使用属性或其他关键字。Using an attribute or another keyword may also be possible.

此提议在某种程度上与 (相关,但更多是) functional purity 和/或的一部分 constant expressions ,这两者都有一些现有的提议。This proposal is somewhat related to (but is more a subset of) functional purity and/or constant expressions, both of which have had some existing proposals.