8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Unity】ローカル変数のreadonly(読み取り専用)を実現する方法【C#】

8
Posted at

はじめに

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 ローカル変数には使えません。
他に相当する予約語が見あたりませんでした。

多言語での書き方

.cpp
// C++
my_class* const myc = new my_class(); 
.java
// Java
final MyClass myc = new MyClass(); 
.js
// JavaScript
const foo = new Foo();

他の言語には変数を読み取り専用化するためのキーワードがあるみたいです。

C#での実現方法

いろいろ調べても「できない」という結論が多かったのですが、using()の副作用を使ったやり方を思いついたので紹介します。

参照を書き換えできないラッパークラスを作ってusing()を使います。

foo.cs
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>クラスの代わりにUniRxReactiveProperty,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行で書けるので短く書けそうです。

参考

8
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?