-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
This is a feature request. I don't know whether this is more of a language or IDE feature, but I can imagine it going either way.
Problem
Consider the process of setting up a dependency injection environment for a class HomeController that depends on another class, DataAccess.
This process would typically (for me, at least) looks something like this:
- Create
DataAccess - Build an interface
IDataAccesswith exactly the same items (Visual Studio does this through a menu-item) - Add a field and constructor (easy with the new constructor syntax) for
HomeControllerthat acceptsIDataAccess dataAccess
That's pretty straight-forward.
But once you start changing those contracts, it gets a little more frustrating.
- Starting in
HomeController, go findDataAccess(note that F12 doesn't work, since we're only looking at the interface at best here. You can F12 to the interface, then look at references to find the class, but...) - Add the desired method to
DataAccess - Copy the method signature, then scroll to the top of the file (often a ways) and F12 to
IDataAccess - Paste the signature into the interface
- Go find
HomeControlleragain and write the line I wanted.
In reality, I often do this backwards via Ctrl+. by referencing a non-existent method, creating the method stub, then implementing the interface.
Proposal
It seems like we could do away with many of these steps using an interfaceof(T) language feature, similar to typeof or nameof. I can also see it as HomeController.interface.
In essence, interfaceof(T) would build a compile-time interface out of T that can be used just as we used IDataAccess before. The compiler would build a logical interface identical to what the IDE would when I right-click a class name.
- Create
DataAccess - Add a field and constructor (easy with the new constructor syntax) for
HomeControllerthat acceptsinterfaceof(DataAccess) dataAccess
Simple still.
Then for the other steps,
- Starting in
HomeController, go findDataAccess(note that F12 does now work, since we have the class referenced here) - Add the desired method to
DataAccess - Go find
HomeControlleragain and write the line I wanted.
My personal preference of adding the unknown method reference first then creating it with ctrl+. is also supported, since it will automatically add onto DataAccess.
If I wanted to inject something else, I could use very similar syntax:
public class DataAccessMocks : interfaceof(DataAccess)
If I had those mocks set up and building with my main project when I added a method, they would raise a compiler error because it didn't implement the (whole) interface.
In essence, I see this kind of as a reverse version of an abstract class. In an abstract class, as we know, the class can decide (or force) "I want children to inherit from me." Using interfaceof, the recipient of that class can say `I don't care whether what I receive has a polymorphic relationship with this class, but I do need its contract maintained exactly."
Potential Concerns
Now, of course, this isn't an alternative to interfaces. Interfaces do a whole lot that isn't useful here, particularly in that to use this interface, one would have to maintain a reference to DataAccess. It's mostly useful in this specific (albeit common) case of dependency injection.
Does this approach, when not in the light of dependency injection, open itself to any bad practices? If overused, this could offer some interesting security holes in code. Imagine if I accepted an interfaceof(DateTime) in the interest of having as open a contract as possible, but then someone passed me something that implemented the DateTime members very poorly. Could that be problematic? This seems like it would be either my fault, or that of the caller.
Conclusion
This seems like it could clean up a lot of dependency injected code, and it might even have alternative uses that I haven't immediately thought of. The current procedure is painstaking, and I'd love some more efficient way of handling this very common scenario.