-
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
Covariant Return Types - Draft Specification #2844
Comments
That's about 80% of use cases I had in mind :( -- there's at least two kind of usages: (1) when you want to override members with more specific types I think the latter usage is as important as the first one. Could we say we don't consider shadowing members for implicit implementation with covariant return types (probably regardless of the presence of the |
I don't know what this means. Members from interfaces are not inherited into classes, so you must be talking about inheriting from interfaces into interfaces. Can you give an example? |
I think this would be considered an "implicit implementation"? class Base {
public string M() => null;
}
interface Interface {
object M();
}
// implicitly implements `Interface.M` with covariant return type
class Derived : Base, Interface {}
|
@alrz In your example, nothing from the interface is inherited into the class, so nothing would be hidden. Because it would be a breaking change to consider class Base {
public string M() => null;
}
interface Interface {
object M();
}
class Derived : Base, Interface
{
object Interface.M() => base.M();
} |
Nitpick - I think you have a couple of typos in the section on interface mapping: For purposes of interface mapping, a class member A matches an interface member B when:
|
I can see a particular use for this when implementing a hierarchy of immutable types - at the moment, mutating methods (that return a near-clone of the original) declared on a base class cause a bit of grief to subclasses. It would be nice, for example, when implementing immutable class public class Role {
// ...
public virtual Role FinishingAt(DateTimeOffset instant) { ... }
// ...
}
public class StudentRole {
// ...
public override StudentRole FinishingAt(DateTimeOffset instant) { ... }
// ...
} At the moment (C#8), the only way to achieve this result is a bit messy (the implementation of Getting rid of all that boilerplate (which is a fertile ground for bugs) would be useful. |
The same program in Java calls class Base {
public String M() { System.out.println("Base.M"); return null; }
}
interface Interface {
default Object M() { System.out.println("Interface.M"); return null; }
// or Object M();
}
class Derived extends Base implements Interface {}
public class Main {
public static void main(String[] args) {
Interface x = new Derived();
x.M(); // prints "Base.M"
}
} So the implicit implementation takes precedence over default implementation. It's shame that the fact that in C# default impl is introduced before covariant return types, is limiting the feature. One solution is that in C# we define a lower precedence for covariant return types (vs identical return types or default implementations) when we're looking for an implicit implementation. so that both programs would call Is that feasible in practice? |
I don't understand the suggestion well enough to comment. |
I know that I've written code where this would happen - but I'm wondering how much of a breaking change this would be ... ... at least in the examples I remember, the purpose of Is there any data to compare "number of places where a different method would be called" vs "number of places where behaviour would change"? My intuition is that this change would fall into the same category as the change to how lambdas captured loop variables: technically breaking, but effectively benign. But, as I said, that's just my (limited) intuition. |
Avoiding implicit implementations entirely doesn't seem blocking, but it would be nice if it could work in basic cases. Could we change the search for mapping members to consider implicit implementations with different covariant returns iff there is no other implementation (including a default one)? That would be an error today, as the interface would not be properly implemented. |
@gafter, perhaps @agocke articulated it better. Afterall, the existence of such member is the only factor that makes it a breaking change. However, it's not like how member lookup works today wrt to closeness: we stop if a candidate set has a winner in a closer scope, but if we want to account for covariant return types only after we didn't find an implementation under current rules, we may need to run a second lookup considering the ones with covariant return types. |
@agocke I don’t see how that would work in binary compatibility scenarios. If a new version of the interface is released with a default implementation then the runtime would change to use that one instead of the implementation from the base class? |
Good point. I can't see how this could work for default implementations. |
Prioritization of runtime support for covariant return types is being tracked internally at https://devdiv.visualstudio.com/DevDiv/_queries/edit/1009909 |
Added Open IssuesIf we have the following interfaces: interface I1 { I1 M(); }
interface I2 { I2 M(); }
interface I3: I1, I2 { override I3 M(); } Note that in Generally, we require an explicit implementation to refer to the original method. The question is, in a class class C : I1, I2, I3
{
C IN.M();
} What does that mean here? What should N be? I suggest that we permit implementing either |
I am planning to check this spec in under #3394 and then close this issue. |
Spec is actually now at https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/covariant-returns.md |
So it's also possible to use with get only properties, but what would prevent this scenario from being possible? Class A defines an abstract get only property. Class B overrides the property using a covariant type and adds a setter to it. When the property is referenced through an instance defined as A, it is get only and if the instance is defined as B, it can be set but the value must be of a type compatible with the type defined by B. |
Abstract
This is a draft proposed specification for covariant return types in C#. Our intent is to permit the override of a method to return a more derived return type than the method it overrides, and similarly to permit the override of a read-only property to return a more derived return type. Callers of the method or property would statically receive the more refined return type from an invocation, and overrides appearing in more derived types would be required to provide a return type at least as specific as that appearing in overrides in its base types.
This is a first draft, so it was necessarily invented from scratch. Many of the ideas introduced are tentative and may be revised or eliminated in future revisions.
Class Method Override
The existing constraint on class override methods
is modified to
And the following additional requirements are appended to that list:
This constraint permits an override method in a
private
class to have aprivate
return type. However it requires apublic
override method in apublic
type to have apublic
return type.Class Property and Indexer Override
The existing constraint on class override properties
is modified to
Interface Method, Property, and Indexer Override
Adding to the kinds of members that are permitted in an interface with the addition of the DIM feature in C# 8.0, we further add support for
override
members along with covariant returns. These follow the rules ofoverride
members as specified for classes, with the following differences:The following text in classes:
is given the corresponding specification for interfaces:
We similarly permit
override
properties and indexers in interfaces as specified for classes in 15.7.6 Virtual, sealed, override, and abstract accessors.Name Lookup
Name lookup in the presence of class
override
declarations currently modify the result of name lookup by imposing on the found member details from the most derivedoverride
declaration in the class hierarchy starting from the type of the identifier's qualifier (orthis
when there is no qualifier). For example, in 12.6.2.2 Corresponding parameters we haveto this we add
For the result type of a property or indexer access, the existing text
is augmented with
A similar change should be made in 12.7.7.3 Indexer access
In 12.7.6 Invocation expressions we augment the existing text
with
Implicit Interface Implementations
This section of the specification
is modified as follows:
This is technically a breaking change, as the program below prints "C1.M" today, but would print "C2.M" under the proposed revision.
Due to this breaking change, we might consider not supporting covariant return types on implicit implementations.
Constraints on Interface Implementation
We will need a rule that an explicit interface implementation must declare a return type no less derived than the return type declared in any override in its base interfaces.
API Compatibility Implications
TBD
Open Issues
If we have the following interfaces:
Note that in
I3
, the methodsI1.M()
andI2.M()
have been “merged”. When implementingI3
, it is necessary to implement them both together.Generally, we require an explicit implementation to refer to the original method. The question is, in a class
What does that mean here? What should N be?
I suggest that we permit implementing either
I1.M
orI2.M
(but not both), and treat that as an implementation of both.LDM notes:
The text was updated successfully, but these errors were encountered: