通过抽象接口引入有限形式的多重继承,这一.NET新提议颇具争议性。该特性是受Java默认方法(Default Methods)的启发。
默认方法的目的在于允许开发人员修改已发布的抽象接口。修改已发布接口将会产生破坏性的更改,因此在Java和.NET中通常是不允许的。默认方法的提出,为接口编写者提供了一种可重写的实现,缓解了向后兼容问题。
在C#版本的提议中,将包括用于如下部分的语法:
方法体(即“默认”实现);
属性访问器体;
静态方法和属性;
私有方法和属性(默认访问是公开的);
覆写方法和属性。
这个提议并不允许接口具有域,因此形式上是一种有限的多重继承,但避免了一些已在C++中发现的问题(尽管域可以使用ConditionalWeakTable和扩展属性模式模拟)。
用例:IEnumerble.Count
为IEnumerable<T>添加Count方法是该特性最广为使用的用例。具体做法并非使用Enumerable.Count这一扩展方法,而是开发人员可以免费获取Count方法,并且如果开发人员能够提供更高效实现的话,能够可选地(optionally)覆写该方法:
interface IEnumerable { int Count() { int count = 0; foreach (var x in this) count++; return count; } } interface IList ... { int Count { get; } override int IEnumerable.Count() => this.Count; }正如从上例中可以看到的,实现IList<T>的开发人员无需担心覆写IEnumerable<T>.Count()方法,因为它将自动获得IList<T>.Count。
大家所关注的一个问题是该提议会使接口膨胀。既然可以在IEnumerable中添加Count方法,为什么不能添加其他所有的IEnumerable扩展方法?
Eirenarch这样写道:
有人会认真考虑将Count()添加到IEnumerable,对此我有点吃惊。这不是和Reset方法同样的问题吗?并非所有的IEnumerable都可重置,或是可安全地做计数,因为其中的一些接口是一次性的。现在看这个问题,我想不起曾在IEnumerable上使用过Count(),只是在数据库LINQ调用中使用过,因为我不想冒险让Count()消费可枚举类型,或是变得低效。为什么要鼓励更多的Count()?
DavidArno补充道:
哈哈,很高兴能看到对这一提议的争论。基础类库(BCL,Base Class Library)团队早就将各种集合类搞得混杂不堪。就这一点而言,我怀疑团队中是否有人真正考虑过Barbara Liskov的建议,她所提出的替换原则如此完全地被打破了。如果将这一提议中的理念赋予团队,这将允许他们造成更大的破坏。想想就十分可怕!
在一次BCL会议上:
“OK,各位,我们想让IEnumerable<T>接口支持cons功能。大家有何建议?”
“这很简单。默认接口方法就能为我们解决这个问题。只需加入(T head, IEnumerable<T> tail) Cons() => throw new NotImplementedException(),这就完事了。IEnumerable的实现者完全可以在闲暇时添加这一实现。”
“非常好,搞定。谢谢大家,本周会议结束。”
注意,LINQ是由另一个独立团队负责的。LINQ的功能并没有计划要切实地迁移到IEnumerable中。
这一更改也会打破当前扩展方法所提供的层次。目前Enumerable.Count方法位于System.Core动态库中,比mscorlib动态库要高两层。可能有人认为将LINQ的部分或完全地加入mscorlib中,会造成该动态库没有必要的膨胀。
另一个批评意见认为这一提议是没有必要的,因为已经存在允许可选地覆写扩展方法的设计模式。
可覆写扩展方法模式
可重新扩展方法依赖于接口检查。理想情况下只需要对一个接口做检查。但是由于一些历史遗留问题,以Enumerable.Count为例,需要检查两个接口。代码如下:
public static int Count(this IEnumerable source) { var collectionoft = source as ICollection; if (collectionoft != null) return collectionoft.Count; var collection = source as ICollection; if (collection != null) return collection.Count; int count = 0; using (var e = source.GetEnumerator()) { while (e.MoveNext()) count++; } return count; }(为清楚起见,例子中移除了错误处理的代码。)
这个模式的缺点是存在可选接口过于宽泛的问题。例如,如果在类中想要覆写Enumerable.Count方法,那么需要实现整个ICollection<T>接口。对于只读类,则要编写大量的NotSupported异常(重申一下,这里由于历史原因要查看的是ICollection<T>接口,而非更小的IReadOnlyCollection<T>接口)。
默认方法,类的公有API