Automatic Interface

Topics: C# Language Design
Jan 16, 2015 at 5:24 AM
Edited Jan 16, 2015 at 7:25 AM
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 Controller that depends on another class, DataAccess.

This process would typically (for me, at least) looks something like this:
  1. Create DataAccess
  2. Build an interface IDataAccess with exactly the same items (Visual Studio does this through a menu-item)
  3. Add a field and constructor (easy with the new constructor syntax) for Controller that accepts IDataAccess dataAccess
That's pretty straight-forward.

But once you start changing those contracts, it gets a little more frustrating.
  1. Starting in Controller, go find DataAccess (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...)
  2. Add the desired method to DataAccess
  3. Copy the method signature, then scroll to the top of the file (often a ways) and F12 to IDataAccess
  4. Paste the signature into the interface
  5. Go find Controller again 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 Controller.interface, but I'm not sure off the top of my head whether that would have potential naming conflicts.

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.
  1. Create DataAccess
  2. Add a field and constructor (easy with the new constructor syntax) for Controller that accepts interfaceof(DataAccess) dataAccess
Simple still.

Then for the other steps,
  1. Starting in Controller, go find DataAccess (note that F12 does now work, since we have the class referenced here)
  2. Add the desired method to DataAccess
  3. Go find Controller again 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.