便利なスクリプトの詰め合わせ【Unity／.NET】

年度末の棚卸。

全て 👇 コチラの Unity パッケージ（UPM）に入ってます。.NET 環境でもファイル単位でつまめます。

https://github.com/sator-imaging/Unity-Fundamentals



<p><details lang="ja" --open><summary>もくじ</summary>

- [`ObservableEvent<T>`](#observableeventt)
    - [標準イベント向け `ObservableEvent<T>`](#標準イベント向け-observableeventt)
    - [Unity イベント向け拡張メソッド](#unity-イベント向け拡張メソッド)
- [`early finally` パターン](#early-finally-パターン)
- [`Sentinel`](#sentinel)
- [UI Toolkit 関連ヘルパー](#ui-toolkit-関連ヘルパー)
    - [`UIToolkitCoreExtensions`](#uitoolkitcoreextensions)
        - [`ExecuteAfter`](#executeafter)
        - [`DisableStyleTransitionScope`](#disablestyletransitionscope)
    - [`UIToolkitEventSubscription`](#uitoolkiteventsubscription)
        - [UI Toolkit イベントの特殊な癖](#ui-toolkit-イベントの特殊な癖)
    - [`UIToolkitDropdownHelper`](#uitoolkitdropdownhelper)
- [xxHash32 / xxHash64 ハッシュの最小実装](#xxhash32--xxhash64-ハッシュの最小実装)
- [64bit `long` 版の ULID](#64bit-long-版の-ulid)
- [`StrictEnum`](#strictenum)
    - [Kotlin 風の列挙型を C# で実装する](#kotlin-風の列挙型を-c-で実装する)
- [`Run`](#run)
- [`UString`](#ustring)
- [`SpanList<T>`](#spanlistt)
- [Leaked Managed Shell の防止](#leaked-managed-shell-の防止)
- [アロケなしで文字列を分割](#アロケなしで文字列を分割)
- [その他](#その他)
    - [`WhenEachEnumerator`](#wheneachenumerator)
    - [`UnityEngine.Object` で `??` `?.` を使う](#unityengineobject-で---を使う)
    - [Leaked Managed Shell Detector](#leaked-managed-shell-detector)
    - [Assembly Definition エディター](#assembly-definition-エディター)
    - [オブジェクトプール](#オブジェクトプール)
    - [UnityEditorMainToolbar](#unityeditormaintoolbar)
- [おわりに](#おわりに)

<!------- End of Details JA Tag -------></details></p>





# `ObservableEvent<T>`

購読と同時に解除タイミングが指定できる `Subscribe` が Rx 唯一の利点、それ以外は別に要らないんじゃね？ という考えに基づいた実験的 API です。

## 標準イベント向け `ObservableEvent<T>`

```cs
private ObservableEvent<int> m_myEvent = new();
public IObservableEvent<int> MyEvent => m_myEvent;  // Sub/Unsub のみ可能

MyEvent.Subscribe(myAction)
    .BindTo(cancellationToken);  // トークンにバインドする。
    .AddTo(disposables);         // または IDisposable コレクションに追加する。
MyEvent.Unsubscribe(myAction);   // 👈 自力で解除

// イベントの呼び出し
m_myEvent.Invoke(310);

// イベント側からサブスクライバーを解除する
m_myEvent.Dispose();
```


<p><details lang="ja" --open><summary><code>ObservableAction&lt;T&gt;</code></summary>

こっちはラッパー噛ませてないので、もしかしたらほんのちょっとだけ効率が良いかも？

```cs
private event Action<int>? Ev_MyEvent;
IObservableAction<int>? cache_MyEvent;
public IObservableAction<int> MyEvent => cache_MyEvent ??= new ObservableAction<T>(
    cb => Ev_MyEvent += cb,
    cb => Ev_MyEvent -= cb);

Ev_MyEvent.Invoke(310);
```

<!------- End of Details JA Tag -------></details></p>



## Unity イベント向け拡張メソッド

```cs
myButton.onClick.Subscribe(() => ...).BindTo(ct);
mySlider.onValueChanged.Subscribe(val => ...).AddTo(disposables);
```

後述の `Sentinel` と組み合わせると、非同期コールバックのハンドリングやタッチ操作での同時押しへの対応等、幅が広がります。


<p><details lang="ja" --open><summary>イベントの依存関係のグラフ化</summary>

`Subscribe` しか提供していないのはマイナスではなく、逆にイベントの相互依存関係を Mermaid でグラフ化するのが容易なのでは？ と AI に聞いてみると
- Microsoft.Build.Locator
- Microsoft.Build.Evaluation
- Microsoft.CodeAnalysis.CSharp

これらのライブラリを使えば C# プロジェクトの静的構文解析が出来るとのことなので、いずれ Subscribe 呼び出しを根こそぎ走査してグラフ化する処理を実装したいですね。

（Subscribe と AddTo/BindTo の組み合わせでそんなのが要らなくなるくらい見通しが良くなりますが）


--

コード例（Microsoft Copilot が生成）

```cs
using System;
using System.IO;
using Microsoft.Build.Locator;
using Microsoft.Build.Evaluation;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;

class Program
{
    static void Main(string[] args)
    {
        // 1. MSBuildの初期化
        MSBuildLocator.RegisterDefaults();

        // 2. プロジェクトファイルのパスを指定
        string projectPath = @"path\to\your\project.csproj";

        // 3. MSBuildプロジェクトをロード
        var projectCollection = new ProjectCollection();
        var project = projectCollection.LoadProject(projectPath);

        Console.WriteLine("Project Properties:");
        foreach (var property in project.AllEvaluatedProperties)
        {
            Console.WriteLine($"{property.Name}: {property.EvaluatedValue}");
        }

        // 4. コンパイル対象のファイルリストを取得
        Console.WriteLine("\nSource Files:");
        foreach (var item in project.GetItems("Compile"))
        {
            Console.WriteLine(item.EvaluatedInclude);
        }

        // 5. Roslynを使って各ファイルを解析
        Console.WriteLine("\nRoslyn Analysis:");
        using (var workspace = MSBuildWorkspace.Create())
        {
            var roslynProject = workspace.OpenProjectAsync(projectPath).Result;

            foreach (var document in roslynProject.Documents)
            {
                Console.WriteLine($"Analyzing: {document.Name}");
                var syntaxTree = document.GetSyntaxTreeAsync().Result;
                
                // 必要に応じて更なる解析処理を追加
            }
        }
    }
}
```

<!------- End of Details JA Tag -------></details></p>





# `early finally` パターン

`Go` とかにある `defer` です。https://go-tour-jp.appspot.com/flowcontrol/12

例えば `ArrayPool<T>` を使う場合、`try-finally` だと貸出／返却コードを離れた場所に書く必要がありますが、Defer を使えば `finally` 句を近くに書けます。

```cs
using var rentalBuffer = ArrayPool<byte>.Shared.Rent(128)
        .Defer(buffer => ArrayPool<byte>.Shared.Return(buffer));  // 👈 finally 句の中身

var span = rentalBuffer.Value.AsSpan(0, 128);  // .Value は拡張メソッドのレシーバー

//...
```


👇 に同じ。

```cs
var buffer = ArrayPool<byte>.Shared.Rent(128);

try
{
    var span = buffer.AsSpan(0, 128);

    //...
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer);
}
```


ArrayPool 以外にも `Interlocked` を使った排他処理や、

```cs
if (Interlocked.Exchange(ref _value, 1) != 0) return;
using var _ = Defer.New(() => Interlocked.Exchange(ref _value, 0));

//...
```


一時的に設定した値を確実に復帰したい時、

```cs
using var _ = foo.Defer(foo.Value, (x, restore) => x.Value = restore);
foo.Value = tempValue;

//...
```


終了時にストリームを頭出ししたい時等にも使えます。

```cs
using var _ = stream.Defer(stream => stream.Position = 0);
```


--

Defer を使ってもパフォーマンス上のデメリットはほぼ無しですが、値型と組み合わせるとちょっと面倒なことになるので言語機能として付けて欲しかった。（去年末にクローズ）

https://github.com/dotnet/csharplang/issues/1398





# `Sentinel`

排他制御ユーティリティーです。

内部で使っている `ConcurrentDictionary` はキーが既に存在するときにロックを行わない、という非常に良くできたクラスなのでロックフリーでスレッドセーフに動作します。

```cs
readonly static SentinelToken _token = Sentinel.GetUniqueToken();

using (Sentinel.ExclusiveScope(_token, out var rejected))
{
    if (rejected) return;  // 既に別のイベントが排他ブロックに入っている場合は true

    // ココから排他ブロック
}
```


排他制御はマルチスレッド処理以外にも、非同期処理をイベントコールバックに登録したときに起きる「スレッドをブロックせず即時終了してしまう」問題への対処や、複数の UI コントロールの入力成立制御等にも使えます。

例えば１秒に１回、どれか一つのボタンの入力しか成立させたくない場合は、

```cs
// 全てのボタンで共有するトークン
readonly static SentinelToken _sharedToken = Sentinel.GetUniqueToken();

myButton.onClick.Subscribe(async () =>
{
    using (Sentinel.ExclusiveScope(_sharedToken, out var rejected))
    {
        if (rejected) return;

        Foo();
        await Task.Delay(1000);  // １秒待ってから排他ブロックを抜ける
    }
});

// 同じトークンを使う別のボタン
otherButton.onClick.Subscribe(async () => ...);
```

とすると、複数のボタンに跨る `Debounce()` として動作するので、スクリーン上のボタンをタップしたまま指がズレて他のボタンに引っかかって入力成立、みたいなシチュエーション防止にも使えます。

その他の細かい挙動は実装次第なので `await` の置き方等々で良しなにします。

```cs
// イベント到達直後の値で１秒後に実行する
var val = GetValue();

await Task.Delay(1000);
Foo(val);
```





# UI Toolkit 関連ヘルパー

Unity Discussions だったかで既に話題に上がっていたように思いますが、Visual Element 用のカスタム Manipulator を UI Builder から設定できるようにならないと UITK の根本的な開発体験は上がりませんね。

流石にデータバインディングだけじゃキツいです。今思えば uGUI の Persistent Callback は良くできたシステムでしたね。


## `UIToolkitCoreExtensions`

細々としたコントロールを提供します。


### `ExecuteAfter`

`ExecuteAfter` は公式 API `ExecuteLater`（ミリ秒指定）の描画フレーム版です。

DropdownField なんかは、設定した値が不正だった場合に元の値に書き戻す処理が UI コントロール側が行う処理と競合する等、RegisterCallback 内で１描画フレーム待たないと上手く動かないなんてシチュエーションが多いです。

困ったら１描画フレーム待ってみてください。上手く行くかもしれません。

※ 可能なら Dropdown は使わずラジオボタンやトグルだけで UI をまとめるのが吉。


### `DisableStyleTransitionScope`

USS トランジションは便利な一方で厄介な側面もあって、例えば初期化時に値を即時適用したいのにアニメーションが発生してしまう等の問題があります。

そんな時は `using (ve.DisableStyleTransitionScope())` を使うとスコープ内に限定してトランジションを無効化できます。


## `UIToolkitEventSubscription`

`ve.RegisterCallbackAsSubscription` でイベント登録解除用の IDisposable を取得できます。


<p><details lang="ja" --open><summary>駄文</summary>

`IObservable<T>`、`Subscribe` を実装するにはどうしたものかって感じなので、とりあえずで作ったやつです。

`RegisterCallback` は VisualElement のメソッド、`ChangeEvent<T>` は `INotifyValueChanged<T>` を実装していなきゃいけない**という訳ではない**という曲者。

（`value` で `ChangeEvent<T>` を発行する義務はなく、発行しなくてもコンパイルは通る）

UITK だと `RegisterCallback<TEvent, TState>(...)` の使用がほぼ必須で、シグネチャを `Subscribe<TEvent, TState>` にしても `Subscribe<TVal, TState>` にしても型名の省略が不可能というのもあり、、、

<!------- End of Details JA Tag -------></details></p>



### UI Toolkit イベントの特殊な癖

UI Toolkit では `TState` だけを入れ替えて複数回登録する、みたいなことが出来ない仕様になっています。

```cs
readonly static EventCallback<ChangeEvent<bool>, VisualElement> OnToggleChanged = (e, ve) => { ... };

// チェックボックスの状態によって表示非表示を切り替えたい（非表示にする処理は同じなのでコールバックは使いまわしたい）
myToggle.RegisterCallback(OnToggleChanged, controlA);    // 無反応！
myToggle.RegisterCallback(OnToggleChanged, textFieldA);  // 無反応！
myToggle.RegisterCallback(OnToggleChanged, sliderA);     // sliderA だけがイベントに反応する

// コールバックが同一インスタンスだった場合は重複した登録を回避し TState に渡すオブジェクトだけを更新する、
// って感じの内部処理が行われているので、面倒だが以下のようにするしかない。
myToggle.RegisterCallback<ChangeEvent<bool>>((e) =>
{
    OnToggleChanged.Invoke(e, controlA);
    OnToggleChanged.Invoke(e, textFieldA);
    OnToggleChanged.Invoke(e, sliderA);
});

// 呼び出し回数が多い場合にアロケ回避する為には、、、
myToggle.RegisterCallback<ChangeEvent<bool>, (VisualElement, VisualElement, VisualElement)>(
    static (e, controls) =>
    {
        OnToggleChanged.Invoke(e, controls.Item1);
        OnToggleChanged.Invoke(e, controls.Item2);
        OnToggleChanged.Invoke(e, controls.Item3);
    },
    (controlA, textFieldA, sliderA));
```


登録解除も面倒というか、登録したものと同じインスタンスを解除時に指定する必要があります。

なので変数名を決めて、入れて、、、とする必要が出てきます。IDisposable なんて要らなそーｗｗ て感じのメソッドですが必要になる時もあるでしょう。


## `UIToolkitDropdownHelper`

後述の `StrictEnum` を使って色々とやるやつです。





# xxHash32 / xxHash64 ハッシュの最小実装

UTF-8 のバイト列で名前を受け取った場合、その文字列自体に意味があったり UI に表示したりする場合は `string`（UTF-16）に変換する意味がありますが、辞書のキーとして使う等、単なる識別子でしかない場合には違いが分かればいいという事になります。

```cs
var name = Encoding.UTF8.GetString(utf8bytes);
nameToDataDict[name] = receivedData;
```


👇 これで良くね？

```cs
var hash = MiniXXHash.XXH32(utf8bytes, seed);
nameHashToDataDict[hash] = receivedData;
```


ネットワーク通信においては、プロトコルの仕様上 UTF-8 を毎フレーム受け取ったりする場合もあるかと思います。そういう場合に結構効いてきます。

https://qiita.com/sator_imaging/items/36c390c0536a6750b788

（キーを４文字や８文字にして UTF-8 バイト列を整数型として咀嚼する、というのが一番ベスト）





# 64bit `long` 版の ULID

プレハブのように Unity のセッションを超えて永続化されるオブジェクトに ID を振る時に便利です。

`long` なので扱いやすく `Guid` や `Ulid` 等の特別な型に対応していない API やファイル形式でも使えるのがポイント。


> ※ 43ビットの整数が約279年をミリ秒で表せるとは思いもしなかったので、内部でスゴイ無駄な処理をしてます。
> 加えてマルチスレッド環境で使えない（訳ではないがちょっと微妙）という問題もあります。直したいですね。


https://qiita.com/sator_imaging/items/576781d95367d5856624

https://github.com/sator-imaging/Half-Ulid





# `StrictEnum`

列挙型のアロケ回避系ユーティリティーです。

特徴は、独自に文字列表現をキャッシュせず C# の内部ライブラリが抱えているキャッシュをぶっこ抜く形で実装している点です。他にも
- Unity の `InspectorName` と C# の `EnumMember` による文字列表現のカスタマイズに標準対応
- `Flags` アトリビュート付き列挙型のフォーマットとパースに対応
- `IsOrdinalFromZero` 等で怪しい Enum を排除

なんかがあります。

フィールド名そのままの文字列表現なんて Json の書き出し位にしか使えなくね？ ということで、`StrictEnum.DisplayNameFactory` を使って表示名をカスタマイズ／翻訳する機能も付いてます。



<!-- omit in toc -->
## 列挙型の型変換を絶対に許さないマン

<p><details lang="ja" --open><summary>（脱線）</summary>

C# の列挙型の問題として `(MyEnum)0` で簡単に好きな値が作れちゃう問題があります。（`Sentinel` では逆にその性質を悪用しています）
フィールド名その物の文字列表現なんて実際のところ使い道なんてないんですが、Enum を文字列にするたびにアロケーションが起きるのも問題です。

それら点だけに目を向ければ Java / Kotlin や Rust の列挙型実装は魅力的ですが、Google が Enum 使うな！ という公式チュートリアルを出す程度には Kotlin/Java の実装にも問題があって、その代替案として `IntDef`、要は C# と同じ単なる名前付き定数を使うことを推奨していたりします。

結論、C# の列挙型がキャストできなきゃ何の問題もないのでは？ となりその解決策がコチラ。

https://github.com/sator-imaging/CSharp-StaticFieldAnalyzer?tab=readme-ov-file#enum-analyzer-and-code-fix-provider

<!------- End of Details JA Tag -------></details></p>



## Kotlin 風の列挙型を C# で実装する

<p><details lang="ja" --open><summary>リフレクションを使って不正な値を作ってもインスタンスが一致しないから、安心安全の完璧な列挙型じゃん！ と思ってた時期がありました。</summary>

※ `record` や ValueObject として実装したらダメ。

```cs
public sealed class EnumLike
{
    public static readonly EnumLike A = new("A");
    public static readonly EnumLike B = new("B");

    public static ReadOnlySpan<EnumLike> Entries => EntriesAsMemory.Span;
    public static readonly ReadOnlyMemory<EnumLike> EntriesAsMemory = new(new[] { A, B });


    /* ===  Kotlin style enum template  === */

    static int AUTO_INCREMENT = 0;  // iota

    public readonly int Ordinal;
    public readonly string Name;

    private EnumLike(string name) { Ordinal = AUTO_INCREMENT++; Name = name; }

    public override string ToString()
    {
        const string SEP = ": ";
        Span<char> span = stackalloc char[Name.Length + 32];

        Ordinal.TryFormat(span, out var written);
        SEP.AsSpan().CopyTo(span.Slice(written));
        written += SEP.Length;
        Name.AsSpan().CopyTo(span.Slice(written));
        written += Name.Length;

        return span.Slice(0, written).ToString();
    }
}
```


`switch` する方法

```cs
switch (val)
{
    case {} when val == EnumLike.A:
        System.Console.WriteLine(val);
        break;

    case {} when val == EnumLike.B:
        System.Console.WriteLine(val);
        break;
}
```

<!------- End of Details JA Tag -------></details></p>





# `Run`

async/await 汚染を広げないための API と、実行スレッド数制御の為の機能等々があります。

```cs
Run.OnMainThread(...);
Run.OnMainThreadAndForget(...);

Run.InThreadPool(...);
```


--

Unity 6 で `Awaitable.MainThreadAsync` と `Awaitable.BackgroundThreadAsync` が付いたことでより効率的に CPU を使える！ ってことで

```cs
Func<Task> job = async () =>
{
    await Awaitable.BackgroundThreadAsync();  // 👈 裏で回す
    var result = HeavyJob();                  // 裏で回すなら非 async の方がほんのり速い

    await Awaitable.MainThreadAsync();  // 今まではコレが無かったのでメインスレッドを浪費してた
    myUnityObj.DoIt(result);
}

// !!
var tasks = new Task[100];
for (int i = 0; i < tasks.Length; i++)
{
    tasks[i] = job.Invoke();
}
```

一気に 100 スレッドで回す！ とかやると逆に遅くなります。

並列スレッド数は基本、CPU のコア数と同じ数に抑えたほうが効率が良いので `Run.InThreadPool` を使って並列実行するスレッド数を制御してやると良いでしょう。


> ※ 内部で `ConcurrentExclusiveSchedulerPair` を使ってますが、うーーんという実装です。一応並列化による恩恵は受けられますが無駄にスレッド数を消費するという状態なので、別の選択肢を探したほうが良いでしょう。
>
> `System.Threading.Channels` `Channel<T>` を使っているライブラリがおススメ。

スレッド使い分け表
- ネットワーク処理のようにほぼ CPU を使わず待ち時間が長いだけの場合
    - メインスレッドで回す（非同期メソッドを使う）
    - ※ `ConfigureAwait(false)` するべき勢と、するべきではない勢がいる
- メッシュ編集のように不可分でかつ CPU を使う重い処理の場合
    - 裏で同期的に回す（Task.Run を使う）
    - 並列実行数をちゃんと管理する





# `UString`

Unity 以外で使う意味が無いので ***U***nity String。`DefaultInterpolatedStringHandler` より速い！

👇 からの勢いで作った物です。

https://qiita.com/sator_imaging/items/edc5be1a55dd867e1c73


--

`AggressiveInlining` 無しで優れたパフォーマンスを実現していたり、`ToCharSpan` や内部バッファーへのアクセス等も提供します。


```cs
// リングバッファーを使った簡易フォーマッター　※256文字を超えるたびに古い結果が書き換えられる
var val = UString.Concat("Value: ", intVal.ToCharSpan("D5"));
//                                        ~~~~~~~~~~~~~~~~~

// 256文字のバッファーじゃ足りない場合
using (UString.RingBufferScope(new char[512]))
{
    _ = int.ToCharSpan("D512");
}

// 内部バッファーに直接アクセス
var (buffer, consumed) = sb.GetRawBuffer();

// 良きようにやってくれる TextMesh Pro 用のワンライナー
UString.SetCharArrayNonAlloc(textComponent, "Value: ", intVal);
// --- or ---
textComponent.SetCharArrayNonAlloc("Value: ", intVal);
```





# `SpanList<T>`

`Memory<T>` にインデックスアクセサーを付けた `Span<T>` のジャグ配列実装。

可能だから、でジェネリック型になっていますが `MessageTemplate` (https://messagetemplates.org/) フォーマットの文字列を一括置き換えする以外の用途はないと思います。もし何かあったら教えてください。


```cs
return "おお{yushaName}よ、しんでしまうとは{nasakenaiType}".FormatNonAlloc(_fromList, _toList);
```
- 最終出力の `string` 以外の全てを `ReadOnlySpan<char>` で指定可能
- ホール名が指定可能なので元文字列の可読性が高い
- 事前に結果の最大長を予測できるので効率よく処理可能

被りが無いようにホール名を付けるのが面倒という欠点がありますが、全てのホール名を決められた場合は fromList も toList も一回の初期化でアプリ存命中ずっと使いまわすことが出来るようになります。

（ホール名が 100, 200 あるとその数だけループが回るのでアレですが）





# Leaked Managed Shell の防止

`ManagedShell` は Leaked managed shell を防止するためのヘルパーです。エディターとランタイムで共通して使えるようになっていて、ヌル許容性も適切にハンドリングします。

```cs
var unityObj = new GameObject();
UnityEngine.Debug.Log(unityObj.name);  // unityObj にヌル警告は出ない

ManagedShell.Dispose(ref unityObj);    // Dispose を通した後は、、、
UnityEngine.Debug.Log(unityObj.name);
//                    ~~~~~~~~ヌルの可能性があります！
```


非常にレアなケースで起きる「削除したはずのオブジェクトが見つかってしまう」問題への対処や `IDisposable` を実装した `UnityEngine.Component` の `Dispose` を呼びだす機能があります。

機能を無効化するためのプリプロセッサーシンボル
- `MANAGED_SHELL_DISABLE_DEEP_DESTROY`
- `MANAGED_SHELL_DISABLE_DISPOSABLE_CHECKS`


https://qiita.com/sator_imaging/items/6bca6c642b6347bd68d8

https://qiita.com/sator_imaging/items/1251fa3a0b51db87a82f





# アロケなしで文字列を分割

分割数制限なし。
```cs
foreach (Range range in "Foo Bar Baz Qux Quux Quuux".SplitEnumerator(' '))
{
    Console.WriteLine(span[range].ToString());
}
```


最大１０分割まで。
```cs
result = "A, B, C".SplitNonAlloc(" ,");     // ["A, B, C"]
result = "A, B, C".SplitAnyNonAlloc(" ,");  // ["A", "B", "C"]

foreach (var item in result) { /* ... */ }

for (int i = 0; i < result.Count; i++) { /* ... */ }
```


URL とかファイルパスとかに便利な２分割メソッド。
```cs
if ("Folder/FileName.ext".TrySplit('/', out var parentDirName, out var fileName))
{
    //...
}

if (url.TrySplitLast('#', out var urlAndQuery, out var anchorName))
{
    //...
}
```


https://qiita.com/sator_imaging/items/1393aa0efa3b064d77ec






# その他

## `WhenEachEnumerator`

https://qiita.com/sator_imaging/items/0facece38f6e1c03bd19


> `EnumeratorCancellation` なんてものがある
> 
> https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.compilerservices.enumeratorcancellationattribute?view=netstandard-2.1


## `UnityEngine.Object` で `??` `?.` を使う

`Nullable()` は `??` `?.` 演算子を正しく使うための拡張メソッドです。

```cs
obj = unityObj.Nullable() ?? throw new NullReferenceException();
go = go.Nullable() ?? new GameObject();
```

アナライザーで Unity オブジェクトの Null-coalesce の扱いを提案や警告ではなくエラーに設定している場合は役に立ちます。


## Leaked Managed Shell Detector

Leaked Managed Shell を引き起こす可能性がある型を一覧します。構文解析等は行っておらず false positive なチェックなので大量にリストされます。


## Assembly Definition エディター

プロジェクト内のすべての `.asmdef` を一覧／編集できます。Unity Scripting Fundamentals を楽に追加するためだけに作りました。

一か所、例外を握りつぶしている場所があるので `.asmdef` のバックアップを取ってからのご利用をお勧めします。


## オブジェクトプール

https://qiita.com/sator_imaging/items/2a387a54a01e91e5d71d

https://qiita.com/sator_imaging/items/80c712ee4fcabfce9163


<!-- omit in toc -->
## ThreadSafeSingleton

https://qiita.com/sator_imaging/items/6c60e462417a5235778a


## UnityEditorMainToolbar

https://qiita.com/sator_imaging/items/f1bdf82016117cd6c7bd


<!-- omit in toc -->
## CancellationToken based Lifecycle Manager for Unity Editor

排除すべきは Unity への依存ではなく、「`System.Math.Clamp` で良いのに `UnityEngine.Mathf.Clamp` を使っている」こういう意味なく Unity に依存してしまっている部分だけです。

https://qiita.com/sator_imaging/items/48296a686e4bbff8676c



<!-- omit in toc -->
## NUnit-compatible Framework for Unity Editor

無用の長物。`Assert.That`、`Is.EqualTo` と `Throws.TypeOf<T>` しか実装していません。


<!-- omit in toc -->
## おまけ）レコード型

`IsExternalInit` はあくまで `init` アクセサーに必要なだけであり、`record` 自体はプロパティーの定義さえ行えばバニラ C# 9.0（Unity）でも使うことが出来ます。

Java っぽいクラス定義が出来て省エネですが、参照型の ValueObject としての挙動が自動実装されるので気を付けましょう。

```cs
public record MyRecord(object Obj, int Value)
{
    private object Obj { get; } = Obj;  // この Obj はプライマリーコンストラクターの Obj
    public int Value { get; internal set; } = Value;  // 同上

    // sealed record にするか virtual メソッドにして上書きすれば脱 ValueObject できる
    public virtual bool Equals(MyRecord? other) => other is not null && ReferenceEquals(other);
    public override int GetHashCode() => HashCode.Combine(typeof(MyRecord), Obj, Value);
}
```





# おわりに

AI のコード生成を試しているとライブラリに存在する API を呼ぶ／テスト書くのは得意だけど、呼ばれる側の API を作るのは苦手って感じなので、これらの API 群も暫くは使えるんじゃないでしょうか。

再利用可能なコルーチン実装とか、列挙型についてのあれこれとか、ネットワーク関連とか `Run`、`HalfUlid` が微妙とか他にもいろいろとあるんですが、気の向くままに作ってたら低レベル・低依存の API が一通り揃ってたって感じで感慨深いです。


以上です。お疲れ様でした。