Show Support covariant return types. Specifically, permit the override of a method to declare a more derived return type than the method it overrides, and similarly to permit the override of a read-only property to declare a more derived type. Override declarations 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. Callers of the method or property would statically receive the more refined return type from an invocation. MotivationIt is a common pattern in code that different method names have to be invented to work around the language constraint that overrides must return the same type as the overridden method. This would be useful in the factory pattern. For example, in the Roslyn code base we would have class Compilation ... { public virtual Compilation WithOptions(Options options)... } class CSharpCompilation : Compilation { public override CSharpCompilation WithOptions(Options options)... }Detailed designThis is a 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. Class Method OverrideThe existing constraint on class override (§14.6.5) 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 a private return type. However it requires a public override method in a public type to have a public return type. Class Property and Indexer OverrideThe existing constraint on class override (§14.7.6) properties
is modified to
The remainder of the draft specification below proposes a further extension to covariant returns of interface methods to be considered later. Interface Method, Property, and Indexer OverrideAdding 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 of override 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 LookupName 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 derived override declaration in the class hierarchy starting from the type of the identifier's qualifier (or this when there is no qualifier). For example, in 12.6.2.2 Corresponding parameters we have
to 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 ImplementationsThis 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. using System; interface I1 { object M(); } class C1 : I1 { public object M() { return "C1.M"; } } class C2 : C1, I1 { public new string M() { return "C2.M"; } } class Program { static void Main() { I1 i = new C2(); Console.WriteLine(i.M()); } }Due to this breaking change, we might consider not supporting covariant return types on implicit implementations. Constraints on Interface ImplementationWe 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 ImplicationsTBD Open IssuesThe specification does not say how the caller gets the more refined return type. Presumably that would be done in a way similar to the way that callers get the most derived override's parameter specifications. If we have the following interfaces: interface I1 { I1 M(); } interface I2 { I2 M(); } interface I3: I1, I2 { override I3 M(); }Note that in I3, the methods I1.M() and I2.M() have been “merged”. When implementing I3, 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 class C : I1, I2, I3 { C IN.M(); }What does that mean here? What should N be? I suggest that we permit implementing either I1.M or I2.M (but not both), and treat that as an implementation of both. Drawbacks
AlternativesWe could relax the language rules slightly to allow, in source, abstract class Cloneable { public abstract Cloneable Clone(); } class Digit : Cloneable { public override Cloneable Clone() { return this.Clone(); } public new Digit Clone() // Error: 'Digit' already defines a member called 'Clone' with the same parameter types { return this; } }Unresolved questions
Design meetings |