【Unity】asmref（asmdefではない）を使うとinternalアクセスできて便利【Assembly Definition Reference Files】

# はじめに
Assembly Definition Files（ `*.asmdef` ）については解説を見かけるのですがAssembly Definition Reference Files（ `*.asmref` ）についての解説を見かけないのでメモがてら書き残します。

# Assembly Definition Files
影響下にあるスクリプトを一つのDLLとしてコンパイルすることで全体のコンパイル時間を短縮してくれたりするやつです。

参考：

* [Unity Assembly Definition 完全に理解した](https://qiita.com/toRisouP/items/d206af3029c7d80326ed)
* [【Unity】「AssemblyDefinitionを分けるとコンパイルが高速になる」ってホント？検証してみた！](https://virtualcast.jp/blog/2020/01/asmdef_compile_speed/)
* [【Unity】asmdefのVersion Definesを使って特定のPackageがインストールされていたら自動的に処理を変える](https://light11.hatenadiary.com/entry/2021/07/19/200401)

# Assembly Definition Reference Files
[Unity2019.2.0で追加された機能](https://unity3d.com/jp/unity/alpha/2019.2.0a6)で、

>Scripting: Added support for Assembly Definition Reference Files (asmref). These allow for adding additional source code directories to an existing Assembly Definition File (asmdef).

DeepL翻訳：
>アセンブリ定義参照ファイル（asmref）のサポートを追加しました。これにより、既存のアセンブリ定義ファイル(asmdef)にソースコードのディレクトリを追加することができます。

とのことです。

機能としてはシンプルで、

* 自身の影響下にあるスクリプトを指定した `*.asmdef` と同じDLLファイルに含める
* 以下のスクリプトファイルに影響する（ `*.asmdef` と同じ）
    * `*.asmref` が存在するディレクトリ内
    * 同ディレクトリ内に存在するディレクトリ、その子,孫,..（以下ループ）ディレクトリ
        * 子ディレクトリを辿る途中で別の `*.asmref` (X)が存在したら、それ以下は(X)の影響下になる

となっています。

作成方法は `*.asmdef` とほぼ同じで、

　　Projectウィンドウを右クリック → `Create` → `Assembly Definition Reference`

で作成できます。


実際に `*.asmref` ファイルを作ってInspectorで確認するとこんな感じ。

![スクリーンショット 2021-09-21 16.11.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/5b398a7b-8cef-a4da-3304-2db84bf92ce2.png)

ファイルの実態は短いテキストファイルで、開いて確認してみると

`Use GUID`がオンのとき：

```
{
    "reference": "GUID:d00ea8eea3b8a4df79ba6bcba16173ef"
}
```
`Use GUID`がオフのとき：

```
{
    "reference": "AutoScreen"
}
```

といった感じになります。

# 用途
とりあえず思いついたやつを３つほどご紹介します。

## *.asmdefによって強制されるスクリプトの配置を好きに変更できる
`*.asmdef` を使用するとディレクトリ構造がある程度強制されますが、`*.asmref` を使うことで `*.asmdef` の配置場所とは関係なくスクリプトを配置できます。

ちなみに同一の `*.asmdef` を参照する `*.asmref` は複数追加できます。

## アセットやPackage Managerから入れたパッケージに外から手を加える
例えば [UniRx](https://github.com/neuecc/UniRx) には `Observable.Using()` が実装されてないのですが、これを追加するためにリポジトリをForkして手を加えたり、 `Assets/` 以下にソースコードをすべて配置してカスタマイズするのは面倒です。

`*.asmref` を使用すると [UniRx](https://github.com/neuecc/UniRx) をPackage Managerから追加しつつ、 `Observable.Using()` のような独自の処理を追加できます。

1. Package ManagerからUniRxを追加
1. 適当なディレクトリ（例えば `Assets/UniRxExtensions/` ）（A）を作成
1. （A）に `*.asmref` ファイルを追加＆ `Assembly Definition` に `UniRx.asmdef` を指定
1. UniRxに追加したい処理を（A）に追加
    * 以下のコードは [UniRx で使える Observable.Using](https://qiita.com/tmr111116/items/29f9cbda2f32de834a28) から引用

```Observab.Using.cs
using System;

namespace UniRx
{
    public static partial class Observable
    {
        // https://github.com/Reactive-Extensions/Rx.NET/blob/371c83c621562a2259580a03f0cb5bf8680ea720/Rx.NET/Source/System.Reactive.Linq/Reactive/Linq/QueryLanguage.Creation.cs#L417-L441
        public static IObservable<TSource> Using<TSource, TResource>(
            Func<TResource> resourceFactory,
            Func<TResource, IObservable<TSource>> observableFactory)
        where TResource : IDisposable
        {
            return Observable.Create<TSource> (observer =>
            {
                var source = default(IObservable<TSource>);
                var disposable = Disposable.Empty;
                try
                {
                    var resource = resourceFactory();
                    if (resource != null)
                    {
                        disposable = resource;
                    }
                    source = observableFactory(resource);
                }
                catch (Exception exception)
                {
                    return new CompositeDisposable(Throw<TSource>(exception).Subscribe(observer), disposable);
                }

                return new CompositeDisposable(source.Subscribe(observer), disposable);
            });
        }
    }
}

```

### ＊＊＊　注意点　＊＊＊

1. 処理を「足す」ことはできるが「引く/変更する」ことはできない
1. Package Managerで入れたパッケージ内の `*.asmdef` の `Assembly Definition References` に何かを足す・上書きすることはできない
    * 例えば [こちら](https://qiita.com/toRisouP/items/8ec18d73d9e8c5169587#4-unirxに直接書き足す) で紹介されてる `Observable.FromUniTask()` を `*.asmref` で足すのは無理
        * そもそも `UniRx.asmdef` には `UniTask.asmdef` への参照が含まれていない
        * なので `UniRx.dll` から `UniTask.dll` を参照できない

## Unityの `internal` クラスにアクセスする
（使用は自己責任でお願いします）

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

こちらの記事では特殊な `*.asmdef` を追加する手法が紹介されてますが、同様のことは `*.asmref` でも可能です。

例えば `UnityEditor.UI` を指定した `*.asmref` を作成すると、その影響下にあるスクリプトはUnityの `internal` クラスにアクセスできます。

`*.asmdef` を使用する場合は同一名のものを複数追加するとコンフリクトが起きて以下のようなエラーになります。

```
Assembly with name 'Unity.InternalAPIEditorBridgeDev.001' already exists (Assets/Foo/NewAssembly.asmdef)
```

が、`*.asmref` を使用する場合は上記のようなコンフリクトを気にする必要がありません。

ただし、配布するライブラリなどでこのテクニックを使用する場合は、 `*.asmref` に指定した `*.asmdef` がプロジェクトに確実に含まれている必要があります。不要なパッケージは削除されてる場合があるので注意が必要です。

## ちなみに
同一のディレクトリに `*.asmdef` と `*.asmref` を両方配置すると以下のようなエラーになります。

```
Folder 'Assets/Foo/' contains multiple assembly definition files (Assets/Foo/NewAssemblyReference.asmref)
Folder 'Assets/Foo/' contains multiple assembly definition files (Assets/Foo/NewAssembly.asmdef)
```

同一のディレクトリに `*.asmref` を複数配置した場合は、同じ `*.asmdef` を指定してある分にはエラーになりませんでした。エラーになるかと思った😇

# さいごに
`*.asmref` は `*.asmdef` のかゆいところに手が届く、そんな存在。ぜひ使ってみてください。