1つのexeから、同じnugetパッケージの複数バージョンを切り替えて使いたい

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

nugetで参照するあるパッケージ(ライブラリ)の異なるバージョンを、一つのexeから切り替えてつかいたい。

基本、一つのexeでnugetで参照できる1つのパッケージの複数のバージョンを同時に使うようなことはできないと思っているのだが、それでも何とかうまいことできないか。試してみる。

前提

  • VisualStudio2022 v17.3.0
  • .NET6

実験リポジトリ

github.com

プロジェクト名が長すぎて、普通にデスクトップとかにクローンすると、なんかうまくビルドできなくなるかも。
→topフォルダ名を短くするとかして試してください。

やったこと

概要

exeでnugetパッケージを直接参照しないようにする。
代わりに、そのパッケージを参照する2つの窓口ライブラリプロジェクトを作成し、そこで2つのバージョンのnugetパッケージを参照させる。

で、exeからはその2つの窓口ライブラリを切り替えて使うことで、何とかうまいことできないかやってみた。

図で書くと下記のようなイメージ。

詳細

  • 窓口となるクラスが実装することになるインターフェースを作成する
    • ILogWriter1:Ver1用のIF。Write()メソッドをもつ。
    • ILogWriter2:Ver2用のIF。Write2()メソッドをもつ。
  • 窓口となるクラスライブラリのプロジェクトをつくり、上記IFを実装するクラスを作成し、IFのPJをPJ参照に入れ、IFのメソッドを実装する。
    • LogWriter1:ILogWriter1を実装するクラス。
    • LogWriter2:ILogWriter2を実装するクラス。
  • その中に、
    • LogWriter1をもつプロジェクトで、NLogのv4.7.15をnugetで参照に入れる。
    • LogWriter2をもつプロジェクトで、NLogのv5.0.4をnugetで参照に入れる。

※ビルド構成マネージャーで、すべてのPJについて、プラットフォームをx64のみにして、ほかのプラットフォームを削除しておく。

  • 下記3つの設定を、ClassLibrary1,2のcsprojの<PropertyGroup>に入れる。

    • <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
    • <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
    • <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> ※これだけ、exeとIFのPJにはいれない
  • LogWriter1,2のプロジェクトの、出力先フォルダを、exeの出力先フォルダにフォルダを掘ってそこに入れるようにする。 今回の構成だとそれぞれ下記にする。

    • ..\SameLibraryDifferentVersionJikken\bin\$(Platform)\$(configuration)\lib1
    • ..\SameLibraryDifferentVersionJikken\bin\$(Platform)\$(configuration)\lib2
  • exeのコードに、呼び分けるコードを書く。

    • 読み込みたいほうのdllを、Assembly.LoadFrom(読みたい方のdllのフルパス)でロードする
    • その中のクラスをasm?.CreateInstance(名前空間名.クラス名)で読み込む
    • 読み込んだクラスのオブジェクトを、Interfaceでキャストする
    • そのオブジェクトで、呼びたいメソッドを呼ぶ

これで、やりたいことができた。

exeと同じフォルダにexeが参照しているdllがさらに参照しているライブラリ(今回の場合はNlog.dll)が存在しないが、 うまくそれぞれの(lib1/lib2フォルダの下にある)NLog.dllを読んでくれてるっぽい。

ClassLibrary1.dllを使ったときに読み込まれるNLog.dll

ClassLibrary1.dllを使ったときに読み込まれるNLog.dll

フォルダ構成

フォルダ構成は下記のような感じ。

備考

exeのPJから、LogWriter1と2のPJを参照してしまうと、exeの出力先にLogWriter1と2が参照するNLog.dllがコピーされてしまう。

そのとき、LogWriter1と2が同じ名前のNLog.dllを参照していてそれがコピーされるので、たまたま後でコピーされたほうだけが残ってしまい、片方が消えるので、直接参照できない。なのでわざわざlib1,2フォルダを掘って、動的にLoadFromで読み込むようにした。

exeからクラスを直接参照しないので、読み込んだクラスの型がわからずインテリセンスが効かないが、それだと実装時の効率がわるいので、インターフェース(ILogWriter1,2)を設けてインテリセンスが効くようにした。

※読み込んだクラスをdynamicにして、インテリセンス聞かないまま実装もできるが、そこは避けてインターフェースを作る手間の方を取った。

またILogWriter2は、ILogWriter1の機能UP版、みたいなイメージで、ILogWriter1を継承する感じにしているが、全く別のIFにしてもいいと思う。

参考

AppendTargetFrameworkToOutputPathAppendRuntimeIdentifierToOutputPathCopyLocalLockFileAssembliesなどのcsprojに追記した項目は下記に説明がある。

learn.microsoft.com

アセンブリの読み込み方のベストプラクティス
とあるが、うーん、むずかしい、、、Assembly。LoadFrom()をしたときに、Loadしたdllが依存するdllが同じ階層にあったらそっちを呼んでくれる動作をすることの裏を取りたかったのだが、なかなか下記ページを読み解けない。。。

learn.microsoft.com

参考書

WinUI3

WinUI3でアプリを作ろうと思ったときのとっかかりによかった。 msdocsに書いてある情報を、体系的に、順番に読みたいな、というときによいかも。(ただし英語)
この本で分からなかった、かゆいところに手が届かなかった部分を私は記事にしてる感じ。

C#①

表紙に書いてある通り、教科書として最適。 これからC#を勉強したいけど、ネットだけで勉強するのは効率が悪いから体系的に学べる本が欲しいときや、 ちょっとC#を勉強してコード書けるようになったけど、もう少し広く深く知りたいなというときによいと思う。
私は仕事で触れるコードを軸に、基本ネットで断片的にC#を学んだので、その知識の隙間を埋めて枝葉を広げるためにとても分かりやすかった。

C#②

C#の文法的に色々できるのは分かったが、いざ実装するときに、わかったことを使ってどう実装すればいいのか?と悩んだときに指針になりそうな本。
「プロパティ等の名前の付け方、どうすればいい?」「情報をクラス外部に見せるときに、プロパティにすべき?メソッドにすべき?」「異常だと判定したいとき、どんなときにどんな例外をスローすべき?」などなど、勉強になる部分が山ほどあった。
私のように「コードは書くけどこれであってるのか自信がない、レビューで指摘されるのが嫌だ、実装時の(心の)よりどころが欲しい」という人に最適。