Можно ли заменить ссылку на сильно названную сборку "слабым" ссылкой?

Я пишу .NET-инструмент, который требует библиотеки SMO ​​SQL Server. Меня не волнует, была ли версия с сервера 2005 (9.0), 2008 (10.0) или 2008 R2 (вероятно, 10.5, не проверялась). Библиотека SMO установлена ​​вместе с SQL Server, поэтому я могу с уверенностью предположить, что в любой системе с установленным SQL Server доступна некоторая версия библиотеки SMO.

К сожалению, библиотеки SMO ​​сильно названы: если я добавлю ссылку на SMO 9.0 в свой проект, он потерпит неудачу (FileNotFoundException), если в системе клиента присутствует только SMO 10.0 и наоборот.

Есть ли способ сообщить компилятору, что любая версия библиотеки подходит мне? Или мне действительно нужно распространять 3 идентичные версии моего инструмента, каждый из которых скомпилирован в другую версию SMO?


Отказ от ответственности: я знаю, что библиотеки SMO ​​(и библиотеки, необходимые библиотекам SMO) могут быть перераспределены. Но есть большая разница между (a) одним тонким 100KB автономным EXE и (b) полномасштабным установочным пакетом, который устанавливает целую кучу предварительных условий.

Отказ от ответственности 2: Я знаю следующие дубликаты:

Однако предоставленные решения не подходят. В вопросе 1 разработчик имеет контроль над DLL, на которую ссылается (что у меня нет); в вопросе 2 разработчик имеет контроль над целевыми системами (чего я тоже не делаю).


Решение. Основываясь на предположении 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://stackoverflow.com/q/6847765/87698
Private Sub TestSmo()
    Dim srv As New Smo.Server()
End Sub

Примечание. Использование Assembly.Load непосредственно в обработчике событий AssemblyResolve не является хорошей идеей, поскольку он рекурсивно вызывает обработчик события, если Load failed.

Ответы

Ответ 1

Как я знаю, невозможно удалить зависимость от точной версии. Это одна из причин, почему существуют сильные имена - чтобы избежать несоответствия версий. Внутренние или даже публичные интерфейсы сборки могут меняться среди версий, и вы можете обнаружить, что новая версия не обратно совместима со старой. Из-за этого .NET ищет версию, используемую во время компиляции, чтобы убедиться, что приложение работает правильно.

Если сторонняя сторона решает, что их новая версия обратная совместимость и если они разворачивают сборку в GAC, они могут добавить политику издателя, которая будет автоматически перенаправлять.

Если вы решите, что хотите принудительно загрузить другую сборку, вы можете использовать подход, названный @chibacity или обработчик реализации для AppDomain.CurrentDomain.AssemblyResolve. Это событие срабатывает, когда .NET не может найти ссылку на сборку, и вы можете реализовать свою собственную логику, чтобы найти ее и загрузить ее, вызвав Assembly.LoadFrom. В этом случае вы полностью зависите от версии, которую вы загружаете.

Ответ 2

Вы можете использовать Перенаправление привязки сборки.

Например:

 <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>