C# – Get types from assembly (reflection-only load)

You can get all types from an assembly by doing a reflection-only load. This allows you to read type info from metadata without running the typical errors associated with fully loading an assembly.

The way to do a reflection-only load is different in .NET Framework and .NET Core. I’ll show examples of how to do this in both.

.NET Framework – Use Assembly.ReflectionOnlyLoadFrom()

In a .NET Framework project, use Assembly.ReflectionOnlyLoadFrom(), like this:

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)

This outputs the following class name:

DatabaseHelperCode language: plaintext (plaintext)

.NET Core – Use MetadataReader or MetadataLoadContext

Assembly.ReflectionOnlyLoadFrom() doesn’t work in a .NET Core project. If you try to use it, you get the following runtime exception:

System.PlatformNotSupportedException: ReflectionOnly loading is not supported on this platform.

Instead, you can use System.Reflection.Metadata.MetadataReader or System.Reflection.MetadataLoadContext. I’ll show examples of how to use both below.

Use MetadataReader

You can use the System.Reflection.Metadata.MetadataReader class like this:

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)

This outputs all user-defined public concrete types in the assembly:

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

Note: This includes non-class types (like struct and enum). I wasn’t able to figure out a way using MetadataReader to determine if it was strictly a class or not.

This isn’t as nice and clean as using Assembly.ReflectionOnlyLoadFrom(), but it gets the job done.

Use System.Reflection.MetadataLoadContext

MetadataLoadContext does a reflection-only read of the assembly and gives you an Assembly object so you can use the reflection API.

First, you have to install the System.Reflection.MetadataLoadContext nuget package. You can install this with the following command in Package Manager Console (View > Other Windows > Package Manager Console):

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

Then you can use MetadataLoadContext to output the list of public type names in the assembly, like this:

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)

This outputs all the public type names:

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