Guide pratique pour inspecter le contenu des assemblys à l’aide de MetadataLoadContext

L’API de réflexion dans .NET permet par défaut aux développeurs d’inspecter le contenu des assemblys chargés dans le contexte d’exécution principal. Toutefois, il est parfois impossible de charger un assembly dans le contexte d’exécution, par exemple, car il a été compilé pour une autre architecture de plateforme ou de processeur, ou il s’agit d’un assembly de référence. L’API System.Reflection.MetadataLoadContext vous permet de charger et d’inspecter ces assemblys. Les assemblys chargés dans le MetadataLoadContext sont traités uniquement comme des métadonnées, c’est-à-dire que vous pouvez examiner les types dans l’assembly, mais vous ne pouvez pas exécuter de code contenu dans celui-ci. Contrairement au contexte d’exécution principal, le MetadataLoadContext ne charge pas automatiquement les dépendances à partir du répertoire actif ; au lieu de cela, il utilise la logique de liaison personnalisée fournie par le MetadataAssemblyResolver qui lui est passé.

Prérequis

Pour utiliser MetadataLoadContext, installez le package NuGet System.Reflection.MetadataLoadContext. Il est pris en charge sur n’importe quel version cible de .Net Framework conforme à .NET Standard 2.0, par exemple .NET Core 2.0 ou .NET Framework 4.6.1.

Créer MetadataAssemblyResolver pour MetadataLoadContext

La création de MetadataLoadContext nécessite de fournir l’instance du MetadataAssemblyResolver. La méthode la plus simple pour en fournir une consiste à utiliser le PathAssemblyResolver, qui résout les assemblys de la collection donnée de chaînes de chemin d’accès d’assembly. Cette collection, outre les assemblys que vous souhaitez inspecter directement, doit également inclure toutes les dépendances nécessaires. Par exemple, pour lire l’attribut personnalisé situé dans un assembly externe, vous devez inclure cet assembly ou une exception sera levée. Dans la plupart des cas, vous devez inclure au moins l’assembly principal, autrement dit, l’assembly contenant des types système intégrés, comme System.Object. Le code suivant montre comment créer le PathAssemblyResolver à l’aide de la collection composée de l’assembly inspecté et de l’assembly principal du runtime actuel :

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

Si vous avez besoin d’accéder à tous les types BCL, vous pouvez inclure tous les assemblys runtime dans la collection. Le code suivant montre comment créer le PathAssemblyResolver à l’aide de la collection composée de l’assembly inspecté et de tous les assemblys du runtime actuel :

// 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);

Créer MetadataLoadContext

Pour créer le MetadataLoadContext, appelez son constructeur MetadataLoadContext(MetadataAssemblyResolver, String), en passant le MetadataAssemblyResolver précédemment créé comme premier paramètre et le nom de l’assembly principal comme second paramètre. Vous pouvez omettre le nom de l’assembly principal, auquel cas le constructeur tente d’utiliser des noms par défaut : « mscorlib », « System.Runtime » ou « netstandard ».

Une fois que vous avez créé le contexte, vous pouvez charger des assemblys dans celui-ci à l’aide de méthodes comme LoadFromAssemblyPath. Vous pouvez utiliser toutes les API de réflexion sur les assemblys chargés, sauf celles qui impliquent l’exécution du code. La méthode GetCustomAttributes implique l’exécution des constructeurs. Utilisez donc la méthode GetCustomAttributesData à la place lorsque vous devez examiner des attributs personnalisés dans le MetadataLoadContext.

L’exemple de code suivant crée MetadataLoadContext, charge l’assembly dans celui-ci et génère des attributs d’assembly dans la console :

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}");
        }
    }
}

Si vous devez tester des types dans MetadataLoadContext pour l’égalité ou l’assignabilité, utilisez uniquement les objets de type chargés dans ce contexte. Le fait de mélanger des types MetadataLoadContext avec des types runtime n’est pas pris en charge. Par exemple, supposez un type testedType dans MetadataLoadContext. Si vous devez tester si un autre type est assignable à partir de celui-ci, n’utilisez pas de code comme typeof(MyType).IsAssignableFrom(testedType). Utilisez du code comme celui-ci à la place :

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)}");
}

Exemple

Pour obtenir un exemple de code complet, consultez la section Inspecter le contenu de l’assembly à l’aide de l’exemple MetadataLoadContext.

Voir aussi