【Unity, C#】internalな型やメンバにアクセスするには、多分これが一番早いと思います

この記事は[【unityプロ技】 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/unityforpro)の13日目の記事です。
この記事におけるソースコードは、全て`Public Domain`です。

※[12/13 15:00 訂正あり](#1213-1500訂正)
※[12/26 続編でました](https://qiita.com/mob-sakai/items/a24780d68a6133be338f)

## TL;DR

- C#には`IgnoresAccessChecksToAttribute`っていう隠された属性があるよ。ググってもほとんど情報出てこないよ。
- この属性を使えば、**任意の外部アセンブリのinternalの型やメンバに対して自由にアクセスできる**よ。ヤバいよ。
- `InternalsVisibleToAttribute`や`AssemblyDefinitionFile`と組み合わせることで、**インテリセンスやブレークポイントも効く**よ。リフレクションより格段に便利だよ。



## おさらい：internalアクセス修飾子

C#には[アクセス修飾子](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers)と呼ばれる、型やメンバのアクセスを制限する仕組みがあります。
例えばクラスAからクラスBのprivateメソッドを呼ぼうとすると、コンパイルの時点で怒られますし、VisualStudioやVisualStudio Code、Rider等といったIDEのインテリセンスには、候補すら出てきません。

```
error CS0122: 'ApplicationTitleDescriptor' is inaccessible due to its protection level
error CS0117: 'EditorApplication' does not contain a definition for 'UpdateMainWindowTitle'
...
```

今回取り上げる`internal`アクセスレベルは[同一アセンブリ（=同じdll）内からのみアクセスできる](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/internal)アクセスレベルです。
このアクセスレベルを持つ型やメンバは、異なるアセンブリ（=別のdll）からは*基本的には*アクセスできません。

## internalな型やメンバにアクセスするモチベーション

>*internalな型やメンバにアクセスする必要があるの？
公開されている(publicな)APIだけで十分では？
苦労してinternalな型やメンバにアクセスして、何がうれしいの？*

internalな型やメンバは、**ライブラリ製作者がライブラリ利用者に対して隠蔽したい項目であり、APIやドキュメントは一般的に非公開**です。
例えば、Unityに何らかのエディタ拡張を加えたい場合は、以下のような[Unityが公開しているAPI(publicなAPI)](https://docs.unity3d.com/ScriptReference/index.html)の利用が基本です。

- メニューの実行：[EditorApplication.ExecuteMenuItem](https://docs.unity3d.com/ScriptReference/EditorApplication.ExecuteMenuItem.html)
- アセットインポートの事前/事後処理：[AssetPostprocessor](https://docs.unity3d.com/ScriptReference/AssetPostprocessor.html)
- インスペクタや自前ウィンドウの実装：[EditorWindow](https://docs.unity3d.com/ScriptReference/EditorWindow.html)
- 次のフレームでアクションを実行する：[EditorApplication.delayCall](https://docs.unity3d.com/ScriptReference/EditorApplication-delayCall.html)
- ツールバーにメニューを追加する：[MenuItem](https://docs.unity3d.com/ScriptReference/MenuItem.html)
- コンテキストメニューを追加する：[ContextMenu](https://docs.unity3d.com/ScriptReference/ContextMenu.html)
- コンパイル後にスクリプトを実行する：[Callbacks.DidReloadScripts](https://docs.unity3d.com/ScriptReference/Callbacks.DidReloadScripts.html)
- パッケージマネージャUIの拡張：[PackageManager.UI.IPackageManagerExtension](https://docs.unity3d.com/ScriptReference/PackageManager.UI.IPackageManagerExtension.html)
- etc.

御存知の通り、公開されているAPIだけでも*「出来ないことなんて何も無いんじゃないの？」*と思えるほど大量の項目が見つかります。
しかし、公開されていない（=非publicな）APIに目を向けると、**さらに多くの事が実現可能なことに気づきます**。

例えば、[【Unity】UIElements で Game ビューにテキストやボタンを追加するエディタ拡張のサンプル](http://baba-s.hatenablog.com/entry/2019/11/26/170000)(@baba_s様)では、リフレクションを利用して、**Unityが公開していない**[GameView](https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/GameView/GameView.cs)クラスへアクセスし、GameビューにオリジナルのGUIを追加しています。

```cs
var assembly = typeof( EditorWindow ).Assembly;
var type     = assembly.GetType( "UnityEditor.GameView" );
var gameview = EditorWindow.GetWindow( type );
...
```

このように、非publicなUnityの世界を知ることは、新しい気づきや便利な拡張機能のヒントになります。
そして、あなたが発見した気づきや便利な拡張機能は、ほかの誰かのヒントになるかもしれません。

---

ただし、大事なことなので二回言いますが、**internalな型やメンバは、ライブラリ製作者がライブラリ利用者に対して隠蔽したい項目であり、APIやドキュメントは一般的に非公開**です。
ググって得られる情報は限定的ですし、無慈悲にもAPIは通告なく変更されます。
IL化や難読化されていることは日常茶飯事で、誤って使えばフリーズする可能性すらあります。
ライブラリのライセンスによっては、明確に逆コンパイルやリバースエンジニアリングが禁止されています。

ありがたいことに、[Unityは公式でGitHubにソースコードを公開しています](https://github.com/Unity-Technologies/UnityCsReference)。internal要素も探し放題です。太っ腹だね！

さあ、実現したいことをキーワードにソースコードを検索し、*それっぽい名前の* APIを見つけ、注意深くソースコードを読み、拡張機能の幅を~~くれぐれも自己責任で~~広げましょう！

## 開発環境

本記事では以下の環境でデモプロジェクトを開発しました。

- macOS Mojave 10.14.6
- Unity 2019.2.15f1
- VisualStudio For Mac 8.3.8
- DotNet Core 3.0.100

デモプロジェクトは以下のリポジトリで公開しています。
https://github.com/mob-sakai/MainWindowTitleModifierForUnity

## 今回のデモでやること

今回は、**Unityエディタのタイトルバーのテキストを変更**してみたいと思います。 
タイトルバーに現在のブランチ名やゲームサーバの接続先環境名(sand/stag/prod等)を表示できると素敵ですよね。
![](https://user-images.githubusercontent.com/12690315/70613020-50af1d80-1c4b-11ea-92aa-75bafdda3fde.png)

[ソースコード](https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/EditorApplication.cs)を見たところ、Unity2019.2から追加されたinternalな型とメンバを使えば簡単に実現できそうです。
具体的には、`EditorApplication.updateMainWindowTitle`イベントに新しいコールバックを追加し、`EditorApplication.UpdateMainWindowTitle()`を呼び出せば、タイトルバーのテキストを変更できそうです：

```cs
// こんな感じで実装すれば実現できそう
Action<ApplicationTitleDescriptor> cb = x => x.title = "なにかしらのエモいタイトル";
EditorApplication.updateMainWindowTitle += cb;
EditorApplication.UpdateMainWindowTitle();
EditorApplication.updateMainWindowTitle -= cb;
```

`updateMainWindowTitle`、`UpdateMainWindowTitle`、`ApplicationTitleDescriptor`は全てinternalな型とメンバであり、このままでは当然コンパイルエラーになります。

```
error CS0122: 'ApplicationTitleDescriptor' is inaccessible due to its protection level
error CS0117: 'EditorApplication' does not contain a definition for 'UpdateMainWindowTitle'
...
```

なんとかしてアクセスしてみましょう！

## 【方法1】Reflection

publicでない型やメンバにアクセスするための方法として、まず思いつくのは[Reflection(リフレクション)](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/reflection)です。
リフレクションにより、**文字列を使ってpublicでない型やメンバにも動的にアクセスできます**。

リフレクションの利点は、internalな型やメンバだけでなく、**privateな型やメンバにもアクセスできる**ことです。
また、有名であるが故に**ググれば情報がたくさん**あり、**リフレクションだからこそできる事も多くあります**。
[C#リフレクションTIPS 55連発](https://qiita.com/gushwell/items/91436bd1871586f6e663)(@gushwell様)という一読すべき記事もあります。


#### リフレクションを使ってinternalアクセスしてみる

実際にやりたいことは4行で済む内容でしたが、リフレクションで表現するとどうなるでしょうか？

```cs
using System;
using System.Linq;
using UnityEditor;
using System.Reflection;

namespace MainWindowTitleModifier
{
	public class Solution1_Reflection
	{
		[MenuItem("MainWindowTitleModifier/Solution1_Reflection", priority = 1)]
		static void Update()
		{
			// ApplicationTitleDescriptorのTypeを取得.
			Type tEditorApplication = typeof(EditorApplication);
			Type tApplicationTitleDescriptor = tEditorApplication.Assembly.GetTypes()
				.First(x => x.FullName == "UnityEditor.ApplicationTitleDescriptor");

			// 関係するイベントとメソッドのInfoを取得.
			EventInfo eiUpdateMainWindowTitle = tEditorApplication.GetEvent("updateMainWindowTitle", BindingFlags.Static | BindingFlags.NonPublic);
			MethodInfo miUpdateMainWindowTitle = tEditorApplication.GetMethod("UpdateMainWindowTitle", BindingFlags.Static | BindingFlags.NonPublic);

			// Action<object>をAction<ApplicationTitleDescriptor>に変換.
			Type delegateType = typeof(Action<>).MakeGenericType(tApplicationTitleDescriptor);
			MethodInfo methodInfo = ((Action<object>)UpdateMainWindowTitle).Method;
			Delegate del = Delegate.CreateDelegate(delegateType, null, methodInfo);

			// UpdateMainWindowTitleを呼び出す前後にイベントの追加/削除.
			eiUpdateMainWindowTitle.GetAddMethod(true).Invoke(null, new object[] { del });
			miUpdateMainWindowTitle.Invoke(null, new object[0]);
			eiUpdateMainWindowTitle.GetRemoveMethod(true).Invoke(null, new object[] { del });
		}

		static void UpdateMainWindowTitle(object desc)
		{
			// UnityEditor.ApplicationTitleDescriptor.title = "Solution1_Reflection"; と同様
			typeof(EditorApplication).Assembly.GetTypes()
				.First(x => x.FullName == "UnityEditor.ApplicationTitleDescriptor")
				.GetField("title", BindingFlags.Instance | BindingFlags.Public)
				.SetValue(desc, "Solution1_Reflection");
		}
	}
}
```

うーん、つらみ。。。
やりたいことは単純なはずなのに、どうしてこうなった！って感じですね。

ともあれ、`MainWindowTitleModifier > Solution1_Reflection`を実行すればタイトルバーのテキストを変えられるようになりました。
![](https://user-images.githubusercontent.com/12690315/70615701-3e83ae00-1c50-11ea-8a9b-66eb04ab4cca.png)

#### リフレクションの欠点

リフレクションの欠点は**生産性が低いことと、実行速度が遅いこと**です。
やりたいことに対してやるべき準備が多く、IDEによるインテリセンスも効かないため、生産性はお世辞にも良くありません。
[なぜリフレクションは遅いのか](https://postd.cc/why-is-reflection-slow/)(POSTD様)によると、静的な呼び出しと比べて**2〜3桁オーダーで実行速度が遅い**ようです。

単純なメソッド呼び出しやsetter/getterについて、リフレクションを使う分には悩むことも少ないでしょう。
ジェネリック、コンストラクタ、in/out/ref/paramsキーワード、関数のオーバーロード、非publicな型を含む`Action<T>`や`Func<T>`、event等、要素が複雑になると、だんだんと長く、ツラくなってきます。~~めげずに頑張ってググりましょう。~~


#### おまけ：リフレクションを効率化して使う

- **`Type`や`MemberInfo`のインスタンスをDictionaryでキャッシュする**
    - 一度生成した`Type`や`MemberInfo`のインスタンスを使いまわすことで、オーバーヘッドを回避します
- **FindMethod、FindProperty等のメソッドをラップする**
    - Public/NonPublic、Instance/Staticを指定しなくても適切な`MemberInfo`を返すようなメソッドを作り、可読性を改善します
- **アセンブリからではなく`string`から型を見つける**
    - `Type.GetType("UnityEditor.ApplicationTitleDescriptor, UnityEditor")`のように、`クラス名フルパス, アセンブリ名`という文字列でも型を取得できます
    - このときの文字列は`Type`インスタンスを使って、`type.AssemblyQualifiedName`で取得できます
- **デリゲート化、式木、IL Emit**
    - [[雑記] 動的コード生成のパフォーマンス](https://ufcpp.net/study/csharp/misc_dynamic.html)(++C++; // 未確認飛行 C様)を参照してください

## 【方法2】InternalsVisibleToAttribute

`InternalsVisibleToAttribute`は、**特定のアセンブリに対して、自身のinternalアクセスを許可させる属性**です。
アセンブリに対する単体テストや、機能拡張に使うことが多く、そういったアセンブリは[フレンドアセンブリ](https://docs.microsoft.com/ja-jp/dotnet/standard/assembly/friend)と呼ばれます。
この属性の強力なところは、**internalな要素をそのまま記述でき、VisualStudioやVisualStudio Code、Rider等のIDEでインテリセンスやブレークポイントが有効になる**ことです。
テストコードを書くに当たり、IDEのサポートが得られるのはありがたいですね！
![](https://user-images.githubusercontent.com/12690315/70616819-a804bc00-1c52-11ea-8ea3-e24f94f6467d.gif)

さて、`InternalsVisibleToAttribute`は、UnityEngine.dllやUnityEditor.dllといった**Unity公式のライブラリにも存在します**。
例えばUnityEditor.dllの[AssemblyInfo.cs](https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/AssemblyInfo/AssemblyInfo.cs)には次のように定義されています。

```cs
[assembly: InternalsVisibleTo("Unity.LiveNotes")]
[assembly: InternalsVisibleTo("Unity.Burst")]
[assembly: InternalsVisibleTo("Unity.Burst.Editor")]
[assembly: InternalsVisibleTo("Unity.Cloud.Collaborate.Editor")]
[assembly: InternalsVisibleTo("Unity.CollabProxy.Editor")]
[assembly: InternalsVisibleTo("Unity.CollabProxy.EditorTests")]
[assembly: InternalsVisibleTo("Unity.CollabProxy.UI")]
[assembly: InternalsVisibleTo("Unity.CollabProxy.UI.Tests")]
[assembly: InternalsVisibleTo("Unity.CollabProxy.Client")]
[assembly: InternalsVisibleTo("Unity.CollabProxy.Client.Tests")]
[assembly: InternalsVisibleTo("UnityEditor.Advertisements")]
[assembly: InternalsVisibleTo("Unity.PackageManager")]
...
//(以下、たくさんのInternalsVisibleToが羅列されている)
[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridgeDev.001")]
[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridgeDev.002")]
[assembly: InternalsVisibleTo("Unity.InternalAPIEditorBridgeDev.003")]
...
```

これらの名前を持つアセンブリは、UnityEditor.dllのフレンドアセンブリです。
よって、**上記の名前を持つアセンブリを生成できれば、UnityEditor.dllに対してinternalアクセスできます**。~~すっごーい！~~

Unityでアセンブリを生成するための仕組みといえば、[AssemblyDefinitionFile](https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html)です。
`AssemblyDefinitionFile`については@toRisouP様が[Unity Assembly Definition 完全に理解した](https://qiita.com/toRisouP/items/d206af3029c7d80326ed)にて詳しく解説されております。(こちらも[【unityプロ技】 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/unityforpro)の記事です)

なお、`AssemblyDefinitionFile`によって生成されるアセンブリ名は、インスペクタ内の`Name`フィールドで設定します。
**アセット名ではない**ことに注意しましょう。

#### `InternalsVisibleTo`を使ってinternalアクセスしてみる

適当な`AssemblyDefinitionFile`を作成し、インスペクタビューから`Name`を変更しましょう。
ここでは、`Unity.InternalAPIEditorBridgeDev.001`という、実に**それっぽい名前**(おそらくUnityの中の人がテストするためのアセンブリ名)を指定します。
![](https://user-images.githubusercontent.com/12690315/70617025-0cc01680-1c53-11ea-8e8c-a7342e9abd56.png)

次に、`Unity.InternalAPIEditorBridgeDev.001.asmdef`があるディレクトリ以下に、次のようなスクリプト(`Solution2_InternalsVisibleToAttribute.cs`)を追加し、`Unity.InternalAPIEditorBridgeDev.001`アセンブリに組み込みましょう。
![](https://user-images.githubusercontent.com/12690315/70617560-39286280-1c54-11ea-8387-81124b502248.png)

```cs
using System;
using UnityEditor;

namespace MainWindowTitleModifier
{
	public class Solution2_InternalsVisibleToAttribute
	{
		[MenuItem("MainWindowTitleModifier/Solution2_InternalsVisibleToAttribute", priority = 2)]
		static void Update()
		{
			Action<ApplicationTitleDescriptor> cb = x => x.title = "Solution2_InternalsVisibleToAttribute";
			EditorApplication.updateMainWindowTitle += cb;
			EditorApplication.UpdateMainWindowTitle();
			EditorApplication.updateMainWindowTitle -= cb;
		}
	}
}
```

リフレクションが完全に消え、[イメージしたようなスッキリしたコード](#今回のデモでやること)になりました！
直接呼び出しになるので、実行速度も申し分ありませんし、IDEのサポート(インテリセンスやブレークポイント)によって格段に生産性が上がりました。
`MainWindowTitleModifier > Solution2_InternalsVisibleToAttribute`を実行すれば、同様にタイトルバーのテキストを変えられます。
![](https://user-images.githubusercontent.com/12690315/70615700-3e83ae00-1c50-11ea-9a5e-5eff0ed8d589.png)

#### `InternalsVisibleTo`の欠点

`AssemblyDefinitionFile`と`InternalsVisibleToAttribute`を使ったinternalアクセスは、非常に手軽な方法です。
しかし、残念ながら、全てにおいて完璧とは言えません：

- **privateアクセスができない**
    - 必要な場合、リフレクションを使ってください
- **ライブラリ側に`InternalsVisibleToAttribute`が定義されている必要がある**
    - **「ライブラリ側に」**属性があることが条件です
    - 例えばライブラリのアップデート等で、ライブラリ側から`InternalsVisibleToAttribute`が変更・削除されると、コンパイルエラーになります
- **ランタイムでは使用できない場面が多い**
    - UnityEngine.dll等は、ビルド時に`InternalsVisibleToAttribute`が綺麗サッパリなくなります
    - これは、エディタ用とは異なるランタイム用アセンブリが使用されるためです
- **アセンブリ名が衝突するとエラー**
    - パッケージを外部に公開する場合、誰かが同じフレンドアセンブリの名前を使い、アセンブリ名が衝突する可能性があります
    - これは潜在的な不具合と言えます

難儀な欠点が多いですが、逆に言えば、**クローズドな環境でエディタ向けの機能を実装する**のであれば、この方法だけでも十分に使えそうです。

## 【方法3】IgnoresAccessChecksToAttribute

`IgnoresAccessChecksToAttribute`は、ほとんど情報が出回っていない*（MSDNにも載ってない）*ミステリアスな属性です。
私は[No InternalsVisibleTo, no problem – bypassing C# visibility rules with Roslyn](https://www.strathweb.com/2018/10/no-internalvisibleto-no-problem-bypassing-c-visibility-rules-with-roslyn/)で偶然知りました。
要約すると：

- `InternalsVisibleToAttribute`とは**逆の方向に作用する**。つまり、**ライブラリ利用者側に設定することで、ライブラリに対するinternalアクセスを許可する**
- フルネームは`System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute`
- Base Class Libraryに載っていないが、ランタイム（CLR/CoreCLR）で作用する
- **`csc.exe`や`MsBuild`を使わずに自力で**コンパイルする際に、`CSharpCompilationOptions.TopLevelBinderFlags`に対して特定のフラグを立てると有効になる

つまり、**どんなライブラリに対してもinternalアクセスができるようになる、夢のような属性**ってことですね！
さっそく、この属性を使ってみましょう。

#### `IgnoresAccessChecksToAttribute`を使ってinternalアクセスしてみる

[先述の記事](https://www.strathweb.com/2018/10/no-internalvisibleto-no-problem-bypassing-c-visibility-rules-with-roslyn/)にある通り、`IgnoresAccessChecksToAttribute`を使うには**`csc.exe`や`MsBuild`を使わずに、自力でコンパイルする必要があります**。
自力でコンパイルする方法を紹介しますが、正直めんどくさいです。
興味のない方は「[IgnoresAccessChecksToの準備がめんどくさいのでなんとかする](#IgnoresAccessChecksToの準備がめんどくさいのでなんとかする)」まで読み飛ばし推奨。

---

まず、C#プロジェクトに以下のコードを追加します。
こうすることで、**「UnityEditorアセンブリに対するアクセスレベルのチェックを無視する」**ようにコンパイラが解釈できます。

```cs
[assembly: System.Runtime.CompilerServices.IgnoresAccessChecksTo("UnityEditor")]
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)]
    internal class IgnoresAccessChecksToAttribute : System.Attribute
    {
        public IgnoresAccessChecksToAttribute(string assemblyName)
        {
            AssemblyName = assemblyName;
        }
 
        public string AssemblyName { get; }
    }
}
```


次に、C#プロジェクトとは別に、**「アクセスチェックを無視できる」**コンパイラのプロジェクトを作ります。コードは以下の通りです。

```cs
// あらかじめMicrosoft.CodeAnalysis.CSharpをnugetでインストールしておく
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

public class InternalAccessibleCompiler
{
	public static void Main (string [] args)
	{
		string inputCsProjPath = args [0];
		string outputAsemblyPath = args [1];
		string outputAsemblyName = Path.GetFileNameWithoutExtension (outputAsemblyPath);

        // C#プロジェクトを読み込みます.
        string[] csproj = File.ReadAllLines(inputCsProjPath);

        // dllとしてコンパイルさせるオプションを生成します.
        CSharpCompilationOptions compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            .WithMetadataImportOptions(MetadataImportOptions.All);

        // BindingFlags.IgnoreAccessibility(1 << 22)を有効化します.
        typeof(CSharpCompilationOptions)
            .GetProperty("TopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic)
            .SetValue(compilationOptions, (uint)1 << 22);

        // プロジェクトからアセンブリ参照一覧を取得→IEnumerable<PortableExecutableReference>に変換します.
        Regex reg_dll = new Regex("<HintPath>(.*)</HintPath>", RegexOptions.Compiled);
        IEnumerable<PortableExecutableReference> metadataReferences = csproj
            .Select(line => reg_dll.Match(line))
            .Where(match => match.Success)
            .Select(match => match.Groups[1].Value)
            .Select(path => MetadataReference.CreateFromFile(path));

        // プロジェクトからシンボル一覧を取得→IEnumerable<string>に変換します.
        Regex reg_preprocessorSymbols = new Regex("<DefineConstants>(.*)</DefineConstants>", RegexOptions.Compiled);
        IEnumerable<string> preprocessorSymbols = csproj
            .Select(line => reg_preprocessorSymbols.Match(line))
            .Where(match => match.Success)
            .SelectMany(match => match.Groups[1].Value.Split(';'));

        // プロジェクトからソースコード一覧を取得→テキストとして読み込み→IEnumerable<SyntaxTree>に変換します.
        CSharpParseOptions parserOption = new CSharpParseOptions(LanguageVersion.Latest,
                preprocessorSymbols: preprocessorSymbols);
        Regex reg_cs = new Regex("<Compile Include=\"(.*\\.cs)\"", RegexOptions.Compiled);
        IEnumerable<SyntaxTree> syntaxTrees = csproj
            .Select(line => reg_cs.Match(line))
            .Where(match => match.Success)
            .Select(match => match.Groups[1].Value.Replace('\\', Path.DirectorySeparatorChar))
            .Select(path => Path.Combine(inputCsProjDir, path))
            .Select(path => CSharpSyntaxTree.ParseText(File.ReadAllText(path), parserOption, path));

        // コンパイルを実行し、dllを生成します.
        CSharpCompilation.Create(outputAsemblyName, syntaxTrees, metadataReferences, compilationOptions)
            .Emit(outputAsemblyPath);
	}
}
```

なお、このコードはコンパイル対象が**Unityが生成したC#プロジェクトである**ことを前提にしています。
他のジェネレータで生成されたC#プロジェクトをコンパイルできないかもしれません。
（きっと、おそらくMSBuildパッケージを使えばたぶん解決できます。）

dotnetを使って実行し、以下のようにC#プロジェクトファイルと出力パスを指定すると、**internalアクセス可能なdll**を生成できます。

```bash
dotnet run -- C#プロジェクトパス 出力dllパス
```

なお、このコンパイラはnuget toolとしても公開しています。（オプションが若干違います）
![](https://user-images.githubusercontent.com/12690315/70731401-f9dc3d80-1d49-11ea-81fd-b743f8760e62.png)
https://www.nuget.org/packages/InternalAccessibleCompiler/

#### `IgnoresAccessChecksTo`の準備がめんどくさいのでなんとかする

`IgnoresAccessChecksTo`をUnityで使うには準備がめんどくさいです。
今回は、先述のコンパイラを同梱済みの[Unity向けに公開しているパッケージ](https://github.com/mob-sakai/InternalAccessibleCompilerForUnity)を使います。
（デモプロジェクトには既にインストールされています。）

1. このパッケージには`dotnet 2.1`以上が必要です
    - コマンドプロンプト(Windows)やターミナル(Mac)で、`dotnet --version`を実行したときに、`2.1.xxx`以上が表示されていればインストール不要です
    - https://dotnet.microsoft.com/download からインストールしてください
1. manifest.jsonの`dependencies`に`"com.coffee.internal-accessible-compiler": "https://github.com/mob-sakai/InternalAccessibleCompilerForUnity.git"`を追加し、パッケージをインストールします
1. プロジェクトビューで`MainWindowTitleModifier.asmdef`を選択し、コンテキストメニュー(右クリック)から`Internal Accessible Compiler > Setting`をクリックします
![](https://user-images.githubusercontent.com/12690315/70728182-49b80600-1d44-11ea-9ef7-9f2709702b81.png)
1. 開いたウィンドウで、以下のように入力します
    - Assembly Names To Access: `UnityEditor`
    - Output Dll Path: `Assets/Editor/Solution3.IgnoresAccessChecksToAttribute/MainWindowTitleModifier.dll`  
![](https://user-images.githubusercontent.com/12690315/70732351-a539c200-1d4b-11ea-9218-99166fbbe902.png)
1. `Compile`をクリックし、dllにコンパイルします

`AssemblyDefinitionFile`やソースコードは、[【方法2】InternalsVisibleTo](#方法2internalsvisibletoattribute)とアセンブリ名やクラス名が被らないようにする以外、ほとんど同じです。
コンパイルが完了すると、`MainWindowTitleModifier.dll`が生成されます。
このままでは同じ名前を持つ`MenuItem`が衝突してしまうので、`MainWindowTitleModifier.asmdef`の`Define Constraints`に適当な文字列を入力して、インポートされないようにしておきましょう。
![](https://user-images.githubusercontent.com/12690315/70614395-d207af80-1c4d-11ea-8c0a-07eedff49fae.png)

`MainWindowTitleModifier.dll`のインポート後、`MainWindowTitleModifier > Solution3_IgnoresAccessChecksToAttribute`を実行すれば、タイトルバーのテキストを変えられます。
![](https://user-images.githubusercontent.com/12690315/70615699-3deb1780-1c50-11ea-93a1-2d116fbbbaa8.png)


#### `IgnoresAccessChecksTo`の欠点

**どんなアセンブリに対してもinternalアクセスができる**ため、この方法は非常に強力です。
[【方法2】InternalsVisibleToの欠点](#internalsvisibletoの欠点)も少し克服していますが、それでも完璧ではありません：

- **privateアクセスができない**
    - 必要な場合、やはりリフレクションを使ってください
- **使うためにはコンパイルする必要がある**
    - コンパイルという手間が発生します
    - 自動化するなり、運用でカバーしましょう
- **コード内で`UNITY_2019`等のシンボルを使う場合、各バージョンのdll生成が必要**
    - コード内のシンボルは上記コンパイルの際に解決されます
    - そのため、UnityのバージョンアップでAPIが変更されると、**APIのバージョン毎にdllを複数生成する**必要があります
    - 生成したdllのインポート設定の`Define Constraints`から、特定のバージョンのdllのみをインポートするようにしましょう
    - 自動化するなり、運用でカバーしましょう

## これまで紹介したinternal要素へのアクセス方法まとめ

今回は外部アセンブリからinternalアクセスする方法を3つ紹介しました。

|方法|利点|欠点|private<br>アクセス|生産性|実行速度|ランタイム|
|---|---|---|--------|-------------|------------|----------|
|[【方法1】<br>Reflection](#方法1reflection)|:thumbsup:情報がたくさんある<br>:thumbsup:privateアクセス可|:thumbsdown:コードの可読性・保守性が低い<br>:thumbsdown:低速な実行速度<br>:thumbsdown:インテリセンスが無効|:white_check_mark:|:ng:|:ng:|:white_check_mark: |
|[【方法2】<br>InternalsVisibleTo](#方法2internalsvisibletoattribute)|:thumbsup:手軽<br>:thumbsup:インテリセンスが有効<br>:thumbsup:高速な実行速度|:thumbsdown:privateアクセス不可<br>:thumbsdown:アセンブリ名が被るとエラー<br>:thumbsdown:ライブラリ側に属性が必要|:ng:|:white_check_mark:|:white_check_mark:|:ng:|
|[【方法3】<br>IgnoresAccessChecksTo](#方法3ignoresaccesscheckstoattribute)|:thumbsup:privateアクセス可<br>:thumbsup:どんなライブラリに対しても有効<br>:thumbsup:インテリセンスが有効<br>:thumbsup:高速な実行速度|:thumbsdown:自力コンパイルが必要<br>:thumbsdown:シンボルの取り扱いに注意が必要|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|

以下は、パッケージマネージャのUIを拡張するパッケージ、[UpmGitExtension](https://github.com/mob-sakai/UpmGitExtension)を実装した際のワークフローです。
（実際にはdll生成の工程を自動化したり、`AssemblyDefinitionFile`の参照関係を整理したり、dllを生成するために使ったディレクトリをパッケージからパージしてます。）

- 一旦、手軽な方法である[【方法2】InternalsVisibleTo](#方法2internalsvisibletoattribute)で実装する
    - 後でパージしやすいように、パッケージディレクトリに専用のディレクトリを作っておく
- **パッケージを公開する前**に[【方法3】IgnoresAccessChecksTo](#方法3ignoresaccesscheckstoattribute)でdllを生成する
    - nugetに公開している[InternalAccessibleCompiler](https://www.nuget.org/packages/InternalAccessibleCompiler/)とシェルスクリプトで一括処理
- パッケージから「dllを生成するために使ったディレクトリ」をパージし、公開
    - `.npmignore`にディレクトリを記載しておき、`npm pack`してパージする
    - packしたものはnpmレジストリにそのまま公開できる
    - packしたものを展開し、upmブランチにプッシュする
- どうしてもprivateアクセスが必要な場面は[【方法1】Reflection](#方法1reflection)を使う
    - [Expose](https://github.com/mob-sakai/UpmGitExtension/blob/preview/Packages/com.coffee.upm-git-extension/Editor/InternalBridge/Expose.cs)クラスのような、リフレクション機能をラップするクラスを作っておくと楽
    - `Expose.FromType(typeof(EditorApplication)).Call("UpdateMainWindowTitle");`のように呼び出せる



## 雑記

UpmGitExtensionの実装において、internal要素へのアクセスは必須事項でした。
UnityやPackageManagerUIのAPIが日々アプデされる状況において、リフレクションのみで対応を続けるのは苦しいものでした。
*製作者が見ても意味のわからないコード、名前か引数が変わりエラーを吐き続けるAPI、直ったと思いきや別ベクトルから迫りくるデグレ...*
UnityやPackageManagerUIがマイナーアップデートする度にAPIは変更され、どこからか不具合が起き、改修を迫られ、心が折れかけました。~~2019.3なんて中身ほとんど別モンやんけ！~~

そんな中、今回紹介した方法を使うことで、手軽にinternalにアクセスできるようになり、テストやソースコードの使いまわし（UnityEditor内部のMiniJsonを使ったり）が容易になり、生産性やコードの見通しが格段に改善され、お肌の調子も良くなりました。
もし、**あなたの近くでC#のリフレクションに苦しんでいるエンジニアを見かけましたら、そっと本記事のリンクをDMしてあげてください**。

## 終わりに

最後に、本当に大事なことなので三回言いますが、**internalな型やメンバは、ライブラリ製作者がライブラリ利用者に対して隠蔽したい項目であり、APIやドキュメントは一般的に非公開です**。
ライブラリ製作者は、決して意地悪でinternalを使っているのではありません。
不用意にその型やメンバを扱うと、**とんでもない結果になるかもしれない**からinternalで隠蔽しているのかもしれません。
internalな型やメンバは、**「そうあるべき正当な理由があってinternalになっている」**ということを理解した上、~~くれぐれも自己責任で~~活用してください。

皆様、よいUnityライフを！


## 12/13 15:00訂正

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">まぁ私もこないだまで知らなくて教えてもらったのですがが。vs-mefとかで投下されてて効果大らすぃ。 <a href="https://t.co/w6AzJoxK4y">https://t.co/w6AzJoxK4y</a></p>&mdash; neuecc (@neuecc) <a href="https://twitter.com/neuecc/status/1205293945057468418?ref_src=twsrc%5Etfw">December 13, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Twitterで@neuecc様からリプライをいただいたのですが、リンク先の**「*AllowPrivate」**というワードが引っかかりました。
[参照しているソースコード](https://github.com/microsoft/vs-streamjsonrpc/blob/master/src/StreamJsonRpc/SkipClrVisibilityChecks.cs)を読む限り、`IgnoresAccessChecksTo`以上のことはしていない様子...
これらの事実から導き出される答えは...
**`IgnoresAccessChecksTo`はprivateアクセスもできるということです！**
よく見たら[元記事](https://www.strathweb.com/2018/10/no-internalvisibleto-no-problem-bypassing-c-visibility-rules-with-roslyn/)でもprivateアクセスしてた...

軽くテストしてみたところ、確かにIDE上ではprivateアクセスがエラーとして表示されましたが、InternalAccessCompilerではエラーなくコンパイルできていました。
もちろん動作も問題ありませんでした。

具体的なワークフローの更新については別記事でまとめますので、もう少々お待ちくださいm(_ _)m。

※[12/26 続編でました](https://qiita.com/mob-sakai/items/a24780d68a6133be338f)