协变返回结果Covariant returns
总结Summary
支持 协变返回类型。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.
动机Motivation
代码中的一种常见模式是,必须使用不同的方法名称来解决语言约束,而重写必须返回与重写的方法相同的类型。It 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. 例如,在 Roslyn 代码库中,我们将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 design
这是 c # 中 协变返回类型 的规范。This 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 Override
类重写方法上的现有约束The existing constraint on class override methods
- 重写方法和重写的基方法具有相同的返回类型。The override method and the overridden base method have the same return type.
已修改为is modified to
- 如果方法具有值 return,则重写方法必须具有可转换的返回类型 (; 如果该方法具有值返回,则返回类型不是 引用返回 值,) 隐式引用转换为重写基方法的返回类型。The override method must have a return type that is convertible by an identity conversion or (if the method has a value return - not a ref return) implicit reference conversion to the return type of the overridden base method.
以下附加要求将追加到该列表:And the following additional requirements are appended to that list:
- 重写方法必须具有一个可通过标识转换转换的返回类型,如果该方法具有值返回,则返回类型为 (; 如果该方法具有 值) 返回,则返回 类型为对重写方法的 (直接或间接) 基类型中声明的重写基方法的返回类型的隐式引用转换。The override method must have a return type that is convertible by an identity conversion or (if the method has a value return - not a ref return) implicit reference conversion to the return type of every override of the overridden base method that is declared in a (direct or indirect) base type of the override method.
- 重写方法的返回类型必须至少具有与可访问 性域) (重写方法相同的可访问性。The override method's return type must be at least as accessible as the override method (Accessibility domains).
此约束允许类中的重写方法 private
具有 private
返回类型。This constraint permits an override method in a private
class to have a private
return type. 但是,它要求 public
类型中的重写方法 public
具有 public
返回类型。However it requires a public
override method in a public
type to have a public
return type.
类属性和索引器替代Class Property and Indexer Override
类重写属性的现有约束The existing constraint on class override properties
重写属性声明应将完全相同的可访问性修饰符和名称指定为继承的属性,并且应在
重写的类型和继承的属性之间进行标识转换。An overriding property declaration shall specify the exact same accessibility modifiers and name as the inherited property, and there shall be an identity conversionbetween the type of the overriding and the inherited property. 如果继承的属性只有一个访问器 (也就是说,如果继承的属性是只读的或只写的) ,则重写属性应仅包括该访问器。If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property shall include only that accessor. 如果继承的属性包含两个访问器 (也就是说,如果继承的属性是读写) ,则重写属性可以包含单个访问器或两个访问器。If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors.
已修改为is modified to
重写属性声明应将完全相同的可访问性修饰符和名称指定为继承的属性,并且 如果继承的属性是只读的,并且具有值 return (而不) 是从重写属性 的类型到继承属性类型的隐式引用转换),则应进行标识转换或 (。An overriding property declaration shall specify the exact same accessibility modifiers and name as the inherited property, and there shall be an identity conversion or (if the inherited property is read-only and has a value return - not a ref return) implicit reference conversion from the type of the overriding property to the type of the inherited property. 如果继承的属性只有一个访问器 (也就是说,如果继承的属性是只读的或只写的) ,则重写属性应仅包括该访问器。If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property shall include only that accessor. 如果继承的属性包含两个访问器 (也就是说,如果继承的属性是读写) ,则重写属性可以包含单个访问器或两个访问器。If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors. 重写属性的类型必须至少具有与可访问 性域) (重写属性相同的可访问性。The overriding property's type must be at least as accessible as the overriding property (Accessibility domains).
下面的草案规范的其余部分建议更进一步扩展接口方法的协变返回,以备稍后考虑。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 Override
使用 c # 8.0 添加 DIM 功能,将添加到接口中允许的成员种类,进一步增加了对成员的支持 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. 这些规则按照为 override
类指定的成员规则进行,但存在以下差异:These follow the rules of override
members as specified for classes, with the following differences:
类中的以下文本:The following text in classes:
重写声明重写的方法称为 重写基方法。The method overridden by an override declaration is known as the overridden base method. 对于
M
在类中声明的重写方法C
,重写的基方法是通过检查的每个基类确定的,该方法C
从的直接基类开始,C
然后继续使用每个连续的直接基类,直到至少有一个可访问方法与M
类型参数替换后具有相同签名的可访问方法。For an override methodM
declared in a classC
, the overridden base method is determined by examining each base class ofC
, starting with the direct base class ofC
and continuing with each successive direct base class, until in a given base class type at least one accessible method is located which has the same signature asM
after substitution of type arguments.
为提供接口的相应规范:is given the corresponding specification for interfaces:
重写声明重写的方法称为 *重写的基方法 _。The method overridden by an override declaration is known as the *overridden base method _. 对于
M
在接口中声明的重写方法I
,重写的基方法是通过检查的每个直接或间接基接口确定的I
,收集一组接口,该接口声明一个具有与M
类型参数替换相同的签名的可访问方法。For an override methodM
declared in an interfaceI
, the overridden base method is determined by examining each direct or indirect base interface ofI
, collecting the set of interfaces declaring an accessible method which has the same signature asM
after substitution of type arguments. 如果这组接口具有一个 _most 派生类型 *,并且此集合中的每个类型都有一个标识或隐式引用转换,并且该类型包含唯一的此类方法声明,则这是 重写的基方法。If this set of interfaces has a _most derived type*, to which there is an identity or implicit reference conversion from every type in this set, and that type contains a unique such method declaration, then that is the overridden base method.
同样,允许 override
接口中的属性和索引器在 15.7.6 Virtual、sealed、override 和 abstract 访问器 中为类指定。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
存在类声明的名称查找 override
当前修改名称查找的结果,方法是在类层次结构中从最派生的声明(从 override
标识符的 (限定符的类型开始)起,或者在没有 this
) 限定符时进行。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 derived override
declaration in the class hierarchy starting from the type of the identifier's qualifier (or this
when there is no qualifier). 例如,在 12.6.2.2 中,有For example, in 12.6.2.2 Corresponding parameters we have
对于在类中定义的虚拟方法和索引器,将从从接收方静态类型开始时找到的函数成员的第一个声明或重写中选取参数列表,并搜索其基类。For virtual methods and indexers defined in classes, the parameter list is picked from the first declaration or override of the function member found when starting with the static type of the receiver, and searching through its base classes.
为此,我们将to this we add
对于在接口中定义的虚拟方法和索引器,将从派生程度最大的类型中找到的函数成员的声明或重写中选取参数列表,这些类型包含函数成员的重写声明。For virtual methods and indexers defined in interfaces, the parameter list is picked from the declaration or override of the function member found in the most derived type among those types containing the declaration of override of the function member. 如果不存在任何唯一的此类类型,则会发生编译时错误。It is a compile-time error if no unique such type exists.
对于属性或索引器访问的结果类型,现有文本For the result type of a property or indexer access, the existing text
- 如果 I 标识实例属性,则结果是具有关联的实例表达式 E 和关联类型(属性的类型)的属性访问。If I identifies an instance property, then the result is a property access with an associated instance expression of E and an associated type that is the type of the property. 如果 T 是类类型,则将从从 T 开始时找到的属性的第一个声明或重写中选取关联的类型,并搜索其基类。If T is a class type, the associated type is picked from the first declaration or override of the property found when starting with T, and searching through its base classes.
扩充了is augmented with
如果 T 是接口类型,则会从在 T 的派生或其直接或间接基接口中找到的属性的声明或重写中选取关联的类型。If T is an interface type, the associated type is picked from the declaration or override of the property found in the most derived of T or its direct or indirect base interfaces. 如果不存在任何唯一的此类类型,则会发生编译时错误。It is a compile-time error if no unique such type exists.
应在 12.7.7.3 索引器访问 中进行类似的更改A similar change should be made in 12.7.7.3 Indexer access
在 12.7.6 调用表达式 中,我们增加了现有文本In 12.7.6 Invocation expressions we augment the existing text
- 否则,结果为具有方法或委托返回类型的关联类型的值。Otherwise, the result is a value, with an associated type of the return type of the method or delegate. 如果调用的是实例方法,并且接收方属于类类型 T,则会从第一个声明中选取关联的类型,或者在从 T 开始并搜索其基类时从找到的方法中选取关联的类型。If the invocation is of an instance method, and the receiver is of a class type T, the associated type is picked from the first declaration or override of the method found when starting with T and searching through its base classes.
替换为with
如果调用的是实例方法,并且接收方是接口类型 T,则会从 T 及其直接和间接基接口的派生接口中找到的方法的声明或重写来选取关联的类型。If the invocation is of an instance method, and the receiver is of an interface type T, the associated type is picked from the declaration or override of the method found in the most derived interface from among T and its direct and indirect base interfaces. 如果不存在任何唯一的此类类型,则会发生编译时错误。It is a compile-time error if no unique such type exists.
隐式接口实现Implicit Interface Implementations
规范的此部分This section of the specification
出于接口映射的目的,类成员将
A
在以下情况下匹配接口成员B
:For purposes of interface mapping, a class memberA
matches an interface memberB
when:
A
和B
是方法,且和的名称、类型和形参列表A
B
完全相同。A
andB
are methods, and the name, type, and formal parameter lists ofA
andB
are identical.A
和B
是属性、和的名称和类型A
B
相同,并且具有与A
(相同的访问器B
,A
如果它不是显式接口成员实现) 。A
andB
are properties, the name and type ofA
andB
are identical, andA
has the same accessors asB
(A
is permitted to have additional accessors if it is not an explicit interface member implementation).A
和B
是事件,而和的名称和类型A
B
是相同的。A
andB
are events, and the name and type ofA
andB
are identical.A
和B
是索引器、和的类型参数列表和形参列表A
B
相同,并且A
具有与 (相同的访问器,B
A
如果它不是显式接口成员实现) 。A
andB
are indexers, the type and formal parameter lists ofA
andB
are identical, andA
has the same accessors asB
(A
is permitted to have additional accessors if it is not an explicit interface member implementation).
按如下所示进行修改:is modified as follows:
出于接口映射的目的,类成员将
A
在以下情况下匹配接口成员B
:For purposes of interface mapping, a class memberA
matches an interface memberB
when:
A
和B
是方法,且和的名称和形参列表相同,并且的返回类型可A
B
A
B
通过隐式引用转换的标识转换为返回类型B
。A
andB
are methods, and the name and formal parameter lists ofA
andB
are identical, and the return type ofA
is convertible to the return type ofB
via an identity of implicit reference convertion to the return type ofB
.A
和B
是属性、和的名称A
B
相同,具有与A
(相同的访问器B
,A
如果它不是显式接口成员实现) ,则的类型可A
转换为的返回类型;B
如果A
是 readonly 属性,则为隐式引用转换。A
andB
are properties, the name ofA
andB
are identical,A
has the same accessors asB
(A
is permitted to have additional accessors if it is not an explicit interface member implementation), and the type ofA
is convertible to the return type ofB
via an identity conversion or, ifA
is a readonly property, an implicit reference conversion.A
和B
是事件,而和的名称和类型A
B
是相同的。A
andB
are events, and the name and type ofA
andB
are identical.A
和B
为索引器,和的形参列表A
B
相同,具有与A
(相同的访问器B
,A
如果它不是显式接口成员实现) ,则的类型A
可转换为的返回类型;B
如果A
为只读索引器,则为隐式引用转换。A
andB
are indexers, the formal parameter lists ofA
andB
are identical,A
has the same accessors asB
(A
is permitted to have additional accessors if it is not an explicit interface member implementation), and the type ofA
is convertible to the return type ofB
via an identity conversion or, ifA
is a readonly indexer, an implicit reference conversion.
从技术上讲,这是一项重大更改,如以下程序所示: C1。"今天,但会打印" C2。"M"。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 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 兼容性问题API Compatibility Implications
TBDTBD
未结的问题Open Issues
该规范并不表示调用方如何获取更精确的返回类型。The 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(); }
请注意,在中 I3
,方法 I1.M()
和已 I2.M()
"合并"。Note that in I3
, the methods I1.M()
and I2.M()
have been “merged”. 实现时 I3
,必须同时实现它们。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? N 应该是什么?What should N be?
我建议您允许实现 I1.M
或 I2.M
(,而不是同时实现) ,并将其视为实现两者。I suggest that we permit implementing either I1.M
or I2.M
(but not both), and treat that as an implementation of both.
缺点Drawbacks
- [] 每个语言更改都必须为其本身付费。[ ] Every language change must pay for itself.
- [] 我们应该确保性能合理,甚至在深层继承层次结构中也是如此[ ] We should ensure that the performance is reasonable, even in the case of deep inheritance hierarchies
- [] 我们应该确保翻译策略的项目不会影响语言语义,即使在使用旧编译器的新 IL 时也是如此。[ ] We should ensure that artifacts of the translation strategy do not affect language semantics, even when consuming new IL from old compilers.
备选方法Alternatives
我们可以略微放宽语言规则,在源中,We 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
- [] 编译为使用此功能的 Api 将在较旧版本的语言中工作?[ ] How will APIs that have been compiled to use this feature work in older versions of the language?
设计会议Design meetings
- 一些讨论 https://github.com/dotnet/roslyn/issues/357 。some discussion at https://github.com/dotnet/roslyn/issues/357.
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-01-08.md
- 针对决策的离线讨论,以支持仅在 c # 9.0 中重写类方法。Offline discussion toward a decision to support overriding of class methods only in C# 9.0.