为了在添加新方法时,为避免向后兼容性问题,不能通过类的公有接口访问默认方法。以IEnumerable.Count为例,看下面的类:
class Countable : IEnumerable { public IEnumerator GetEnumerator() {…} }鉴于并未覆写IEnumerable.Count方法,因此不能这样编写代码:
var x = new Countable(); var y = x.Count();而是需要做类型转换:
var y = ((IEnumerable)x).Count();这时为实现在类的公有API上暴露接口方法,需要添加样板代码。这样的做法限制了对类提供默认实现这一技术的有用性。
使用一个默认方法覆写另一个默认方法
一个接口中的默认方法可以覆写另一个接口中的默认方法。这一点可以在IEnumerable.Count用例中看到。
正常情况下,需要对方法显式地指定override关键字,否则新方法在处理上将与其它方法无关。
也可以将一个接口方法标记为“override abstract”。通常没有必要指定abstract关键字,因为所有的抽象接口方法默认就是abstract的。
扩展方法与默认参数的解析顺序
Zippec提出了一个重要问题,即如果新添加的接口方法与用于该接口的扩展方法命名相同时,将会发生什么:
将当前API升级为默认方法时会发生什么?我是否可以认为它们应该比扩展方法在覆写解析上具有更高的优先级?让我们以Count()方法为例。我们可以从IEnumerable上得到该方法吗?如果是这样,它将隐藏使用该特性在C#中重新编译后的LINQ IEnumerable.Count()实现,这是否会更改被调用的代码?我认为对于IQueryable,这是一个问题。
如果该问题存在,为缓解该问题,我们在BCL中以属性方式得到Count方法。由于默认方法会更改任何自定义扩展方法的现有实现,这是否意味着在已经存在的BCL接口上,我们永远无法获得任何默认方法(只能获得属性)?
尽管不常见,一些开发人员的确创建了自己的扩展方法库,镜像了LINQ中的库,但是具有不同的行为。如果扩展方法是作为默认方法移入接口中的,那么就会失去置换扩展库的能力。
用例:INotifyPropertyChanged
下面给出了另一个用例,一般人们在考虑新特性时可能使用:
interface INotifyPropertyChanged { event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(args); protected void OnPropertyChanged([CallerMemberName] string propertyName) => OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); protected void SetProperty(ref T storage, T value, PropertyChangedEventArgs args) { if (!EqualityComparer.Default.Equals(storage, value)) { storage = value; OnPropertyChanged(args); } } protected void SetProperty(ref T storage, T value, [CallerMemberName] string propertyName) => SetProperty(ref storage, value, new PropertyChangedEventArgs(propertyName)); }但是,该用例并不会真正工作,因为接口没有提供生成事件的方法。接口只是定义了事件的Add和Remove方法,没有定义用于存储事件句柄列表的底层代理。
在提议中并未考虑这一问题,因此该问题是可以更改的。通用语言运行平台(CLR)的确为存储事件的“生成Accessor方法”预留了位置,虽然当前仅能使用VB语言。
更多支持的声音
HaloFour写道:
这看上去非常像是一个意识形态上的争论。其中有一些已知的问题自发布.NET 1.0以来,就一直没有被团队很好的解决。长期以来,标准解决方案一直是摆在那里的,但是这些方案常将API弄得完全一团糟,给出了IFoo、IFoo2、IFoo3、IFooEx、IFooSpecial、IFooWithBar这样的内容。扩展方法为解决这些问题做了大量工作,但是局限于那些可以在扩展方法中明确识别和分发的问题。除此之外,扩展方法缺乏特化(Specialization)。
默认实现很好地解决了这些问题。它允许Java团队使用额外的帮助方法(Helper Method)覆写在Java中已长期存在的接口,其中一些的确通过各种实现得以特化,例如Map#computeIfPresent。
其它一些批评的声音
HerpDerpImARedditor写道:
噢,该提议会引发那些积习难改的面条式代码(Spaghetti Code)。可能我考虑不周全,敬请谅解,但是这个模式解决了哪些在实现层无法解决的问题?这样的提议看上去抹煞了接口与具体实现之间存在的华丽差异。是否要让IDE完全指定运行时的出处?我不能认为这能与控制反转(IoC)一起工作。