C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
224: Reflecting Over Assemblies and Types
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.CreateInstanceto create the object and print:"Successfully registered service: [ClassName]". - Ensure that the undecorated class is ignored by your scanner.
There are no comments for now.