はじめに
C#の定数について調べていると、 constよりstatic readonlyを使いましょう という記載をよく見かけますが、本当でしょうか。
本当にstatic readonlyはconstの代用になり、不変であると信じていいのでしょうか。
ダメです。static readonlyな変数の中身は書き換えることができる場合があります。
という話をしていきます。
先に結論
絶対に変わってほしくないconstのかわりにはstaticでreadonlyなpropertyをつかいましょう。
static readonlyの中身を書き換える方法
まずは実際にstatic readonlyの中身を書き換えてみましょう。
using UnityEngine;
namespace SandBox
{
public class Test : MonoBehaviour
{
//static readonlyな変数.
static readonly MyClass readonlyVariable = new("FIRST INPUT");
//内部に状態を持つクラス.
class MyClass
{
public string text;
public MyClass(string input)
{
text = input;
}
}
private void Start()
{
//ここではFIRST INPUTである.
Debug.Log(readonlyVariable.text);
//内部の状態を変えようと試みる.
readonlyVariable.text = "NEXT INPUT";
//ここでは...?
Debug.Log(readonlyVariable.text);
}
}
}
結果は・・・
書き込みができてしまいました。これではプログラム全体を通して変数が不変とはいえません。
上の例ならわかりやすいですが、static readonlyで定数のように扱いたいList<>が外部から空にされていたら・・・?
read only(読み取り専用)とはとういうことだったのでしょうか?
「読み取り専用」は「不変であること」とは違う
readonlyを付けた変数には、代入することができなくなります。
ただし、代入を禁止しているだけで、値の普遍性を保証しているわけではないのです。
結論 : staticでreadonlyなpropertyをつかいましょう
constのように使える不変な変数が実現したければ、
static readonlyなpropertyなら値が変わることがありません。
例を見てみましょう。
using UnityEngine;
namespace SandBox
{
public class Test : MonoBehaviour
{
//static readonlyなpropertyに変更.
static MyClass readonlyProperty => new("FIRST INPUT");
//内部に状態を持つクラス.
class MyClass
{
public string text;
public MyClass(string input)
{
text = input;
}
}
private void Start()
{
//ここではFIRST INPUTである.
Debug.Log(readonlyProperty.text);
//内部の状態を変えようと試みる.
readonlyProperty.text = "NEXT INPUT";
//ここでは...?
Debug.Log(readonlyProperty.text);
}
}
}
もう少し詳しい解説
以下の2つの例では、同じように見えて実は違うふるまいをする書き方を並べました。
//不変
static MyClass ReadonlyProperty => new("FIRST INPUT");
//不変ではない
static MyClass GetonlyProperty { get; } = new("FIRST INPUT");
これらをそれぞれ、同じ意味のコードでわかりやすく書き換えます。
//不変
static MyClass ReadonlyProperty
{
get
{
return new("FIRST INPUT");
}
}
//不変ではない
private static readonly MyClass getonlyProperty = new("FIRST INPUT");
static MyClass GetonlyProperty
{
get
{
return getonlyProperty;
}
}
上の例では、変数のように振る舞いつつも実は変数を持っておらず、毎回インスタンスを作っていることがわかります。
なので、何度ReadonlyPropertyを参照しようとも、参照するたびに新しいインスタンスが返されるため書き換えることができないのでした。
下の例ではstatic readonlyな変数をもちつつも、readonlyは不変であるわけではないため、
変数の参照を返した先で内容を書き換えることができてしまうのでした。
毎回インスタンスを生成することになるのでメモリアロケーションの面では難点がありますが、不変性が確保できるという点との天秤ですね。