.NETのテンプレートを比較してみる

この記事は[C# Advent Calendar 2024](https://qiita.com/advent-calendar/2024/csharplang)(シリーズ2)の18日目の記事です。

https://qiita.com/advent-calendar/2024/csharplang

別のアドベントカレンダーでこちらの記事を書いていたら、もう少し深追いしてみたい項目が出てきたのでC#アドベントカレンダーにエントリーしてもう一記事書くことにしました。

https://qiita.com/noobow/items/96f3671b524560f7daeb

# 本記事の目的
[前述の記事](https://qiita.com/noobow/items/96f3671b524560f7daeb)にて、「コンソールアプリケーション」のテンプレートで作成したプロジェクトでは`System.Windows.Forms`が参照されておらず使用できないことを書きました。何が違うのか調べてみると、以下のような違いがあります。（この画像は前述の記事にも載せているものです）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149348/67950fb8-f98c-8f0b-99fd-c599f541b834.png)
この違いはどこから生まれてくるのか確認してみたいと思います。

# 比較してみる
作成後無編集の状態で「Windowsフォームアプリケーション」と「コンソールアプリケーション」を比較してみました。
Visual Studio 2022の以下のテンプレートを選択して作成したものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149348/6786d9ab-db5c-ee3f-7e33-06ae98149d18.png)
比較しやすいようにソリューション名・プロジェクト名を同じにしておいてWinMergeで比較してみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149348/562ec2b9-8b76-69b2-fa00-055a1b7415f3.png)
それぞれの違いを見ていきましょう。

## .sln
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149348/ebd8c366-792b-675f-b9fa-b04498941894.png)
これは固有のIDのようなものが違うだけで、本質的な違いはないですね。

## Program.cs
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149348/b1c1d73b-5b16-7351-d283-77e215e224d9.png)
これは違って当然ですね。
テンプレートには初期状態では以下のコードが書かれます。
* Windowsフォームアプリケーション
→初期フォームを表示するためのコード
* コンソールアプリケーション
→サンプルコードとしてHello World

C#では9.0より[トップレベルステートメント](https://ufcpp.net/study/csharp/misc/miscentrypoint/#top-level-statements)が書けるようになりましたが、テンプレートではコンソールアプリのみとなっているようです。

## .csproj
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149348/8558026b-92ac-f4ae-70a9-71343e23795c.png)
予想通りですが、違いはここになります。以下のような違いがありました。
|  |Windowsフォーム  |コンソール  |
|---|---|---|
|OutputType  |WinExe  |Exe  |
|TargetFramework  |net8.0-windows  |net8.0  |
|UseWindowsForms  |true  |記載なし  |

今回の検証目的である`System.Windows.Forms`はどうやって参照されているのかには`UseWindowsForms`が関係ありそうに見えますが、他の項目もWindowsのプログラムを作ることに関係ある値に見えるのでこちらも一体で関係あるようにも見えます。

# 確認してみる
コンソールアプリのcsprojを編集して、それぞれの項目の意味を確認してみます。
まずは他の項目はそのままに、ダイレクトに関係しそうな項目名に見える`UseWindowsForms`だけを追加してみました。
```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <UseWindowsForms>true</UseWindowsForms> <!-- 追加 -->
  </PropertyGroup>

</Project>
```
この状態だとコンパイルエラーになりました。エラー内容は以下です。
```
Windows フォームまたは WPF を使用しているとき、またはそのようなプロジェクトまたはパッケージを参照しているときには、ターゲット プラットフォームを Windows に設定する必要があります (通常は TargetFramework プロパティに '-windows' を含めることによる)。
```
エラー内容に記載のとおりTargetFrameworkを`net8.0-windows`に変更してみます。
```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework> <!-- 変更 -->
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <UseWindowsForms>true</UseWindowsForms> <!-- 追加 -->
  </PropertyGroup>

</Project>
```
コンパイルが通りました。また、検証目的であった`System.Windows.Forms`の参照も追加されていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149348/df52ff75-c5f0-e255-6876-9006467d22ec.png)
この状態で実行するとコンソールが表示されます。WinFormsアプリではコンソールは表示されませんから、やはりまだ違う状態であることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149348/4de7ffe4-0d79-d67e-b60a-5b6ea2372e9e.png)
では最後に、OutputTypeを`WinExe`に変更してみます。これで、結局Widowsフォームアプリケーションのテンプレートから作成したのと同じ状態になりました。
```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType> <!-- 変更 -->
    <TargetFramework>net8.0-windows</TargetFramework> <!-- 変更 -->
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <UseWindowsForms>true</UseWindowsForms> <!-- 追加 -->
  </PropertyGroup>

</Project>
```
この状態で実行するとコンソールが表示されなくなりました。
当たり前と言えば当たり前ですが**csprojの相違点三点全てがそろわないとWinFormsアプリにならない**ということがわかりました。

# 公式ドキュメントより
ここまで実際にcsprojを編集して確認しましたが、Microsfotの公式ドキュメントを調べてみます。

## TargetFramework
https://learn.microsoft.com/ja-jp/dotnet/standard/frameworks
>プラットフォーム固有のライブラリの場合、プラットフォーム固有のフレーバーをターゲットにする必要があります。 たとえば、WinForms プロジェクトと WPF プロジェクトは net9.0-windows をターゲットにする必要があります。

## OutputType
https://learn.microsoft.com/ja-jp/dotnet/core/compatibility/sdk/5.0/automatically-infer-winexe-output-type
>OutputType は、Windows Presentation Foundation (WPF) アプリと Windows フォーム アプリでは、自動的に WinExe に設定されます。 OutputType が WinExe に設定されている場合、アプリの実行時にコンソール ウィンドウは開きません。

## UseWindowsForms
https://learn.microsoft.com/ja-jp/dotnet/core/project-sdk/msbuild-props-desktop
>WinForms をインポートして使用する場合は、UseWindowsForms を true に設定する。

ということで、キーワードがわかってから調べてみるとちゃんとドキュメントに記載されていることがわかりました。