In general - how to find all derived classes
Reducing your question to its core, we get to the following:
How can I pre-emptively write code for a base class which will automatically take into account any derived classes I make in the future?
The rephrasing should help you realize that this is not going to be easy. How could you pre-emptively reference something that does not yet exist?
This immediately starts raising the reflection red flag in my head.
Note that I call it a red flag because reflection is one of those things that could be indicative of bad design. But there are valid use cases for it, and IMO yours is a valid use case.
How could you pre-emptively reference something that does not yet exist? If you think about it, this is no different from how you can reference a file on the file system before that file is created. There are some ways to do this:
- Require that the file is created in a specific directory that the application will monitor (this is an analogy for strict namespaces)
- Require that the file name fits a predetermined pattern and search the entire file system for files that match this pattern (this is an analogy for class name conventions)
- Require that the file has the correct filetype and search the entire file system for files of this type (this is an analogy for finding all derived classes of a given base class/interface)
The third bullet point clearly relates to what you're trying to do here.
I'm skipping the implementation of this reflection methodology because there are complicated consequences which I'm addressing in the next part.
In general - how to find classes related to the derived classes
So you've written a reflection method which finds all derivations of Request
. Great! But for every found type, how are you going to find its repository?
Option 1 - Since you've opened the reflection door, you can freely use it. For each found type (let's call it foundType
), you could use reflection to find a class named {foundType}Repository
and expect it to have a specific method named GetAll{foundType}s
and call that.
But you should see that this is a really bad way to handle it (this is exactly why I consider reflection to be a red flag for good approaches). It's stringly typed and it's going to lead to many bugs whenever a developer doesn't conform to the naming standards.
It would be a lot more type safe if all these repositories had a shared inheritance, so you wouldn't need to guess that they implement the GetAll{foundType}s
method.
Option 2 - If we're going the type safe route, the quickest solution would be to have generic repositories, something along the line of:
public class RequestRepository<TRequest> where TRequest : Request
{
public IEnumerable<TRequest> GetAll() { /* ... */ }
}
Because then, for every foundType
you can simply instantiate a RequestRepository<foundType>
and use that. This actually handles everything nicely!
However! If all request repositories can be handled using the exact same generic logic, then they really aren't different from each other. If they were different, the different requests would be handled differently at least in some way. You'd expect there to be derived versions of RequestRepository<TRequest>
(e.g. FooRequestRepository : RequestRepository<FooRequest>
) to account for these different behaviors, and these derived classes would be ignored when you instantiate a RequestRepository<foundType>
.
If this generic approach would work, that would mean that a simpler RequestRepository
which returns all Request
objects (regardless of derived type) would work just as well, which renders your question moot. I can only infer that this is therefore not a viable approach, and that you cannot create a generic RequestRepository<TRequest>
to handle all the derived request types.
So how to you find the matching repository for each request type?
Well, this is the same answer as when we asked how to find each request type.
- Strict namespaces
- Class name conventions
- Finding all derived classes of a given base class/interface
As you can see, the problem outset is the same, and you can start repeating the same solution.
In conclusion
Can this be done? Sure. Reflection is a powerful tool, and it's technically possible to do all this.
However, the more you look at it, the more you realize that you're either going to end up with bad practice, stringly typed, naming convention based coding; or you're going to end up realizing that your codebase can be reduced to something dramatically simpler, which defeats the purpose of your implementation.
As the complexity of your codebase increases, the odds of this still being a good idea dramatically decrease.
This isn't a technical limitation, but rather a bad expectation from the developer. You're expecting your codebase to auto-reference classes that you never explicitly referenced. While not impossible, this automated behavior invariably leads to enforcing arbitrary naming/reference standards and unexpected automated behaviors, which very easily start flying in the face of good practice.
However, there is a way...
I happen to be a big fan of Entity Framework, and the exact feature you're asking for is actually something that Entity Framework allows you to do. My experience is with Code First, Table-Per-Type inheritance. I would expect that this also works for the other inheritance strategies, but I haven't yet used them so I can't be sure.
In short, you set it up as follows:
public abstract class Request
{
public int Id { get; set; }
public string GeneralRequestValue { get; set; }
}
public class FooRequest : Request
{
public string SpecificFooValue { get; set; }
}
public class BarRequest : Request
{
public string SpecificBarValue { get; set; }
}
public class DemoContext : DbContext
{
public DbSet<Request> Requests { get; set; }
}
While you only reference one DbSet<>
, EF will actually make three tables for you:
- Requests
- FooRequests
- BarRequests
Every FooRequest and BarRequest will have their own entry in the Requests table. EF is aware of the shared inheritance and its query methodology allows for inheritance-related lookups. The interesting thing here is that this allows you to do exactly what you want, out of the box:
var fooRequests = db.Set<FooRequest>().ToList();
var barRequests = db.Set<BarRequest>().ToList();
var allRequests = db.Set<Request>().ToList();
allRequests
will be of type List<Request>
, but the elements in that list will be of the appropriate FooRequest
/BarRequest
type.
Notice how there is little to no reference to the derived classes. The classes exist by themselves, but EF doesn't ask for you to explicitly reference the classes. If you were to create a BazRequest
derived class tomorrow, all you would need to do is create the class and update your database schema (which should not cause conflicts since it will only add a new "BazRequests" table).
In conclusion
This seems to be exactly what you want, but I only know of this behavior in Entity Framework (Code First, TPT). I am unaware of any other libraries which offer the same functionality.
listOfOtherType.addAll(findAll[Foo|Bar]WithXYZ)
.Request
? I'm wondering what differentiates the differentRequest
concrete sub classes.FooRequest
andBarRequest
types are never referenced in this code. They only appear as part of your dao variable names. Similar to what Erik asked: why do you need aFooRequest
class and aBarRequest
class.