-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Champion "default interface methods" (16.3, Core 3) #52
Comments
Why are we limiting to only extend by allowing At Summit last year, @MadsTorgersen and I discussed how this could be used in the context of helping to properly implement public interface IDisposable
{
bool Disposed { get; protected set; }
void Dispose() =
{
Dispose(true);
GC.SuppressFinalize(this);
};
protected virtual void Dispose(bool disposing);
} (Putting this here so it doesn't get lost. Pending the outcome of the question asked on dotnet/roslyn#17054 (comment) I can repost the comment to the right place.) |
The spec says there are no alternatives, but there is one: the delegation pattern as suggested in Roslyn issues [Proposal] Deligation class and Proposal: C# interface delegation. In my opinion at least, the delegation pattern has far more merit than default interface methods:
Should I submit a PR to update the spec to reflect this alternative, or will you do it, @gafter? |
@DavidArno Those "alternatives" do not address the principal motivation for the proposal, and are not, therefore, alternatives:
|
@scottdorman |
@gafter I disagree that it doesn't make sense. Take the example of
This is true, but isn't this proposal sort of breaking that statement? We're basically saying "Implement this interface but you don't have to provide your own implementation for this interface method because the interface is already giving you one." You can then decide later on to implement the method yourself, in which case the default interface method is effectively ignored/overridden with the actual class implementation.
How so? This is simply saying that if you implement this interface there is a protected member that must be implemented. In that respect, it doesn't seem like it's any different than saying "The syntax for an interface is extended to permit private methods (the default access is public)", which is what we have in the proposal now. |
In that case I'm downvoting this proposal as it:
Since the underlying aim of this proposal can be achieved using the non-alternative delegation pattern approach, which doesn't suffer from any of the above problems, this doesn't seem a good proposal. |
@DavidArno Given my earlier example of providing a default correct implementation of One of the goals of this proposal is:
How would using your delegation pattern achieve that (in my earlier example) such that existing classes which already implement that member aren't broken but new classes can utilize the default implementation? Just because a proposal requires CLR changes isn't a reason for it not to be done. If that were the case, there's a lot of language features we've gotten over the last several releases that wouldn't exist. I don't see how this introduces multiple inheritance to the language. Yes, it provides a solution that other languages use multiple inheritance to solve, but it doesn't change the inheritance rules for C#. A class can still only inherit from a single base class and multiple interfaces. |
I think Proposal: C# interface delegation is useful, would reduce a lot of boiler plate code that has to be written, and would love to see it in the language; however, I don't think it addresses the ability to provide a default implementation for an interface method or property nor does it extend the interface syntax to allow private methods, static methods, "override" methods (and hopefully protected methods). [Proposal] Deligation class looks to achieve a similar result, albeit with a much uglier and more confusing (in my opinion) syntax so it wouldn't be able to achieve the goals set out in this proposal either. |
I think interfaces can just be allowed to have an unnamed nested class, that implements the interface. The nested class then can provide all the proposed features. Thus the main interfacing portion will not be polluted. Also, how does this go with proposed extension everything proposal? |
This actually has alternative with generic extension method I agree with @DavidArno The proposal is conflict in itself. The purpose is to create function with |
Well, it's not like we can't mimic this today and I don't think that multiple inheritance is bad per se because as far as I understand things like the diamond problem is going to result an error but I can certainly see that it might open a can of worms. :) I voted for this because I think it can come in handy at times but I really like to see more love to composition too. |
Since @gafter seems opposed to having // built-in types
public interface IDisposable
{
void Dispose();
}
public class Disposable : IDisposable
{
private readonly Func<bool> _isDisposeNeeded;
private readonly Action _dispose;
public Disposable(Func<bool> isDisposeNeeded, Action dispose)
{
_isDisposeNeeded = isDisposeNeeded;
_dispose = dispose;
}
public void Dispose()
{
if (_isDisposeNeeded())
{
_dispose();
GC.SuppressFinalize(this);
}
}
}
// my type
internal class MyDisposableType : delegate Disposable
{
private bool _disposed;
internal MyDisposableType() : delegate(() => !_disposed, PerformDispose) {}
private void PerformDispose()
{
_disposed = true;
// etc
}
}
I wouldn't as that isn't necessary. As @Thaina says, this can already be achieved with extension methods. |
If multiple inheritance is seen as no worse than single inheritance, then why not just allow full-blown multiple inheritance, rather than this "by the back door" partial approach? |
@DavidArno Sure, I'd vote for it, haha... :) I think that this solves a different problem though, even if we had MI in the language, it wouldn't solve the following problem:
I've been asked many times before what's the difference between abstract classes and interfaces and my guess is that many people don't know the difference or they do but end up making the wrong choice and choose interfaces over abstract classes so in order to help these people a "plaster" is introduced into the language and I'm in need of one because I'm not perfect, yet. 😉 Now, I completely agree with you on the following part:
This doesn't make a lot of sense because the next request would be to enable this for classes and maybe more specifically abstract classes, I mean why not? people are going to "abuse" interfaces just to achieve this so to me it makes more sense to add MI into the language at the same time. It's a new feature so they can always throw an error at compile-time in cases it isn't "safe" such as in the diamond problem. |
Am I missing something re,
For, as in my reply to @scottdorman, extension methods surely already provide this? |
I think abstract is outdated. Object in the real world actually isn't hierarchy. Instead it is a composite of multiple aspect. Abstract really is just a class packed along with interface that it should be separated and composed It just extension method was introduced so late so we stuck with abstract class to contain concrete method. Abstract class that need to derived to implement Which I think it's wrong. It should be explicitly state at compile time what it could do For the virtual method. I want to explain that most of the time virtual method has concrete logic. And request you to always include Instead. Interface callback is more proper way to do thing like that. Because if logic really concrete and just want to allow some extension in the future. You should just made interface and extension method like a jigsaw that would be zigzag between concrete and extensible logic. So you can control where you think it should be really extended static class Ext
{
public static void DoSomething<T>(this T obj) where T : MyInterface
{
// startup
obj.Before();
if(obj.Predicate())
// concrete logic a
else // concrete logic b
obj.After();
}
}
public interface MyInterface
{
void Before();
bool Predicate{ get; }
void After();
} With this it more powerful to extend any class you want in any composite way you need than hierarchical that could be derived with only one linear inheritance |
@DavidArno I think that you're looking at it from the wrong angle. I don't think that the question should be whether extension methods CAN come in place of the "default interface methods" but whether they SHOULD and by that I mean sometimes there's functionality that NEEDS to be part of the interface but was missed from the design and it's too late to introduce new functionality or changes into the system so today people have two choices either to create a new interface or introduce a breaking change, both of these options might be bad, depends on your position. Now, adding functionality through extension methods can solve the problem but sometimes it can be considered a hack to a faulty design, just as an extreme example extending a collection with a This is how I think about extension methods "if all you have is a hammer, everything looks like a nail". |
Re:
You can't provide a custom implementation of an extension method. Example: interface IA {
public bool Something { get { return true; } }
}
class C : IA {
public bool Something { get { return false; } }
} |
Stating that abstract classes are dated is a bold statement to make. In object-oriented hierarchies don't come from the object themselves but from the way we perceive reality and think about objects so it's really about the way we (humans) tend to organize information. Objects aren't hierarchical but the relations between them might be for example your own family tree.
I think that you refer to Extension methods wouldn't help here. |
@rolfbjarne Yes, well, you can "extend" the object and "pretend" that somehow you extended the type. :) |
I think you can. I haven't checked the code below, but I think it would work: public interface IA
{
bool Something { get; }
}
public static class IAExtentions
{
public static bool SomethingElse(this IA a) => true;
}
class C : IA
{
public bool Something => false;
public bool SomethingElse() => false;
} The Of course, if the rules on where extension methods can be defined were relaxed, the interface could be modified to the following, to avoid creating a new type just for the extension, and without breaking existing implementations of public interface IA
{
bool Something { get; }
public static bool SomethingElse(this IA a) => true;
} But that may be taken the relaxation of the rules too far. |
I may be wandering into the realms of "devils advocate" here, but taking the definition of the open/closed principle as ""software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" (taken from Wikipedia), then surely using extension methods to extend an existing interface is exactly what we should do, rather than being a hack? |
It is just one perspective that we use to perceived a world. And which I was state, it outdated Today there are many approach try to workaround to do composition object. Because that what we really use to construct object in factory. object is composed. Many times some object derived from something but lose some functionality. It does not hierarchy but mutation
My family tree is really great example. I may have something similar to my parents but I have many aspect lose from each of them. Object-oriented model hierarchy is wrong way to interpret real world. It can only has one parent and it can only expand If I would represent my family tree I would use tree node, not object inheritance. I think relation is data. Not an object structure Object that we made in programming also don't like a natural object. It mostly like artificial object that, as I said, construct from factory, which almost composed of various interface and module
That's why I don't said it bad. I just said it outdated. Because in the old day, that circumstance make an object-oriented hierarchy perspective most reasonable. But now we have better perspective and better tool we can revise that it have another approach which might be better In my opinion interface IStream : IReadableStream,IWriteableStream,ISeekableStream{}
class MemoryStream : IStream { }
class HttpRequestStream : IWriteableStream { }
class HttpResponseStream : IReadableStream { } |
There are many interpretation to this principle, some even contradicts one another so let's stick to the wiki one: "entity can allow its behaviour to be extended without modifying its source code." Now, you're right you can "extend" the object with new behaviour without ever touching the original class but it's important to note that you're not actually extending anything but adding new behaviour to existing objects, after all, extension methods are just static methods in disguises and we're basically passing objects to them so in some cases where things are dependent on the type it won't work, so here are two scenarios where it won't work:
Beyond these two technical points there are also authors of frameworks and libraries that want to deliver new functionality to their existing types without breaking anyone, just a matter of decision and with this proposal their consumers can override the default methods whenever they want at their own time.
Again, it really depends, if by design I have an interface that is missing core functionality then using extension methods to do that would be a "hack" because I can't touch the interface but if default interface methods would exist then I could add the functionality to the interface itself. I guess that you're more pragmatic in the sense that it doesn't bother you that core functionality isn't part of the interface as long as you have access to it whereas in my mind extension methods are used to provide additional functionality on top of existing one, I'd usually put them in separate namespace and/or different assembly. |
I didn't say you would use inheritance to implement it I just said that hierarchies exist in the relations that exist between objects but your nodes can actually be hierarchical for example
You're right, it is an opinion and a choice but you can read about why they designed it the way they did here. |
I would like to argue that this is the underlying mechanism of class and object instance method anyway. Extension method just expose that underlying mechanism to the surface This is the perspective that was actually blown my mind. Instance method is just actually function that always pass instance object as How instance method actually differ from extension method? And I think it is the core concept as golang. You can write new method for any class in golang like extension method. It very powerful and it actually work like charm |
They differ in the rules that are applied to them for example instance methods can be virtual and as such calls to them are polymorphic whereas static methods cannot be made virtual and aren't subject to polymorphism. Now, you copied only part of the sentence but my point above was that when things are dependent on the type then extension methods will fail to work in some circumstances because they operate on an instance of the type and aren't part of the type itself. Just because an instance of the type is passed to an instance method at run-time doesn't mean that when you pass an object to a static method it makes it the same thing. |
That's a shame, but I guess there was no harm in trying 😉 |
I might not have understood it correctly, but shouldn't you be able to call default interface methods on concrete classes that don't override them? public interface ITest
{
void Default() => Console.WriteLine("With Default Impl");
void NoDefault();
}
public class TestImplementer : ITest
{
public void NoDefault() => Console.WriteLine("Without Default Impl");
} I would expect to be able to do the following: var tester = new TestImplementer();
tester.Default();
tester.NoDefault(); But in the following SharpLab example it won't compile. |
You need to cast the class to the interface first ( |
Or explicity declare it: ITest tester = new TestImplementer();
tester.Default();
tester.NoDefault(); |
I suppose you could do public static class TestExtensions
{
public static void Default(this ITest iTest)
{
Console.WriteLine("Through extension");
iTest.Default();
}
} But I'm sure it has it's own issues in terms of choosing the correct overload. |
Are there plans to support default interface methods in .Net Framework? (as opposed to .Net Core) |
@junkbondtrader there are no plans to rev the CLR for framework to understand DIM IL optcodes |
Thank you @jmarolf |
Did this make the cut for 8.0? |
Yes, but not conversion operators |
This page should probably be updated: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods |
One thing I wish we could be compromised I just think if DIM would always required to be explicitly implement. It could avoid both unintentionally collision and diamond inheritance while keeping all other benefit except easiness of assume implementation ps. revisiting because today I have read this blog ps2. Have made #3906 for allow this behaviour as compiler flag |
The crux of the article is:
Probably this guy hasn't heard about the concept of typeclasses: https://en.wikipedia.org/wiki/Type_class They are an alternative to OOP-like inheritance (or rather old Java-like inheritance as OOP doesn't preclude multiple inheritance and in fact popular languages like C++ have multiple inheritance) and are akin to Java and C# interfaces (as they only define behaviour, not state). Type classes are present in Rust (traits), Swift (protocols), Scala (in Scala 2 they are emulated using implicits and OOP, but Scala 3 has more opinionated and purpose built syntax) and of course Haskell where they originated. Type classes can provide default implementations just like interfaces in Java 8+ or C# 8+. Are people complaining that Rust, Swift, Scala, Haskell, etc are broken because of default type class method implementations? Haven't heard that even once. One of the primary use cases for default interface methods in Java were new functionalities for collections. Base interfaces got new default interface methods with correct but potentially inefficient implementation. Concrete subclasses were free to override these default implementations with more efficient ones. Since default interface methods in Java are virtual (like all other non-private non-static methods) this works as intended even when type of variable (holding reference to a collection) is not precise (i.e. it has type |
Based on StackOverflow question Can someone explain the following implicit interface implementation with interface IA
{
public string Method1() => "IA.Method1";
public string Method2() => "IA.Method2";
}
class A : IA { }
abstract class B : A { }
class C : B
{
public string Method1() => "C.Method1";
public virtual string Method2() => "C.Method2";
} This code IA o = new C();
Console.WriteLine(o.Method1());
Console.WriteLine(o.Method2()); makes this output:
If I remove
Bug or feature? How must it work by design? |
Might be a question for the runtime repo? Looks like the IL emitted by the compiler in both cases is:
|
Nope, this is a Roslyn/C# question - Roslyn secretly turns If a method is not virtual, it will not implement the interface from the runtime perspective. |
This looks like a runtime bug to me. According to the following IL, which looks like I would expect it to look, type C doesn't claim to implement IA and none of the methods claim to explicitly implement methods from IA.
|
The runtime specifically implements default interface methods as a fallback. Only if the "old algorithm" cannot come up with an implementation, default interface methods are considered. In If you believe this to be a bug, let's move this to the runtime repo. I don't own the CoreCLR type system anymore and people in charge of it should be aware. |
|
For example, neither method in C2 implements the interface in this code https://sharplab.io/#v2:EYLgtghgzgLgpgJwDQxAgrgOyQExAagB8ABAJgAJiBGAdgFgAoAb0fLcqoDZKAWcgWQgBLTAAoAlK3YsG7OeQAqcWKMxwA7uQDCE8QG4p8xcpiqN20roOz2AX0PkH1bsT5KVASQCC5APaSbNhkjNmoAOmoATlFfMP44GAALXxwqKwc5cKiYuITknEtxfQd7BlLGEXgEADMIAGM4cm9GYPZiAGYOAAYBPJS08XIAXgA+cgAib1yk/vHrTM7qHviZgolhscmvafzSOcZyhjJyHxAmnyZyQ4hgWAR6mEoKACFyM4urxkZjrTfyZ5aTkWVGWfVS61GEy0O1m8zanQAbkIEDB0BAADbdXqrQobKEwgr7MpfI4ULwUM7NZhA8hIlFozFLbH5AZ48bkglUIkLWnI1EYrErXYQzYcoUpPbWa63GD3OqPY7PCknCiXQ4/ZVKwGBSjA0GrVmQ8ZaUic7nw3n0gVM8VrQZGk0EyUHRhAA==:
Presence of DIM should make no difference here. |
|
This is a valid thing to do in IL. C# won't compile it, but it works fine in IL: interface IA
{
void X();
}
abstract class A : IA
{
// NOTE: no implementation for IA. C# would yell. IL or runtime don't care though.
}
class B : A
{
public virtual void X()
{
Console.WriteLine("Hi");
}
} |
Interesting. Changing the runtime behavior would be a breaking change then. |
Open issues are being tracked at #406
LDM history:
The text was updated successfully, but these errors were encountered: