Поделиться через


Рекомендации по загрузке сборок

Замечание

Эта статья связана с .NET Framework. Он не применяется к более новым реализациям .NET, включая .NET 6 и более поздние версии.

В этой статье рассматриваются способы избежать проблем с идентификацией типа, которые могут привести к InvalidCastException, MissingMethodException и другим ошибкам. В статье рассматриваются следующие рекомендации:

Первая рекомендация, понять преимущества и недостатки контекстов нагрузки, предоставляет фоновую информацию для других рекомендаций, так как все они зависят от знаний контекстов нагрузки.

Общие сведения о преимуществах и недостатках контекстов нагрузки

В домене приложения сборки можно загружать в один из трех контекстов или загружать их без контекста:

  • Контекст загрузки по умолчанию содержит сборки, найденные путем проверки глобального кэша сборок, хранилища сборок хоста, если среда выполнения размещена (например, в SQL Server), и ApplicationBase и PrivateBinPath домена приложения. Большинство перегрузок метода Load загружают сборки в этот контекст.

  • Контекст load-from содержит сборки, загружаемые из мест, которые загрузчик не просматривает. Например, надстройки могут быть установлены в каталоге, который не находится в пути к приложению. Assembly.LoadFrom, AppDomain.CreateInstanceFromи AppDomain.ExecuteAssembly являются примерами методов, которые загружаются по пути.

  • Контекст, предназначенный только для отражения, содержит сборки, загруженные методами ReflectionOnlyLoad и ReflectionOnlyLoadFrom. Код в этом контексте не может быть выполнен, поэтому здесь не рассматривается. Дополнительные сведения см. в статье "Практическое руководство. Загрузка сборок в контекст Reflection-Only".

  • Если вы создали временную динамическую сборку с помощью reflection emit, эта сборка не связана ни с каким контекстом. Кроме того, большинство сборок, загруженных с помощью LoadFile метода, загружаются без контекста, а сборки, загруженные из массивов байтов, загружаются без контекста, если только их удостоверение (после применения политики) не устанавливает, что они находятся в глобальном кэше сборок.

Контексты выполнения имеют преимущества и недостатки, как описано в следующих разделах.

Контекст загрузки по умолчанию

Когда сборки загружаются в контекст загрузки по умолчанию, их зависимости загружаются автоматически. Зависимости, загруженные в контекст загрузки по умолчанию, автоматически обнаруживаются для сборок в этом контексте или в контексте загрузки из файла. Загрузка по идентификатору сборки повышает стабильность приложений, гарантируя, что неизвестные версии сборок не используются (см. раздел "Избегание привязки по частичным именам сборок").

Использование контекста загрузки по умолчанию имеет следующие недостатки:

  • Зависимости, загруженные в другие контексты, недоступны.

  • Сборки нельзя загружать из расположений, находящихся вне пути поиска, в контекст загрузки по умолчанию.

контекст Load-From

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

Загрузка сборок с помощью Assembly.LoadFrom метода или одного из других методов, которые загружаются по пути, имеет следующие недостатки:

  • Если сборка с тем же идентификатором уже загружена в контексте загрузки, LoadFrom возвращает загруженную сборку, даже если был указан другой путь.

  • Если сборка загружена с помощью LoadFrom, а затем в контексте загрузки по умолчанию пытается загрузить ту же сборку по отображаемому имени, попытка загрузки завершается ошибкой. Это может произойти при десериализации сборки.

  • Если сборка загружена с LoadFrom, а путь проверки включает сборку с тем же удостоверением, но в другом расположении, может возникнуть InvalidCastException, MissingMethodException или непредвиденное поведение.

  • LoadFrom требует FileIOPermissionAccess.Read и FileIOPermissionAccess.PathDiscovery, или WebPermission, по указанному пути.

  • Если для сборки существует родной образ, он не используется.

  • Сборка не может быть загружена как доменно-независимая.

  • В .NET Framework версии 1.0 и 1.1 политика не применяется.

Нет контекста

Загрузка без контекста окружения является единственным вариантом для временных сборок, создаваемых с использованием технологии reflection emit. Загрузка без контекста — единственный способ загрузить несколько сборок с одинаковым идентификатором в один домен приложения. Избегаются затраты на проверку.

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

Загрузка сборок без контекста имеет следующие недостатки:

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

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

  • Загрузка нескольких сборок с одинаковым удостоверением без контекста может вызвать проблемы с идентификацией типа, аналогичные тем, которые вызваны загрузкой сборок с одинаковым удостоверением в разные контексты. См. раздел "Избегайте загрузки сборки в несколько контекстов".

  • Если для сборки существует родной образ, он не используется.

  • Сборка не может быть загружена как доменно-независимая.

  • В .NET Framework версии 1.0 и 1.1 политика не применяется.

Избегайте привязки к частичным именам сборок

Частичная привязка имени происходит, когда указывается только часть отображаемого имени сборки при ее загрузке FullName. Например, можно вызвать метод Assembly.Load с простым именем сборки, пропуская версию, культуру и маркер открытого ключа. Или вы можете вызвать Assembly.LoadWithPartialName метод, который сначала вызывает Assembly.Load метод и, если не удается найти сборку, выполняет поиск в глобальном кэше сборок и загружает последнюю доступную версию сборки.

Привязка частичного имени может вызвать множество проблем, включая следующие:

  • Метод Assembly.LoadWithPartialName может загрузить другую сборку с тем же простым именем. Например, два приложения могут устанавливать две совершенно разные сборки, которые оба имеют простое имя GraphicsLibrary в глобальный кэш сборок.

  • Сборка, которая фактически загружена, может быть не совместима с обратной совместимостью. Например, не указывая версию, может привести к загрузке гораздо более поздней версии, чем версия, используемая программой. Изменения в более поздней версии могут привести к ошибкам в приложении.

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

  • Установка новых приложений может нарушить существующие приложения. Приложение, использующее LoadWithPartialName метод, может быть нарушено путем установки более новой, несовместимой версии общей сборки.

  • Может произойти непредвиденная загрузка зависимостей. При загрузке двух сборок, которые совместно используют зависимость, загрузка их с частичной привязкой может привести к одной сборке с помощью компонента, с которым он не был создан или протестирован.

Из-за проблем, которые могут возникнуть, LoadWithPartialName метод был помечен как устаревший. Вместо этого рекомендуется использовать Assembly.Load метод и указать полные отображаемые имена сборок. Ознакомьтесь с преимуществами и недостатками контекстов нагрузки и рассмотрите возможность переключения на контекст загрузки по умолчанию.

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

Избегайте загрузки сборки в несколько контекстов

Загрузка сборки в несколько контекстов может вызвать проблемы с идентичностью типа. Если один и тот же тип загружается из одной сборки в разные контексты, это всё равно как если бы были загружены два разных типа с одинаковым именем. Возникает InvalidCastException при попытке приведения одного типа к другому типу, с запутанным сообщением о том, что тип MyType не может быть приведён к типу MyType.

Например, предположим, что ICommunicate интерфейс объявлен в сборке с именем Utility, на которую ссылается ваша программа, а также другими сборками, которые загружает программа. Эти другие сборки содержат типы, реализующие ICommunicate интерфейс, что позволяет программе использовать их.

Теперь рассмотрим, что происходит при запуске программы. Сборки, на которые ссылается ваша программа, загружаются в контекст загрузки по умолчанию. Если вы загружаете целевую сборку по её идентификатору, используя метод Load, она будет находиться в типовом контексте загрузки, и её зависимости тоже. Программа и целевая сборка будут использовать одну и ту же Utility сборку.

Однако предположим, что целевая сборка загружается по пути к файлу, используя метод LoadFile. Сборка загружается без контекста, поэтому ее зависимости не загружаются автоматически. Возможно, у вас есть обработчик для события AppDomain.AssemblyResolve, чтобы предоставить зависимость, и он может загрузить сборку Utility без контекста с помощью метода LoadFile. Теперь, создавая экземпляр типа, содержащегося в целевой сборке, и пытаясь присвоить его переменной типа ICommunicate, произойдет исключение InvalidCastException, потому что среда выполнения рассматривает интерфейсы ICommunicate в двух копиях сборки Utility как разные типы.

Существует множество других сценариев, в которых сборка может быть загружена в несколько контекстов. Лучший подход заключается в том, чтобы избежать конфликтов, переместив целевую сборку в путь вашего приложения и используя метод Load с полным именем для отображения. Затем сборка загружается в контекст загрузки по умолчанию, и обе сборки используют одну и ту же Utility сборку.

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

В разделе "Рассмотрение перехода к контексту загрузки по умолчанию" обсуждаются альтернативы использования загрузки по пути к файлу, такие как LoadFile и LoadFrom.

Избегайте загрузки нескольких версий сборки в одном контексте

Загрузка нескольких версий сборки в один контекст загрузки может вызвать проблемы с идентификацией типа. Если один и тот же тип загружается из двух версий одной сборки, это так же, как если бы было загружено два разных типа с одинаковым именем. Возникает InvalidCastException при попытке приведения одного типа к другому типу, с запутанным сообщением о том, что тип MyType не может быть приведён к типу MyType.

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

В контексте загрузки по умолчанию эта проблема может возникать при использовании Assembly.Load метода и указания полных отображаемых имен сборок, включающих разные номера версий. Для сборок, загруженных без контекста, проблема может быть вызвана с помощью Assembly.LoadFile метода для загрузки одной сборки из разных путей. Среда выполнения считает две сборки, загруженные из разных путей, как разные сборки, даже если их идентичность одинакова.

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

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

Внимательно просмотрите код, чтобы убедиться, что загружается только одна версия сборки. С помощью AppDomain.GetAssemblies метода можно определить, какие сборки загружаются в любое время.

Рассмотрите возможность переключения на контекст загрузки по умолчанию

Изучите шаблоны загрузки и развертывания сборок приложения. Можно ли исключить сборки, загруженные из массивов байтов? Можно ли переместить сборки в маршрут проверки? Если сборки находятся в глобальном кэше сборок или в пути проверки домена приложения (т. е. его ApplicationBase и PrivateBinPath), можно загрузить сборку по её идентификатору.

Если невозможно поместить все сборки в путь проверки, рассмотрите варианты, такие как использование модели надстроек .NET Framework, размещение сборок в глобальный кэш сборок или создание доменов приложений.

Рассмотрите возможность использования модели Add-In .NET Framework

Если вы используете контекст загрузки для реализации надстроек, которые обычно не устанавливаются в базе приложений, используйте модель надстроек .NET Framework. Эта модель обеспечивает изоляцию на уровне домена или процесса приложения, не требуя самостоятельного управления доменами приложений. Дополнительные сведения о модели надстроек см. в разделе "Надстройки" и "Расширяемость".

Рассмотрите возможность использования глобального кэша сборок

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

Рассмотрите возможность использования доменов приложений

Если определить, что некоторые сборки не могут быть развернуты в пути проверки приложения, рассмотрите возможность создания нового домена приложения для этих сборок. AppDomainSetup Создайте новый домен приложения и используйте AppDomainSetup.ApplicationBase свойство, чтобы указать путь, содержащий сборки, которые требуется загрузить. Если у вас есть несколько каталогов для исследования, можно задать ApplicationBase как корневой каталог и использовать AppDomainSetup.PrivateBinPath для идентификации подкаталогов, которые нужно исследовать. Кроме того, можно создать несколько доменов приложений и задать ApplicationBase для каждого домена приложения соответствующий путь для сборок.

Обратите внимание, что для загрузки этих сборок можно использовать Assembly.LoadFrom метод. Так как они теперь находятся в пути зондирования, они будут загружены в контекст загрузки по умолчанию вместо контекста загрузки "из объекта". Однако рекомендуется переключиться на Assembly.Load метод и указать полные отображаемые имена сборок, чтобы убедиться, что всегда используются правильные версии.

См. также