Практическое руководство. Проверка содержимого сборки с помощью MetadataLoadContext

API отражения в .NET по умолчанию позволяет разработчикам проверять содержимое сборок, загруженных в основной контекст выполнения. Однако иногда невозможно загрузить сборку в контекст выполнения, например, если она была скомпилирована для другой архитектуры платформы или процессора либо если это базовая сборка. API System.Reflection.MetadataLoadContext позволяет загружать и проверять такие сборки. Сборки, загруженные в MetadataLoadContext, обрабатываются только как метаданные, то есть вы можете проверять типы в сборке, но не выполнять код, содержащийся в ней. В отличие от основного контекста выполнения MetadataLoadContext не загружает зависимости из текущего каталога автоматически. Вместо этого используется пользовательская логика привязки, предоставляемая MetadataAssemblyResolver, переданной в нее.

Необходимые компоненты

Чтобы использовать MetadataLoadContext, установите пакет NuGet System.Reflection.MetadataLoadContext. Она поддерживается в любой целевой платформе, совместимой с .NET Standard 2.0, например .NET Core 2.0 или .NET Framework 4.6.1.

Создание MetadataAssemblyResolver для MetadataLoadContext

Создание MetadataLoadContext требует предоставления экземпляра MetadataAssemblyResolver. Самый простой способ указать, что он использует PathAssemblyResolver, который разрешает сборки из заданной коллекции строк пути сборки. Эта коллекция, помимо сборок, которые необходимо проверить напрямую, также должна включать все необходимые зависимости. Например, для чтения настраиваемого атрибута, расположенного во внешней сборке, следует включить эту сборку, иначе будет вызвано исключение. В большинстве случаев следует включать по крайней мере основную сборку, то есть сборку, содержащую встроенные системные типы, такие как System.Object. В следующем коде показано, как создать PathAssemblyResolver с помощью коллекции, состоящей из проверенной сборки и основной сборки текущей среды выполнения:

var resolver = new PathAssemblyResolver(new string[] { "ExampleAssembly.dll", typeof(object).Assembly.Location });

Если требуется доступ ко всем типам BCL, в коллекцию можно включить все сборки среды выполнения. В следующем коде показано, как создать PathAssemblyResolver с помощью коллекции, состоящей из проверенной сборки и все сборки текущей среды выполнения:

// Get the array of runtime assemblies.
string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");

// Create the list of assembly paths consisting of runtime assemblies and the inspected assembly.
var paths = new List<string>(runtimeAssemblies);
paths.Add("ExampleAssembly.dll");

// Create PathAssemblyResolver that can resolve assemblies using the created list.
var resolver = new PathAssemblyResolver(paths);

Создание MetadataLoadContext

Чтобы создать MetadataLoadContext, вызовите его конструктор MetadataLoadContext(MetadataAssemblyResolver, String), передав ранее созданный MetadataAssemblyResolver в качестве первого параметра и основное имя сборки в качестве второго параметра. Имя основной сборки можно опустить, в этом случае конструктор попытается использовать имена по умолчанию: "mscorlib", "System.Runtime" или "netstandard".

После создания контекста в него можно загрузить сборки с помощью таких методов, как LoadFromAssemblyPath. Все API отражения можно использовать в загруженных сборках, за исключением тех, которые используют выполнение кода. Метод GetCustomAttributes требует выполнения конструкторов, поэтому, если необходимо исследовать пользовательские атрибуты в MetadataLoadContext, вместо этого следует использовать метод GetCustomAttributesData.

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

var mlc = new MetadataLoadContext(resolver);

using (mlc)
{
    // Load assembly into MetadataLoadContext.
    Assembly assembly = mlc.LoadFromAssemblyPath("ExampleAssembly.dll");
    AssemblyName name = assembly.GetName();

    // Print assembly attribute information.
    Console.WriteLine($"{name.Name} has following attributes: ");

    foreach (CustomAttributeData attr in assembly.GetCustomAttributesData())
    {
        try
        {
            Console.WriteLine(attr.AttributeType);
        }
        catch (FileNotFoundException ex)
        {
            // We are missing the required dependency assembly.
            Console.WriteLine($"Error while getting attribute type: {ex.Message}");
        }
    }
}

Если необходимо протестировать типы для MetadataLoadContext равенства или назначения, используйте только объекты типов, загруженные в этот контекст. MetadataLoadContext Сочетание типов с типами среды выполнения не поддерживается. Например, рассмотрим тип testedType в MetadataLoadContext. Если вам нужно проверить, можно ли назначить другой тип из него, не используйте код, например typeof(MyType).IsAssignableFrom(testedType). Используйте следующий код:

Assembly matchAssembly = mlc.LoadFromAssemblyPath(typeof(MyType).Assembly.Location);
Type matchType = assembly.GetType(typeof(MyType).FullName!)!;

if (matchType.IsAssignableFrom(testedType))
{
    Console.WriteLine($"{nameof(matchType)} is assignable from {nameof(testedType)}");
}

Пример

Пример полного кода см. на странице примера Проверка содержимого сборки с помощью MetadataLoadContext.

См. также