祝 Unity 2021.2 C# 9 対応！

# 2021.2リリース

公式のPushが弱いのか、あんまり話題になっていない気がしますが、こっそりC#9に対応しました。😎

[リリースノート](https://unity3d.com/unity/whats-new/2021.2.0)
> Scripting: Updated C# language version to 9.0 for compilation and IDE's

[マニュアル](https://docs.unity3d.com/2021.2/Documentation/Manual/CSharpCompiler.html)
> C# language version
> C# 9.0

内部的には、Roslynのバージョンが、2.1.0.0->3.9.0.0にアプデートされました。

# 使える機能

言語バージョンは上がりましたが、ランタイムのバージョンが上がったわけではないので、すべての機能が使えるわけではありません。

- パターンマッチの強化
    - 不等式
    - `and`/`or`式
    - `is not`式
- 関数ポインタ
    - Delegateを作らない、Marshalingしない分だけ早い
- `new X()`のクラスXの推論
    - `List<Hoge> dic = new(0)`
- ラムダ式にstaticつけられる
    - staticラムダはDelegateがキャッシュされるのでアロケートが起こらない
- `cond ? a : b`としたとき、a,bの共通の祖先の型を推論
- 拡張メソッド`GetEnumerator`を持っているものもforeach可能に
- ラムダ式の引数を破棄可能に（`_`）
- ローカル関数に属性がつけられる
- `partial`なメソッド
- `nint`/`nuint`

# 使えない機能

コンパイラ君の頑張りではどうにもならないシリーズ

- `[SkipLocalsInit]`によるスタックメモリの0初期化の回避
- 戻り値型の共変
- Module初期化メソッド（`[ModuleInitilizer`）
- Stdcall以外の呼出し規約の関数ポインタの利用
- トップレベルステートメント

# ひと手間加えると使えるもの

`IAsyncEnumerator`とかが入った時もそうでしたが…
コンパイラが必要とするクラスが含まれていないので、適当に追加してやれば動きます。
（最初から入れておいてくれればいいのに…）

- `record`クラス/`init`プロパティ
    - どこかに[`IsExternalInit`](https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/Common/src/System/Runtime/CompilerServices/IsExternalInit.cs#L18) っを書いておけばOK

# SourceGeneratorは使えそうで使えない？

コンパイラのバージョンが上がったということで、Roslynのバージョンが上がりました。3.9.0.0になったわけですが、SourceGeneratorは3.8.0.0からです。つまり使える！！！

…かと思ったのですが、残念ながら動きませんでした。
~~Unityはcsprojではなく、[cscに直接ソースファイル群を渡してコンパイルしているっぽいのですが](https://github.com/Unity-Technologies/UnityCsReference/blob/2020.2.0a13/Editor/Mono/Scripting/Compilers/MicrosoftCSharpCompiler.cs#L141)SourceGeneratorはcscより上のレイヤーで処理してるんじゃないかなとか~~
この辺の実装が2021.2で大幅に変更されていました。今のところ、本当にcscを直接叩いているのかもよくわかってないです。

またIDE側についてですが、そもそもcsprojが存在しないのでそれっぽいcsprojを`com.unity.ide.vscode`などが生成してIDEに読ませています。そしてこのでっち上げられたcsprojがSDK形式ではなく、.NETFramework時代の古い形式となっています。おそらくSouceGeneratorはSDK形式のcsprojじゃないと動かないんじゃないかと考えています。

ちなみに、SourceGeneratorも[Analyzerと同じ手順で導入](
https://docs.unity3d.com/2021.2/Documentation/Manual/roslyn-analyzers.html)して試しました。

一応、動いたゾって言ってる人いるけど…🤔
https://forum.unity.com/threads/source-generators-via-roslyn-analyzers-in-2020-2.1003386/

SourceGeneratorのうま味半減…というか、これなら自前のGeneratorでも変わらないですが、`<CompilerGeneratedFilesOutputPath>`を使って別でやるという手もあります。
非Unityと共有するGeneratorや将来のUnityに期待を込めてSouceGenerator化しておくのはありだとは思います。
（「`not found command "dotnet"`って出たんだけど😡」っていう苦情が非エンジニアから飛んでくると思うので、SDKを同梱するとか運用上の工夫は必要かもしれません）
https://github.com/Cysharp/UnitGenerator#use-for-unity

# CodeAnalyzerも微妙？

CodeAnalyzerは2020で入ったものですが、Roslynつながりで。
しかし、これも完全に動いたとは言い難いところです。

導入したAnalyzerはマニュアルに例示されているもの[`ErrorProne.NET.CoreAnalyzers/0.1.2`](https://docs.unity3d.com/2021.2/Documentation/Manual/roslyn-analyzers.html)です。

まずUnity側ですが、コンパイルが走るとちゃんと警告が出てくれます。

しかし、その状態で`com.unity.ide.vscode`によって吐かせたcsprojをVSCodeに読ませたところ、警告が出ませんでした。
一方、ここに書かれている[`Microsoft.CodeAnalysis.NetAnalyzers/5.0.3`](https://docs.microsoft.com/ja-jp/dotnet/fundamentals/code-analysis/overview)はVSCodeでも反応するので謎です。
TargetFrameworkが原因の可能性もあるので、Standerd2.0の`ErrorProne.NET.CoreAnalyzers/0.4.0-beta.1`でも試しましたが同様でした。

# 以前のバージョンでも使いたい

C#9,8をポーティングしてくれるライブラリが存在します。
ちなみに2021.2では、[`EditorCompilationInterface.DirtyAllScripts`](https://github.com/Unity-Technologies/UnityCsReference/blob/2020.2/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs#L166)などがなくなった影響で動作しません。まぁ、コンパイラ変わってますし仕方ないね。

https://github.com/mob-sakai/CSharpCompilerSettingsForUnity/

### ざっくりとした仕組み

UnityEditorのC#コンパイルのProcessStartInfoを、リフレクションで上書きすることで実現しています。

1. `[InitializeOnLoadMethod] Coffee.CSharpCompilerSettings.Core.Initialize`
1. [`CompilationPipeline.assemblyCompilationStarted += OnAssemblyCompilationStarted`](https://github.com/mob-sakai/CSharpCompilerSettingsForUnity/blob/1.4.0/Plugins/CSharpCompilerSettings/Core.cs#L309)
1. [`OnAssemblyCompilationStarted(string name)`](https://github.com/mob-sakai/CSharpCompilerSettingsForUnity/blob/1.4.0/Plugins/CSharpCompilerSettings/Core.cs#L207)
1. [DLLのパスを取得](https://github.com/Unity-Technologies/UnityCsReference/blob/61f92bd79ae862c4465d35270f9d1d57befd1761/Editor/Mono/Scripting/ScriptCompilation/ScriptAssembly.cs#L51)
1. [`ChangeCompilerProcess`](https://github.com/mob-sakai/CSharpCompilerSettingsForUnity/blob/1.4.0/Plugins/CSharpCompilerSettings/Core.cs#L142)
1. [CompleのProcessStartInfoを取得](https://github.com/mob-sakai/CSharpCompilerSettingsForUnity/blob/1.4.0/Plugins/CSharpCompilerSettings/Core.cs#L150)
1. [ProcessStartInfoを、カスタムコンパイラを使うように書き換え](https://github.com/mob-sakai/CSharpCompilerSettingsForUnity/blob/1.4.0/Plugins/CSharpCompilerSettings/CompilerInfo.cs#L47)
1. [ProcessStartInfoを実行](https://github.com/mob-sakai/CSharpCompilerSettingsForUnity/blob/1.4.0/Plugins/CSharpCompilerSettings/Core.cs#L202)
1. UnityはカスタムコンパイラでコンパイルされたDLLをロードする

カスタムコンパイラは、GUIから指定できます。

https://www.nuget.org/packages/Microsoft.Net.Compilers.Toolset/4.0.0-6.final

「[コンパイラのバージョンだけを下げる方法](https://ufcpp.net/blog/2017/12/msnetcompilers/)」の逆です。
MSBuildを使っている場合、参照に追加するだけでコンパイラが切り替わってくれるとこのことですが、Unityはcscを直接叩いてコンパイルをしているため、上記のようにコンパイルのProcessStartInfoを上書きする必要があるものと思われます。

このライブラリを使えば、.NET5 SDKでビルド（TargetFrameworkはStanderd2.0）できるんじゃないかと思いましたが、`Microsoft.Net.Compilers.Toolset`はv4.7まででした…。ただ、「StartProcessInfoを上書きする」ところまで改造を入れれば、`dotnet build ...`とすることも出来そうな気がします。