前書き
この記事は、2023のUnityアドカレの12/10の記事です。
今年は、完走賞に挑戦してみたいと思います。Qiita君ぬい欲しい!
再代入不可能なローカル変数
C++やJavaScript、Goなどにはconst変数という機能があり、任意の型のローカル変数に対してconstと修飾することで、その変数を再代入不可能にすることができます。
長いコードの中で、寿命も長い(スコープが広い)変数を扱うとき、うっかりどこかで書き換えられてしまうといったミスを防ぐことができます。
C#には、それらと同等の機能はないのですが、類似機能と、何とか工夫して実現する方法をご紹介します。
const
最もすぐに思いつくものはconst変数でしょう。C#にも一応const修飾されたローカル変数の機能はあります。
const int const_primitive = 0;
const_primitive = 1; // error
しかし、これはプリミティブ型(int, floatなど)以外には利用できません。
// これはできない
const TimeSpan const_struct = TimeSpan.FromSeconds(0);
const Regex const_class = new Regex(".*");
ref readonly
ref readonly
は参照先の書き換えを禁止することができます。これは値型(プリミティブ型を含む)だけでなく参照型に対しても利用できます。
TimeSpan mutable_struct = TimeSpan.FromSeconds(0);
readonly ref TimeSpan readonly_struct = ref mutable_struct;
Regex mutable_class = new Regex(".*");
readonly ref Regex readonly_class = ref mutable_struct;
しかし、書き換え不能なのは参照先だけであり、参照自体を変更することはできてしまうのです。
Regex mutable_class = new Regex(".*");
readonly ref Regex readonly_class = ref mutable_struct;
readonly_class = mutable_struct; // error
readonly_class = ref mutable_struct; // 合法
using
using変数は変更できません。
using var using_var = new FileStream(".*");
using_var = new FileStream("a.txt"); // error
usingの本来の用途は、IDisposableを実装した型のインスタンスを、スコープを抜けたときに自動Disposeするという目的です。
{
using var using_var = new FileStream(".*");
...
}
// スコープを抜けたのでusing_var.Dispose()が自動で呼び出される
つまり、IDisposable以外の型の変数には使えません。
Disposableをラップして汎用化する
元の型と相互利用可能なラッパークラスを作ってみます。
struct Const<T> : IDisposable
{
public T Value { get; private set; }
private Const(T value) => Value = value;
public static implicit Const<T>(T value) => new Const<T>(value);
public static implicit T(Const<T> cValue) => cValue.Value;
}
このように暗黙のキャストを使うことで、ラップ/アンラップを隠すことができます。
using Const<Regex> my_const = new Regex(".*");
my_const = new Regex(".*"); // error
// 自動でアンラップされる
Process("hoge", reg);
static void Process(string text, Regex reg);
しかし、メンバを利用したいときはValueにアクセスするしかありません。ライブラリでトークンやハンドラとして提供するような型であればそれでもいいかもしれませんが、これを受け入れてまで再代入不可能に拘りたいかといわれれば、微妙なところです。
using Const<Regex> my_const = new Regex(".*");
my_const.Value.IsMatch("hoge"); // メンバを利用したいときはValueにアクセスするしかない
まとめ
結構惜しいところまでは行った気がするのですが、実務で使いたいとは思えませんね…。言語機能としてあってくれると嬉しいですが…
何かいい方法をご存知でしたらコメントいただけましたら幸いです。