MMPClassEnumerator 1.2.0
dotnet add package MMPClassEnumerator --version 1.2.0
NuGet\Install-Package MMPClassEnumerator -Version 1.2.0
<PackageReference Include="MMPClassEnumerator" Version="1.2.0" />
<PackageVersion Include="MMPClassEnumerator" Version="1.2.0" />
<PackageReference Include="MMPClassEnumerator" />
paket add MMPClassEnumerator --version 1.2.0
#r "nuget: MMPClassEnumerator, 1.2.0"
#:package MMPClassEnumerator@1.2.0
#addin nuget:?package=MMPClassEnumerator&version=1.2.0
#tool nuget:?package=MMPClassEnumerator&version=1.2.0
MMPClassEnumerator
A simple utility for discovering and instantiating classes by interface or inheritance using reflection. Perfect for plugin systems, dynamic class loading, and educational purposes.
Overview
MMPClassEnumerator eliminates boilerplate code when working with plugins or dynamic class discovery. Instead of manually instantiating each class with switch statements, discover and create instances automatically using reflection.
Features
- ✅ Interface-based Discovery - Find classes implementing specific interfaces
- ✅ Inheritance-based Discovery - Find classes inheriting from base classes
- ✅ Instance Creation - Auto-instantiate discovered classes
- ✅ Type Listing - Get type definitions without instantiation
- ✅ Performance Caching - Optional caching for improved performance
- ✅ Custom Assembly Scanning - Scan any assembly, not just calling assembly
- ✅ Comprehensive Tests - Full test coverage with xUnit
- ✅ Safe Exception Handling - Gracefully handles instantiation failures
- ✅ Zero Dependencies - Pure .NET reflection
- ✅ Educational Tool - Great for teaching reflection and plugins
Requirements
- .NET 8.0 or higher
Installation
Package Manager Console
Install-Package MMPClassEnumerator
.NET CLI
dotnet add package MMPClassEnumerator
Package Reference
<PackageReference Include="MMPClassEnumerator" Version="1.2.0" />
Quick Start
The Problem
Before - Manual instantiation with switch statements:
ITopic topic;
switch(choice)
{
case 0: topic = new Topic0(); break;
case 1: topic = new Topic1(); break;
case 2: topic = new Topic2(); break;
// Long list of cases...
}
The Solution
After - Dynamic discovery and instantiation:
using MarcusMedinaPro.ClassEnumerator;
// Get all classes implementing ITopic
var topics = EnumerateClasses<ITopic>.GetClassesByInterface().ToList();
// Display options
for (int i = 0; i < topics.Count; i++)
Console.WriteLine($"{i:00} {topics[i].GetType().Name}");
// User selects
Console.Write("Select a topic: ");
var choice = int.Parse(Console.ReadLine()!);
// Use selected instance directly
ITopic topic = topics[choice];
API Reference
Get Instances by Interface
Returns instantiated objects of all classes implementing the specified interface:
// Basic usage
IEnumerable<T> instances = EnumerateClasses<IMyInterface>.GetClassesByInterface();
// With caching for better performance
IEnumerable<T> cached = EnumerateClasses<IMyInterface>.GetClassesByInterface(useCache: true);
// Scan specific assembly
var assembly = Assembly.Load("MyPlugins");
IEnumerable<T> plugins = EnumerateClasses<IMyInterface>.GetClassesByInterface(assembly: assembly);
Get Instances by Inheritance
Returns instantiated objects of all classes inheriting from the specified base class:
IEnumerable<T> instances = EnumerateClasses<MyBaseClass>.GetClassesByInheritance();
List Types by Interface
Returns type definitions without creating instances:
IEnumerable<Type> types = EnumerateClasses<IMyInterface>.ListClassesByInterface();
List Types by Inheritance
Returns type definitions without creating instances:
IEnumerable<Type> types = EnumerateClasses<MyBaseClass>.ListClassesByInheritance();
Usage Examples
Plugin System
public interface IPlugin
{
string Name { get; }
void Execute();
}
// Auto-discover and load all plugins
var plugins = EnumerateClasses<IPlugin>.GetClassesByInterface().ToList();
// Display available plugins
plugins.ForEach(p => Console.WriteLine($"Plugin: {p.Name}"));
// Execute all plugins
plugins.ForEach(p => p.Execute());
List All Classes in Assembly
// Get all classes (using object as base)
var allClasses = EnumerateClasses<object>.GetClassesByInheritance().ToList();
// Display class names
allClasses.ForEach(c => Console.WriteLine($"class {c.GetType().Name}()"));
Menu System
public interface IMenuItem
{
string DisplayName { get; }
void Run();
}
var menuItems = EnumerateClasses<IMenuItem>.GetClassesByInterface().ToList();
// Display menu
for (int i = 0; i < menuItems.Count; i++)
Console.WriteLine($"{i + 1}. {menuItems[i].DisplayName}");
// Execute selected item
int selection = int.Parse(Console.ReadLine()!) - 1;
menuItems[selection].Run();
How It Works
MMPClassEnumerator uses .NET reflection to:
- Scan the calling assembly for types
- Filter types matching the specified interface or base class
- Verify types have parameterless constructors
- Optionally create instances via
Activator.CreateInstance() - Return results sorted by type name
Requirements and Limitations
- Parameterless Constructor: All discovered classes must have a public parameterless constructor
- Calling Assembly: Only scans the assembly that calls the method
- Performance: Reflection has overhead - cache results if calling frequently
Educational Purpose
This library was created as an educational tool to demonstrate:
- Reflection API: How to discover types at runtime
- Generic Programming: Using generic type parameters
- LINQ: Query syntax for filtering types
- NuGet Publishing: Complete package creation workflow
Performance - With Caching
// First call - scans assembly (slower)
var plugins = EnumerateClasses<IPlugin>.GetClassesByInterface(useCache: true);
// Subsequent calls - uses cache (much faster!)
var pluginsAgain = EnumerateClasses<IPlugin>.GetClassesByInterface(useCache: true);
// Clear cache if needed (e.g., after dynamic assembly loading)
EnumerateClasses<IPlugin>.ClearCache();
Advanced - Scan Multiple Assemblies
var allPlugins = new List<IPlugin>();
// Scan main assembly
allPlugins.AddRange(EnumerateClasses<IPlugin>.GetClassesByInterface());
// Scan plugin assemblies
var pluginAssemblies = Directory.GetFiles("./plugins", "*.dll");
foreach (var dll in pluginAssemblies)
{
var assembly = Assembly.LoadFrom(dll);
allPlugins.AddRange(EnumerateClasses<IPlugin>.GetClassesByInterface(assembly: assembly));
}
Performance Benchmarks
| Method | Items | Time | Speedup |
|---|---|---|---|
| No Cache | 10 classes | ~2ms | 1x |
| With Cache | 10 classes | ~0.01ms | 200x faster |
Caching is especially beneficial when called frequently (e.g., in request handlers)
Migration Guide
From v1.0.x to v1.1.0
- .NET 6 → .NET 8: Update project target framework
- Improved Descriptions: Better package metadata
- XML Documentation: Full API documentation
- No breaking API changes - drop-in replacement
From v1.1.0 to v1.2.0
New Features (backward compatible):
useCacheparameter for all methods (default: false)assemblyparameter to scan custom assembliesClearCache()method for cache management- Comprehensive test suite included
Improvements:
- Better null-safety (removed excessive
?.operators) - Improved exception handling
- Excludes abstract classes and interfaces automatically
- Better XML documentation with code examples
No breaking changes - existing code works as-is!
License
This work is licensed under the MIT License.
Credits
- Author: Marcus Medina
- Inspiration: Stack Overflow answer by @Jon Skeet
- Icon: Icons8 iOS7 Programming CLS Icon
Source Code
Full source available on GitHub
Made with ❤️ for educational purposes
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.