もんりぃ is undefined.

育児ネタとか、技術ネタとか。

Assembly Definition Files の地雷ポイントについて解説してみる

はじめに

この記事はUnityゆるふわサマーアドベントカレンダー 2018の7日目の記事になります。

qiita.com

6日目の記事は@Nitudonさんの「IncrementalCompiler時代のUnityC#Tips(C#7.2)」でした!

私の記事では、Assembly Definition Files を使う上での地雷ポイントをダラダラと書き殴ってみたいと思います。

🤔 Assembly Definition Files is 何?

もの凄く粗く言うと「Unity における C# スクリプトコンパイル単位を分割する機能」って感じです。

詳しくは Gotanda.unity #7 にて LT をした際の資料がありますので、そちらをご参照くださいませ。

speakerdeck.com

また、テラシュールブログさんの記事もとても分かりやすいので、あわせて紹介しておきます。

tsubakit1.hateblo.jp

💣 地雷ポイント

この記事では、上記 LT 資料の後半に書いた内容を深掘りしてみたいと思います。

1: AssetBundle と組み合わせるとヤヤコシイ

Scene / Prefab / ScriptableObject などを AssetBundle から読み込んでいる場合に、それらの Asset から参照されるスクリプトを Assembly Definition Files 対応する場合は、AssetBundle の再構築が必須になります。

詳しい顛末はこちらの記事にまとめてありますので、ご参照くださいませ。

2: static メソッドを用いている場合に参照の追加でハマりがち

そもそも Assembly Definition Files を適用した場合、 .asmdef ファイルの単位で dll が分割されることになるので、そのままでは dll 外のクラスなどが参照できなくなります。

この問題を回避するために、References という機能が用意されており、「この .asmdef から生成される dll は、References に定義されている .asmdef から生成される dll 達を参照する」という設定を施すことができます。

この機能を設定する必要がある時というのは The type or namespace name 'FooBar' could not be found. Are you missing an assembly reference? といった類いのコンパイルエラーが出るので、その型を含む .asmdef を探して References に追加するワケです。

で、この節での地雷ポイントとしては「親クラスで定義されている static メソッドを用いる場合には親クラスが属する .asmdef も References に追加する必要がある」という点になります。

例えば以下のようなコード群があるとします。

// Parent.cs (A.asmdef)
public abstract class Parent
{
    public static void Foo()
    {
        // Do something
    }
}

// Child.cs (B.asmdef)
public class Child : Parent
{
}

// Usage.cs (C.asmdef)
public class Usage
{
    public void Bar()
    {
        Child.Foo(); // ココで、本来は Parent のメソッドである Foo() を Child 経由でコールしている
    }
}

このような構造の場合、 B.asmdef から A.asmdef を参照し、 C.asmdef から B.asmdef を参照するだけで問題無さそうにも見えますが、実際には Usage.cs から Parent.cs に含まれる static メソッドを実行しているため C.asmdef からは B.asmdef に加えて A.asmdef も参照しないと NG というコトになります。

static が不必要に濫用されているプロジェクトの場合、この辺の管理が相当大変になることが予想されます。

3: partial クラスが使いづらくなる

元々 C# には partial という仕組みがあり、あるクラスの定義を複数のファイルに分割定義することができます。

// Foo1.cs
public partial class Foo
{
    public void Bar()
    {
    }
}

// Foo2.cs
public partial class Foo
{
    public void Baz()
    {
        this.Bar();
    }
}

この分割されたクラスを別々の Assembly として定義し、dll を分割した場合にはお互いに参照ができなくなるため、たとえ References の設定をしていたとしても this.Bar(); の行でコンパイルエラーが発生します。

partial は、Assembly の中での分割定義をサポートする機能に過ぎないため、Assembly Definition Files を用いる時点で殆どその機能を有効に使えなくなると言えます。

4: IDE のサポートが薄い

Visual Studio や JetBrains Rider などの IDE 側のサポートがまだ薄いため、2018年8月上旬時点では「自動的に import を追加する際に、 .asmdef も修正する」といった気の利いた機能がありません。*1

毎回 Unity Editor と IDE とを行き来するのがちょっとだけ面倒だったりするので、IDE 側の手厚いサポートが待たれますね。*2

5: 時々「全スクリプトコンパイルが無効になる」などの謎エラーが発生する

原因がサッパリ分からないのですが、時々以下のような現象が発生します。

  • 全ての Scene, Prefab, ScriptableObject が参照するスクリプトが Missing になる
  • -batchmode 引数を付けてコマンドラインから起動する際に -executeMethod で指定するクラスを見つけてくれなくなる

これらの現象が発生すると、マシンを再起動する別のバージョンの Editor で開き直すかをしないと現象が改善しません。*3

Library/ScriptAssemblies/ 以下を全消ししても改善しないことから、マシンレベルで何らかのキャッシュをしてしまっているものと思われますが、今の所詳細は不明です。

🎉まとめ

適切に使えばコンパイル時間が短くなるなどのメリットがある Assembly Definition Files ですが、未だに多少の問題点が残っていたり、これまで使えていたハックが使えなくなる可能性があったりします。

各プロジェクト毎に、「導入すべきかどうか?」は適宜判断する必要がありそうです。

さて、Unityゆるふわサマーアドベントカレンダー 2018の8日目は@baba_s_さんの「TextMesh Pro で文字列中に表示したい画像を Sprite Atlas に簡単にまとめられる「Simple Sprite Packer」紹介」です。楽しみですね!

*1:私が知らないだけで、「あるよ!」という場合にはコメント欄などでお教えいただけると幸いです。

*2:Rider は 2018.2 というバージョンでサポートされるという噂があります。

*3:完全にこれで直るかどうかは怪しい所だったりもしますが、経験上コレで何とかなっているケースが多いです。