Skip to content

JA 01_faq_and_rationale

github-actions[bot] edited this page Aug 20, 2026 · 2 revisions

Chapter 01: 設計思想と FAQ

🇺🇸 English | 🇯🇵 日本語 | Introduction

本章では、ゼロアロケーションを実現する v4 ジェネレーターのアーキテクチャ上の設計哲学について解説するとともに、API設計やコード合成、最新の .NET 機能との統合に関するよくある質問に回答します。

1. 設計哲学

.NET MAUI や WPF、WinUI、Avalonia などの XAML フレームワークにおいて、DependencyProperty の記述は非常に冗長になることで知られています。DependencyPropertyGenerator の最大の目的は、大規模なプロジェクトであっても IDE のレスポンスを低下させることなく、冗長な定型コードであるボイラープレートを撲滅することです。

高スループットかつゼロアロケーションなコード生成を実現するため、v4 のパイプラインでは以下の積極的なアーキテクチャ変更を取り入れています。

インデントを廃止したフラットなコード合成

ジェネレーターは NormalizeWhitespace() の呼び出しや手動でのインデント処理を完全に廃止し、すべて左詰めのフラットなコードを出力します。

  • パフォーマンスの向上: ホットパスにおいて、数千に及ぶインデント用の空白文字列のアロケーションを排除します。
  • AI / LLM への親和性: Gemini Flash のような軽量な AI モデルは、コードを予測する際に空白のインデントをハルシネーションとして出力したり、ずらしてしまうことがよくあります。フラットな出力にすることで、フォーマットに依存しない決定的かつ安定したコード生成が可能になります。

AST の再構築を禁止する直接的なトークンストリーミング

コード合成において SyntaxFactory による抽象構文木である AST の再構築や変異を厳格に避けています。クラス定義を生成したり、new() を明示的な型へ変換するような動的な DefaultValueExpression を解決したりする際、解析済みの AST から直接トークンを抽出し、自作の SourceWriter へ直接ストリーミングします。

  • Gen2 アロケーションのゼロ化: AST ノードの新規構築を完全にバイパスすることで、Gen2 ガベージコレクションのスパイクを未然に防ぎます。

ref struct ベースの SourceWriter

コード生成のロジックは、スタックに割り当てられる SourceWriter および ClassScope ラッパーの上で動作します。これにより、文字列を組み立てる際のヒープアロケーションを完全に回避しています。

Note

トークンストリーミングの詳細なコード仕様、SourceWriter / ClassScope の実装規約、および AST Mutation 比較のマイクロベンチマーク測定結果については、05. コード生成とパフォーマンス最適化 を参照してください。

2. よくある質問 (FAQ)

そもそも、なぜ単一の属性で複数の UI フレームワーク(WPF, MAUI, Avalonia 等)に対応しているのですか?

「Write Once, Run on Any XAML Platform(一度書けば、どの XAML プラットフォームでも動く)」 を実現するためです。 データ抽出フェーズを純粋な DTO に隔離し、出力をプラットフォーム固有の Strategy に委譲することで、全く同じ [DependencyProperty] 属性から DependencyProperty.Register (WPF)、AvaloniaProperty.Register (Avalonia)、BindableProperty.Create (MAUI) を自動で出し分けることができます。これにより、マルチプラットフォーム向け OSS ライブラリ作者や、フレームワーク移行中の企業チームにおける #if のボイラープレート地獄を解消します。

Avalonia 12.2 などで公式の Source Generator が登場した場合、本ライブラリのサポートはどうなりますか?

Avalonia 12.2 で公式のジェネレーターが登場した際は、その仕様と普及状況を評価します。 公式ジェネレーターはプラットフォーム固有の最適化に優れますが、「WPF や MAUI とコードベースを共有するための単一属性」としての需要がある限り、本ライブラリは AvaloniaFrameworkGenerator を維持します。コミュニティが完全に公式へ移行し、マルチターゲットの需要が消滅したと判断できた場合にのみ、段階的な非推奨化を検討します。

なぜ MVVM Toolkit で見られるようなフィールド単位の属性や C# 13 の partial property ではなく、クラス単位の属性を採用しているのですか?

クラスレベルの属性は、通常の Dependency Property と、明確なバッキングフィールドにマッピングしづらい Attached Property の両方を宣言する上で、統一されたメンタルモデルを提供できるためです。ただし、C# 13 の partial property サポートもすでにネイティブ実装されており、背後にある同じ AST 解析ロジックを利用してクラスレベルの属性とシームレスに共存できるようになっています。

フラット出力によってインデントを廃止した場合、「定義へ移動」やデバッグ体験が損なわれませんか?

いいえ、損なわれません。生成されたコードのフォーマットは本来 IDE やフォーマッタの責任領域です。単なる見た目のためだけに、ジェネレーターのホットパス上で打鍵のたびに数メガバイトもの空白文字列をアロケーションするのは非常に非効率です。 もしデバッグ目的で生成ファイルを読む必要がある場合は、IDE のフォーマット機能を利用すれば瞬時にインデントされます。さらに、#line ディレクティブが埋め込まれているため、スタックトレースやコンパイルエラーは常に元のソースファイルへ正確にマッピングされます。

Coerce や Validate、FrameworkPropertyMetadataOptions などの複雑な機能はどうやって扱うのですか?

規約ベースの partial メソッドを通じて解決します。例えば partial void CoerceIsActive(ref bool value)partial bool ValidateIsActive(bool value) といったメソッドを定義しておけば、ジェネレーターがそれを自動検知し、適切なメタデータコールバックとして配線します。また、AffectsRender = trueBindsTwoWayByDefault のような各フレームワーク特有のフラグは、属性の引数として宣言するだけで適用されます。

なぜ「ゼロアロケーション」と謳っているのに、全体では 2.22 MB もアロケートされているのですか?

独自に実装した AST トークン抽出から文字列組み立て、そして SourceWriter のバッファリングに至る内部の合成パイプラインにおけるアロケーションは厳密に 0 Bytes です。残りの 2.22 MB は、Roslyn の API 境界で発生する避けられないオーバーヘッドです。具体的には、インクリメンタルパイプラインのキャッシュオーバーヘッド、GeneratorInitializationContext のセットアップ、そして生成したコードをコンパイラホストに渡す際の最終的な SourceText 文字列のアロケーションによるものです。ジェネレーターのコアロジック自体は完全にゼロアロケーションを維持しています。 エンドツーエンドのベンチマーク測定結果と詳細な削減比率については、05. コード生成とパフォーマンス最適化の Ⅵ. パフォーマンス指標 を参照してください。

XAML Hot Reload, Live Preview, NativeAOT は壊れませんか?

壊れません。このジェネレーターは、実行時のリフレクションや Reflection.Emit を一切使わず、完全に標準的で静的な C# コードを出力します。したがって、NativeAOT や Trimming に対して 100% 安全です。コンパイル時に静的フィールドとプロパティラッパーを生成する仕組みのため、XAML Hot Reload や Language Server Protocol とも完璧に連動し、手書きのコードと全く同じように動作します。


🇺🇸 English | 🇯🇵 日本語 | Introduction

Clone this wiki locally