默认接口方法
注意
本文是特性规范。 此规范是功能的设计文档。 它包括建议的规范变更,以及功能设计和开发过程中所需的信息。 这些文章将持续发布,直至建议的规范变更最终确定并纳入当前的 ECMA 规范。
功能规范与已完成的实现之间可能存在一些差异。 这些差异记录在相关的语言设计会议 (LDM) 说明中。
可以在有关规范的文章中了解更多有关将功能规范子块纳入 C# 语言标准的过程。
支持者问题:https://github.com/dotnet/csharplang/issues/52
总结
添加对虚拟扩展方法的支持 - 接口中方法的具体实现。 实现此类接口的类或结构体必须有一个最具体的接口方法实现,该方法可以由类或结构体实现,也可以从其基类或接口继承。 虚拟扩展方法使 API 作者能够在未来版本中为接口添加方法,而不会破坏与该接口现有实现的源代码或二进制兼容性。
这些方法类似于 Java 的“默认方法”。
(基于可能的实现技术)此功能需要 CLI/CLR 的相应支持。 利用该功能的程序无法在早期版本的平台上运行。
动机
这一功能的主要动机是
- 默认接口方法使 API 作者能够在未来版本中为接口添加方法,而不会破坏与该接口现有实现的源代码或二进制兼容性。
- 此功能使 C# 能够与支持类似功能的 Android (Java) 和 iOS (Swift) API 进行互操作。
- 事实证明,添加默认接口实现提供了“特征”语言特性的元素 (https://en.wikipedia.org/wiki/Trait_(computer_programming))。 事实证明,特征是一种强大的编程技术 (http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf)。
详细设计
扩展了接口的语法以允许
- 声明常量、运算符、静态构造函数和嵌套类型的成员声明;
- 方法或索引器、属性或事件访问器的正文(即“默认”实现);
- 声明静态字段、方法、属性、索引器和事件的成员声明;
- 使用显式接口实现语法的成员声明;以及
- 显式访问修饰符(默认访问权限为
public
)。
带有主体的成员允许接口为没有提供自己的实现的类和结构体中的方法提供“默认”实现。
接口不能包含实例状态。 虽然现在允许使用静态字段,但接口中不允许使用实例字段。 接口中不支持实例自动属性,因为它们将隐式声明隐藏的字段。
静态方法和私有方法可以对用于实现接口公共 API 的代码进行有用的重构和组织。
接口中的方法重写必须使用显式接口实现语法。
在使用 variance_annotation 声明的类型参数的范围内声明类类型、结构类型或枚举类型是错误的。 例如,下面的 C
声明就是一个错误。
interface IOuter<out T>
{
class C { } // error: class declaration within the scope of variant type parameter 'T'
}
接口中的具体方法
这一功能的最简单形式是在接口中声明具体方法,即带有主体的方法。
interface IA
{
void M() { WriteLine("IA.M"); }
}
实现该接口的类无需实现其具体方法。
class C : IA { } // OK
IA i = new C();
i.M(); // prints "IA.M"
类 C
中 IA.M
的最终重写是 IA
中声明的具体方法 M
。 请注意,类不会从其接口继承成员,这一特性并未改变这种规则。
new C().M(); // error: class 'C' does not contain a member 'M'
在接口的实例成员中,this
具有封闭接口的类型。
接口中的修饰符
接口的语法被放宽,以允许在其成员上使用修饰符。 允许以下各项:private
、protected
、internal
、public
、virtual
、abstract
、sealed
、static
、extern
和 partial
。
除非使用了 sealed
或 private
修饰符,否则声明中包含主体的接口成员就是 virtual
成员。 virtual
修饰符可用于原本隐含为 virtual
的函数成员。 同样,尽管 abstract
是没有主体的接口成员的默认修饰符,但该修饰符可以显式指定。 可以使用 sealed
关键字声明非虚拟成员。
如果接口的 private
or sealed
函数成员没有主体,则属于一个错误。 private
函数成员可能没有修饰符 sealed
。
访问修饰符可用于所有允许使用的接口成员。 访问级别 public
是默认级别,但也可以显式提供。
未结的问题:我们需要明确规定访问修饰符(如
protected
和internal
)的确切含义,以及哪些声明可以重写它们(在派生接口中)或实现它们(在实现接口的类中)。
接口可声明 static
成员,包括嵌套类型、方法、索引器、属性、事件和静态构造函数。 所有接口成员的默认访问级别为 public
。
接口不得声明实例构造函数、析构函数或字段。
已关闭的问题:接口中是否应允许运算符声明? 可能不是转换运算符,而是其他运算符呢? 决定:允许使用运算符,但转换、相等和不等式运算符除外。
已关闭的问题:是否应允许在隐藏来自基接口的成员的接口成员声明中使用
new
? 决定:是。
已关闭的问题: 我们目前不允许在接口或其成员上使用
partial
。 这将需要一个单独的提议。 决定:是。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface
在接口中显式实现
显式实现允许程序员在编译器或运行时无法找到的接口中为虚拟成员提供最具体的实现。 允许实现声明显式实现特定的基接口方法,方法是在声明中限定接口名称(在这种情况下不允许使用访问修饰符)。 不允许隐式实现。
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
void IA.M() { WriteLine("IB.M"); } // Explicit implementation
}
interface IC : IA
{
void M() { WriteLine("IC.M"); } // Creates a new M, unrelated to `IA.M`. Warning
}
接口中的显式实现不得声明 sealed
。
接口中的公共 virtual
函数成员只能在派生接口中显式实现(在声明中用最初声明方法的接口类型限定名称,并省略访问修饰符)。 成员在实现的位置必须可访问。
重新抽象
在接口中声明的虚拟(具体)方法可以在派生接口中重新抽象化。
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
abstract void IA.M();
}
class C : IB { } // error: class 'C' does not implement 'IA.M'.
在 abstract
的声明中需要使用 IB.M
修饰符,以表示 IA.M
正在被重新抽象。
这在派生接口中非常有用,因为在派生接口中,方法的默认实现并不合适,应该由实现类来提供更合适的实现。
最具体的实现规则
我们要求每个接口和类在类型或其直接和间接接口中出现的每个虚拟成员都有一个最具体的实现。 最具体的实现是一种独特的实现,它比其他所有实现都更具体。 如果没有实现,则该成员本身被视为最具体的实现。
如果 M1
是在 T1
类型上声明的,M2
是在 T2
类型上声明的,并且以下任一情况下,一个实现 M1
被认为比另一个实现 M2
更具体。
-
T1
的直接或间接接口中包含T2
,或 -
T2
是接口类型,但T1
不是接口类型。
例如:
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
void IA.M() { WriteLine("IC.M"); }
}
interface ID : IB, IC { } // compiles, but error when a class implements 'ID'
abstract class C : IB, IC { } // error: no most specific implementation for 'IA.M'
abstract class D : IA, IB, IC // ok
{
public abstract void M();
}
public class E : ID { } // Error. No most specific implementation for 'IA.M'
最具体的实施规则确保程序员在冲突发生时明确地解决冲突(即由菱形继承引起的歧义)。
因为我们在接口中支持显式重新抽象,所以也可以在类中支持显式重新抽象
abstract class E : IA, IB, IC // ok
{
abstract void IA.M();
}
已关闭的问题:是否应该在类中支持显式接口抽象实现? 决定:否
此外,如果在类的声明中,某些接口方法的最具体实现是在接口中声明的抽象实现,则属于错误。 这是一条使用新术语重述的现有规则。
interface IF
{
void M();
}
abstract class F : IF { } // error: 'F' does not implement 'IF.M'
在一个接口中声明的虚拟属性,有可能在一个接口的 get
访问器中有一个最具体的实现,而在另一个接口的 set
访问器中有一个最具体的实现。 这被认为违反了最具体的实现规则,并会产生编译器错误。
static
和 private
方法
由于接口现在可能包含可执行代码,因此将普通代码抽象为专用方法和静态方法非常有用。 我们现在允许在接口中使用这些。
已关闭的问题:我们应该支持专用方法吗? 我们应该支持静态方法吗? 决定:同意
未结的问题:我们是否应该允许接口方法为
protected
、internal
或其他访问方式? 如果支持,语义是什么? 它们默认virtual
吗? 如果是这样,有办法让它们转换成非虚拟的吗?
已关闭的问题:如果支持静态方法,那么我们是否应该支持(静态)运算符? 决定:同意
基接口调用
本部分中的语法尚未实现。 这仍是一项积极的提议。
从具有默认方法的接口派生出来的类型中的代码,可以显式地调用该接口的“基”实现。
interface I0
{
void M() { Console.WriteLine("I0"); }
}
interface I1 : I0
{
override void M() { Console.WriteLine("I1"); }
}
interface I2 : I0
{
override void M() { Console.WriteLine("I2"); }
}
interface I3 : I1, I2
{
// an explicit override that invoke's a base interface's default method
void I0.M() { I2.base.M(); }
}
允许实例(非静态)方法通过使用语法 base(Type).M
命名,非虚拟地调用直接基接口中可访问实例方法的实现。 当由于菱形继承而需要提供的重写功能通过委托给一个特定的基本实现来解决时,这种方法非常有用。
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
override void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
override void IA.M() { WriteLine("IC.M"); }
}
class D : IA, IB, IC
{
void IA.M() { base(IB).M(); }
}
使用语法 virtual
访问 abstract
或 base(Type).M
成员时,要求 Type
包含唯一的最特殊重写用于 M
。
绑定基子句
接口现在包含类型。 这些类型可作为基接口在基子句中使用。 在绑定基子句时,可能需要知道基接口的集合,以便绑定这些类型(例如,在其中查找和解决受保护的访问权限)。 因此,接口的基子句的含义是循环定义的。 为了打破这种循环,我们添加了一条新的语言规则,与已为类设置的类似规则相对应。
在确定接口的 interface_base 的含义时,暂时假定基接口为空。 直观地说,这可确保基子句的含义不能递归地依赖于它本身。
我们曾经有以下规则:
“当一个类 B 派生于一个类 A 时,A 依赖于 B 是一个编译时错误。一个类直接依赖于它的直接基类(如有),并直接依赖于它所嵌套的类(如有)。 根据这一定义,一个类所依赖的类的完整集合就是直接依赖关系的反式和传递闭包。”
如果接口直接或间接地从自身继承,则属于编译时错误。 接口的基接口是显式基接口及其基接口。 换而言之,基接口集是显式基接口、它们的显式基接口等的完全传递闭包。
我们将对其进行如下调整:
当一个类 B 派生于一个类 A 时,A 依赖于 B 是一个编译时错误。一个类直接依赖于它的直接基类(如有),并直接依赖于它所嵌套的类型(如有)。
当接口 IB 扩展接口 IA 时,如果 IA 依赖于 IB,这会在编译时导致错误。 接口直接依赖于它的直接基础接口(如有),直接依赖于它所嵌套的类型(如有)。
根据这些定义,一个类型所依赖的类型的完整集合就是直接依赖关系的反式和传递闭包。
对现有程序的影响
此处提出的规则无意影响现有计划的含义。
示例 1:
interface IA
{
void M();
}
class C: IA // Error: IA.M has no concrete most specific override in C
{
public static void M() { } // method unrelated to 'IA.M' because static
}
示例 2:
interface IA
{
void M();
}
class Base: IA
{
void IA.M() { }
}
class Derived: Base, IA // OK, all interface members have a concrete most specific override
{
private void M() { } // method unrelated to 'IA.M' because private
}
在涉及默认接口方法的类似情况下,同样的规则也会产生类似的结果:
interface IA
{
void M() { }
}
class Derived: IA // OK, all interface members have a concrete most specific override
{
private void M() { } // method unrelated to 'IA.M' because private
}
已关闭的问题:确认这是规范的预期结果。 决定:同意
运行时方法解析
已关闭的问题:规范应描述面对接口默认方法的运行时方法解析算法。 我们需要确保语义与语言语义一致,例如哪些声明的方法可以或不可以重写或实现
internal
方法。
CLR 支持 API
为了便于编译器检测它们何时正在为支持此功能的运行时进行编译,对支持此功能的运行时的库进行修改,以通过 https://github.com/dotnet/corefx/issues/17116中讨论的 API 声明该事实。 我们添加
namespace System.Runtime.CompilerServices
{
public static class RuntimeFeature
{
// Presence of the field indicates runtime support
public const string DefaultInterfaceImplementation = nameof(DefaultInterfaceImplementation);
}
}
未结的问题:这是 CLR 功能的最佳名称吗? CLR 功能的作用远不止于此(如放宽保护限制、支持接口中的重写等)。 它也许应该叫做“接口中的具体方法”或“特征”?
要指定进一步的区域
- [ ] 对现有接口添加默认接口方法和重写所造成的源代码和二进制兼容性影响进行编目将很有帮助。
缺点
此建议需要对 CLR 规范进行协调更新(以支持接口和方法解析中的具体方法)。 因此,这项功能相当“昂贵”,但如果与我们预计同样需要进行 CLR 更改的其他功能结合使用,可能会更值得。
替代方案
无。
未解决的问题
- 在上文的提案建议中都提出了开放式问题。
- 有关开放问题的列表,另请参阅https://github.com/dotnet/csharplang/issues/406。
- 详细规范必须描述运行时使用的解析机制,以选择要调用的精确方法。
- 需要详细研究新编译器生成的元数据与旧编译器使用的元数据之间的交互。 例如,我们需要确保我们使用的元数据表示不会导致在接口中添加默认实现时,在旧编译器编译时中断实现该接口的现有类。 这可能会影响我们可以使用的元数据表示。
- 设计必须考虑与其他语言和其他语言现有编译器的互操作性。
已解决的问题
抽象重写
早期的草稿规范包含了对继承方法进行“再抽象”的能力:
interface IA
{
void M();
}
interface IB : IA
{
override void M() { }
}
interface IC : IB
{
override void M(); // make it abstract again
}
我在 2017-03-20 的笔记显示,我们决定不允许这样做。 但是,它至少有两个用例:
- 该功能的一些用户希望与 Java API 进行互操作,而 Java API 就依赖于这一功能。
- 使用特征编程就能从中受益。 重新抽象是“特性”语言功能 (https://en.wikipedia.org/wiki/Trait_(computer_programming)) 的要素之一。 类中允许以下内容:
public abstract class Base
{
public abstract void M();
}
public abstract class A : Base
{
public override void M() { }
}
public abstract class B : A
{
public override abstract void M(); // reabstract Base.M
}
遗憾的是,除非得到允许,否则无法将这些代码重构为一组接口(特征)。 根据贾里德的贪婪原则,这应该是允许的。
已关闭的问题:是否应该允许重新抽象? [是] 我的笔记搞错了。 LDM 注释指出,接口中允许重新抽象。 不在类中。
virtual 修饰符与 sealed 修饰符
来自 Aleksey Tsingauz:
我们决定允许在接口成员上显式使用修饰符,除非有理由禁止某些修饰符。 这引出了一个关于虚拟修饰符的有趣问题。 是否应在具有默认实现的成员上强制使用这个功能?
我们可以这么说:
- 如果没有实现,也没有指定 virtual 或 sealed,我们就认为该成员是 abstract。
- 如果有一个实现,并且既没有指定 abstract 也没有指定 sealed,我们就认为该成员是 virtual。
- 要让一个方法既不是 virtual,也不是 abstract,就必须使用 sealed 修饰符。
或者,我们也可以说虚拟成员需要一个 virtual 修饰符。 也就是说,如果某个成员的实现没有显示标注虚拟修饰符,那么它既不是虚拟的,也不是抽象的。 当一个方法从一个类转移到一个接口时,这种方法可能会提供更好的体验:
- 抽象方法保持抽象。
- 虚拟方法保持虚拟。
- 没有任何修饰符的方法既不是虚拟的,也不是抽象的。
- sealed 修饰符不能应用于非重写的方法。
你有什么看法?
已关闭的问题: 具体方法(带实现)是否应该隐式
virtual
? [是]
决定: 于 2017-04-05 在 LDM 中做出:
- 非虚拟应通过
sealed
或private
显式表示。 sealed
是使带有主体的接口实例成员成为非虚拟成员的关键字。- 我们希望允许接口中的所有修饰符
- 接口成员的默认可访问性是公共的,包括嵌套类型
- 接口中的私有函数成员默认是密封的,不允许在其上使用
sealed
。 - 专用类(在接口中)是允许的,并且可以被密封,这意味着在类的意义上被密封。
- 如果没有好的提案,仍然不允许对接口或其成员进行部分实现。
二进制兼容性 1
当一个库提供默认实现时
interface I1
{
void M() { Impl1 }
}
interface I2 : I1
{
}
class C : I2
{
}
我们知道,I1.M
在 C
中的实现是 I1.M
。 如果对包含 I2
的程序集作如下修改并重新编译该怎么办
interface I2 : I1
{
override void M() { Impl2 }
}
但 C
不会被重新编译。 程序运行时会发生什么? 调用 (C as I1).M()
- 运行
I1.M
- 运行
I2.M
- 引发某种运行时错误
决定:于 2017-04-11 做出:运行 I2.M
,这是运行时明确无误的最具体的重写。
事件访问器(已关闭)
已关闭的问题:事件可以“逐步”重写吗?
请参阅此案例:
public interface I1
{
event T e1;
}
public interface I2 : I1
{
override event T
{
add { }
// error: "remove" accessor missing
}
}
不允许此事件的“部分”实现,因为作为一个类,事件声明的语法不允许仅提供一个访问器,必须同时提供两个访问器(或者都不提供)。 通过允许语法中的抽象移除访问器不包含主体而隐式抽象,也可以达到同样的目的:
public interface I1
{
event T e1;
}
public interface I2 : I1
{
override event T
{
add { }
remove; // implicitly abstract
}
}
请注意,这是一种新的(拟议的)语法。 在目前的语法中,事件访问器的主体是强制性的。
已关闭的问题: 事件访问器是否可以通过省略主体而隐式成为抽象,就像接口中的方法和属性访问器通过省略主体而隐式成为抽象一样?
决定:(2017-04-18) 否,事件声明都需要具体的访问器(或者两者都不需要)。
类中的重新抽象(已关闭)
已关闭的问题:我们应确认允许这样做(否则添加默认实现将是一个破坏性更改):
interface I1
{
void M() { }
}
abstract class C : I1
{
public abstract void M(); // implement I1.M with an abstract method in C
}
决定:(2017-04-18) 是,为接口成员声明添加正文不应该破坏 C 语言。
封闭重写(已关闭)
上一个问题隐含地假定 sealed
修饰符可以应用于接口中的 override
。 这与规范草案相矛盾。 是否允许密封重写? 应考虑封装对源代码和二进制兼容性的影响。
已关闭的问题:我们是否应该允许密封重写?
决定:(2017-04-18) 不允许在接口中的重写上使用 sealed
。 在接口成员中,sealed
的唯一用途是在初始声明中使它们成为非虚拟的。
菱形继承和类(已关闭)
在菱形继承的情况下,提案草案更倾向于类重写,而不是接口重写:
我们要求每个接口和类在类型或其直接和间接接口中出现的每个接口方法都有最具体的替代。 最具体的重写是唯一的重写,它比其他所有重写都更具体。 如果没有重写,则方法本身被视为最具体的重写。
如果
M1
是在T1
类型上声明的,M2
是在T2
类型上声明的,并且以下任一情况下,一个重写M1
被认为比另一个重写M2
更具体。
T1
的直接或间接接口中包含T2
,或T2
是接口类型,但T1
不是接口类型。
场景如下:
interface IA
{
void M();
}
interface IB : IA
{
override void M() { WriteLine("IB"); }
}
class Base : IA
{
void IA.M() { WriteLine("Base"); }
}
class Derived : Base, IB // allowed?
{
static void Main()
{
IA a = new Derived();
a.M(); // what does it do?
}
}
我们应该确认此行为(或者作出不同的决定)
已关闭的问题:确认上述最具体的重写的规范草案,因为它适用于混合类和接口(类优先于接口)。 请参阅 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#diamonds-with-classes。
接口方法与结构体(已关闭)
默认接口方法和结构体之间存在一些不利的交互。
interface IA
{
public void M() { }
}
struct S : IA
{
}
请注意,接口成员是不可继承的:
var s = default(S);
s.M(); // error: 'S' does not contain a member 'M'
因此,客户端必须装箱结构才能调用接口方法
IA s = default(S); // an S, boxed
s.M(); // ok
这样装箱就失去了 struct
类型的主要优势。 此外,任何突变方法都不会产生明显的效果,因为它们是在结构的装箱副本上操作的:
interface IB
{
public void Increment() { P += 1; }
public int P { get; set; }
}
struct T : IB
{
public int P { get; set; } // auto-property
}
T t = default(T);
Console.WriteLine(t.P); // prints 0
(t as IB).Increment();
Console.WriteLine(t.P); // prints 0
已关闭的问题:对此我们能做些什么:
- 禁止
struct
继承默认实现。 在struct
中,所有接口方法都将被视为抽象方法。 然后,我们可能会花些时间来决定如何让它更好地发挥作用。- 设计出某种代码生成策略,以避免装箱。 在
IB.Increment
这样的方法中,this
的类型可能类似于一个受IB
约束的类型参数。 与此同时,为了避免在调用方中装箱,非抽象方法将从接口继承。 这可能会大大增加编译器和 CLR 的实现工作。- 不用担心,它无伤大雅。
- 有其他想法?
决定: 不用担心它,把它看作一个疣就好。 请参阅 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#structs-and-default-implementations。
基接口调用(已关闭)
C# 8 中没有实现这一决定。 未实现 base(Interface).M()
语法。
草案规范建议采用一种以 Java 为灵感的基础接口调用语法:Interface.base.M()
。 我们需要选择一种语法,至少是最初的原型语法。 我最喜欢的是 base<Interface>.M()
。
已关闭的问题:基成员调用的语法是什么?
决定:语法是 base(Interface).M()
。 请参阅 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation。 被命名的接口必须是基接口,但不一定是直接的基接口。
未结的问题:类成员是否允许基接口调用?
决定:是。 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation
重写非公开接口成员(已关闭)
在接口中,使用 override
修饰符可重写基接口中的非公共成员。 如果是“显式”重写,即命名了包含成员的接口,则省略访问修饰符。
已关闭的问题:如果是没有命名接口的“隐式”重写,则访问修饰符是否必须匹配?
决定:只有公共成员可以隐式重写,且访问权限必须匹配。 请参阅 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list。
未结的问题:访问修饰符是必须的、可选的,还是在诸如
override void IB.M() {}
这样的显式重写中省略的?
未结的问题:
override
是必需的、可选的,还是在显式重写(如void IB.M() {}
)时省略的?
如何在类中实现非公共接口成员? 也许它必须显式完成?
interface IA
{
internal void MI();
protected void MP();
}
class C : IA
{
// are these implementations? Decision: NO
internal void MI() {}
protected void MP() {}
}
已关闭的问题:如何在类中实现非公共接口成员?
决定:只能显式实现非公共接口成员。 请参阅 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list。
决定:接口成员上不允许使用 override
关键字。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member
二进制兼容性 2(已关闭)
请考虑下面的代码,其中每个类型都在一个单独的程序集中
interface I1
{
void M() { Impl1 }
}
interface I2 : I1
{
override void M() { Impl2 }
}
interface I3 : I1
{
}
class C : I2, I3
{
}
我们知道,I1.M
在 C
中的实现是 I2.M
。 如果对包含 I3
的程序集作如下修改并重新编译该怎么办
interface I3 : I1
{
override void M() { Impl3 }
}
但 C
不会被重新编译。 程序运行时会发生什么? 调用 (C as I1).M()
- 运行
I1.M
- 运行
I2.M
- 运行
I3.M
- 确定为 2 或 3
- 引发某种类型的运行时异常
决定:引发异常 (5)。 请参阅 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#issues-in-default-interface-methods。
接口中允许 partial
吗? (已关闭)
鉴于接口的使用方式可能与抽象类类似,在 partial
处定义它们可能会很有用。 这对生成器特别有用。
提案:删除不得声明接口和接口成员
partial
的语言限制。
接口中的 Main
? (已关闭)
未结的问题: 接口中的
static Main
方法是否可以作为程序的入口点?
确认支持公共非虚拟方法的意向(已关闭)
我们能否确认或撤销允许接口中非虚拟公共方法的决定?
interface IA
{
public sealed void M() { }
}
部分关闭的问题:(2017-04-18) 我们认为它将是有用的,但会再回来处理。 这是思维模式的绊脚石。
接口中的 override
会引入新成员吗? (已关闭)
有几种方法可以观察重写声明是否引入了新成员。
interface IA
{
void M(int x) { }
}
interface IB : IA
{
override void M(int y) { } // 'override' not permitted
}
interface IC : IB
{
static void M2()
{
M(y: 3); // permitted? Decision: No.
}
override void IB.M(int z) { } // permitted? What does it override? Decision: No.
}
未结的问题:接口中的重写声明会引入新成员吗? (已关闭)
在类中,重写方法在某些意义上是“可见”的。 例如,其参数名称优先于重写方法中的参数名称。 也许可以在接口中复制这种行为,因为总有一个最特殊的重写。 但是,我们想重复这种行为吗?
另外,有可能“重写”一个重写方法吗? [模拟]
决定:接口成员上不允许使用 override
关键字。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member。
具有专用访问器的属性(已关闭)
我们说,专用成员不是虚拟的,虚拟和专用成员的组合是不被允许的。 那么,具有专用访问器的属性呢?
interface IA
{
public virtual int P
{
get => 3;
private set { }
}
}
允许这样做吗? 这里的 set
访问器是否为 virtual
? 在可以访问的地方是否可以重写? 下面是否只隐式实现了 get
访问器?
class C : IA
{
public int P
{
get => 4;
set { }
}
}
下面的内容是否可能是错误,因为IA.P.set既不是虚拟的,也不可访问?
class C : IA
{
int IA.P
{
get => 4;
set { } // Decision: Not valid
}
}
决定:第一个示例看起来有效,而最后一个示例无效。 这个问题的解决方式与 C# 中已经实现的方式类似。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#properties-with-a-private-accessor
基接口调用,第二轮(已关闭)
C# 8 中没有实现这一点。
我们之前“解决”了如何处理基本调用的问题,但实际上并没有提供足够的表达能力。 事实证明,在 C# 和 CLR 中,与 Java 不同,需要同时指定包含方法声明的接口和要调用的实现的位置。
我建议在接口中使用以下语法进行基调用。 我并不喜欢它,但它说明了任何语法都必须能够表达的内容:
interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I4 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3, I4
{
void I1.M()
{
base<I3>(I1).M(); // calls I3's implementation of I1.M
base<I4>(I1).M(); // calls I4's implementation of I1.M
}
void I2.M()
{
base<I3>(I2).M(); // calls I3's implementation of I2.M
base<I4>(I2).M(); // calls I4's implementation of I2.M
}
}
如果没有歧义,可以写得简单一些
interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I4 : I1 { void I1.M() { } }
interface I5 : I3, I4
{
void I1.M()
{
base<I3>.M(); // calls I3's implementation of I1.M
base<I4>.M(); // calls I4's implementation of I1.M
}
}
或
interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3
{
void I1.M()
{
base(I1).M(); // calls I3's implementation of I1.M
}
void I2.M()
{
base(I2).M(); // calls I3's implementation of I2.M
}
}
或
interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I5 : I3
{
void I1.M()
{
base.M(); // calls I3's implementation of I1.M
}
}
决定:决定采用 base(N.I1<T>).M(s)
,承认如果我们有一个调用绑定,以后这里可能会有问题。https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-14.md#default-interface-implementations
对未执行默认方法的结构发出警告? (已关闭)
@vancem 断言,如果值类型声明未能重写某些接口方法,即使它从接口中继承了该方法的实现,我们也应认真考虑发出警告。 因为它会导致装箱,并破坏受约束的调用。
决定:这似乎更适合用于分析器。 此外,此警告似乎也会产生噪声,因为即使默认接口方法从未被调用,也不会发生装箱,它还是会触发。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#warning-for-struct-not-implementing-default-method
接口静态构造函数(已关闭)
接口静态构造函数何时运行? 目前的 CLI 草案建议在访问第一个静态方法或字段时发生。 如果两者都没有,那么它可能永远不会运行?
[2018-10-09 CLR 团队建议“照搬我们对值类型所做的工作(对每个实例方法的访问进行 cctor 检查)”]
决定:静态构造函数也会在进入实例方法时运行,如果静态构造函数不是 beforefieldinit
,在这种情况下,静态构造函数会在访问第一个静态字段之前运行。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#when-are-interface-static-constructors-run
设计会议
2017-03-08 LDM 会议记录2017-03-21 LDM 会议记录2017-03-23 会议“默认接口方法的 CLR 行为”2017-04-05 LDM 会议记录2017-04-11 LDM 会议记录2017-04-18 LDM 会议记录2017-04-19 LDM 会议记录2017-05-17 LDM 会议记录2017-05-31 LDM 会议记录2017-06-14 LDM 会议记录2018-10-17 LDM 会议记录2018-11-14 LDM 会议记录