-
Notifications
You must be signed in to change notification settings - Fork 4.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
Proposal: Extension Implementations #8127
Comments
I'm not sure that I see how this works. #258 relies on the concrete class actually implementing the interface so the CLR will expect the slots for the implementing methods which it then wires up to the default implementations in the absence of an override provided by the class. Without that there are no slots to wire up or to call. The instance of |
@HaloFour Yes, to cast an object to an interface which we provided the implementation afterwards the vtable shall be handled otherwise. I think this is something that is required to implement traits. But you can freely use it with generic constraints e.g. void F<T>(T obj) where T : IHasM { }
F(new A()); Then we can dispatch the right method from constrained |
I'm not sure I see how you can "handle" slots in a vtable that don't exist. And being able to state that any given type now is a given interface seems quite messy and could have wide ramifications on existing code. If I had an interface check buried somewhere deep in a library and all of a sudden some unexpected classes started to pass that check it could cause unexpected results. |
@HaloFour These are known as trait objects in Rust, when you define a function like class A { public void M() { } }
trait THasM { void M(); }
interface IHasM { void M(); }
implement IHasM for A { }
implement THasM for A { }
void F1(IHasM arg) { }
void F2(THasM arg) { }
void F3<T>(T arg) where T : IHasM { }
void F4<T>(T arg) where T : THasM { }
F1(new A()); // error as usual
F2(new A()); // ok, vtable pointer
F3(new A()); // ok, virtual extension methods
F4(new A()); // ok, same as above |
@alrz I've not seen much discussion regarding the actual implementation of traits in C# but I don't think it would be comparable to Rust. I'd imagine that they'd be more like traits in Scala: interfaces where some of the methods provide default implementations, and that types are then required to extend directly. You'd need those method slots in the vtable to exist in order to be able to dispatch them, and classes that don't adopt that trait directly would not have those slots. An external mechanism would not be able to modify the vtable of an instance, especially of a type outside of that assembly. You'd also have generic type constraints enforced by the CLR which would definitely forbid all of I just don't see it. Rust doesn't fit on the CLR. |
@HaloFour Just like #258 this needs CLR support of course, but if |
@alrz That it does, but that's a much more limited change to the CLR keeping to the existing type structure and vtable mechanics. The only difference there being that if an interface defines a member that a concrete class does not implement but there is a known default implementation for the interface that the CLR will automatically assign the default implementation to the existing vtable slot rather than failing to load the type. Your proposal, however, is a very aggressive change involving adding a completely new form of typing (traits) as a first-class citizen along-side interfaces and basically making vtables a free-for-all. My opinion is that I don't like the extent to which this proposal goes and I don't like that any type anywhere could have it's core contracts amended by an external actor. I don't think that someone should be able to arbitrarily fake that |
@HaloFour That's not where I'm going with this, I just mentioned traits to make my point and clear up the differences, as I said this has nothing or a little to do with traits but the syntax is flexible enough to be reused for trait implementations as well. You can think of |
@alrz Whatever it's called I'd think that the CLR mods would remain the same. You couldn't achieve Rust's type system is trait-based, the CLR is not. Rust owns it's type system outright, C# does not. I just don't see such radical changes to the CLR happening rather than a more iterative approach to support traits/mixins as done by other languages more similar to C# where a class must directly adopt the trait. |
Even without interfaces, I think internal extension Random {
public bool NextBool() {
return this.Next(2) == 0;
}
}
// instead of
internal static class RandomExtensions {
public static bool NextBool(this Random self) {
return self.Next(2) == 0;
}
} This'd also work for extension properties, operators etc. I'll update the opening post to use this syntax. |
Maybe, but we already have a syntax for extension methods. Also, if written like an instance method it might be even less clear that the |
@HaloFour Currently, I don't know how extension properties would look like, because there is no way to define |
Yes, we already have a proposal covering extension properties (and other things): #112 That's not related to some kind of "extension implementation" though so it doesn't seem particularly related to this proposal.
Sometimes this is quite intentional, but indeed #227 would at least be able to warn when it's not. Either way since extension methods are static and not invoked via |
@HaloFour Yes it's not particularly related but I wanted a syntax to cover both cases and be consistent with C# overall (
So there is no CLR enforcement. Technically, you should check |
We are now taking language feature discussion in other repositories:
Features that are under active design or development, or which are "championed" by someone on the language design team, have already been moved either as issues or as checked-in design documents. For example, the proposal in this repo "Proposal: Partial interface implementation a.k.a. Traits" (issue 16139 and a few other issues that request the same thing) are now tracked by the language team at issue 52 in https://github.com/dotnet/csharplang/issues, and there is a draft spec at https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md and further discussion at issue 288 in https://github.com/dotnet/csharplang/issues. Prototyping of the compiler portion of language features is still tracked here; see, for example, https://github.com/dotnet/roslyn/tree/features/DefaultInterfaceImplementation and issue 17952. In order to facilitate that transition, we have started closing language design discussions from the roslyn repo with a note briefly explaining why. When we are aware of an existing discussion for the feature already in the new repo, we are adding a link to that. But we're not adding new issues to the new repos for existing discussions in this repo that the language design team does not currently envision taking on. Our intent is to eventually close the language design issues in the Roslyn repo and encourage discussion in one of the new repos instead. Our intent is not to shut down discussion on language design - you can still continue discussion on the closed issues if you want - but rather we would like to encourage people to move discussion to where we are more likely to be paying attention (the new repo), or to abandon discussions that are no longer of interest to you. If you happen to notice that one of the closed issues has a relevant issue in the new repo, and we have not added a link to the new issue, we would appreciate you providing a link from the old to the new discussion. That way people who are still interested in the discussion can start paying attention to the new issue. Also, we'd welcome any ideas you might have on how we could better manage the transition. Comments and discussion about closing and/or moving issues should be directed to #18002. Comments and discussion about this issue can take place here or on an issue in the relevant repo. This particular feature request would be satisfied by a combination of type classes (dotnet/csharplang#110) and default interface methods (dotnet/csharplang#52), both of which are under consideration for C#. |
Consider this two interfaces.
So every class that implements
Iterator
is effectively anIterable
. To establish this relation, one might think of default methods:But this doesn't help because you can't say if the implementor is an
Iterator
. We might define the default method as generic constrained to anIterator
.But this design is somehow flawed (you might want more default generic implementations of the same method with different constraints) and limiting (you don't always have access to
Iterable
declaration).So you decide to declare an extension method for every
Iterator
.And now you didn't establish any relation to
Iterable
interface at all. You know that it does have a method namedGetIterator
(duck typing) but the compiler doesn't.So, here's my solution: define an implementation for every type that is an
Iterator
.Q: OK, What if we have other constraints as well? A: then you might define it as a generic implementation,
This is basically Rust's syntax for implementations but as an alternative you might suggest,
which is closer to Swift's extensions.
And how this would work? Via virtual extension methods (#258). The compiler generates a static class for extension methods on the target type (or on the constrained generic type).
Note that it is possible to add members without any interface in question, e.g.
It would be nice to be able to name the generated static class, then it's possible to refactor existing classes like
Enumerable
(using an analyzer, perhaps) in this manner without breaking existing code, otherwise, extension methods would be accessible via the target type itself, e.g.C.ExtensionMethodForC
.This can neatly cover the #7844 use case. Suppose we have two classes with no relation to each other.
As discussed in the aforementioned issue, this kind of duck typing can be inferred by the compiler but it cannot be done without a high risk of breaking existing code.
There’s an important restriction for implementations, either the interface or the type you’re implementing it for must be defined in the same assembly.
PS: (1) There are numerous open proposals regarding template/structural types, extension classes, etc, but none of them are directly related to #258 so I decided to open another one. (2) This is not about traits and zero-cost abstraction, but same syntax can be used for implementing traits for arbitrary types.
The text was updated successfully, but these errors were encountered: