C# – アセンブリをロードせずに、アセンブリで定義された型のリストを取得する

アセンブリを読み込まずにアセンブリから型情報を取得するには、次の 2 つの方法があります。

  • リフレクションのみ / メタデータの読み込み
  • ソース ファイルからパターンを検索します。

この記事では、アセンブリ内の型のリストを出力するための両方のアプローチを示します。

リフレクションのみ / メタデータの読み込み

アセンブリをロードしたくない理由はたくさんあります。おそらく、Assembly.LoadFrom() を使用してロードしようとしてエラーが発生している可能性があります。たとえば、依存関係の解決で問題が発生している可能性や、不適切な画像形式の例外が発生している可能性があります。

リフレクションのみのロードを実行するポイントは、アセンブリを完全にロードしようとする際に発生するすべての問題に遭遇することなく、メタデータ (定義された型など) を読み取ることができることです。

このコードを .NET Framework から実行するか、.NET Core から実行するかによって、実行方法が異なります。

.NET Framework – Assembly.ReflectionOnlyLoadFrom() を使用

.NET Framework プロジェクトからリフレクションのみの読み込みを行うには、次のように Assembly.ReflectionOnlyLoadFrom() を使用します。

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)

これにより、次のクラス名が出力されます:

DatabaseHelperCode language: plaintext (plaintext)

.NET コア

Assembly.ReflectionOnlyLoadFrom() は .NET Core プロジェクトでは使用できません。実行しようとすると、次の実行時例外が発生します:

代わりに、System.Reflection.Metadata.MetadataReader または System.Reflection.MetadataLoadContext を使用できます。両方のアプローチを以下に示します。

MetadataReader を使用

次のように System.Reflection.Metadata.MetadataReader クラスを使用できます:

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)

これにより、アセンブリ内のすべてのユーザー定義のパブリック具象型が出力されます:

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

注:これには非クラス型 (struct や enum など) が含まれます。 MetadataReader を使用して、それが厳密にクラスであるかどうかを判断する方法を理解できませんでした.

これは、Assembly.ReflectionOnlyLoadFrom() を使用するほどきれいではありませんが、仕事は完了します。

System.Reflection.MetadataLoadContext を使用

MetadataLoadContext はアセンブリのリフレクションのみの読み取りを行い、リフレクション API を使用できるように Assembly オブジェクトを提供します。

まず、System.Reflection.MetadataLoadContext nuget パッケージをインストールする必要があります。これは、パッケージ マネージャー コンソール ([表示]> [その他のウィンドウ]> [パッケージ マネージャー コンソール]) で次のコマンドを使用してインストールできます。 :

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

次に、MetadataLoadContext を使用して、次のように、アセンブリ内のパブリック タイプ名のリストを出力できます。

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)

これにより、すべての公開型名が出力されます:

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

アセンブリではなくソース ファイルを検索

プロジェクトのソース ファイルにアクセスできる場合、型情報のリストを取得するための代替オプションは、正規表現パターンのファイルを検索することです。これを行う 1 つの方法は、PowerShell を使用することです:

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

これにより、プロジェクトで定義されているすべてのパブリック クラス名が出力されます:

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)

これはヒューリスティックなアプローチです。示されている他のアプローチよりも単純ですが、誤検知を返す可能性があるため、精度も低くなります.たとえば、次のコメント アウトされた条件付きでコンパイルされたクラスなど、アセンブリでコンパイルさえされていないクラスを返します。

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

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

    }
#endif
Code language: C# (cs)

検索を実行すると、正規表現パターン「public class \w+」に一致するすべての文字列が返されます:

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)

コメント化/コンパイルされたクラス (強調表示) が含まれていることに注意してください。この情報をどのように使用しているかによって、これが問題になる場合とそうでない場合があります。