エンジニアリング

VS Code で .NET Framework とマルチターゲット:すべてのフレームワークを、C# でも F# でも

net48 がアクティブな状態で

本格的な .NET ライブラリを開くと、ほぼ必ずこんな行があります。

<TargetFrameworks>net462;net48;netstandard2.0;net8.0;net10.0</TargetFrameworks>

プロジェクトファイルは 1 つ、フレームワークは 5 つ。そして、そのうち 1 つでしかコンパイルされない #if NETFRAMEWORK のコードの島。多くのエディターは黙って 1 つのフレームワークだけを選び、残りは存在しないものとして扱います。その結果、.NET Framework のお客様のためにまさにデバッグしているコードが、灰色で、解析されず、幻のエラーだらけになります。

SharpLsp 0.23 は、プロジェクトが宣言するすべてのフレームワークに完全な体験を提供します。F# でも C# とまったく同じように。 実際に何ができるのかを見ていきましょう。

フレームワークを切り替えると、コードが変わる

フォーカスしているファイルがマルチターゲットのプロジェクトに属していると、ステータスバーにそのアクティブなフレームワーク(たとえば net48)が表示されます。クリックするか SharpLsp: Select Target Framework を実行して、別のフレームワークを選びます。

切り替えは プロジェクト全体に、両方の言語で 適用されます。

既定値は Visual Studio と同じく <TargetFrameworks> の最初のエントリです。

C#:フレームワークごとに 1 つの Roslyn コンテキスト

MSBuildWorkspace はフレームワークごとに 1 つの Roslyn プロジェクト(Csv(net48)、Csv(net8.0))を作るので、各ドキュメントはフレームワークごとにコンテキストを持ち、すべてのリクエストは アクティブな コンテキストから応答します。

マルチターゲットでよく問題になるのは、プロジェクト全体にわたるクエリです。参照の検索、CodeLens の件数、実装、呼び出し階層と型階層、デッドコード解析は、各プロジェクトのアクティブなフレームワークを検索し、それぞれの場所を 1 回だけ 報告します。フレームワークごとに、あるいはプロパティのアクセサー経由で、同じ使用箇所が重複してカウントされることはありません。

F#:フレームワークごとに MSBuild のオプション

F# も同じ扱いで、コンパイラオプションは推測ではありません。SharpLsp はフレームワークごとに、MSBuild にプロジェクトの実際のデザイン時コンパイルを問い合わせます。

dotnet msbuild  -p:TargetFramework=net48 -p:DesignTimeBuild=true
  -p:SkipCompilerExecution=true -p:ProvideCommandLineArgs=true ...
  -getItem:FscCommandLineArgs -getItem:ReferencePathWithRefAssemblies

そのため net48 は --targetprofile:mscorlib、4.8 の参照アセンブリ、NETFRAMEWORK;NET48;…_OR_GREATER でコンパイルされ、ソースの順序、glob、条件、Directory.Build.* は SharpLsp ではなく MSBuild のものです。所要時間はフレームワークあたり約 1 秒。最初のフレームワークは読み込み時に、ほかは最初の切り替え時に構築され、その後は保持されます。

見た目以上に重要な点が 2 つあります。

ほかの F# プロジェクトを参照する F# プロジェクトは、MSBuild がその参照のために実際に選んだフレームワークから、メモリ上で読み込みます。

どの OS でも解析できる

SDK は Microsoft.NETFramework.ReferenceAssemblies を自動的に参照するので、.NET Framework のコードは macOS と Linux でも解析されます。 net48 の補完、ナビゲーション、診断は MacBook でも動きます。ただし実行は別の話です。

ターゲット 解析 ビルド 実行/テスト デバッグ
net462–net481 すべての OS すべての OS Windows のみ 理由を示して拒否
netstandard2.0/2.1 すべての OS すべての OS なし(ライブラリ) —
net8.0 以降 すべての OS すべての OS そのランタイムが必要 netcoredbg

ランタイムがないフレームワーク(たとえば 8 と 10 しかないマシンでの net9.0)は、失敗したテストや欠けたテストとしてではなく、名前を挙げて 実行不可と報告されます。

フレームワークを理解するテストエクスプローラー

テストエクスプローラーは、VSTest と MTP の両方で、C# と F# のマルチターゲットのテストプロジェクトを理解するようになりました。

デバッグ:デスクトップ CLR について正直に

netcoredbg は .NET をデバッグでき、.NET Framework のデスクトップ CLR はデバッグできません。決してアタッチされないデバッガーを待つテストホストはただハングするだけなので、SharpLsp はそうさせません。

実際のリポジトリで実証

このような機能には合成フィクスチャだけでは足りません。そこで Windows の CI チャンクが、マルチターゲットの実在のリポジトリ 3 つを固定コミットでクローンし、実際の拡張機能を通して動かします。

リポジトリ 言語 ターゲット
kekyo/GitReader F# と C# net35…net481、netstandard1.6–2.1、最新の .NET
JoshClose/CsvHelper C# net462;net47;net48;netstandard2.0;2.1;net8.0;net9.0
NLog/NLog C# net35;net46;netstandard2.0;netstandard2.1

どれも読み込みに成功し、#if NETFRAMEWORK のコード内で言語リクエストに応答し、.NET Framework のテストアセンブリを検出して、フレームワークごとの結果を報告します。

試してみる

VS Code Marketplace または Open VSX から SharpLsp 0.23 以降に更新し、マルチターゲットのソリューションを開いて、ステータスバーのフレームワークをクリックしてください。

うまく動かないプロジェクトを見つけたら、Issue を作成してください。SharpLsp を壊す実在の <TargetFrameworks> 行こそ、私たちが見たいものです。