Premise
I've never wrestled much with "that's the way we do it". I can usually think my way through why something is the way it is and either accept it because the reasons hold up, or write it off and refactor until it's good. On a recent project something in my heart started arguing with my head, and my heart won.
Background
I found myself resolving collections of interfaces to carry out a bunch of common tasks. Simple enough to do, not especially intuitive, so here's an example. First, an interface and a few concrete implementations.
services.AddSingleton<IWorkItem, ConcreteWorkItem1>();
services.AddSingleton<IWorkItem, ConcreteWorkItem2>();
services.AddSingleton<IWorkItem, ConcreteWorkItem3>();
Now you can resolve them wherever you need them.
public sealed class SomeClass(IEnumerable<IWorkItem> workItems)
You get all of the concrete implementations, and then you do the work, which usually means a foreach.
foreach(var workItem in workItems)
{
workItem.DoWork();
}
The 'Problem'
In this project I have nearly 50 of these "WorkItems" and the number keeps growing. They all do business-valid things, essentially a bunch of independent and configurable calculations, and I'm registering every one of them by hand. This isn't really a problem. I've always subscribed to Occam's razor, and 50 registrations in a file is a thing every level of engineer can read without help, so that's what I should do. That IS the correct answer. But I felt drawn to the heart solution, the one where I don't have to remember to register anything.
var assembly = Assembly.GetAssembly(typeof(IWorkItem));
var types = assembly.GetTypes()
.Where(t => t.IsClass
&& !t.IsAbstract
&& t.GetInterfaces().Contains(typeof(IWorkItem)));
foreach (var type in types)
{
services.AddSingleton(typeof(IWorkItem), type);
}
And that's that. Write a new WorkItem, it gets registered, nobody has to think about it.
It is harder to understand, it's frightening if you aren't comfortable with reflection, and it will get you a raised eyebrow in code review. It also has a trapdoor, because Assembly.GetAssembly(typeof(IWorkItem)) scans the assembly the interface lives in. Move an implementation into a different project and it stops being registered, with no error and no warning, just a calculation that quietly no longer runs.
I know all that. I love it anyway, and I can't tell you why. It's staying.