C# – Rufen Sie eine Liste der in einer Assembly definierten Typen ab, ohne sie zu laden

C# – Rufen Sie eine Liste der in einer Assembly definierten Typen ab, ohne sie zu laden

Es gibt zwei Möglichkeiten, Typinformationen aus einer Assembly zu erhalten, ohne sie zu laden:

  • Nur Reflexion/Laden von Metadaten.
  • Durchsuchen Sie die Quelldateien nach einem Muster.

In diesem Artikel zeige ich beide Ansätze zur Ausgabe einer Liste von Typen in einer Assembly.

Nur Reflexion/Laden von Metadaten

Es kann viele Gründe geben, warum Sie die Assembly nicht laden möchten. Vielleicht treten beim Versuch, es mit Assembly.LoadFrom() zu laden, Fehler auf. Sie könnten beispielsweise Probleme beim Auflösen von Abhängigkeiten haben oder vielleicht erhalten Sie die Ausnahme wegen eines schlechten Bildformats.

Der Punkt beim reinen Reflektionsladen besteht darin, dass Sie Metadaten (z. B. die definierten Typen) lesen können, ohne auf all die Probleme zu stoßen, die beim Versuch auftreten, die Assembly vollständig zu laden.

Dies geschieht unterschiedlich, je nachdem, ob Sie diesen Code von .NET Framework oder .NET Core ausführen.

.NET Framework – Verwenden Sie Assembly.ReflectionOnlyLoadFrom()

Verwenden Sie Assembly.ReflectionOnlyLoadFrom() wie folgt, um einen Reflection-only-Ladevorgang aus einem .NET Framework-Projekt auszuführen:

var assemblyPath = @"D:\Projects\TestLib\bin\Debug\TestLib.dll";

var assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);

foreach (var type in assembly.GetTypes())
{
	Console.WriteLine(type.Name);
}
Code language: C# (cs)

Dies gibt den folgenden Klassennamen aus:

DatabaseHelperCode language: plaintext (plaintext)

.NET Core

Sie können Assembly.ReflectionOnlyLoadFrom() nicht in einem .NET Core-Projekt verwenden. Wenn Sie es versuchen, erhalten Sie die folgende Laufzeitausnahme:

Stattdessen können Sie System.Reflection.Metadata.MetadataReader oder System.Reflection.MetadataLoadContext verwenden. Ich werde unten zeigen, wie beide Ansätze aussehen.

MetadataReader verwenden

Sie können die System.Reflection.Metadata.MetadataReader-Klasse wie folgt verwenden:

using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;

var assemblyPath = @"D:\Projects\aspdotnet-background-dblogger\bin\Debug\net5.0\BackgroundDatabaseLogger.dll";

using (var sr = new StreamReader(assemblyPath))
{
	using (var portableExecutableReader = new PEReader(sr.BaseStream))
	{
		var metadataReader = portableExecutableReader.GetMetadataReader();

		foreach (var typeDefHandle in metadataReader.TypeDefinitions)
		{
			var typeDef = metadataReader.GetTypeDefinition(typeDefHandle);

			if (string.IsNullOrEmpty(metadataReader.GetString(typeDef.Namespace)))
				continue; //if it's namespace is blank, it's not a user-defined type

			if (typeDef.Attributes.HasFlag(TypeAttributes.Abstract) || !typeDef.Attributes.HasFlag(TypeAttributes.Public))
				continue; //Not a public concrete type

			Console.WriteLine(metadataReader.GetString(typeDef.Name));
		}
	}
}
Code language: C# (cs)

Dies gibt alle benutzerdefinierten öffentlichen konkreten Typen in der Assembly aus:

Program
Program
Startup
DatabaseLoggerService
TestEnum
TestStruct
LogMessage
LogRepository
RecipesControllerCode language: plaintext (plaintext)

Hinweis:Dies schließt Nicht-Klassen-Typen (wie Struct und Enum) ein. Ich konnte keinen Weg finden, MetadataReader zu verwenden, um festzustellen, ob es sich ausschließlich um eine Klasse handelt oder nicht.

Das ist nicht so schön und sauber wie die Verwendung von Assembly.ReflectionOnlyLoadFrom(), aber es erledigt die Arbeit.

System.Reflection.MetadataLoadContext verwenden

MetadataLoadContext liest die Assembly nur auf Reflexion und gibt Ihnen ein Assembly-Objekt, damit Sie die Reflexions-API verwenden können.

Zuerst müssen Sie das Nuget-Paket System.Reflection.MetadataLoadContext installieren. Sie können dies mit dem folgenden Befehl in der Paket-Manager-Konsole (Ansicht> Andere Fenster> Paket-Manager-Konsole) installieren :

Install-Package System.Reflection.MetadataLoadContext
Code language: PowerShell (powershell)

Dann können Sie MetadataLoadContext verwenden, um die Liste der öffentlichen Typnamen in der Assembly wie folgt auszugeben:

using System.Reflection;
using System.Runtime.InteropServices;

var assemblyPath = @"D:\Projects\aspdotnet-background-dblogger\bin\Debug\net5.0\BackgroundDatabaseLogger.dll";

var resolver = new PathAssemblyResolver(new List<string>(Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll")) 
{
	assemblyPath
});

using (var metadataContext = new MetadataLoadContext(resolver))
{
	Assembly assembly = metadataContext.LoadFromAssemblyPath(assemblyPath);

	foreach (var type in assembly.GetTypes())
	{
		if (type.IsPublic)
		{
			Console.WriteLine(type.Name);
		}
	}
}
Code language: C# (cs)

Dies gibt alle öffentlichen Typnamen aus:

Program
Startup
DatabaseLoggerService
ILoggerService
TestEnum
TestStruct
ILogRepository
LogMessage
LogRepository
RecipesControllerCode language: plaintext (plaintext)

Durchsuchen Sie die Quelldateien anstelle der Assembly

Wenn Sie Zugriff auf die Quelldateien des Projekts haben, besteht eine alternative Option zum Abrufen einer Liste mit Typinformationen darin, die Dateien nach einem Regex-Muster zu durchsuchen. Eine Möglichkeit dazu ist die Verwendung von PowerShell:

 ls -r "D:\Projects\aspdotnet-background-dblogger\" | select-string -pattern "public class \w+" -raw
Code language: PowerShell (powershell)

Dies gibt alle im Projekt definierten öffentlichen Klassennamen aus:

public class Program
public class Startup
public class RecipesController : ControllerBase
public class DatabaseLoggerService : BackgroundService, ILoggerService
public class LogMessage
public class LogRepository : ILogRepositoryCode language: plaintext (plaintext)

Dies ist ein heuristischer Ansatz. Es ist einfacher als die anderen gezeigten Ansätze, aber es ist auch weniger genau, da es falsche positive Ergebnisse zurückgeben kann. Beispielsweise würde es Klassen zurückgeben, die nicht einmal in der Assembly kompiliert sind, wie die folgenden auskommentierten und bedingt kompilierten Klassen:

/*
    [Route("[controller]")]
    [ApiController]
    public class WeatherController : ControllerBase
    {
       
    }
*/

#if MOVIES
    [Route("[controller]")]
    [ApiController]
    public class MoviesController : ControllerBase
    {

    }
#endif
Code language: C# (cs)

Die Ausführung der Suche würde alle Zeichenfolgen zurückgeben, die mit dem Regex-Muster „öffentliche Klasse \w+“ übereinstimmen:

public class Program
public class Startup
public class RecipesController : ControllerBase
public class WeatherController : ControllerBase
public class MoviesController : ControllerBase
public class DatabaseLoggerService : BackgroundService, ILoggerService
public class LogMessage
public class LogRepository : ILogRepository
Code language: plaintext (plaintext)

Beachten Sie, dass es die kommentierten / kompilierten Klassen (hervorgehoben) enthielt. Je nachdem, wie Sie diese Informationen verwenden, kann dies ein Problem sein oder auch nicht.