Skip to Content
Course content

224: Reflecting Over Assemblies and Types

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a project the other day where I needed to implement a plugin system. I didn't want to manually add every new plugin class to a giant switch statement or a hard-coded list every time I added a feature. It felt wrong. I wanted the program to just "look" at itself, find everything that looked like a plugin, and load it automatically. That's where Reflection comes in—specifically, reflecting over assemblies and types.

Looking under the hood of the current project

First, I needed to figure out how to get a list of every class available to me. I started with the assembly. In C#, an assembly is essentially the compiled .dll or .exe. I decided to grab the assembly where my main program lives.

var assembly = typeof(Program).Assembly;
var allTypes = assembly.GetTypes();

foreach (var t in allTypes)
{
    Console.WriteLine(t.FullName);
}

When I ran this, it was a mess. I saw Program, System.Object, and dozens of internal .NET types I didn't care about. It worked, but it was too noisy. I don't want every type; I only want the ones that are actually intended to be plugins.

Cutting through the clutter

To fix this, I defined a simple interface called IPlugin. Any class that implements this interface is, by definition, a plugin. Now, instead of just printing every type, I can filter the list. I'll use LINQ here because it makes the intent much clearer.

public interface IPlugin { void Execute(); }

public class SavePlugin : IPlugin { public void Execute() => Console.WriteLine("Saving..."); }
public class LoadPlugin : IPlugin { public void Execute() => Console.WriteLine("Loading..."); }

// ... inside my main method ...
var pluginTypes = assembly.GetTypes()
    .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);

foreach (var type in pluginTypes)
{
    Console.WriteLine($"Found a plugin: {type.Name}");
}

I added !t.IsInterface and !t.IsAbstract because IsAssignableFrom returns true for the interface itself and any abstract base classes. I only want concrete classes that I can actually instantiate. Now the output is clean: just SavePlugin and LoadPlugin.

Venturing into other assemblies

But here's the catch: what if the plugins aren't in the main project? What if they are in a separate Plugins.dll file that I drop into a folder? typeof(Program).Assembly won't find those because they aren't in the same compiled unit.

I tried using Assembly.LoadFrom("Plugins.dll"). This felt more flexible. I can point the app to a specific file path, load that assembly into memory, and then perform the same GetTypes() scan I did before.

var externalAssembly = Assembly.LoadFrom("Plugins.dll");
var externalPlugins = externalAssembly.GetTypes()
    .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface);

One thing to watch out for: if Plugins.dll depends on other libraries that aren't in the app's execution folder, GetTypes() will throw a ReflectionTypeLoadException. It's a bit of a pain, but it's C#'s way of saying, "I see the type, but I can't find the stuff it needs to function."

Bringing those types to life

Finding the Type object is great, but a Type is just a description. I can't call Execute() on a Type object. I need an actual instance of that class. Since I don't know the class names at compile time, I can't use the new keyword.

This is where Activator.CreateInstance comes in. It's the magic wand of Reflection.

foreach (var type in pluginTypes)
{
    // Create an instance of the type and cast it to our interface
    var plugin = (IPlugin)Activator.CreateInstance(type);
    plugin.Execute();
}

Now the loop is complete: I scan the assembly, filter for the interface, instantiate the objects on the fly, and execute them. I've essentially built a micro-version of how many Dependency Injection containers work under the hood.




📋 Practical Task

Build an Automated Attribute-Based Service Registry

Instead of scanning for an interface, your task is to scan for a custom Attribute. This is a common pattern for marking classes that should be automatically registered in a system without requiring them to inherit from a specific base class.

Requirements:

  • Create a custom attribute class named [ServiceAttribute].
  • Create three different classes (e.g., EmailService, LoggingService, DatabaseService). Decorate two of them with [Service] and leave one undecorated.
  • Write a method that scans the current assembly and finds all classes decorated with [ServiceAttribute].
  • For each discovered service, use Activator.CreateInstance to create the object and print: "Successfully registered service: [ClassName]".
  • Ensure that the undecorated class is ignored by your scanner.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.