C#
assembly
classes
programming
.NET

C List All Classes in Assembly

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Listing all classes in a .NET assembly is a reflection task. The basic approach is to obtain an Assembly, call GetTypes(), and filter the result with Type.IsClass, but the details matter because "all classes" may include non-public, nested, and abstract types.

Start with the Right Assembly

If the assembly is part of the current application, you can obtain it from a known type or from the executing assembly:

csharp
1using System;
2using System.Reflection;
3
4Assembly assembly = Assembly.GetExecutingAssembly();
5Console.WriteLine(assembly.FullName);

If the assembly is an external file, load it explicitly:

csharp
using System.Reflection;

Assembly assembly = Assembly.LoadFrom("MyLibrary.dll");

The source matters because external assemblies can fail to load some dependent types.

List All Class Types

The usual reflection pattern is:

csharp
1using System;
2using System.Linq;
3using System.Reflection;
4
5Assembly assembly = Assembly.GetExecutingAssembly();
6
7var classes = assembly
8    .GetTypes()
9    .Where(t => t.IsClass)
10    .OrderBy(t => t.FullName);
11
12foreach (var type in classes)
13{
14    Console.WriteLine(type.FullName);
15}

This includes:

  • public classes
  • internal classes
  • abstract classes
  • nested classes

So if you only want a smaller subset, filter further.

Filter for Concrete Public Classes

If your goal is plugin discovery or instantiable application types, you may want only public, non-abstract, top-level classes:

csharp
1var publicConcreteClasses = assembly
2    .GetTypes()
3    .Where(t => t.IsClass && t.IsPublic && !t.IsAbstract && !t.IsNested)
4    .OrderBy(t => t.FullName);

This is a much narrower definition than "all classes," and it is often the one people actually want.

Filter by Interface or Base Class

Reflection gets more useful when you scan for a specific contract rather than every class:

csharp
1using System;
2using System.Linq;
3using System.Reflection;
4
5public interface IPlugin { }
6
7Assembly assembly = Assembly.GetExecutingAssembly();
8
9var pluginTypes = assembly
10    .GetTypes()
11    .Where(t => t.IsClass && !t.IsAbstract && typeof(IPlugin).IsAssignableFrom(t));

This pattern is common in:

  • plugin systems
  • dependency injection registration
  • handler discovery
  • command or job scanning

Handle ReflectionTypeLoadException

GetTypes() can fail if some referenced dependencies are missing or incompatible. In that case, .NET may throw ReflectionTypeLoadException.

A defensive version looks like this:

csharp
1using System;
2using System.Linq;
3using System.Reflection;
4
5try
6{
7    foreach (var type in assembly.GetTypes().Where(t => t.IsClass))
8    {
9        Console.WriteLine(type.FullName);
10    }
11}
12catch (ReflectionTypeLoadException ex)
13{
14    foreach (var type in ex.Types.Where(t => t != null && t.IsClass))
15    {
16        Console.WriteLine(type!.FullName);
17    }
18
19    foreach (var loaderException in ex.LoaderExceptions)
20    {
21        Console.Error.WriteLine(loaderException?.Message);
22    }
23}

This lets you recover partial results while still surfacing the dependency problem.

Be Deliberate About Scope

Sometimes developers say "list all classes in assembly" when they really mean one of these:

  • list all classes in the current assembly
  • list all classes in all loaded assemblies
  • list all implementations of a certain interface

Those are different tasks. Being explicit about the scope keeps the reflection code simpler and avoids scanning far more than you need.

Performance and Startup Considerations

Reflection is powerful, but repeated broad scans can be expensive. If you perform assembly scanning at startup, cache the results instead of calling GetTypes() repeatedly on every request or every command execution.

This is especially important in large applications or plugin systems.

Common Pitfalls

  • Assuming GetTypes() returns only public types. It does not.
  • Filtering with IsClass and forgetting that abstract classes still match.
  • Loading the wrong assembly file and debugging the wrong binary output.
  • Ignoring ReflectionTypeLoadException when external dependencies are missing.
  • Scanning every loaded assembly when you only need one known assembly or one known interface contract.

Summary

  • Use reflection to obtain an Assembly and call GetTypes().
  • Filter with Type.IsClass to list classes, then narrow further if needed.
  • Decide whether you want all classes, only public classes, or only concrete implementations of a contract.
  • Handle ReflectionTypeLoadException when external assemblies may have missing dependencies.
  • Be precise about which assembly you are scanning and why.

Course illustration
Course illustration

All Rights Reserved.