WinForms のプロジェクトファイルはこう変わった

— .NET Framework 旧スタイル vs .NET(SDK スタイル)徹底比較

WinForms を .NET Framework 時代から触ってきた方は、.NET(.NET Core/5+)で .csproj の書き心地が激変しているのを感じるはず。本記事では、両者の“プロジェクト管理ファイル(.csproj)”の仕様差を中心に、WinForms で効くポイントだけをギュッと整理します。最後に移行時のチェックリストと、授業・チーム開発での注意点も付けました。


要約(先に結論)

  • SDK スタイル(.NET)は、<Project Sdk="…"> を先頭に書く簡潔な形式。ソースや .resx を自動で取り込む既定ルール(グロブ)があるため、個別列挙がほぼ不要。
  • WinForms を .NET で使うには、Windows TFM(例:net8.0-windows/net9.0-windows)と <UseWindowsForms>true</UseWindowsForms> が鍵。
  • NuGet は既定で PackageReference。.csproj に直接 <PackageReference> を書く(旧来の packages.config からの大転換)。
  • 旧スタイルでおなじみの Properties\AssemblyInfo.cs は既定で自動生成(必要ならオフ可)。
  • Designer の自動生成コードは C# の進化も相まって短くなった(button1.Click += button1_Click; など)。

SDKスタイルの既定取り込みはMSBuildのグロブ(ワイルドカード)で“拡張子ベース”に分類されています。代表的には次のとおりです。

  • Compile(=C#のソース): **/*.cs
  • EmbeddedResource(=埋め込みリソース。WinForms の .resx 含む): **/*.resx
  • None(=上記以外のファイル): **/* から *.cs と *.resx を除外
  • bin/ と obj/ などは既定で除外(DefaultItemExcludes)。この既定の Include/Exclude は .NET SDK 側で定義されており、旧式の .NET Framework プロジェクトのように1ファイルずつ列挙する必要はありません。 

調整したい場合は:

  • 既定の自動取り込みを全体で切る
<PropertyGroup>
  <EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
  • 種別ごとに切る(例:C#だけ手動管理したい)
<PropertyGroup>
  <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
  <!-- EmbeddedResource/None も同様に個別に切れる -->
</PropertyGroup>
  • 特定フォルダを外す
<ItemGroup>
  <Compile Remove="Legacy/**" />
  <EmbeddedResource Remove="Legacy/**" />
  <None Remove="Legacy/**" />
</ItemGroup>

(重複指定でビルドエラーが出る場合の対処や、各フラグの解説は公式ドキュメントが詳しいです。)  

要するに、拡張子ごとの既定グロブで見ています。WinForms の場合でも、.cs は Compile、.resx は EmbeddedResource に自動で入り、その他は None 扱いになって必要に応じて「出力へコピー」などのメタデータを付ける、という整理です。 


1. .csproj の見た目がこう違う

旧スタイル(.NET Framework 例:4.8)

<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
    <ProjectGuid>{GUID}</ProjectGuid>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="System" />
    <Compile Include="Form1.cs" />
    <Compile Include="Form1.Designer.cs">
      <DependentUpon>Form1.cs</DependentUpon>
    </Compile>
    <EmbeddedResource Include="Form1.resx">
      <DependentUpon>Form1.cs</DependentUpon>
    </EmbeddedResource>
    <!-- 参照やファイルを大量に列挙 -->
  </ItemGroup>

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

SDK スタイル(.NET 8/9 など)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework> <!-- 例: net9.0-windows -->
    <UseWindowsForms>true</UseWindowsForms>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <!-- 必要なときだけパッケージを列挙 -->
    <PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
  </ItemGroup>
</Project>

ポイント

  • SDK スタイルでは **\*.cs や **\*.resx が既定で自動取り込みされるため、個別の <Compile Include> や <EmbeddedResource Include> がほぼ不要です。
  • WinForms/WPF の有効化は プロパティで宣言的に行う(<UseWindowsForms>true</UseWindowsForms> / <UseWPF>true</UseWPF>)。
  • テンプレートによっては Sdk="Microsoft.NET.Sdk.WindowsDesktop" を使うものもありますが、最近のテンプレは Microsoft.NET.Sdk + UseWindowsForms 指定が主流です。

2. NuGet の管理:packages.config → PackageReference

  • 旧来:packages.config を保持し、packages フォルダに個別展開。
  • 現在:.csproj に <PackageReference> を書くスタイルが既定。解決済みパッケージはユーザー毎のグローバルキャッシュ(%USERPROFILE%\.nuget\packages 等)へ。
  • 利点:プロジェクト間での参照一貫性・復元の安定性が向上し、プロジェクトファイル一つで完結しやすい。

既存の .NET Framework プロジェクトでも、Visual Studio のコンテキストメニュー等で PackageReference へ変換可能です。


3. Windows 対応の指定(TFM と WinForms 有効化)

  • TargetFramework(TFM) は Windows 対応を明示する -windows サフィックスが基本例:net6.0-windows / net8.0-windows / net9.0-windows
  • WinForms 有効化:<UseWindowsForms>true</UseWindowsForms>
  • AnyCPU / x86 / x64 は従来通り選べます(COM 連携や古い OLEDB ドライバ使用時はアーキテクチャに注意)。

4. Designer のコード生成は“より C# らしく”

(.NET Framework の自動生成でよく見た形)

this.button1.Click += new System.EventHandler(this.button1_Click);

(.NET のテンプレートで一般的な形)

button1.Click += button1_Click;
  • C# の型推論・ハンドラメソッドグループ変換が効くため、冗長な new EventHandler(…) が不要
  • もちろん、private void button1_Click(object sender, EventArgs e) というシグネチャは同じです。

5. AssemblyInfo とアプリ設定の扱い

  • AssemblyInfo
    • SDK スタイルでは、[AssemblyTitle] などの属性は 既定で自動生成。必要に応じて .csproj にプロパティを書くか、<GenerateAssemblyInfo>false</GenerateAssemblyInfo> で生成停止して 手書きの AssemblyInfo.cs を復活させられます。
  • 設定ファイル
    • .NET Framework の App.config + ConfigurationManager はそのまま使えます。
    • .NET(SDK スタイル)で ConfigurationManager を使う場合は、NuGet の System.Configuration.ConfigurationManager パッケージを追加してください(サンプルに記載)。
    • あるいは appsettings.json + Microsoft.Extensions.Configuration` 系でモダンに構成管理する選択肢もあります。

6. リソースと .resx の扱い

  • 旧スタイルでは .resx を <EmbeddedResource Include="…"> で都度列挙
  • SDK スタイルでは .resx も既定で自動取り込み。Form1.resx / Properties\Resources.resx などの強く型付けされたリソース クラスも従来と同様に利用できます。

7. ビルド・発行まわりの違い(触りだけ)

  • ClickOnce:.NET でもサポート(プロジェクトの発行から設定)。
  • 自己完結(Self-contained)/ 単一ファイル発行:.NET 側なら選択可能。配布形態のバリエーションが広がります。
  • トリミング(未使用 API の除去)など近代的最適化は、WinForms では慎重に。デザイナ生成コードやリフレクションを多用するため、既定では無効推奨です。

8. 旧 → 新 乗り換え時の“よくある注意点”

  • 参照コンポーネントが .NET Framework 専用のままになっていないか
    • ベンダー製コントロールの .NET 版が必要なことがある。
  • AnyCPU / x86 / x64 の整合性
    • COM、古い OLEDB、印刷ドライバ系はx86 縛りが残る場合あり。
  • BinaryFormatter など古い API は非推奨・削除方向
    • 置き換え(System.Text.Json 等)を検討。
  • 設定の読み方
    • .config を続投するか、appsettings.json に移るかをチームで決める。
  • ファイルの配置
    • SDK スタイルは既定の出力がシンプル。実行時読み込みデータ(例:questions.csv)は
      • 読み取りだけなら「出力ディレクトリにコピー → 常にコピー」で AppContext.BaseDirectoryから読めるようにする、
      • 書き込みが発生するデータなら %AppData% / %LocalAppData%(Environment.GetFolderPath)配下に保存する、など運用方針を分けると混乱しません。

9. 旧→新 対応表(チートシート)

観点.NET Framework 旧スタイル.NET(SDK スタイル)
プロジェクトタグ<Project ToolsVersion=…><Project Sdk="Microsoft.NET.Sdk">
TFMv4.8 などnetX.Y-windows
WinForms 有効化参照とテンプレに依存<UseWindowsForms>true</UseWindowsForms>
ソース取り込み<Compile Include="…"> 列挙既定グロブで自動
.resx<EmbeddedResource Include="…"> 列挙自動(必要時のみ明示)
NuGetpackages.config<PackageReference>
パッケージ格納solution 配下 packagesユーザーのグローバルキャッシュ
AssemblyInfoProperties\AssemblyInfo.cs 手書き既定は自動生成(停止も可)
Designer のイベントnew EventHandler(…) 多用button1.Click += button1_Click;
発行MSI/ClickOnce などClickOnce、自己完結、単一ファイル等

10. ミニ実験(手を動かして確認)

  1. イベントの自動生成を確認
  • .NET の WinForms テンプレで Form1.Designer.cs を開き、button1.Click += button1_Click; の短い書式になっているか見る。
  1. questions.csv を実行時に読む
var csvPath = Path.Combine(AppContext.BaseDirectory, "questions.csv");
var lines = File.ReadAllLines(csvPath); // 読み取りのみ
  • プロパティで 「出力ディレクトリにコピー → 常にコピー」 を設定しておく。
  • 書き込みが必要なら、次のように保存先を切り替える:
var dataDir = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "YourAppName");
Directory.CreateDirectory(dataDir);
var savePath = Path.Combine(dataDir, "userState.json");
  1. PackageReference を体験
  • .csproj に1つ PackageReference を足してビルド → packages.config 不要で参照できることを確認。

11. 既存プロジェクトの移行ガイド(ざっくり)

  1. バックアップ/ブランチを切る
  2. ターゲットを .NET(net8.0-windows など)に変更
  3. .csproj を SDK スタイルへ最小化(不要な ItemGroup を削除、UseWindowsForms を追加)
  4. NuGet を PackageReference 化
  5. サードパーティ コントロールを .NET 版へ載せ替え
  6. ビルド構成(AnyCPU/x86/x64) を再確認
  7. 実行時テスト(印刷・ファイル I/O・COM 連携など“周辺機能”を重点的に)

12. 授業・チーム開発での着眼点

  • .csproj は“読めるようにする”:最小構成 + コメントで、誰が見ても意図が分かる形に。
  • 実行時データの設計ルール
    • 読むだけ → 出力にコピー
    • 書く可能性あり → %LocalAppData% へ
  • イベント購読の書き方を統一:button1.Click += button1_Click; の短い形に統一し、匿名ラムダの解除問題(解除できない)に触れておく。
  • 設定の方針:ConfigurationManager を使うか、appsettings.json へ寄せるかを最初に決める。

付録:WinForms(.NET)の最小 .csproj 例

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>

訪問数 1 回, 今日の訪問数 2回