唯讀實例成員
注意
本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。
功能規格與已完成實作之間可能有一些差異。 這些差異是在的相關
倡導議題: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
,這兩者都已有一些現有的提案。