19
12

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-22

この記事はUnity Advent Calendar 2021の23日目の記事です。

TL;DR

  • アセンブリにInternalsVisibleToAttribute属性が含まれている場合、指定したアセンブリからinternalアクセスを許可するよ
  • Unity 2019.2以降はasmrefを使うと、他のasmdefInternalsVisibleToAttribute属性を追加してinternalアクセスを許可できるよ
  • コンパイラを変更するパッケージ「CSharpCompilerSettingsForUnity」を使うと、アセンブリにIgnoresAccessChecksToAttribute属性を追加してinternal/privateアクセスできるよ

非publicな型やメンバにアクセスしたい!

以前、非publicな型やメンバにアクセスする方法について記事を書きました。

その中で、外部アセンブリからinternalアクセスする方法を3つ紹介しました。

方法 利点 欠点 private
アクセス
生産性 実行速度 プレイヤービルド
【方法1】
Reflection
:o: 情報がたくさんある
:o: privateアクセス可
:x: コードの可読性・保守性が低い
:x: 低速な実行速度
:x: インテリセンス無効
:white_check_mark: :ng: :ng: :white_check_mark:
【方法2】
InternalsVisibleTo
:o: 手軽
:o: インテリセンスが有効
:o: 高速な実行速度
:x: privateアクセス未対応
:x: 「使用するライブラリ側」に属性が必要
:ng: :white_check_mark: :white_check_mark: :ng:
【方法3】
IgnoresAccessChecksTo
:o: privateアクセス可
:o: どんなライブラリに対しても有効
:o: 高速な実行速度
:x: コンパイルが面倒
:x: インテリセンス無効
:white_check_mark: :ng: :white_check_mark: :white_check_mark:

これらの手法についていくつかのアップデートがありました。

【方法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を指定しましょう。
同じフォルダにasmdefasmrefの両方があるとエラーになるので注意してください。
image.png

ステップ2: AssemblyInfoExtensions.csを作成する

次に、コンテキストメニューCreate > C# Scriptから同じフォルダにcsファイルを作成し、AssemblyInfoExtensionsと名付けましょう。
名前は適当でもいいですが、同じアセンブリに対して同名のソースファイルがあるとエラーになるので注意してください。
image.png

ステップ3: AssemblyInfoExtensions.csを編集する

これまでのステップで、AssemblyInfoExtensions.csUnity.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);
    }
}

image.png

無事、internalクラスにアクセスできました!

注意点

この手法の欠点は、コンパイル済みアセンブリ(*.dll)に対して利用できないことです。
例えば、UnityEngineUnityEditor等には対応できません。

ただし、対象となるアセンブリにInternalsVisibleToAttribute属性が含まれている場合、そのアセンブリ経由でinternalアクセスできます。
例えばUnityEditorUnity.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);
    }
}

image.png

【方法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経由でパッケージがインストールされます。
image.png

ステップ2-1: (プロジェクト全体のコンパイラを変更する場合) Project Settingsからコンパイラパッケージを設定する

Project Settings > C# Compilerでプロジェクト全体で使用するコンパイラを差し替えられます。

  • Compiler TypeBuiltinからCustom Packageに変更する
  • NameOpenSesame.Net.Compilers.Toolsetに変更する
  • Version4.0.1に変更する
  • 必要に応じてTarget Assembliesにアセットパスを追加する(必要最低限のアセンブリを対象にすることをおすすめします)
  • 画面下部にある「Apply」をクリックする

ステップ2-2: (asmdef単位でコンパイラを変更する場合) asmdefのインスペクタからコンパイラパッケージを設定する

CSharpCompilerSettingsForUnityはasmdef単位でのコンパイラ変更をサポートしています。
プロジェクトビューでasmdefを選択し、インスペクタにあるEnable C# Compiler Settingsにチェックしてください。
- Compiler TypeBuiltinからCustom Packageに変更する
- NameOpenSesame.Net.Compilers.Toolsetに変更する
- Version4.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
    }
}

image.png

無事、internalクラスにアクセスできました!

おまけ

CSharpCompilerSettingsForUnityの他の機能について紹介まで。

  • C#バージョンの変更(〜C# 10)
    • .Net5/6等のAPIが必要な一部機能には対応していません。
  • Analyzer/Source Generatorパッケージの適用
  • dllとしてアセンブリを別ディレクトリに出力

まとめ

方法 利点 欠点 private
アクセス
生産性 実行速度 プレイヤービルド
【方法2.1】
Assembly Definition Reference
:o: 手軽
:o: インテリセンスが有効
:o: 高速な実行速度
:x: privateアクセス未対応
:x: コンパイル済みアセンブリ(*.dll)は未対応
:ng: :white_check_mark: :white_check_mark: :white_check_mark:
【方法3.1】
CSharpCompilerSettingsForUnity
& OpenSesameCompiler
:o: privateアクセス可
:o: どんなライブラリに対しても有効
:o: 高速な実行速度
:x: インテリセンス無効 :white_check_mark: :ng: :white_check_mark: :white_check_mark:

本記事に関するプロジェクトを以下のリポジトリにアップしました。
https://github.com/mob-sakai/UnityAdventCalendar2021

19
12
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
19
12