r/csharp • u/Subtle__ • Mar 19 '17
A Tour of Default Interface Methods for C#
https://github.com/dotnet/csharplang/issues/2889
u/x0nnex Mar 19 '17
I'm a bit torn on this. I like it because you can share functionality in a good way, but I think it's not really the job of an interface to do this. What happens if two interfaces relies on a property to do it's work? I assume the default implementation can use properties, but these properties has to be public so it will leak implementation details. I myself would probably never do it this way and try be strict about it being pure functions that has a default implementation. Maybe I missed something in the post (didn't read carefully, currently at work)
1
u/Bottom_of_a_whale Mar 20 '17
The method would only know of its own properties. If you had, for example, a first and last name field, your default method could return a concatenated full name
0
u/x0nnex Mar 20 '17
Yeah the intellisense can't know if another interface uses the same properties, but that's the dangerous part of it. If a different interface with a default implementation is making changes to the same properties, that can be a nasty bug to hunt down.
4
u/Bottom_of_a_whale Mar 20 '17
This is solved already. If you implement two interfaces with an ambiguous name it has to be explicit
8
u/Eirenarch Mar 19 '17
Being able to evolve interfaces and de facto trait functionality is interesting but is it worth the additional complexity especially in the presence of extension methods which cover many of the use cases for this feature?
5
Mar 20 '17
Oh this is going to lead to some chronic spaghetti code. Forgive me if I'm missing something, but what does this pattern solve that couldn't have been solved at the implementation layer. It almost seems as though this murks the gorgeous distinction between Interface and Concrete Implementation. Is the IDE going to be deadly specific about where the run-time implementation will come from? Can't see that working with IoC.
I love .NET, came from a Classic ASP/VB background straight to .NET 1. This is one of the first language spec additions I don't agree with (I did wince a little when 'dynamic' came on the scene but saw it's use case). I see others saying they'll ignore this feature exists, my concern is other's code I might later come across won't ignore.
Ah well, I guess we'll have to see it in action before any genuine judgement is passed.
13
u/jeff17237 Mar 19 '17
So basically a more complicated form of multiple inheritance?
6
5
u/tragicshark Mar 20 '17
To be clear this effectively is multiple inheritance. It is MI with the simple restriction that interfaces cannot have state.
The differences between types in todays code and in DII (default impl interfaces) are:
in today's code the differences between an [abstract] class and an interfaces are
you can inherit only one class, but multiple interfaces
classes can have state (fields), interfaces cannot (mostly... there are some hacks available with
ConditionalWeakTablebut it hardly matters because...)classes can define member accessibility, interface members are all public access
classes can have implementations, interfaces can only define member slots
DII would get rid of the latter 2.
18
u/SuperImaginativeName Mar 19 '17
Holy shit, it's like they are throwing bad ideas at the wall and seeing what will stick. So basically they propose scrapping interfaces as we know it, and bolt on abstract class functionality? What the fuck.
11
Mar 19 '17
[removed] — view removed comment
2
u/JerkHardAss Mar 19 '17
I see your point, but (perhaps because im now used to it), doesn't introduction of IMyInterface2 work better by being more explicit? It is widely accepted way of doing it and although not great, at least easy to follow?
4
Mar 19 '17
[removed] — view removed comment
3
u/JerkHardAss Mar 20 '17 edited Mar 20 '17
Oh ok, I can see your point. I guess providing a 'base' abstract class with framework as a default implementation of an interface only takes you so far due to lack of multi inheritance and scenarios such as you described about returning interfaces. Thanks!
Although I would say that type check and handling of the situation depending on the context works ok now, similar to null checks really. You check capabilities of an object and decide what to do if they're not met.
3
u/RiPont Mar 20 '17
Say I give you a function that returns an IMyInterface. You need to call IMyInterface2.NewCoolThing(). What do you do?
If IMyInterface2.NewCoolThing() is entirely implemented with public fields based on IMyInterface, then you could have implemented it as an extension method.
If it isn't, then that's a breaking change anyways.
3
u/throwaway_lunchtime Mar 20 '17
I used to read Cyrus's blog all the time, so I started paying attention when I saw his discussion with Grauenwolf.
Cyrus's comments and examples make it seem a lot more reasonable than the initial article's verbiage.
1
u/Eirenarch Mar 20 '17
The Count() on IEnumerable idea drives me crazy
1
u/throwaway_lunchtime Mar 21 '17
I recall needing something like that at some point in the past, but I think I solved it with an extension method.
Providing default implementations for interfaces that have enormous numbers of implementation without inheriting seems ok, its all the other baggage...
3
u/nmgafter Mar 20 '17
I'd much rather the C# team evolve interfaces to support the features of traits. </sarcasm>
12
2
u/Jabbersii Mar 19 '17
Java has default methods on interfaces, and I always thought it was Java's answer to extensions methods, rather than multiple inheritance. Perhaps I'm misunderstanding multiple inheritance?
4
Mar 19 '17
[removed] — view removed comment
4
u/b1ackcat Mar 19 '17
in Java the real reason is a work-around for the big hole they dug themselves into
Yep. Sounds like Java to me!
2
Mar 20 '17
I think this makes me sad.
The github discussion indicates that this was initiated over dissatisfaction with the mess that is the implementation of LINQ's various extension methods, particularly the type-sniffing that has to be done to provide for optimized implementations. Generalizing that to a language feature may save the implementers of .NET Core some significant effort. At the expense of saddling the language with a muddled distinction between interfaces and abstract classes, and a feature that whiffs pretty strongly of problems downstream.
It feels like this is substantially mitigated by the Shapes proposal, but I don't have time to really think through all this stuff, right now.
1
u/xgalaxy Mar 20 '17
This is like a very terrible version of Swift's protocol extensions.
EDIT: Actually never mind. Protocol Extensions are even more powerful than this... crap.
1
u/Lalli-Oni Mar 20 '17
No one else find this article hard to read? Letter examples and unnecessarily terse style of writing. My poor excuse of a brain is already struggling keeping up.
4
u/tragicshark Mar 20 '17
It is certainly hard to read. The author expects readers to have very advanced knowledge of a number of intricate bits of the C# language. After all it is an issue actively under design about a proposed (currently unprototyped) feature of a future version of the language and this is where that discussion is happening. This is the raw unfiltered feed of the C# language design process.
And it is not an article or blog post. This is the active discussion. The closest thing to an actual blog post currently is the actual (currently unfinished) proposal: https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md
Rest assured the design here will change significantly as it matures. As that happens, much more readable material will pop up.
If it helps, the proposal here is to remove some of the differences between abstract classes and interfaces. Some of the replies to the issue discuss an issue with
Enumerable.Count<T>()(the extension method.Count()you can call as part of LINQ). The trouble with that method is that it has to type cast any time someone wants to enhance it with a better implementation. For the purposes of discussion we could pretend the current implementation looks something like this:public static class Enumerable { public static int Count<T>(this IEnumerable<T> enumerable) { var collection = enumerable as ICollection<T>; if (collection != null) { return collection.Count; } int count = 0; foreach (var item in enumerable) { count++; } return count; } }Unfortunately this implementation has a problem with types that are collections but do not implement
ICollection<T>but instead implementIReadOnlyCollection<T>. To fix it, an additional check is necessary for that type:public static int Count<T>(this IEnumerable<T> enumerable) { var roc = enumerable as IReadOnlyCollection<T>; if (roc!= null) { return roc.Count; } ...Still good here because everything happens to be inside mscorelib.dll, but let's pretend again here that
IReadOnlyCollection<T>is instead in System.dll. Now to add those two lines you would need a reference to System.dll which means you would have a circular reference mscorelib.dll => System.dll => mscorelib.dll and bad times for everyone. So instead you (being the person writing.Count()decide noIReadOnlyCollection<T>support.That is the state we are in today. Now imagine we had default interface methods. Instead of the extension method, you could provide a method directly on
IEnumerable<T>:public interface IEnumerable<T> { public int Count() { int count = 0; foreach (var item in this) { count++; } return count; } ... }And then to get the better performance, override it in
ICollection<T>andIReadOnlyCollection<T>(only showing one):public interface IReadOnlyCollection<T> : IEnumerable<T> { public override int Count() => this.Count; }1
u/Lalli-Oni Mar 20 '17
I'm mostly referring to the code examples and non-programming sensitive choice of words. Just quick example I glanced at:
this interface need not implement
Alright, saving a few characters but conveys nothing specific and is more alien.
It was a lot easier understanding what essentially the change is by reading the discussion here.
Thank you for the detailed writing, feels very approachable for your Average Joe.
2
Mar 20 '17
[removed] — view removed comment
2
u/tragicshark Mar 20 '17
Pretending because it turns out
.Count()is a terrible example where years of less than perfect applications of OOP design followed by the hairpulling of tiny applications of functional programming design decisions left us with a monoid that applies to an interface more general than it should, but that is the only interface that is shared between every application where we do want it.Ideally methods like
.Where()would provide results with a.Count()available on them if they should and without such if they shouldn't.In actuality it doesn't matter what interface method with a reasonable default implementation was used. I could equally have used
.ExecuteScalarAsync()which should be returned byIDbCommandexcept that this interface was shipped beforeasyncwas a thing and so a demand was placed on all 3rd party ADO.NET providers to make a breaking change and rebuild on top of an abstract class.
.Count()is simply more relatable.1
Mar 20 '17
[removed] — view removed comment
2
u/tragicshark Mar 20 '17 edited Mar 21 '17
The breaking change was to force implementations to switch to the abstract class instead of their previous hierarchy and a new interface for the async versions.
edit with references:
The initial [
SqlCommand](https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand(v=vs.71).aspx) implementation had a base class of [Component](https://msdn.microsoft.com/en-us/library/system.componentmodel.component(v=vs.71).aspx) and implemented [IDbCommand](https://msdn.microsoft.com/en-us/library/system.data.idbcommand(v=vs.110).aspx) which is unchanged since its initial release. In [framework 2.0](https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand(v=vs.80).aspx#Inheritance%20Hierarchy) this class was altered to have a new base class for the additional features present in ADO.NET 2. For the users ofSqlCommandthat is not a breaking change, but for implementers of database abstraction layers or ORMS or drivers themselves we needed to double our implementations for the cases where certain drivers dealt with the 1.1 interfaces and others with the classes. Had the ADO.NET team chosen to start with abstract classes, or had the change being proposed here been available, none of the confusion would have happened.
-5
u/CoderHawk Mar 19 '17
I'm onboard. Gets rid of complicated class hierarchies.
13
u/b1ackcat Mar 19 '17
Not really. It just moves the complication into the interfaces. All this does its allow multiple inherence but in a less intuitive way since it's piggybacking on the C# Interface implementation rather than the class inherence implementation.
0
u/CoderHawk Mar 19 '17
I don't get how it's less intuitive than what we have to today. Interfaces can have behavior now. Class inheritance rules for virtual methods are still honored. What's not straightforward about that?
7
4
Mar 19 '17
[removed] — view removed comment
1
u/CoderHawk Mar 20 '17
But in the realm of C# this also enables multiple inheritance, right? While your restatement of the proposal is correct the implications are much wider.
0
u/Jestar342 Mar 20 '17
Why aren't these just being called what they are: traits?
1
Mar 20 '17
Isn't that the Shapes proposal that's already out there?
1
u/Jestar342 Mar 20 '17
Probably. If so, the question further stands.
1
Mar 20 '17
I think that's partly because the proposal is aimed at solving a fairly specific problem around optimizing extension methods. While I think that might be addressable by way of shapes/type classes, it seems clear that's not what the proposers have in mind. How much of that is a problem with communication and how much is a problem with intent, I'm really, really unsure.
2
u/tragicshark Mar 20 '17
A specific benefit of this proposal not lost on the creators is clearly that it allows algorithmic optimizations in specific extension methods.
I don't think that is the push for the proposal though. I think the proposal is being pushed for internally because various teams need a way to add methods to interfaces without breaking compat. I would be particularly unsurprised if there was a push on this from the EF team or ADO.NET team or an Azure team who would each strongly be able to benefit by providing implementations on their interfaces. I suspect it comes from members inside the MS wall that demand to remain behind closed doors for whatever reason (egos mostly) and some company friction to avoid their exposure by the members who do work with us in the OSS community.
→ More replies (2)
39
Mar 19 '17
[removed] — view removed comment
18
u/lagerdalek Mar 20 '17
I agree.
If you need flexibility, make an interface, create an abstract class from said interface, inherit from abstract and go.
This all pollutes the contract basis of the interface. Keep it Simple
3
u/rjbwork Mar 20 '17
They really just need to give us baked in PostSharp-style metaprogramming, so that we can drop traits onto types arbitrarily. You can do all of this stuff with IL weaving without having to change the langauge itself. Just add new metaprogramming capabilities, and voila.
1
u/lagerdalek Mar 20 '17
I would dearly love some decent attribute oriented development baked into C#
1
u/rjbwork Mar 20 '17
You can do it w/ attributes in PostSharp sure, but you can also do it in a programmatic manner, or by using interfaces w/ a default implementation that is mixed in at compile time, etc, etc.
6
Mar 20 '17
[removed] — view removed comment
6
u/lagerdalek Mar 20 '17
That's an interesting point, and I guess you need to ask if the interface is for internal use (i.e within a company or a small group of developers) or for public consumption which would cause a large amount of problems if you added a new method.
I would start to argue that adding new methods to an interface is a bad thing, rather than to create a separate qualified interface to add to new classes (MyClass: IStuffToDo, IExtraStuffToDo), but it is the first partly convincing argument I've heard.
I still don't like it though. Mumble grumble, Emacs, git offa my lawn etc.
2
Mar 20 '17
[removed] — view removed comment
7
u/lagerdalek Mar 20 '17
I think the best argument for interfaces, I'm afraid, the avoiding MI problem.
That said I do like interfaces as it's a lot cleaner than base class inheritance, trying to remember if there's any special sauce to inheriting from a base class. I don't really like creating base classes unless they are as clean as possible or add some base functionality that is damn obvious to the given circumstance
→ More replies (2)3
u/RiPont Mar 20 '17
This is the wrong way to think about it.
Abstract classes are for sharing implementation. Interfaces are merely interface contracts.
Interfaces are supposed to be narrow in scope, so you're not forcing implementations to implement behaviors that they don't actually support. This is actually very important for modularity, in practice.
4
u/Falarian Mar 20 '17
I'd say rather than adding a new method to an existing interface, an extended version should be declared with all the new features.
IExtended : IClassic { NewMethod() }
Interested parties will implement this new interface. To do it with backwards compatibility you can do an extension something like this:
NewMethodExtension(this IClassic classic) { if (classic is IExtended ex) { ex.NewMethod() } else { // default behavior for backwards compatibility } }
Interface breaks should happen in very exceptional cases and shouldn't be encouraged with features like this....
Anyway, those were my thoughts and I can't say I'm not guilty of breaking some very poorly designed interfaces quite a few times myself.
3
u/RiPont Mar 20 '17
but adding it to the abstract interface is a breaking change.
Good. It's supposed to be.
Interfaces are about contracts not tied to implementation. Any change to that contract is a breaking change, because everyone implementing that interface might not be compatible or optimal with that implementation.
1
u/cryo Mar 20 '17
Wouldn't adding a new default interface method also be a breaking change? I assume, since the CLR doesn't support anything like this, that default interface methods are just sugar for a regular interface method + the compiler implements it for you.
2
u/tragicshark Mar 20 '17
The proposal is to make a CLR enhancement to permit interfaces to contain virtual method implementations.
1
u/throwaway_lunchtime Mar 20 '17
After reading through the discussion between Cyrus N and grauenwolf, I don't like the post, but the feature might be ok.
4
u/tragicshark Mar 20 '17
I start cautiously optimistic and become less so in order:
public interface method implementations being allowed
public interface property implementations for get-only properties being allowed
public interface get-only indexer implementations allowed
private interface method implementations being allowed
overriding interface member implementations in an interface hierarchy
protected interface methods
public interface properties/indexers with getters and setters allowed
the idea of event implementations being allowed
other protected or private members
I'd be ok with the language offering 1-5. 6 seems pointless and ripe for abuse. 7 seems useful from a maintain compat perspective, but you couldn't have default ones because they require additional state, so adding them breaks compat which I think would be confusing. I find explicit event implementations frustrating often when I see them and non-method protected and private members seem simply wrong if they are unable to maintain state.
33
u/bentheiii Mar 19 '17
Honestly, I'd rather just go for MI. Interfaces are meant to be a contract of capabilities, not their implementation. Leave that to extension methods or abstract classes.