C# – Get a list of types defined in an assembly without loading it

There are two ways to get type information from an assembly without loading it:

  • Reflection-only / metadata load.
  • Search through the source files for a pattern.

In this article, I’ll show both approaches for outputting a list of types in an assembly.

Reflection-only / metadata load

There could be many reasons why you wouldn’t want to load the assembly. Perhaps you’re running into errors trying to load it using Assembly.LoadFrom(). For example, you could be having problems resolving dependencies, or perhaps you’re getting the bad image format exception.

The point of doing a reflection-only load is that you can read metadata (such as the defined types) without running into all of the problems that come with trying to fully load the assembly.

This is done differently depending on if you’re executing this code from .NET Framework or .NET Core.

.NET Framework – Use Assembly.ReflectionOnlyLoadFrom()

To do a reflection-only load from 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:

DatabaseHelper
Code language: plaintext (plaintext)

.NET Core

You can’t use Assembly.ReflectionOnlyLoadFrom() in a .NET Core project. If you try, you’ll 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 how both approaches 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 RecipesController
Code 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 RecipesController
Code language: plaintext (plaintext)

Search the source files instead of the assembly

If you have access to the project’s source files, then an alternative option for getting a list of type info is to search through the files for a regex pattern. One way to do that is by using PowerShell:

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

This outputs all public class name’s defined in the project:

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

This is a heuristic approach. It’s simpler than the other approaches shown, but it’s also less accurate since it can return false positives. For example, it would return classes that aren’t even compiled in the assembly, such as the following commented out and conditionally compiled classes:

/* [Route("[controller]")] [ApiController] public class WeatherController : ControllerBase { } */ #if MOVIES [Route("[controller]")] [ApiController] public class MoviesController : ControllerBase { } #endif
Code language: C# (cs)

Running the search would return all of the strings match the regex pattern “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)

Notice that it included the commented / compiled out classes (highlighted). This may or may not be a problem depending on how you’re using this information.

Leave a Comment