Можно ли заменить ссылку на сильно названную сборку "слабым" ссылкой?
Я пишу .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>