能否用“弱”引用替换对强名称程序集的引用?

17

我正在编写一个需要SQL Server SMO库的.NET工具。我不在乎它是来自Server 2005(9.0)、2008(10.0)还是2008 R2(可能是10.5,没有检查过)的版本。SMO库随SQL Server一起安装,因此我可以安全地假设在任何安装了SQL Server的系统上,某个版本的SMO库也可用。

不幸的是,SMO库是强命名的:如果在我的项目中添加对SMO 9.0的引用,而客户端系统上只有SMO 10.0存在,则会失败(FileNotFoundException),反之亦然。

是否有一种方法可以告诉编译器我对该库的任何版本都可接受?或者我真的需要分发3个相同版本的工具,每个版本都编译为不同版本的SMO吗?


声明:我知道SMO库(以及SMO库所需的库)可以重新分发。但是,一个精简的100KB独立EXE和安装了很多先决条件的完整安装程序之间有很大的区别。

免责声明2:我知道以下内容的重复:

但是提供的解决方案并不适合。在问题1中,开发人员对所引用的DLL具有控制权(而我没有);在问题2中,开发人员对目标系统也有控制权(而我也没有)。


有趣的问题。既然我对这个主题一无所知,对依赖注入也几乎一无所知,那么它能通过这种方式实现吗?我的意思是,它们不同版本的程序集是您打算注入的依赖项吗?这只是一个想法... - Smudge202
抱歉,我的答案完全错误,Specific Version 是一个仅在构建时设置的选项 - Justin
@Smudge202 有趣的想法,但是有一个难点。DI取决于某种类型(例如接口),可以作为注入的基础。鉴于这个来自第三方,该类型将存在于第三方库中 - 砰 - 您就会依赖于第三方库的特定版本。 - Tim Lloyd
3个回答

6
据我所知,无法消除对确切版本的依赖。这就是强名称存在的原因之一 - 避免版本不匹配。程序集的内部甚至公共接口在版本之间可能会发生变化,您可能会发现新版本与旧版本不兼容。因此,.NET查找编译期间使用的版本,以确保应用程序正常工作。
如果第三方决定他们的新版本向后兼容,并且如果他们将程序集部署到GAC,则可以添加 publisher policy,它将自动重定向。
如果您决定强制加载另一个程序集,则可以使用@chibacity提到的方法或实现AppDomain.CurrentDomain.AssemblyResolve的处理程序。当.NET无法找到引用的程序集时,将触发此事件,您可以通过调用Assembly.LoadFrom来实现自己的逻辑来查找并加载它。在这种情况下,您完全可以加载任何版本。

3
脚注:如果未签名程序集 A 通过强名称引用依赖于已签名的 B v1,通过处理 AssemblyResolve 事件,您可以使 A 使用 B v2,但前提是 B v2 也被签名。如果 B v2 没有被签名,A 将拒绝使用它。 - Daniel Earwicker
1
请注意,“also signed”指的是具有相同的“publicKeyToken”。我想这意味着替换的DLL必须来自同一作者。 - Daniel Earwicker

6
你可以使用程序集绑定重定向
例如:
 <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Telerik.Web.UI" publicKeyToken="121fae78165ba" />
        <bindingRedirect
               oldVersion="2010.0.0.1"
               newVersion="2011.1.315.40" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

更新

从您的评论中我看到我们需要略微反向思考。

一个非常有前途的方法,但不幸的是,它仅将对版本X的依赖性替换为对版本Y(强)的依赖性。我仍然依赖于一个特定的版本。

我进行了一些实验,编译了一个程序集的版本:4.0.0.0,但希望确保它能加载该版本以及一些选择的旧版本。通过这种方式,您不会依赖于任何单个版本,而是针对您配置的任何版本。

以下内容将确保如果系统上存在以下任何版本之一,则将加载VersionedAssembly:4.0.0.0、3.0.0.0、2.0.0.0、1.0.0.0。

   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <dependentAssembly>
            <assemblyIdentity name="VersionedAssembly" publicKeyToken="20d85e" />
            <bindingRedirect oldVersion="4.0.0.0" newVersion="1.0.0.0"/>
         </dependentAssembly>
         <dependentAssembly>
            <assemblyIdentity name="VersionedAssembly" publicKeyToken="20d84e" />
            <bindingRedirect oldVersion="4.0.0.0" newVersion="2.0.0.0"/>
         </dependentAssembly>
         <dependentAssembly>
            <assemblyIdentity name="VersionedAssembly" publicKeyToken="20d84e" />
            <bindingRedirect oldVersion="4.0.0.0" newVersion="3.0.0.0"/>
         </dependentAssembly>
      </assemblyBinding>
   </runtime>

一种非常有前途的方法,但不幸的是,它仅仅是用版本Y(强烈)替换了对版本X的依赖。我仍然对一个特定版本有依赖。 - Heinzi
@Heinzi 是的,但你需要将所有旧版本都指向最新版本。这样,你最终会得到W、X、Y、Z版本都指向Z的结果。但更重要的是,这些都是受支持和经过测试的版本。如果存在不同的版本,那么它是否与你实际编译的版本兼容呢? - Tim Lloyd
@Heinzi,您还可以使用通配符样式版本控制,例如 oldVersion="0.0.0.0-65535.65535.65535.65535" - Tim Lloyd
很遗憾,我无法重现您的结果。我已经使用对v9的引用编译了应用程序。如果我在配置文件中添加一个重定向9->10,它仅适用于安装有10的系统。显然,只有找到的第一个匹配的dependentAssembly指令才会被使用:如果我在9->10之前添加一个重定向9->9,它仅适用于安装有9的系统。 - Heinzi
我已经使用Ladislav的建议解决了我的问题。我仍然对您的程序集重定向方法感兴趣,但只是出于科学好奇心(因为我的问题已经解决)。在您投入过多精力之前,我想提一下这一点。 - Heinzi
显示剩余2条评论

0

基于Ladislav的建议覆盖AssemblyResolve,我能够想出以下解决方案:

Sub Main()
    ...
    Dim assembly = GetSmoAssembly()
    If assembly Is Nothing Then
        ' no suitable Version of SMO found
        ...
    Else
        ' load correct assembly
        Dim returnAssembly As ResolveEventHandler = Function() assembly
        AddHandler AppDomain.CurrentDomain.AssemblyResolve, returnAssembly
        TestSmo()
        RemoveHandler AppDomain.CurrentDomain.AssemblyResolve, returnAssembly
    End If
    ...
End Sub

Private Function GetSmoAssembly() As Assembly
    Try
        Return Assembly.Load("Microsoft.SqlServer.Smo, Version=9.0.242.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91")
    Catch ex As FileNotFoundException
    End Try

    Try
        Return Assembly.Load("Microsoft.SqlServer.Smo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91")
    Catch ex As FileNotFoundException
    End Try

    Return Nothing
End Function

' Needs to be in a separate method, see https://dev59.com/oFnUa4cB1Zd3GeqPcqpp
Private Sub TestSmo()
    Dim srv As New Smo.Server()
End Sub

注意:在AssemblyResolve事件处理程序中直接使用Assembly.Load不是一个好主意,因为如果Load失败,它会递归调用事件处理程序。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接