この記事はUnity Advent Calendar 2021の23日目の記事です。
TL;DR
- アセンブリに
InternalsVisibleToAttribute
属性が含まれている場合、指定したアセンブリからinternalアクセスを許可するよ - Unity 2019.2以降は
asmref
を使うと、他のasmdef
にInternalsVisibleToAttribute
属性を追加してinternalアクセスを許可できるよ - コンパイラを変更するパッケージ「CSharpCompilerSettingsForUnity」を使うと、アセンブリに
IgnoresAccessChecksToAttribute
属性を追加してinternal/privateアクセスできるよ
非publicな型やメンバにアクセスしたい!
以前、非publicな型やメンバにアクセスする方法について記事を書きました。
- 【Unity, C#】internalな型やメンバにアクセスするには、多分これが一番早いと思います
- 【Unity, C#】続・privateな型やメンバにアクセスするには、多分これが一番早いと思います
その中で、外部アセンブリからinternalアクセスする方法を3つ紹介しました。
方法 | 利点 | 欠点 | private アクセス |
生産性 | 実行速度 | プレイヤービルド |
---|---|---|---|---|---|---|
【方法1】 Reflection |
情報がたくさんある privateアクセス可 |
コードの可読性・保守性が低い 低速な実行速度 インテリセンス無効 |
||||
【方法2】 InternalsVisibleTo |
手軽 インテリセンスが有効 高速な実行速度 |
privateアクセス未対応 「使用するライブラリ側」に属性が必要 |
||||
【方法3】 IgnoresAccessChecksTo |
privateアクセス可 どんなライブラリに対しても有効 高速な実行速度 |
コンパイルが面倒 インテリセンス無効 |
これらの手法についていくつかのアップデートがありました。
【方法2.1】 (Unity 2019.2~) Assembly Definition Referenceを使ってInternalsVisibleToAttribute
属性を追加する
Unity 2019.2にて、Assembly Definition Reference機能が追加されました。
(DeepL翻訳)
Assembly Definition Reference は、Assembly Definition への参照を定義するアセットです。
フォルダに Assembly Definition Reference アセットを作成すると、そのフォルダにあるスクリプトを参照する Assembly Definition に含めることができます(新しいアセンブリを作成するのではありません)。
子フォルダ内のスクリプトも、独自の Assembly Definition または Assembly Definition Reference アセットがない限り、含まれます。
要するに、別の場所にあるasmdef
に対してソースファイルを追加できる機能です。
これによって、配布パッケージをフォークしたり埋め込みすることなく拡張できるようになりました。
同じアセンブリ内のソースファイルとして扱われるためinternalアクセスが可能で、partial class
であればprivateアクセスも可能です。
そして、InternalsVisibleToAttribute
属性を追加することで、任意のアセンブリからでもinternalアクセスを許可できます。
例として、Unity.TextMeshPro
アセンブリにあるTMP.TMP_ListPool<T>
という、リストプールクラスを外部から使用してみましょう。
なお、Unity 2021からはCollectionPool<T0,T1>
が標準実装されているため、このサンプル自体には利用価値は無いです。
ステップ1: Assembly Definition Reference (*.asmref)を作成する
まずは適当な場所にフォルダを作り、コンテキストメニューCreate > Assembly Definition Reference
からasmref
ファイルを作成し、対象となるAssembly DefinitionとしてUnity.TextMeshPro
を指定しましょう。
同じフォルダにasmdef
とasmref
の両方があるとエラーになるので注意してください。
ステップ2: AssemblyInfoExtensions.cs
を作成する
次に、コンテキストメニューCreate > C# Script
から同じフォルダにcsファイルを作成し、AssemblyInfoExtensions
と名付けましょう。
名前は適当でもいいですが、同じアセンブリに対して同名のソースファイルがあるとエラーになるので注意してください。
ステップ3: AssemblyInfoExtensions.cs
を編集する
これまでのステップで、AssemblyInfoExtensions.cs
がUnity.TextMeshPro
アセンブリに組み込まれました!
最後に、InternalsVisibleToAttribute
属性を追加しましょう。
アセンブリ名はasmdef
に記載されているアセンブリ名(ファイル名では無いので注意!)や、Assembly-CSharp
等の特殊アセンブリ名を指定しましょう。
// AssemblyInfoExtensions.cs
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Your-Assembly-Name")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Assembly-CSharp")]
ステップ4: internalアクセスできるか試す
ステップ3で指定したアセンブリから、internalアクセスができるか試してみましょう。
// TMP_ListPoolUser.cs (Assembly-CSharp)
using TMPro;
using UnityEngine;
public class TMP_ListPoolUser : MonoBehavior
{
private void Start()
{
var l = TMP_ListPool<int>.Get();
l.Add(0);
l.Add(1);
l.Add(2);
Debug.Log($"List count is {l.Count}");
TMP_ListPool<int>.Release(l);
}
}
無事、internalクラスにアクセスできました!
注意点
この手法の欠点は、コンパイル済みアセンブリ(*.dll)に対して利用できないことです。
例えば、UnityEngine
やUnityEditor
等には対応できません。
ただし、対象となるアセンブリにInternalsVisibleToAttribute
属性が含まれている場合、そのアセンブリ経由でinternalアクセスできます。
例えばUnityEditor
はUnity.TextMeshPro.Editor
等のアセンブリからはinternalアクセスできます。
// UnityEditorアセンブリ内AssemblyInfo.cs
...
[assembly: InternalsVisibleTo("Unity.TextMeshPro.Editor")]
...
例として、Unity.TextMeshPro.Editor
にasmref経由でソースファイルを追加し、「指定秒数後にアクションを実行するinternalメソッドEditorApplication.CallDelayed
」を使ってみましょう。
// TMPEditor_UnityEditor_internal.cs (Unity.TextMeshPro.Editor)
using UnityEngine;
using UnityEditor;
public class TMPEditor_UnityEditor_internal
{
[MenuItem("Test/CallDelayed")]
private static void CallDelayed()
{
Debug.Log("[CallDelayed] Start!");
EditorApplication.CallDelayed(() => Debug.Log("[CallDelayed] Called!"), 5.0f);
}
}
【方法3.1】 CSharpCompilerSettingsForUnityを使ってIgnoresAccessChecksToAttribute
属性を追加する
CSharpCompilerSettingsForUnityはUnityのコンパイルタスクの設定を変更するパッケージです。
コンパイラ(csc)をOpenSesame.Net.Compilers.Toolsetに差し替えることで、自動的にIgnoresAccessChecksToAttribute
属性が追加され、他アセンブリに対してinternal/privateアクセスできるようになります。
ステップ1: CSharpCompilerSettingsForUnityをインストールする
PackageManagerを開き、左上の「+」ボタン→Add package from git URL...
をクリックしてください。
https://github.com/mob-sakai/CSharpCompilerSettingsForUnity
を指定し、Addボタンを押すと、git経由でパッケージがインストールされます。
ステップ2-1: (プロジェクト全体のコンパイラを変更する場合) Project Settingsからコンパイラパッケージを設定する
Project Settings > C# Compiler
でプロジェクト全体で使用するコンパイラを差し替えられます。
-
Compiler Type
をBuiltin
からCustom Package
に変更する -
Name
をOpenSesame.Net.Compilers.Toolset
に変更する -
Version
を4.0.1
に変更する - 必要に応じてTarget Assembliesにアセットパスを追加する(必要最低限のアセンブリを対象にすることをおすすめします)
- 画面下部にある「Apply」をクリックする
ステップ2-2: (asmdef単位でコンパイラを変更する場合) asmdefのインスペクタからコンパイラパッケージを設定する
CSharpCompilerSettingsForUnityはasmdef単位でのコンパイラ変更をサポートしています。
プロジェクトビューでasmdef
を選択し、インスペクタにあるEnable C# Compiler Settings
にチェックしてください。
-
Compiler Type
をBuiltin
からCustom Package
に変更する -
Name
をOpenSesame.Net.Compilers.Toolset
に変更する -
Version
を4.0.1
に変更する - 下部にある「Apply」をクリックする
ちなみに、一度設定しておけばCSharpCompilerSettingsForUnityをアンインストールしても動作します。
(同じフォルダにあるCSharpCompilerSettings_*.dll
を削除すると動作しなくなります)
ステップ3: ソースファイルを追加する
ソースコードではinternal/privateアクセスが可能です。
Unity 2021.1以降では「初回コンパイル前にユーザーコードが実行できない」「コンパイルフローが大幅に変更された」影響で、internal/privateアクセスは#if CUSTOM_COMPILE 〜 #endif
に記述する必要があります。
using System;
using TMPro;
using UnityEngine;
public class TMP_ListPoolUser_OpenSesame : MonoBehaviour
{
private void Start()
{
// Unity 2021.1以降では必須
#if CUSTOM_COMPILE
var l = TMP_ListPool<int>.Get();
l.Add(0);
l.Add(1);
l.Add(2);
Debug.Log($"List count is {l.Count}");
TMP_ListPool<int>.Release(l);
#endif
}
}
無事、internalクラスにアクセスできました!
おまけ
CSharpCompilerSettingsForUnityの他の機能について紹介まで。
- C#バージョンの変更(〜C# 10)
- .Net5/6等のAPIが必要な一部機能には対応していません。
- Analyzer/Source Generatorパッケージの適用
- dllとしてアセンブリを別ディレクトリに出力
まとめ
方法 | 利点 | 欠点 | private アクセス |
生産性 | 実行速度 | プレイヤービルド |
---|---|---|---|---|---|---|
【方法2.1】 Assembly Definition Reference |
手軽 インテリセンスが有効 高速な実行速度 |
privateアクセス未対応 コンパイル済みアセンブリ(*.dll)は未対応 |
||||
【方法3.1】 CSharpCompilerSettingsForUnity & OpenSesameCompiler |
privateアクセス可 どんなライブラリに対しても有効 高速な実行速度 |
インテリセンス無効 |
本記事に関するプロジェクトを以下のリポジトリにアップしました。
https://github.com/mob-sakai/UnityAdventCalendar2021