Visual Studio 2017でビルドしているWinFormsアプリを.NET Coreでも並行開発可能にする方法

日記に近いメモです。

## 前置き

メンテナンスしている、とあるWinFormsアプリは.NET Frameworkで動作しているのですが、開発者としては.NET 5に備え.NET Core 3.1に移行したいところです。
しかし、手元のWinFormsアプリは**Visual Studio 2017 Express**で開発しているので、そう簡単に.NET Core(の新しいcsproj形式)に移行できません。
WinFormsデザイナーが使用できないと、UIに手を入れるのが非常に面倒になります。

そこで「移行」するのではなく、Visual Studio 2017(.NET Framework)で開発可能なまま.NET Coreでもビルドできる状態を目指していきます。
(使用していないソースコードを削除する以外は、ソースコードは変えません。)
Visual Studio 2019が使えればこんな面倒なことはしません。

## 対象のソースコード

下記のようなディレクトリ構成だとします。細かいファイルは省略してます。
なお、このアプリはx86でしか動作しませんが、開発環境はx64です。

```
SolutionFolder/
├───MyApps.sln
├───MyFormsApp/
│   ├───MyForms.csproj
│   ├───Form1.cs
│   ├───...
│   └───Program.cs
└───MyLib/
    └───MyLib.csproj
```

## 準備

基本的には[Windows フォーム デスクトップ アプリを .NET Core に移植する方法](https://docs.microsoft.com/ja-jp/dotnet/core/porting/winforms)を参考にします。

### .NET Portability Analyzer
.NET Portability Analyzer で事前に検証すべきですが、最近はWindows 互換機能パックにより互換性が高めなので、明らかに[.NET Coreで使用できない機能](.NET Core で使用できない .NET Framework テクノロジ)(WCFとか)を使用していないのであれば、自己責任でやらなくてもいいかもしれません。

### .NET Core SDK
アプリはx86でしか動作しませんが、開発環境に合わせて.NET Core 3.1 SDK x64をインストールします。

`dotnet`コマンドが動作すること確認しておきます。

```powershell
PS > dotnet --list-sdks
3.1.100 [C:\Program Files\dotnet\sdk]
PS > dotnet --list-runtimes
Microsoft.AspNetCore.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
```

### csprojに含まれないソースコードを削除する

.NET Coreではワイルドカードでソースコードを指定するため、使用していないソースコード(ゴミ)が残っている場合は削除しておきます。

## 新しいcsprojを作成する

docs.microsoft.comの例に倣い、`MyFormsAppCore`というディレクトリを作成して、その中に.NET Core用のプロジェクトを作ります。

**MyFormsAppの中に作ると、`dotnet`コマンドで毎回プロジェクトファイルを指定しないと行けないので面倒になります。**

```
SolutionFolder/
├───MyApps.sln
├───MyFormsApp/
│   ├───MyForms.csproj
│   ├───Form1.cs
│   ├───...
│   └───Program.cs
├───MyLib/
│   └───MyLib.csproj
└───MyFormsAppCore/      <--- New folder for core project
```

[dotnet/try-convert](https://github.com/dotnet/try-convert)というツールもありますが、今回は手動で行います。

```powershell
PS > cd SolutionFolder/MyFormsAppCore
PS SolutionFolder\MyFormsAppCore> New-Item -Type File MyFormsAppCore.csproj
```

`dotnet new winforms`コマンドで作成してもいいんですが、
どうせ`*.cs`ファイルを消して`.csproj`ファイルを作成する必要があるので直接作ります。

## csprojの編集

下記を貼り付けます。

```xml:MyFormsAppCore.csproj
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
    <AssemblyName>MyCoreApp</AssemblyName>
    <RootNamespace>WindowsFormsApp1</RootNamespace>
    <Deterministic>false</Deterministic>
    <LangVersion>7.3</LangVersion>
    <PlatformTarget>x86</PlatformTarget>
    <RuntimeIdentifier>win-x86</RuntimeIdentifier>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="..\MyFormsApp\**\*.cs" Exclude="..\MyFormsApp\obj\**\*.cs" />
    <EmbeddedResource Include="..\MyFormsApp\**\*.resx" />
  </ItemGroup>

</Project>
```

- `AssemblyName` / `RootNamespace` は実際のプロジェクトに合わせて(古いcsprojファイルを参照して)変えます。
- `LangVersion`は Visual Studio 2017で使用可能な7.3までにします。
- `AssemblyVersion.cs`で、`[assembly: AssemblyVersion("1.0.*")]`のようなワイルドカードを使用していたので、エラー CS8357が発生します。今回はソースコードは変えない前提なので、`<Deterministic>false</Deterministic>`を追加します。
- Visual Studio 2017でビルドしていると、`obj`ディレクトリ内に自動生成された`*.cs`ファイルが作成されるので、`Exclude`属性で除外しています。
- 前述の通り、x86でしか動作しないので`PlatformTarget`に`x86`を指定しています。
- x86でしか動作しないWinFormsアプリなので、コマンドラインでの指定を省略できるように`RuntimeIdentifier`に`win-x86`を指定しています。x64でも動作させるなら、指定は外しておくべきでしょう。

### 追加の編集

デフォルトで、ある程度使える設定はされていますが、何も設定をしなくていいわけではありません。
下記の設定(タグ)を、旧csprojからコピペが必要でした。
パスも調整する必要があります。

- `ApplicationIcon`
- `ApplicationManifest`

## NuGet参照
使用するライブラリの精査が面倒なら、 Windows 互換機能パックへの参照を追加します。

```powershell
PS SolutionFolder\MyFormsAppCore> dotnet add package Microsoft.Windows.Compatibility
```

手元のプロジェクトだとMEFを使用していたので、

```powershell
PS SolutionFolder\MyFormsAppCore> dotnet add package System.Composition
```
だけで済みました。

## プロジェクト参照
使用するライブラリへの参照を追加します。
元の`MyFormsApp.csproj`の中から、`..\MyLib\MyLib.csproj`を指定している`ItemGroup`タグをコピーして、`MyFormsAppCore.csproj`に貼り付けます。

```xml
...
  <ItemGroup>
    <ProjectReference Include="..\MyLib\MyLib.csproj">
    <Project>{8E29561D-E424-4AAB-A4FA-E966EF653A0F}</Project>
    <Name>MyLib</Name>
  </ItemGroup>
...
```

ライブラリ自体は.NET Frameworkのままですが、結果的に手元のプロジェクトは何もせずにビルドできました。
正しくは事前に.NET Coreプロジェクト化したcsprojを用意して、`dotnet add reference`で追加する必要があるでしょう。

## ビルド
上手く行っていれば、問題なくビルドできるはずです。

```powershell
# Debugビルド
PS SolutionFolder\MyFormsAppCore> dotnet build
# Releaseビルド
PS SolutionFolder\MyFormsAppCore> dotnet build -c Reelase
```

## 実行

```powershell
PS SolutionFolder\MyFormsAppCore> dotnet run
```

## 発行
発行する方法はいくつかありますが、batファイルを用意することにします。

```
dotnet publish -c Release --output publish
```

実行後、`SolutionFolder\MyFormsAppCore\publish\MyCoreApp.exe`が作成されているので、それを実行します。

`PublishTrimmed`や`PublishSingleFile`を使用したければ、[dotnet publish](https://docs.microsoft.com/ja-jp/dotnet/core/tools/dotnet-publish)コマンドのリファレンスに従って、オプションは調整しましょう。