【.NET Community Toolkit】ソースジェネレータのファイルサイズ調査

### はじめに

先週（2022年8月4日）`.NET Community Toolkit 8.0` が正式にローンチされました。

[Announcing .NET Community Toolkit 8.0! MVVM, Diagnostics, Performance, and more! - .NET Blog](https://devblogs.microsoft.com/dotnet/announcing-the-dotnet-community-toolkit-800/)

私は WPF の開発に [Prism](https://github.com/PrismLibrary/Prism) を使用しています。
Prism に大きな不満はないのですが、MAUI や WinUI3 の次世代のフレームワークが登場している転換期の中で、Prism は .NET Foundation から脱退されており（[Issue #2683](https://github.com/PrismLibrary/Prism/issues/2683)）、Microsoft としても Community Toolkit を押してる空気を感じます。

少し悲しい気持ちになるところもありますが、技術者として新しい技術に惹かれる気持ちもあります。と言う訳で、 .NET Community Toolkit について調査してみました。

### .NET Community Toolkit の売り

8.0 から MVVM 特有のボイラープレートコードをソースジェネレータにより自動生成できるようになったそうです。
いくつかのソースジェネレータが用意されていますが、今回は 下記3 の `INotifyPropertyChanged 属性` について紹介したいと思います。


1. [ObservableObject 属性](https://docs.microsoft.com/ja-jp/dotnet/communitytoolkit/mvvm/generators/observableproperty)
2. [RelayCommand 属性](https://docs.microsoft.com/ja-jp/dotnet/communitytoolkit/mvvm/generators/relaycommand)
3. [INotifyPropertyChanged 属性](https://docs.microsoft.com/ja-jp/dotnet/communitytoolkit/mvvm/generators/inotifypropertychanged)　★今回はここの調査

### INotifyPropertyChanged 属性 って何がうれしいの？

一般的に ViewModel では `INotifyPropertyChanged` を実装した 抽象クラス を継承すると思います。(Community Toolkit では [ObservableObject](https://github.com/CommunityToolkit/dotnet/blob/main/CommunityToolkit.Mvvm/ComponentModel/ObservableObject.cs), Prism では [BindableBase](https://github.com/PrismLibrary/Prism/blob/master/src/Prism.Core/Mvvm/BindableBase.cs) のことです) 

ソースジェネレータを使用すれば、それらを継承する必要がなくなります。
C# は 多重継承 が禁止されていますので（それらを継承しないことで）、MVVM (Binding) に対応してないクラスを継承することができます。

### ほな積極的に INotifyPropertyChanged 属性 を使っていくわ！

ちょっと待ってください。 `.NET Community Toolkit 8.0` の売りは ソースジェネレータ と言いつつ、公式では `INotifyPropertyChanged ジェネレータ` の積極的な使用をオススメしていません。

[INotifyPropertyChanged attributes - .NET Community Toolkit | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/inotifypropertychanged)

> These attributes are only meant to be used in cases where the target types cannot just inherit from the equivalent types (eg. from `ObservableObject`). If that is possible, inheriting is the recommended approach, as it will reduce the binary size by avoiding creating duplicated code into the final assembly.

上記の通り、ソースジェネレータの使用の度に重複コードが生成されてファイルサイズが増えるので、何かを継承する必要がなければ `ObservableObject` を継承してね　とのことです。

公式の言ってることは理解しますが、クラスを継承せずに機能を実現できる `INotifyPropertyChanged ジェネレータ` を使用しておいた方が後々潰しが効きます。
つまり、ファイルサイズの増加量が許容できる範囲であれば、脳死で `INotifyPropertyChanged ジェネレータ` を使用しておきたいです。

### で、どんだけサイズ増えるの？

と言う訳で、`INotifyPropertyChanged ジェネレータ` の使用によって どれだけファイルサイズが増えるか実測してみました。
以下は VS2022 17.3.0 + .NET6.0 で Release ビルドした dll のファイルサイズです。

| クラス数 | 継承版    | ソースジェネレータ版 |
| -------- | --------- | -------------------- |
| 1        | 5.0 KByte | 12.0 KByte           |
| 10       | 5.5 KByte | 49.5 KByte           |
| 100      | 9.5 KByte | 444.5 KByte          |
| 1000     | 52.0 KByte | 5.3 **M**byte       |

継承版では +50Byte/class、 ソースジェネレータ版では +5500Byte/class でファイルサイズが増加しました。

このサイズをどうみるかは開発製品や実行環境によると思いますが、目くじらを立てる程のサイズではないかと思いました。

### 検証コード
T4 で生成しました。

```
<#@ template language="C#" #>
<#@ assembly name="System.Core" #>

<# for (int i = 0; i < 1000; i++ ) { #>
//[CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChanged] // SourceGenerator
public sealed partial class MyObject<#= i #>
    //: CommunityToolkit.Mvvm.ComponentModel.ObservableObject   // Inheritance
{ }

<# } #>
```

### 検証環境

- VS2022 17.3.0
- .NET6.0
- CommunityToolkit.Mvvm 8.0.0