Visual Studio2022で.NET Framework 4.8と.NET 6のソースコードを共存させる (WinForms＆WPF)


WinForms, WPFなどのWindowsベースのレガシーアプリ開発をやっていて、
最近ようやくRC版のリリースを終えた.NET 6でも問題なく動くかどうかを確認したかったので、
とりあえずWinFormsやWPFで、ソースコードの移植作業を極力行わない方法でどの程度対応出来るか試してみた。
(いくつか方法はあると思うが、そのうちの一つの方法、ということで)

クロスプラットフォームでの挙動については未確認。
C++連携なども現状は未確認(<font color="gray">※クロスプラットフォームなどを見据えてP/Invokeを使った方法への切り替えが推奨されているみたいです。C++/CLIは完全Windows固有の仕様のため。</font>)。

保証期間に暫く猶予もあって、未だにWinFormsやWPFの資産を抱えていて.NET Framework 4.8を採用・運用している会社も割と多いと思う。
いずれ、.NET 6や後続の.NET 8への移行を考えなければ、サポート期間気づいたら終了みたいなこともあり得るので、レガシーな技術持っている会社はよく注視しておこう。

プロジェクトはこちら

https://github.com/belre/WinFormsMVC

WPFのプロジェクトについては、もう少しお待ちください。

# 想定環境

OS : Windows バージョン20H2 (ビルド19042.1348)

## Windows Forms

①

- フレームワーク : .NET Framework 4.8
- 開発環境 : Visual Studio 2019 Professional
- C# 7.0で開発

②

- フレームワーク ： .NET 6
- 開発環境： Visual Studio 2022 Preview版 (17.0.0)
- C# 10.0で共有
 - 一旦C# 7.0と同じコードであるものとする. 恐らく移植すると、null許容型に関する警告(8.0辺りで採用されたやつ)がたくさん出るんじゃないかな。

## WPF

割と小規模なプログラムならありがちな構成だと思う

①

- フレームワーク : .NET Framework 4.8
- 開発環境 : Visual Studio 2019 Professional
- C# 7.0で開発
- ライブラリ
 - Prism.Core 7.2.0.1422
 - Prism.Wpf 7.2.0.1422

②

- フレームワーク ： .NET 6
- 開発環境： Visual Studio 2022 Preview版 (17.0.0)
- C# 10.0
- ライブラリ
 - Prism.Core 7.2.0.1422
 - Prism.Wpf 7.2.0.1422
 - Microsoft.Xaml.Behaviors.Wpf 1.1.19 ( **追加** )


# 1. .NET 6用のソリューションとプロジェクトを作成

　まずはVisual Studio 2022で.NET 6用のソリューションを作成する。
　この名前を"WinFormsMVCDotnet6"とする。
　ディレクトリの構成はこんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/b1eda1b4-10aa-33ed-1946-7685058d7b0f.png)

　次に、Visual Studio 2022でWinFormsなどのプロジェクトを作成すると、
　ソリューションの下のディレクトリにプロジェクトフォルダが作成されるようになる。

　C#は基本的にプロジェクト直下のファイルしか参照出来ないので、一度プロジェクトをVSで作成したら、
　以下のようなディレクトリ配置になるようにcsprojファイルを整えてソースコードを読ませるようにしよう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/bef1d1d7-a035-7428-8b76-7ccd703a3ab0.png)

# 2. 条件付きコンパイル

https://smdn.jp/programming/netfx/conditional/

**.NET 5以降のプロジェクトでは、**プリプロセッサNETFRAMEWORKを付けると、
.NET Frameworkであることを認識できる条件付きコンパイルを実行出来るが、
それは.NET 5以降のフレームワークで動く場合に限定される。

従って、.NET Framework側には新たに条件付きコンパイルとしてNETFRAMEWORKを付与する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/8e1bc4e8-90a0-2591-99ea-d78c5d2d86ba.png)

# 3. Properties\AssemblyInfo.cs

.NET側のプロジェクトでは、次のようなエラーが出てしまう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/3c0782cd-9e48-3414-d6c9-9d9498d0b5f1.png)

これはAssemblyInfoにアプリケーションの情報を記していて、
中間ファイル生成時にこんな感じで生成されるからであるが

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/e0156313-edb8-3759-12b6-6ea3700fa6f3.png)

.NET 6はプロジェクトの設定画面からデフォルトでパッケージの名称やバージョンを直接指定出来る。
従って、本来一つ定義すべきバージョンなどの情報が2か所で定義されているので問題が起きている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/7586143b-c034-aef9-55f2-36fcd7b23150.png)

そこで、AssemblyInfo.csに先ほど設定した条件付きコンパイルNETFRAMEWORKを指定して、.NET 6でビルドする必要がある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/0664845f-f9e6-3c15-43a9-d86253a3505b.png)


# 4. リソースファイル

　ソースコードがResources.resxなどに依存している場合、このままプログラムを実行するとResources.resxを読み込めなくなる。
　これに関しては解決策を考えたが、リソースファイルごと共有するのは難しそうなので、新たに別のリソースファイルをPropertiesに作成する。

　例えば、「ConfigDotnet6.resx」としよう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/7df02ded-d089-a461-0eb9-bb380b450852.png)

　そして、リソースファイルの参照をしている該当箇所に、次のように書くとよろし。

```C#
#if NETFRAMEWORK
	return int.Parse(WinFormsMVC.Properties.Resources.MAX_DEPTH_TREEFORM);
#else
	return int.Parse(WinFormsMVCDotnet6.Properties.ConfigDotnet6.MAX_DEPTH_TREEFORM);
#endif
```

## なぜリソースファイルの共存が出来ないか

自動生成コードDesigner.csの中にResourceManagerクラスからパッケージ名を読み込む必要があって、そのときにリソースファイルのパッケージ名を割り当てているため。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/4d6333d4-feec-be35-36c2-1df07077dbb2.png)

# 5. binフォルダのディレクトリを分割

出力パスを分かりやすい形式で直しておきましょう

.NET 6側

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/56526555-b8dd-4894-a364-1c5c2ce9599d.png)

.NET Framework側

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/91926612-75b7-938c-843c-b788565cd504.png)

# 6. 中間出力ディレクトリ(objフォルダ)

.NET Frameworkからnugetのライブラリを採用すると、objフォルダが原因で問題が発生する。

.NET Coreの系列のプロジェクトでは、project.assets.jsonがobj直下に作られる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/c85714b5-31c9-75c3-92da-ef015ba6644a.png)

この情報が残りっぱなしになっていると、.NET Framework側のビルドでこのファイルを読んでしまうので、.NET Framework側でビルドが出来なくなる。

そこで中間ファイルを分割する。

## .NET Frameworkのプロジェクト

.NET Framework側では、csprojファイルに以下のように追記する。

```XML:WinFormsMVC.csproj
<PropertyGroup>
  ...(省略)...
  <BaseIntermediateOutputPath>obj-netframework\$(AssemblyName)\</BaseIntermediateOutputPath>
  ...(省略)...
</PropertyGroup>
```

## .NET 6のプロジェクト

.NET 6側は、MSBuild 15以降のビルドではビルドプロパティとしてDirectory.Build.propsを指定せよとの指示がある。

https://docs.microsoft.com/ja-jp/visualstudio/msbuild/customize-your-build?view=vs-2022

そこで、プロジェクト直下にDirectory.Build.propsを作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/53d9455c-f894-f2ca-bec6-04b12dc64402.png)

中身を次のようにする

```XML:Directory.Build.Props
<Project>
 <PropertyGroup>
	<BaseIntermediateOutputPath>obj-net6\$(AssemblyName)\</BaseIntermediateOutputPath>
	<MSBuildProjectExtensionsPath>obj-net6\$(AssemblyName)\</MSBuildProjectExtensionsPath>
 </PropertyGroup>
</Project>
```

各プロパティの中身については、以下を参照のこと

https://docs.microsoft.com/ja-jp/visualstudio/msbuild/common-msbuild-project-properties?view=vs-2022

# 7. 中間ファイルの干渉が起きるので、不要な別のバージョンの中間ファイルをプロジェクトから外す

また、このままビルドすると実は問題が起きる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/934c814b-1a47-c41f-c68c-f6a63d944c39.png)

これは先ほど生成した中間ファイル「obj-netframework」または「obj-net6」が互いに干渉しあっていて、その中にある
.NETFramework,Version=v4.8.AssemblyAttributes.cs
.NETCoreApp,Version=v6.0.AssemblyAttributes.cs
などのファイルを両方読み込んでしまうためである。

対処法は単純。
中間ファイルをプロジェクトから削除しよう。
ファイル自体は必要なので、Visual Studioの画面から削除する。
エクスプローラなどから間違っても削除してはならない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/3bd8ac43-f142-524b-26a9-1f13f72ab172.png)

## .NET Framework側で発生するエラーについて

「obj-***」と名前が付いたものは、IDE側から読み取ってくれないのに、MSBuildではなぜか認識してしまうことがある。
その場合は、一度ソリューションエクスプローラに「obj-net6」をドラッグで直接インポートして、その後すぐ消してしまおう。

# 8. nugetライブラリを使ってライブラリのインストール、アップデート

.NET Framework限定でしか使えないライブラリがあったり、バージョン違いがあったりするものもあるので、各自注意

# 完了

これで、単純なプロジェクトであれば、ソースコード共存は完了。

ただし、現実にはnuget周りのライブラリについては、各種ライブラリによって対応方法が異なると思われるので、
その場合は、先ほど使用した条件付きコンパイルNETFRAMEWORKなどを使用して慎重に内容を変更すること。

# WPFの移植で詰まったポイント

基本的な流れは同じであるが、WPFを採用している場合はいくつか注意点があったので、並べておく。
これは、.NET Coreに移行した時に変更があった部分でもある。
ほかにもいろいろ注意点があると思うので、WPF自体の変更やライブラリ固有の移植状況については調査されたし


## System.Windows.Interactivity.dllはなくなり、 Xaml.Behaviors.Wpfに代替される

https://elf-mission.net/programming/wpf/wpf-tips/xaml-behaviors-wpf-interactivity/

https://qiita.com/nqdior/items/c9634f5c753cf4e53d4d


## System.Drawing.Imageライブラリを使っている場合は再実装が必要

これが一番致命的な変更だったかな。

どうしても処理の高速化などを想定していたために、BitmapImageクラスなどではなくImageクラスを採用せざる得ない場合がある。
その場合は、再実装が必要(代替案は調査中)。

参考になりそうなサイト
ただVS 2022だと正常に参照をインポートしてくれない・・・。

http://morokyuu.way-nifty.com/blog/2018/05/csystemwindowsm.html

## 文字コードShift-JISはデフォルトで使えない

文字コード問題

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/ba7252ab-112a-79e9-d29a-2835390ff147.png)

https://blog.hitsujin.jp/entry/2019/01/27/200055