reflection
C#
namespace
programming
.NET

Getting all types in a namespace via reflection

Master System Design with Codemia

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

Introduction

In .NET, reflection lets you inspect assemblies and types at runtime, which makes it possible to list classes, interfaces, and other types that belong to a given namespace. The main catch is that namespaces are only logical groupings, not runtime containers, so the usual pattern is to load an assembly, enumerate its types, and then filter by the Namespace property.

Start From an Assembly, Not From the Namespace

Reflection works against assemblies. A namespace does not exist as a standalone runtime object you can query directly.

csharp
1using System;
2using System.Linq;
3using System.Reflection;
4
5Assembly assembly = typeof(string).Assembly;
6
7foreach (Type type in assembly.GetTypes())
8{
9    Console.WriteLine(type.FullName);
10}

That gives every loadable type in the assembly. From there, filtering by namespace is straightforward.

Filter Types by Namespace Name

If you want only the types in one exact namespace, compare type.Namespace.

csharp
1using System;
2using System.Linq;
3using System.Reflection;
4
5Assembly assembly = Assembly.GetExecutingAssembly();
6string targetNamespace = "MyApp.Services";
7
8Type[] matchingTypes = assembly.GetTypes()
9    .Where(t => t.Namespace == targetNamespace)
10    .ToArray();
11
12foreach (Type type in matchingTypes)
13{
14    Console.WriteLine(type.Name);
15}

This matches only types whose namespace is exactly MyApp.Services. It will not include types in nested namespaces such as MyApp.Services.Internal.

Include Nested Namespaces When Needed

Sometimes you want everything under a namespace prefix rather than one exact namespace.

csharp
1using System;
2using System.Linq;
3using System.Reflection;
4
5Assembly assembly = Assembly.GetExecutingAssembly();
6string rootNamespace = "MyApp.Services";
7
8Type[] matchingTypes = assembly.GetTypes()
9    .Where(t => t.Namespace != null &&
10                (t.Namespace == rootNamespace || t.Namespace.StartsWith(rootNamespace + ".")))
11    .ToArray();
12
13foreach (Type type in matchingTypes)
14{
15    Console.WriteLine(type.FullName);
16}

That pattern is useful when a feature area is spread across sub-namespaces and you want them all in one reflection scan.

Handle ReflectionTypeLoadException

Assembly.GetTypes() can throw ReflectionTypeLoadException if some referenced types cannot be loaded. That is common in plugin systems or partially available dependency graphs. When that happens, use the successfully loaded types from the exception.

csharp
1using System;
2using System.Linq;
3using System.Reflection;
4
5Type[] GetLoadableTypes(Assembly assembly)
6{
7    try
8    {
9        return assembly.GetTypes();
10    }
11    catch (ReflectionTypeLoadException ex)
12    {
13        return ex.Types.Where(t => t != null).ToArray()!;
14    }
15}

This makes reflection code more robust in real applications where assembly loading can be messy.

Decide Which Kinds of Types You Actually Want

An assembly can contain classes, interfaces, enums, delegates, nested types, compiler-generated types, and more. Often the real requirement is narrower than "all types."

csharp
1using System;
2using System.Linq;
3using System.Reflection;
4
5Assembly assembly = Assembly.GetExecutingAssembly();
6string targetNamespace = "MyApp.Handlers";
7
8Type[] handlers = GetLoadableTypes(assembly)
9    .Where(t => t.Namespace == targetNamespace)
10    .Where(t => t.IsClass && !t.IsAbstract)
11    .ToArray();
12
13foreach (Type handler in handlers)
14{
15    Console.WriteLine(handler.FullName);
16}

Filtering early for concrete classes or assignable interfaces is often more useful than dumping every possible type name.

Reflection Is Powerful, but Use It Deliberately

Common reasons for this pattern include plugin discovery, handler registration, serialization metadata, and convention-based dependency injection. Those are valid use cases, but reflection still has a cost. It is slower and more fragile than compile-time wiring, so it should solve a real problem rather than being used out of habit.

A good rule is to keep reflection near application startup or in explicit discovery steps rather than on hot execution paths.

Common Pitfalls

  • Expecting a namespace itself to be a queryable runtime object.
  • Using exact namespace comparison when nested namespaces should have been included.
  • Forgetting that GetTypes() can throw ReflectionTypeLoadException.
  • Returning every type when the real requirement was only concrete classes or only types implementing an interface.
  • Doing repeated reflection scans in performance-sensitive code paths.

Summary

  • Reflection starts from assemblies, not from namespaces directly.
  • Filter Assembly.GetTypes() results by the Namespace property.
  • Use exact comparison for one namespace or prefix matching for nested namespaces.
  • Handle ReflectionTypeLoadException when working with imperfect assembly graphs.
  • Narrow the results to the type kinds you actually need instead of returning everything.

Course illustration
Course illustration

All Rights Reserved.