Automatic interfaces instead of explicit interfaces? #1231
Replies: 76 comments 7 replies
-
This sounds like a good use for #107. |
Beta Was this translation helpful? Give feedback.
-
For the time being you can also write an extension that does that or suggest this as a refactoring feature on the Roslyn repo. I mean it can be something like open the quick action, "Add member to interface", then a dialog would open which may let you select the interface(s) you want to add the member to or something like that. Going further with this idea it might be a good idea to add an option to the dialog that would implement the method automatically and throw "NotImplementedException", just a thought. I mean this will be much quicker than waiting for #107 to happen and to be frank, I can't see this being added as a language feature. |
Beta Was this translation helpful? Give feedback.
-
@eyalsk Sorry I'm new here - do you suggest to open this discussion on the Royln github repository, too? I would agree that such refactorings ease the pain, but they do not remove the root cause. |
Beta Was this translation helpful? Give feedback.
-
This sounds like an issue that can be solved for the biggest part with smarter tooling rather than a language addition. Roslyn analyzers were already mentioned. I'd like to add this:
Recent versions of Visual Studio have a new capability, "Go to Implementation" (Ctrl+F12), as explained e. g. in this post: https://weblogs.asp.net/morteza/How-to-find-the-implementation-of-an-interface-in-Visual-Studio-2013-2015.
I'd say the root cause here is ASP.NET Core's design (requiring interfaces excessively) more than a missing language feature. It would be a pity having to tack on new features to a language just to satisfy one library's needs. (Btw. for the record, I'm not saying ASP.NET Core is badly designed.) |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Yup. :)
I don't disagree.
It's really not the same thing, when you have multiple interfaces, how would the language know what interface to update? and then there's also the case of having an abstract class, the "only" two options to go here is to update all of them or none of them, other options are likely to be too complex to even consider it but then #107 should cover this and more which makes this even less desired as a language feature. |
Beta Was this translation helpful? Give feedback.
-
We want this one so badly. I'd propose reusing the
For the record, It's a constant hassle having to play the delete-regenerate-move game to crank out header interface files for every minor tweak, purely to feed them to the DI container. In a library scenario you wind up with nearly twice the number of files to manage (hence the "move" step to hide the worthless interfaces in a subdirectory). Automating chores is the name of the game, right? |
Beta Was this translation helpful? Give feedback.
-
The entire point of using interfaces there is the intentional indirection, the ability to separate the contract from the implementation. Trying to find a language hack around that would defeat the purpose. If you're finding it too cumbersome then why not dispose of the interface entirely? Have the service just be a class bound to itself and declare the methods as |
Beta Was this translation helpful? Give feedback.
-
It isn't valid to blindly assume there will never be another implementation and bake that into a bunch of libraries, but since most IoC containers do require registration of a single concrete service implementation, the proposal addresses a real-world problem. |
Beta Was this translation helpful? Give feedback.
-
Probably not for library authors, no. But I think it's perfectly applicable to application developers where those dependencies are not going to be consumed outside of the project. Even so, I don't like the idea of having a language feature that somehow tightly couples contracts to their implementations. That's a complete reversal of the typical relationship between those types and prevents an expected degree of flexibility in where they are defined and how the implementation implements the members. You'd need further language support to do something as basic as exposing additional |
Beta Was this translation helpful? Give feedback.
-
@HaloFour is exactly right. This would defeat the whole point of SOLID and the open/closed principle. Changing the interface should not be done all the time, never if possible. |
Beta Was this translation helpful? Give feedback.
-
If you get to this point, you are almost certainly correct. One solution to this situation would be eliminating the use of interfaces altogether for types that only have a single implementation. Sometimes this is not possible (e.g. managing a public API surface area across versions), but if the only reason for the interface is use in testing, then it's likely the interface is not doing anything for you at all and can be removed. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour The brittleness is a good point and it's the primary reason I've waited so long to jump into the fray. The real problem here is related to developer effort, not runtime needs of any kind. What if interface definitions were changed to allow them to reference a concrete implementation. Then the tight coupling is at the other end of the pipe -- everything "out there" that needs the interface is still referencing the interface itself, but developers are spared the hassle of updating it until something happens that truly requires expressing the literal contract.
Which is great when you're done but it's still a giant hassle when you're in active development. I get that I'm arguing for convenience over theoretical correctness. That just speaks to the degree of inconvenience involved. It's a real-world, down-in-the-trenches type of problem that I doubt will have an academically-ideal solution. A third excuse for not just going all-concrete comes to mind: standards. "Always use interfaces," so sayeth the enterprise architects, and it was so. (Again, real-world pain.) |
Beta Was this translation helpful? Give feedback.
-
That is the very coupling that I am arguing against. The interface should have no concept of any existing implementations, if any, let alone some special "reference" implementation that establishes its contract. I understand that there is some real world pain here. Some of this is self-imposed and unnecessary (e.g. decrees of "Always use interfaces"). I've been there myself. I'd rather see tooling which can help in these matters much more than a language feature which would seemingly encourage this type of coupling beyond development churn. |
Beta Was this translation helpful? Give feedback.
-
Meanwhile, in COM interop... (I know, I know, doesn't make it a good idea... it just popped into my head that I'd seen this somewhere via attributes.) |
Beta Was this translation helpful? Give feedback.
-
Perhaps I'm being a little broad in my definitions, but I'd say that what the OP has asked for is duck-typing, rather than it being one possible solution. The requested change would mean that a variable of type Unless this feature was opt-in, you'd then completely lose the ability to ask for a specific type as input. Now, programming to abstractions is often a good idea but not always; you may end up weakening the design of your application by allowing any old type that has the correct shape to be stuffed into any location in the program. I think that's why, letting aside any technical issues, this is not going to be popular with C# devs, who tend to favour strong nominal typing. |
Beta Was this translation helpful? Give feedback.
-
It's really not what he's asking though, the OP is speaking about the implementer/dealer not the consumer and suggests to have a feature where when a class is defined it implicitly defines an interface for it without ever creating an actual interface so nothing changes from the consumer's standpoint, it still matches nominally and duck typing has nothing to do with it. Note that what you're describing is related more to structural type system than duck typing but neither of them applies here. |
Beta Was this translation helpful? Give feedback.
-
Nod, in the Dart world if you declare the equiv of: public class Person {
private readonly string name;
public Person(string name) {
this.name = name;
}
public string Greet(string name) {
return $"Hello {name}, I am {this.name}.";
}
} The compiler also generates the equiv of: public interface Person {
string Greet(string name);
} When you consume But you can create an instance of It's an interesting duality, but it's not one that can be applied seamlessly to C# for a number of reasons. |
Beta Was this translation helpful? Give feedback.
-
@Richiban In a DI container world like ASP.Net Core, there are not that many possibilities to avoid these Interfaces. Especially if you move to Integration tests (how do my service containers behave together?) there is no real possibility to move to a different model. Abstract base classes do not work that nice with DI, the mocking tooling is less advanced and I still need to do extra ceremony which I do not need or want. And tbh, I like this model - it is for the most part easy, but it does introduce extra noise which I do not care for and which I would rather avoid. Now, I do lots of work in python and I like duck typing a lot - I don't think it's as scary as a lot of people think it is. But I understand the concerns for the C# ecosystem - as such a source generator would at least ease the pain. And yes, I am more concerned about the workflow than anything else. At the moment it looks like this:
Note that 6 - 9 happen a lot, e.g. due to iterative user stories. Also, 6 can be even more painful if you refactor / rename. |
Beta Was this translation helpful? Give feedback.
-
I personally like it. I don't want my tests testing my class. I want it testing my contract. Adding something to the class shouldn't update my contract. Changes to the contract happen explicitly and through conscious decision on my part about what is needed between the consumer and the component.
I disagree. I absolutely despise cases in our code where our classes couple and it's nigh impossible to actually tell the contract between them. Having these interfaces makes it explicitly clear about what the boundary is, and makes it extremely easy to understand the coupling between components and when it feels appropraite, and when it feels like it has gotten out of control. |
Beta Was this translation helpful? Give feedback.
-
I worked on a library several years before that article that heavily relied on Source generators allow a team to eliminate the majority of the "noise" which you described. The parts you still can't avoid with source generators are:
This is really a minimum amount of noise (further reduction is possible through other naming conventions, but it doesn't seem to provide much benefit). I'm interested to know if there are any other reasons why a source generator approach wouldn't work for the particular development and testing strategy used by your team. |
Beta Was this translation helpful? Give feedback.
-
Yes. I'm suggesting implicit interfaces as an alternative to that approach.
Pretty much anything you have to bulk-generate (whether you're doing so with an IDE or a source-generator) tends to be junk. In the case of Implicit interfaces can satisfy that need without introducing junk members to a namespace. |
Beta Was this translation helpful? Give feedback.
-
It doesn't matter if this is implemented as a source generator or as a compiler feature you're still going to end up with an additional type because there's no support in the CLR to declare two separate types, one a class and one an interface, of the same name. At best as a compiler feature you'll get syntax which lets you say that you also want an interface to be generated. Since an interface is a separate nominal type, you're going to be expected to name that interface. So the syntax would have to be something like this: public class Person : implicit IPerson {
string Greet(string who);
} And frankly that's not an improvement syntactically or behaviorally from what's possible with a source generator: [ImplicitInterface("IPerson")]
public partial class Person {
public string Greet(string who) { ... }
} The latter has a lot of advantages, not the least of which is that it is actually likely to happen. A generator also enables configurable policy, such as automagick naming of the interface and selection of which members to expose, which a language feature cannot have without being significantly more complex which drastically reduces the ROI and likelihood of acceptance. IMO, the chances that the CLR team would consider gutting large aspects of the runtime in order to merge the concepts of interfaces and classes into a single shared type which will have a drastic consequences on every CLR-based language that exists is zero. |
Beta Was this translation helpful? Give feedback.
-
That sounds like an implementation detail - as said, I don't know the CLR internals. I would suppose the interface and class could have different names internally, and the implicit interface name would get resolved at compile-time. But I have no real idea. |
Beta Was this translation helpful? Give feedback.
-
Even assuming that the declaration site could be an implementation and this interface automagickally named and resolved, that "implementation detail" still has far reaching affects on the rest of the language (and runtime). Every single location where a class is used now has to go through the interface instead. A method couldn't accept a |
Beta Was this translation helpful? Give feedback.
-
I see, yes. Well, then maybe it only conceptually works that way - maybe there's no actual interface generated at all, and |
Beta Was this translation helpful? Give feedback.
-
Feels like that is solvable with source generators. |
Beta Was this translation helpful? Give feedback.
-
Using this approach it would be. Interesting, I might try that out 😀 |
Beta Was this translation helpful? Give feedback.
-
Remember, you're going to have to mark all members on the class your extending as |
Beta Was this translation helpful? Give feedback.
-
I know this is rather old, but I've finally managed to build a Source Generator which can create an Interface from a class - see https://github.com/codecentric/net_automatic_interface. It should support most common use cases (supports all features I can think of except Indexers) and in any case, the generated interface is partial, so it can be extended. Took somewhat longer, a new child and navigating family & covid got in the way. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Our team has written lots of ASP.NET Core code the last few months and most of our Interfaces are pretty pointless - they exist so that the DI can wire up the whole app, and maybe help writing unit tests. In other words, most Interfaces are only implemented once or maybe twice (concrete implementation and test).
But working with so many ISomethings is annoying - you have a lot more files, cannot jump directly to the implementation and adding a method to a class now involves at least two operations (modify class, modify interface).
I would like to suggest that an automatic Interface is introduced, so that we can mark classes as the source of an interface - if I change the class, the Interface changes with this class.
E.g.
generates an "implicit" interface:
TL;DR: Please add a language feature which reduces the noise from boilerplate interfaces which are only needed for testing and infrastructure.
Beta Was this translation helpful? Give feedback.
All reactions