概要
C# 9.0のソースジェネレーターの実例としてよく挙げられるのが、INotifyPropertyChanged
です。
そこで実際にWPFに組み込もうとしたところ、結構苦労したので、その注意点を説明します。
組み込むソースジェネレータ
INotifyPropertyChanged
を実装するソースジェネレータは 公式サンプルもありますが、より多機能な@okazukiさんのプロジェクトを組み込みます。
テストアプリ
題材とするテストアプリです。ViewModelは上記の記事のままです。ソースジェネレータを組み込んでいないので、まだビルドすることはできません。
ViewModel
public partial class MainWindowViewModel
{
[AutoNotify("Xxxx")]
private string _firstName;
[AutoNotify]
private string _lastName;
[AutoNotify]
public string FullName => $"{Xxxx} {LastName}";
}
View
<Window
x:Class="MvvmGenerator.WpfTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MvvmGenerator.WpfTests"
Width="300" Height="250">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Xxxx}" />
<TextBox Text="{Binding LastName}" />
<TextBlock Text="{Binding FullName}" />
</StackPanel>
</Window>
ソースジェネレータプロジェクト
コンソールアプリケーションではプロジェクト参照でも動きますが、現時点(2021/04/11)ではWPFでは動きません。
nugetパッケージ化
まず、ソースジェネレータプロジェクトをnugetパッケージ化する必要があります。
通常のパッケージとは違うので、いくつか変更するところがあります。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeBuildOutput>false</IncludeBuildOutput>
<IncludeSymbols>false</IncludeSymbols>
<DevelopmentDependency>true</DevelopmentDependency>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking> <TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput</TargetsForTfmSpecificContentInPackage>
<Version>1.3.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0-3.final" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.0" PrivateAssets="all" />
</ItemGroup>
<Target Name="_AddAnalyzersToOutput">
<ItemGroup>
<TfmSpecificPackageFile Include="$(OutputPath)\MvvmGenerator.dll" PackagePath="analyzers/dotnet/cs" />
</ItemGroup>
</Target>
</Project>
プロパティ名 | 値 | 説明 |
---|---|---|
IncludeBuildOutput | false | パッケージに通常のdll組み込みはしない |
IncludeSymbols | false | パッケージにシンボルファイル(.pdb)は含めない |
SuppressDependenciesWhenPacking | true | プロジェクトで使用しているパッケージの依存関係は含めない |
DevelopmentDependency | true | 開発用のパッケージとして認識されるようにする。 |
TargetsForTfmSpecificContentInPackage | $(Targ... | パッケージに組み込むカスタムターゲットを指定する |
TargetsForTfmSpecificContentInPackage
から下記のカスタムターゲットを指定します。
<Target Name="_AddAnalyzersToOutput">
<ItemGroup>
<TfmSpecificPackageFile Include="$(OutputPath)\MvvmGenerator.dll" PackagePath="analyzers/dotnet/cs" />
</ItemGroup>
</Target>
.csprojファイルの編集後ビルドして、.nupkgファイルを作成します。
作成したnupkgファイルの中を見てみると、通常のnupkgファイルとは異なった構成になっているのが分かります。
ローカルでパッケージ配布
作成したパッケージはnuget.orgを使用してもいいですが、自分が簡単に使うだけなら、ローカルフォルダに置いてnugetレポジトリとして、VisualStudioに登録することで使えるようになります。
詳しいやり方は以下を参照してください。
WPFプロジェクト
.csproj編集
作成したnugetパッケージをWPFプロジェクトに追加します。
ここでも.csprojファイルの修正が必要です。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MvvmGenerator" Version="1.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
<IncludePackageReferencesDuringMarkupCompilation>
をtrue
に設定します。この長いプロパティは最近追加されたもののようで、まだMicrosoft Docsにも含まれていません。
動作確認
この状態でビルドして動作させます。
2つのTextBox
に入力された内容が、下のTextBlock
に反映されることから、INotifyPropertyChanged
が正しく実装されていることが分かります。
参考
https://github.com/ufcpp/ValueChangedGenerator/
https://zenn.dev/naminodarie/articles/32973a36fcbe99
ソースコード
今回の完全なソースコードをGithubにおいておきます。okazukiさんの記事のレポジトリをフォークしたものです。
https://github.com/soi013/MvvmGenerator
環境
VisualStudio 2019 Version 16.9.3
.NET 5
C# 9.0
謝辞
WPFで組み込もうとしたところ、うまく動かなかったので、@okazukiさんに助けてもらいました。ありがとうございました。