-
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
141: Practice Exercise: Building a Simple Plugin Loader with Reflection
Think of a plugin system like a universal power socket in your wall. The socket doesn't know if you're going to plug in a lamp, a vacuum cleaner, or a toaster. It doesn't care about the internal circuitry of the appliance; it only cares that the plug fits the holes and operates at the right voltage. As long as the appliance follows that "contract," the house provides the power, and the appliance does its job.
In C#, Reflection is how we build that socket. We define an interface (the socket shape), and then we use Reflection to look at a compiled DLL file (the appliance), check if it has a class that fits that interface, and if it does, we "plug it in" and run it—all without the main program ever having a hard-coded reference to that plugin project.
The Contract: Defining the Interface
Before we can load anything dynamically, we need a shared language. I usually put this in a separate, tiny class library project that both the main app and the plugins reference. If the main app doesn't know what the interface is, it can't cast the reflected type to anything useful.
public interface IPlugin
{
string Name { get; }
void Execute();
}
This is our "socket." Any class that implements IPlugin is now compatible with our loader.
The Magic Trick: Scanning the DLL
Here is where the actual Reflection happens. We aren't using new PluginClass() because we don't know the name of PluginClass at compile time. Instead, we load the assembly from a file path and sift through its types.
using System.Reflection;
// Load the assembly from the disk
Assembly pluginAssembly = Assembly.LoadFrom("MyCoolPlugin.dll");
// Find all types that implement IPlugin and aren't abstract
var pluginTypes = pluginAssembly.GetTypes()
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
foreach (var type in pluginTypes)
{
// Create an instance of the type
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
Console.WriteLine($"Loaded: {plugin.Name}");
plugin.Execute();
}
I'll let you in on a secret: Activator.CreateInstance is the heavy lifter here. It looks at the type we found via Reflection and tells the CLR, "I don't know exactly what this is, but I know it has a constructor. Go ahead and make one."
Tying it Together in the Main Loop
In a real-world scenario, you wouldn't just load one hard-coded DLL. You'd probably point your app at a /plugins folder and loop through every .dll file found there. This allows you to add new functionality to your software just by dropping a file into a folder—no recompiling the main executable required.
One thing to keep in mind: Reflection is slower than direct calls. If you're calling a plugin method ten thousand times a second in a tight loop, you'll feel the performance hit. But for loading a module at startup? It's a negligible cost for a massive gain in flexibility.
📋 Practical Task
Build a Dynamic Text-Processing Pipeline
Your goal is to create a system where the main application can load various "Text Processors" from external DLLs to transform a string of text.
Requirements:
- Create an interface called
ITextProcessorwith a methodstring Process(string input). - Create a separate Class Library project that implements
ITextProcessor. Create at least two classes in this library: one that converts text to UPPERCASE and one that reverses the string. - In your main Console Application, implement a loader that:
- Scans a specific folder for
.dllfiles. - Uses Reflection to find all classes implementing
ITextProcessor. - Instantiates them and passes a sample string (e.g., "Hello Reflection!") through each processor, printing the result of each to the console.
- Scans a specific folder for
Pro Tip: Make sure your plugin DLL is actually copied to the output folder of your main application, or provide the absolute path to the DLL in your Assembly.LoadFrom call, otherwise you'll run into FileNotFoundException.
There are no comments for now.