Могу ли я иметь ссылку на сборку любой версии другой сборки?

  • Я разрабатываю библиотеку классов (MyClassLibrary).
  • Я полагаюсь на библиотеку сторонних классов (ThirdPartyClassLibrary).
  • Мне нужно использовать ту же версию ThirdPartyClassLibrary, что и мои пользователи. например, если я установил статическое значение в ThirdPartyClassLibrary, пользователь должен увидеть это изменение.
  • Пользователи моего класса могут быть в зависимости от любой из четырех различных версий ThirdPartyClassLibrary.
  • ThirdPartyClassLibrary большой, я не хочу распространять его с помощью моего программного обеспечения.
  • Я отразил все 4 версии ThirdPartyClassLibrary и подтвердил, что вещи, которые я буду с ними делать, совместимы во всех версиях (интерфейсы одинаковы, подписи методов одинаковы и т.д.).
  • Мне нужно, чтобы звонки в ThirdPartyClassLibrary исполнялись! Я не могу размышлять обо всем, когда мне нужно что-то называть.
  • MyClassLibrary будет загружаться во время выполнения, поэтому я не могу ожидать, что пользователи будут взаимодействовать с перенаправлением привязки сборки или другими настройками времени разработки (или любыми настройками вообще, мои пользователи не могут ничего делать).
  • Я хотел бы воспользоваться проверкой кода во время компиляции, поэтому в идеале не было никакого отражения.

Как я могу написать MyClassLibrary, чтобы при загрузке в процесс все правильно работало с версией ThirdPartyClassLibrary, которую пользователь загрузил?

Ответ 1

Одним из способов решения проблемы было бы использовать событие AppDomain.AssemblyResolve во время выполнения. Это срабатывает всякий раз, когда разрешение сборки выходит из строя. Вы можете использовать это, чтобы загрузить другую версию сборки, которую пытается загрузить среда CLR.

Я добавил очень простое демо на GitHub:

https://github.com/danmalcolm/AssemblyResolutionDemo

Это настраивается следующим образом:

  • Основное приложение App.exe напрямую ссылается на сборку ThirdPartyLibrary.dll версии 2.0.0.0.

  • Он также ссылается на MyLibrary, который ссылается на более старую версию ThirdPartyLibrary версии 1.0.0.0.

  • Событие AppDomain.AssemblyResolve используется для перенаправления на версию, используемую приложением, когда версия 1.0.0.0 не загружается

AssemblyResolve обрабатывается следующим образом:

public static void Initialise()
{
    AppDomain.CurrentDomain.AssemblyResolve += ResolveThirdPartyLibrary;
}

private static Assembly ResolveThirdPartyLibrary(object sender, ResolveEventArgs args)
{
    // Check that CLR is loading the version of ThirdPartyLibrary referenced by MyLibrary
    if (args.Name.Equals("ThirdPartyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fbcbfac3e44fefed"))
    {
        try
        {
            // Load from application base directory. Alternative logic might be needed if you need to 
            // load from GAC etc. However, note that calling certain overloads of Assembly.Load will result
            // in the AssemblyResolve event from firing recursively - see recommendations in
            // http://msdn.microsoft.com/en-us/library/ff527268.aspx for further info
            var assembly = Assembly.LoadFrom("ThirdPartyLibrary.dll");
            return assembly;
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception);
        }
    }
    return null;
}

Нам нужно привязать событие к загрузке третьей частичной библиотеки, следовательно, явный метод инициализации.

Обратите внимание, что событие срабатывает только при сбое разрешения сборки. Если версия ThirdPartyLibrary, на которую ссылается MyClassLibrary (1.0.0.0), была доступна в GAC, тогда она будет загружена успешно, и AssemblyResolve не будет запускаться. Тогда будет использоваться 2 разных версии.

Я демонстрирую здесь, что этот механизм можно использовать, я не говорю, что это хорошая идея. Есть несколько вещей, которые вам нужно учитывать в зависимости от среды, в которой работает ваше приложение, и как оно настроено/установлено/поддерживается и т.д.

Ответ 2

Нет, вы не можете создать MyClassLibrary со ссылкой на ThirdPartyClassLibrary таким образом, чтобы "просто использовать любую версию ThirdPartyClassLibrary.dll во время выполнения".

Когда вы создаете свою библиотеку, номер версии любых ссылочных сборок включается в манифест сборки. Запуск инструмента ILDASM против вашей сборки будет выглядеть примерно так:

...
.assembly extern ThirdPartyClassLibrary
{
  ...
  .ver 1:0:0:0
}
...

Указано имя и версия ThirdPartyClassLibrary. Во время выполнения CLR будет пытаться загрузить ThirdPartyClassLibrary.dll, когда он сначала запускает инструкции в MyClassLibrary.dll, которые ссылаются на него. Он будет выглядеть специально для версии 1.0.0.0 для ThirdPartyClassLibrary.dll(а также будет иметь соответствующий открытый ключ, если это сильная команда).

Вот краткий обзор того, как CLR обнаруживает и связывает сборки во время выполнения (полная информация в http://msdn.microsoft.com/en-us/library/yx7xezcf(v=vs .110).aspx):

Шаг 1. Определите правильную версию сборки, просмотрев файлы конфигурации. Мы вернемся к этому ниже, но пока, если вы не скажете об этом иначе, CLR попытается загрузить точная версия, указанная в сборке ссылок, поэтому она будет искать версию 1.0.0.0.

Шаг 2. Проверьте, было ли имя сборки привязано до и, если это так, использует ранее загруженную сборку. Обратите внимание, что "имя сборки" в этом контексте включает имя и версию, токен открытого ключа и т.д., А не только имя файла dll.

Шаг 3. Проверьте глобальный сборщик GAC (только узлы с сильными именами)

Шаг 4. Найдите сборку через кодовые базы или зондирование. По существу CLR ищет в разных местах, чтобы попытаться найти (конкретную версию) AssemblyB.dll где-нибудь. Произошла ошибка, если она не может найти конкретную версию. Он не будет автоматически возвращаться к более ранней или более поздней версии.

К сожалению, это означает, что вещи не будут "просто работать" и поддерживать то, что вы описали выше. Если приложение, которое ссылается на MyClassLibrary, ссылается на более позднюю версию (2.0.0.0) ThirdPartyClassLibrary, могут возникнуть некоторые плохие вещи при разрешении ссылки MyClassLibrary на ThirdPartyClassLibrary:

  • CLR не может найти версию 1.0.0.0 AssemblyB, используемую Assembly A, и возникает ошибка
  • Версия 1.0.0.0 устанавливается в GAC и успешно загружается. Хотя код в приложении использует ThirdPartyClassLibrary версии 2.0.0.0, ваша библиотека использует версию 3rdPartyClassLibrary версии 1.0.0.0.

Единственное, что вы можете сделать, это настроить приложение, используя вашу библиотеку, чтобы среда CLR объединила ссылки на разные версии ThirdPartyClassLibrary.dll на одну версию. Это возвращает нас к шагу 1 процесса связывания сборки, описанному выше - мы существенно меняем версию ThirdPartyClassLibrary, которую ищет CLR.

Переадресация связывания (http://msdn.microsoft.com/en-us/library/twy1dw1e.aspx) предназначены для ссылки на ссылки на разные версии сборки на одну версию. Они обычно определяются в файле конфигурации приложения (Web.config, MyApp.exe.config), но также могут быть определены глобально на уровне машины (machine.config).

Вот пример перенаправления связывания, который перенаправляет все более ранние версии ThirdPartyClassLibrary.dll на версию 2.0.0.0:

<configuration>

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="AssemblyB" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

</configuration>

Обратите внимание, что это может быть автоматически обработанное Visual Studio 2013, которое обнаруживает случаи, когда ссылки на разные версии сборки ссылаются и добавляет переадресацию привязки для тебя. Кроме того, в консоли управления пакетами NuGet доступна команда Add-BindingRedirect.

Связывание переадресаций является возможным решением и может быть прагматичным выбором в некоторых сценариях. Однако они могут также путать пользователей вашей библиотеки. Если это практично, вам следует рассмотреть возможность распространения различных версий вашей библиотеки, построенных на разных версиях сторонней библиотеки.