会社のプロジェクトCompanyName.SDK
に統合する必要があるCompanyName.SomeSolution
という名前の機能を備えたライブラリを開発します
CompanyName.SDK.dll
はNuGetパッケージを介して展開する必要があります。また、CompanyName.SDK
パッケージは、サードパーティのNuGetパッケージに依存しています。良い例として、 Unity
を見てみましょう。現在の依存関係は、v3.5.1405-prerelease
of Unity
です。
CompanyName.SomeSolution.Project1
はUnity
v2.1.505.2
に依存します。 CompanyName.SomeSolution.Project2
はUnity
v3.0.1304.1
に依存します。
CompanyName.SDK
をこのソリューションに統合すると、Unity
v3.5.1405-prerelease
への依存関係が追加されます。 CompanyName.SomeSolution
には、上記の2つとCompanyName.SomeSolution.Application
に依存する1つの実行可能な出力プロジェクトCompanyName.SDK
があるとします
そして、ここから問題が始まります。すべてのUnity
アセンブリは、バージョン指定子のないすべてのパッケージで同じ名前を持ちます。ターゲットフォルダーでは、Unity
アセンブリの1つのバージョンのみになります:v3.5.1405-prerelease
via bindingRedirect
in app.config
。
Project1
、Project2
、およびSDK
のコードは、コーディング、コンパイル、およびテストされた依存パッケージの正確に必要なバージョンをどのように使用できますか?
注1:Unity
は単なる例です。実際の状況は、サードパーティのモジュールが別のサードパーティのモジュールに依存しており、3から4のバージョンが同時にあるため、10倍悪化しています。
NOTE2:別のパッケージの最新バージョンではない依存関係を持つパッケージがあるため、すべてのパッケージを最新バージョンにアップグレードできません。
注3:依存パッケージにバージョン間で重大な変更があると仮定します。この質問をするのは本当の問題です。
注4:私は質問について知っています 同じ依存アセンブリの異なるバージョン間の競合について しかし、そこの答えは問題の根本を解決しません-彼らはただ隠しますそれ。
NOTE5:「DLL Hell」問題解決の約束は一体どこにあるのでしょうか?それは別の位置から再び現れています。
注6:GACの使用が何らかの形でオプションであると思われる場合は、ステップバイステップガイドを作成するか、リンクをお知らせください。
Unity
パッケージは、 Composition Root という1つの場所でのみ使用する必要があるため、良い例ではありません。また、Composition Root
は、アプリケーションのエントリポイントにできるだけ近い場所にある必要があります。あなたの例ではCompanyName.SomeSolution.Application
です
それとは別に、私が現在働いているところでは、まったく同じ問題が現れます。そして、私が見るところ、この問題はしばしばロギングのような横断的な懸念によってもたらされます。適用できる解決策は、サードパーティの依存関係をファーストパーティの依存関係に変換することです。それを行うには、その概念の抽象化を導入します。実際、これを行うと、次のような他の利点があります。
CompanyName.SDK
のすべてのクライアントはUnity
依存関係を本当に必要としますか?)それでは、架空の.NET Logging
ライブラリの例を見てみましょう。
CompanyName.SDK.dll
は.NET Logging 3.0
に依存しますCompanyName.SomeSolution.Project1
は.NET Logging 2.0
に依存しますCompanyName.SomeSolution.Project2
は.NET Logging 1.0
に依存します
.NET Logging
のバージョン間に重大な変更があります。
ILogger
インターフェイスを導入することにより、独自のファーストパーティの依存関係を作成できます。
public interface ILogger
{
void LogWarning();
void LogError();
void LogInfo();
}
CompanyName.SomeSolution.Project1
およびCompanyName.SomeSolution.Project2
は、ILogger
インターフェイスを使用する必要があります。これらは、ILogger
インターフェイスのファーストパーティの依存関係に依存しています。これで、その.NET Logging
ライブラリを1か所で保持し、1か所で行う必要があるため、更新を簡単に実行できます。 .NET Logging
ライブラリーの1つのバージョンが使用されるため、バージョン間の変更を壊すことも問題ではなくなりました。
ILogger
インターフェイスの実際の実装は、異なるアセンブリに配置する必要があり、.NET Logging
ライブラリを参照する場所にのみ配置する必要があります。アプリケーションを構成するCompanyName.SomeSolution.Application
で、ILogger
抽象化を具体的な実装にマップする必要があります。
このアプローチを使用しており、抽象化と実装を配布するためにNuGet
も使用しています。残念ながら、バージョンの問題は独自のパッケージで発生する可能性があります。この問題を回避するには、会社のNuGet
経由で展開するパッケージに Semantic Versioning を適用します。 NuGet
を介して配布されるコードベースで何かが変更された場合、NuGet
を介して配布されるすべてのパッケージを変更する必要があります。たとえば、ローカルのNuGet
サーバーには次のようなものがあります。
DomainModel
Services.Implementation.SomeFancyMessagingLibrary
(DomainModel
およびSomeFancyMessagingLibrary
を参照)このパッケージ間のバージョンは同期されます。バージョンがDomainModel
で変更された場合、同じバージョンがServices.Implementation.SomeFancyMessagingLibrary
で変更されます。アプリケーションが内部パッケージの更新を必要とする場合、すべての依存関係は同じバージョンに更新されます。
この問題を解決するには、コンパイル後のアセンブリレベルで作業します...
ILMerge を使用してアセンブリをマージしてみてください。
ilmerge/target:winexe /out:SelfContainedProgram.exe Program.exe ClassLibrary1.dll ClassLibrary2.dll
結果は、プロジェクトとその必要な依存関係の合計であるアセンブリになります。これには、モノラルのサポートを犠牲にし、アセンブリID(名前、バージョン、カルチャなど)を失うなど、いくつかの欠点があります。したがって、マージするすべてのアセンブリを自分で構築する場合に最適です。
だからここに来る...
代わりに この記事 で説明されているように、プロジェクト内にリソースとして依存関係を埋め込むことができます。関連部分は次のとおりです(重要なのは私のものです)。
実行時に、CLRは依存するDLLアセンブリを見つけることができません。これは問題です。これを修正するには、アプリケーションの初期化時にregister AppDomainのResolveAssemblyイベントを持つコールバックメソッドコードは次のようになります。
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
String resourceName = "AssemblyLoadingAndReflection." +
new AssemblyName(args.Name).Name + ".dll";
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) {
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
};
これで、スレッドが、依存するDLLファイル、AssemblyResolveイベントのタイプを参照するメソッドを初めて呼び出すが発生し、上記のコールバックコードは埋め込みのDLLリソースが必要です。)を見つけ、引数としてByte []を受け取るAssemblyのLoadメソッドのオーバーロードを呼び出してロードします。
これは私があなたの靴を履いていた場合に使用するオプションであり、初期起動時間をいくらか犠牲にしていると思います。
ご覧ください こちら 。これらの<probing>
各プロジェクトのapp.config内のタグは、CLRがアセンブリを検索するときに検索するカスタムサブフォルダーを定義します。