はじめに
C++を書いてた人が「C#はローカル変数を読み取り専用にできない」と嘆いていたので実現方法を考えてみました。
やりたいこと
C#の文法で分からない事があります。
読み取り専用の参照型ローカル変数を作る方法はあるのでしょうか?
他の言語で例を示しますと
・C++では my_class* const myc = new my_class();
・Javaでは final MyClass myc = new MyClass();
と書きますがC#ではどう書くのかが分かりません。
ひょっとして方法がないですか !?
読み取り専用にしたい目的は
(1)プログラムの読み手に、スコープを抜けるまで変更しない意志を伝える。
(2)コンパイラーに変数の不変を保証させる。
(3).NETランタイムの最適化を控えめに期待する。
(4)僕の好み。(^^;)
調べた内容
・const ローカル変数には使えるのですが、参照型はstringとnullしか指定出来ません。
・readonly ローカル変数には使えません。
他に相当する予約語が見あたりませんでした。
多言語での書き方
// C++
my_class* const myc = new my_class();
// Java
final MyClass myc = new MyClass();
// JavaScript
const foo = new Foo();
他の言語には変数を読み取り専用化するためのキーワードがあるみたいです。
C#での実現方法
いろいろ調べても「できない」という結論が多かったのですが、using()の副作用を使ったやり方を思いついたので紹介します。
参照を書き換えできないラッパークラスを作ってusing()を使います。
using System;
using UnityEngine;
public class FooScript : MonoBehaviour
{
void Start()
{
using (var foo = new Readonly<Foo>(new Foo(1)))
{
// valueを通じてインスタンスにアクセス可能
Debug.Log("id: " + foo.value.id);
// 再代入はコンパイルエラー:
// Using variable 'foo' is immutable. The assignment target must be an assignable variable, property or indexer
foo = null;
}
}
}
public class Foo
{
public readonly int id;
public Foo(int id)
{
this.id = id;
}
}
public class Readonly<T> : IDisposable
{
public readonly T value;
public Readonly(T value)
{
this.value = value;
}
void IDisposable.Dispose()
{
}
}
using()に渡した変数は再代入不可になります。再代入するコードはコンパイルエラーになるので、やりたいことは実現できています。
-
using()が必須 -
using()に渡すインスタンスに制限がある-
IDisposableを実装している必要がある - スコープを抜けるときに
Dispose()が呼ばれても大丈夫な実装である必要がある
-
-
using()の副作用を利用している - 一行で書けない、長い
なので推奨はしませんが、どうしてもやりたくなったときに実装コストとトレードオフで検討してみてください。
ちなみに自分で実装したReadonly<T>クラスの代わりにUniRxのReactiveProperty,ReadOnlyReactivePropertyを使うこともできます。
using (var foo = new ReactiveProperty<Foo>(new Foo(1)).ToReadOnlyReactiveProperty())
{
Debug.Log("id: " + foo.Value.id);
foo = null; // この行はコンパイルエラー
}
C# 8.0だともっと短く書けるかも
using var a = new DeferredMessage("a");
C# 8.0だとusingが1行で書けるので短く書けそうです。