为什么这个项目需要程序集绑定重定向?

Why is an assembly binding redirect required for this project?

我已经使用 https://icanhasdot.net 分析了我一直在试验的示例项目的 NuGet 依赖项。以下是结果:

该图未显示特定版本,但我的项目目标是 .NET Framework 4.5,并且基于 Visual Studio 中的“管理 Nuget”视图,我知道所有 NuGet 包(即图表中的所有绿色方块)在我的项目中需要 Newtonsoft.Json >= 6.0.8,例如:

我想为我的项目使用稍微更新的 Newtonsoft.Json 版本,所以我添加了 8.0.2 版本。因为这绝对是 >= 6.0.8,所以我不认为这会导致问题。但是,当我 运行 程序时,我立即得到一个 System.IO 异常,说 Newtonsoft.Json 6 找不到东西。

我通过将带有程序集绑定重定向 () 的 app.config 文件添加到新版本的 Newtonsoft.Json 来解决这个问题,这解决了这个问题。但是,我不明白如果我的项目依赖的所有 NuGet 包都要求 >= 6.0.8,而 8.0.2 肯定是,为什么需要这样的绑定。

.NET 运行时代有一个叫做强命名的概念。

我可能弄错了很多技术细节,但基本上一个没有强命名的程序集有效地说“我的名字是 zivkan.utilities.dll”,当另一个程序集针对我的程序集编译时,参考说“我需要 zivkan.utilities.dll 中名为 zivkan.utilities.thing 的 class”。因此,它对版本一无所知,您可以放入任何包含 zivkan.utlities.thing class 的 zivkan.utilities.dll,运行 时间将尝试 运行 它。

如果我使用强名称符号 zivkan.utilities.dll,现在程序集将自己宣传为“我的名字是 zivkan.utilites.dll 版本 1.0.0,带有 public 密钥 ...”(我是将在我的其余回答中留下 public 关键部分)。现在,当针对它编译另一个程序集时,编译后的参考显示“我需要 zivkan.utilities.dll 版本 1.0.0”。现在执行此操作时,.NET 运行time 将仅加载 zivkan.utilities.dll 版本 1.0.0,如果版本不同,则会失败,如您所见,您会收到错误消息。该程序可以有一个绑定重定向来告诉 .NET 运行time 程序集加载器,当它看到 0.0.0.0 和 2.0.0.0 版本之间的 zivkan.utilties 请求时使用版本 2.0.0.0,这就是你解决问题的方法。

NuGet 版本和程序集版本是两个不同的概念,但由于它们通常具有相同的值(或非常相似的值),所以区别不大。程序集版本是 运行 时间的事情,而 NuGet 包版本是构建时间的事情。

所以,想象一下没有绑定重定向的情况。您的程序 CommandLineKeyVaultClient 已加载并且依赖于 Newtonsoft.Json 版本 8.0.2。 .NET 运行 时间加载 Newtonsoft.Json.dll 并确认它确实是版本 8.0.2。然后 .NET 运行 时间看到 CommandLineKeyVaultClient 也依赖于 Microsoft.Rest.ClientRuntime.dll,比方说版本 1.0.0.0。因此,.NET 运行 时间加载该 dll 并确认程序集版本号。 Microsoft.Rest.ClientRuntime.dll 依赖于 Newtonsoft.Json.dll 版本 6.0.8。 .NET 运行 时间看到 Newtonsoft.Json 版本 8.0.2 已经加载,但版本不匹配并且没有绑定重定向,所以让我们尝试在磁盘上加载 Newtonsoft.Json.dll (实际上有一个钩子可以用来告诉加载程序从不同的目录加载 dll,当你真的需要加载同一个程序集的不同版本时,你可以)。当它尝试时,它发现程序集的版本与命名的强依赖项不匹配,并且没有说“无法加载 Newtonsoft.Json.dll 版本 6.0.8”,这是真的,因为磁盘上的版本实际上是 8.0 .2.

如果您使用 PackageReference 来使用 NuGet 包,NuGet 不仅会查看可传递的 NuGet 依赖项,还会查看项目依赖项并构建所需的所有程序集(项目或 nuget)的图表。然后 MSBuild 应该自动检测两个不同的程序集何时依赖于同一程序集名称的不同版本并生成绑定重定向。因此,在使用 PackageReference 时,这通常不会成为问题。

但是,如果您使用 packages.config 来定义您的 NuGet 依赖项,NuGet 将在检测到版本冲突时尝试添加绑定重定向(我认为您可以选择退出)。但是由于这是在您修改该项目中的 NuGet 依赖项(安装、升级或卸载包)时计算的,因此绑定重定向可能会不同步,并且项目到项目的依赖项以及 NuGet 打包这些内容存在问题项目参考使用。

无论如何,我希望这能解释为什么当您的所有项目都具有 NuGet 依赖项 >= 6.0.8 时出现 dll 加载错误。我再次重申,程序集版本和 NuGet 版本是不同的东西,即使它们具有相同的值,.NET 运行time 允许您同时加载同一程序集的不同版本,并且在您需要指令时不想要那个,这就是绑定重定向。

我认为所选答案和评论线程中存在一些错误。我会尽力清理它。

首先你要知道包版本和程序集版本是完全不同的两个东西。您可以发布包的 1.0.0 版,包中的程序集可以是 99.0.0.0 版。那将是一种不好的做法,但没有什么可以阻止它。

默认情况下,在运行时解析对强命名程序集的引用时,.Net Framework(意味着不是 .Net Core,或 .Net v5 或 v6)强制执行精确的版本匹配。也就是说,如果您构建的程序集 A 具有对程序集 B 的 v 1.0.0.0 的 build-time 引用,则默认情况下它将在运行时需要版本 1.0.0.0 的程序集 B,假设 B 是强命名的。打败它的唯一方法是使用 或发布者政策,它本身需要使用 GAC。

您认为 Nuget 版本控制规则(例如,>= 1.0.0)与程序集绑定机制有某种关系是可以理解的,但事实并非如此。它只是指定当 运行“NuGet 更新”时您将自动接受哪些较新的包版本。它与接受比构建时使用的版本更新的程序集版本的运行时行为无关。

.Net Core(至少 v5 和 v6,也许更早)对强命名没有任何意义,因此不强制执行任何版本匹配。如果您需要不同的行为,您可以使用 https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/understanding-assemblyloadcontext API.

.Net Framework 中的强命名主要是一种工具,它允许不同版本的程序集在运行时在应用程序域中同时加载 side-by-side。该功能需要使用 GAC。 SN在.Net core中本质上是没有意义的。