【Unity】 Assembly Definition Files を使った上でinternalにアクセスする方法

# 追記

さらに**ヤバい**やり方があるらしいです

* [【Unity, C#】internalな型やメンバにアクセスするには、多分これが一番早いと思います](https://qiita.com/mob-sakai/items/f3bbc0c45abc31ea7ac0)


# 今回の話の内容を2行で

`Assembly Definition Files`を使ってdllを分けると、テストのときに`iternal`なアクセスレベルの要素にアクセスができずに困る。
対策として、`AssemblyInfo.cs`を書こうという話。

```cs:AssemblyInfo.cs
#if UNITY_EDITOR
using System.Runtime.CompilerServices;

// 指定したdllから、internalアクセスレベルへのアクセスを許可する
[assembly: InternalsVisibleTo("MyLibrary.Test")] //adfで定義した名前と一致させる
#endif
```

# 本題

## Assembly Definition Files

Unityには、`Assembly Definition Files`(以下`adf`)という機能があります。
これは`adf`ファイルが置かれたディレクトリのスクリプトを別のdllに分けてコンパイルするという機能です。

`adf`ファイルを使うことで、プロジェクト本体とは独立性が高い部分を別のライブラリに切り出して依存関係を管理するなど、プロジェクト構成の管理に使うことができます。

詳しくは[テラシュールブログさんのこの記事](http://tsubakit1.hateblo.jp/entry/2018/01/18/212834)を参考にしてください。

## C#のinternalアクセシビリティ

話が変わって、C#には`iternal`というアクセスレベルが用意されています。
これは「同一のプロジェクト内（同一dll内）からのみアクセス可能にする」というアクセスレベルです。



## Assembly Definition Filesとinternalを組み合わせる

`adf`と`internal`を組み合わせることで、ライブラリが提供するオブジェクトの利用方法を制限することができます。
（ライブラリ内では制約なしで自由にメソッドコールできるが、ユーザからは一部のメソッドしか利用できないなど）

### 使用例:コンストラクタをinternalにする

たとえば、こんなクライアントがあったとします。

```cs
using System;

namespace MyLibrary
{
    /// <summary>
    /// 何かのクライアントがあって
    /// </summary>
    public class HogeClient
    {
        private string _token;

        /// <summary>
        /// インスタンス化にはTokenが必要なのだが、
        /// このTokenを使ったコンストラクタはライブラリ外に露出させたくないので internal
        /// </summary>
        /// <param name="token"></param>
        internal HogeClient(string token)
        {
            _token = token;
        }
    }

    /// <summary>
    /// Clientのファクトリー
    /// </summary>
    public class HogeClientFactory
    {
        /// <summary>
        /// Clientの生成時に、Tokenを取得して埋め込む
        /// 不正なTokenを埋め込んだ状態でClientは生成できない
        /// </summary>
        public HogeClient CreateClient()
        {
            var token = FetchToken();
            if (!ValidationToken(token)) throw new Exception("Invalid token.");

            return new HogeClient(token);
        }

        private string FetchToken()
        {
            /**
             * ScriptableObjectとか、サーバとか、何らかの方法で
             * Tokenを取得する想定
             */

            return "sample_token";
        }

        private bool ValidationToken(string token)
        {
            /**
             * Tokenの有効性を確認したりとか？
             */

            return true;
        }
    }
}
```

ここに、次のような`adf`ファイルを定義し、`HogeClient`を別のdllとして分離します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47146/0ee905d0-27e6-ccd6-c239-5d642b7fda74.png)


こうすることで、`HogeClient`のコンストラクタは外部からアクセス不可になり、必ずファクトリ経由でのインスタンス化をユーザに強制することができるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47146/ebba5c15-5bf5-89ec-d2a4-5f470924a918.png)


# 問題点:テストの時どうするの？

`adf`と`internal`を組み合わせることで、ライブラリの使用方法を制限することができるようになりました。
が、**ライブラリのテストを書きたいときはこの制限は非常に邪魔になります。**

## 例:さっきのクライアントのテストを書きたい

テスト用の`adf`が切られていたとして。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47146/cfce9185-930c-b3b4-d81f-719893de7446.png)


この状態でテストを書くと、`internal`にやはりアクセスできないので`HogeClient`が初期化できない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47146/d6295699-defb-ddcf-7ed1-69bcfbadc2b8.png)

# 解決策:AssemblyInfo.csを配置する

解決策として、`adf`ファイルが置かれている階層に`AssemblyInfo.cs`を配置して次のコードを書くことで対処できます。


```cs:AssemblyInfo.cs
#if UNITY_EDITOR
using System.Runtime.CompilerServices;

// MyLibrary.Testプロジェクトに対してはinternalアクセスを許可する
[assembly: InternalsVisibleTo("MyLibrary.Test")] //adfで定義した名前と一致させる
#endif
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47146/1a1fc0b9-024e-3304-ed9e-ddc0dda0d647.png)




`AssemblyInfo.cs`内に`InternalsVisibleTo`を書くことで、特定のプロジェクト（dll）に対してのみinternalアクセスを許可することができます。
これでテスト用ライブラリに対して許可をしてあげることで、テスト時にinternalアクセスが可能になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47146/ca8271a1-f7d2-a0b5-f678-3fbbf2ce768d.png)