共用方式為


唯讀實例成員

注意

本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。

功能規格與已完成實作之間可能有一些差異。 這些差異是在的相關 語言設計會議(LDM)記錄中擷取的。

您可以在 規格一文中深入瞭解將功能描述文件納入 C# 語言標準的過程

倡導議題:https://github.com/dotnet/csharplang/issues/1710

總結

提供一種方式在結構中明確指定個別實例成員不會修改狀態,就像 readonly struct 指定任何實例成員不修改狀態一樣。

值得注意的是,唯讀實例成員 != 純實例成員 實例成員保證狀態不會被更改。 readonly 實例成員只保證不會修改實例狀態。

readonly struct 上的所有實例成員都可以視為隱含 只讀實例成員, 明確 只讀實例成員 以相同方式宣告。 例如,如果您呼叫了一個非唯讀的實例成員(無論是在目前實例上還是實例的欄位上),那麼它們仍然會建立隱藏的複本。

動機

在 C# 8.0 之前,用戶可以建立 readonly struct 類型,編譯器強制規定所有欄位都是唯讀的,並且實例成員不修改狀態。 不過,在某些情況下,您有一些 API 會公開可存取的欄位,或混合變動和非變動成員。 在這些情況下,您無法將類型標示為 readonly(這將是重大變更)。

這通常不會有太大的影響,但 in 參數的情況除外。 當使用具有 in 參數的非只讀結構時,編譯器會為每個實例成員調用製作參數的副本,因為它無法保證調用不會修改內部狀態。 這可能會導致許多複本,使整體效能比直接以值傳遞結構更差。 如需範例,請參閱 sharplab 上的此程序代碼

其他可能發生隱藏複本的情境包括 static readonly 欄位字面值。 如果將來得到支援, 的 blittable 常數最終會面臨相同的情況:也就是說,如果結構未標示為 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;
    }
}

唯讀只能套用至不會變動包含類型的存取子。

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

Readonly 可以套用至某些自動實作的屬性,但不會有有意義的效果。 編譯程式會將所有自動實作的 getter 視為唯讀,不論 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; }

Readonly 可以套用至手動實作的事件,但不能套用類似字段的事件。 只讀無法套用至個別事件存取子("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 { }
}

其他一些語法範例:

  • 表達式主體成員: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,這兩者都已有一些現有的提案。