转载

是否每个.NET中的集合类型都应该实现所有.NET类型接口?

是否每个.NET中的集合类型都应该实现所有.NET类型接口? 在1月14日进行的.NET核心API审查视频中 ,这一问题在API相关的重要问题中居首位。这段视频录制了针对.NET基础类库的十个变更请求的相关讨论。

[视频] GitHub Issue #316:为正则集合(包括CaptureCollection、GroupCollection和MatchCollection)实现IList<T>、IReadOnlyList<T>和IList接口

在.NET类库中,但凡返回集合类型的属性或方法,多数都会选择使用强类型的集合。这种集合不是诸如IEnumerable<Foo>或IList<Foo>等类型,而是强类型的FooCollection。这种方式对于向后兼容来说更为理想,因为可以放心地在类中加入新的方法,而不必依赖于不安全的类型转换。

其实原因还不只是这一条。在.NET 1.0版本中还没有出现泛型,意味着IList<T>等接口也不存在,因此只能通过创建自定义的类型来保证类型安全。

当.NET发布之后,这些强类型中还有很大一部分没有升级为支持泛型集合接口的类型。因此微软所面对的第一个问题就是,是否应该为正则集合实现泛型接口?

第二个问题是,是否应该为只读集合实现IList<T>等接口?自从.NET 2.0发布以来,由于只读集合接口的出现,IList<T>应当只用于可变集合类型,这一问题本应迎刃而解。但由于只读集合接口相对较新,有许多 API依然只支持IList<T>类型,并仅仅通过文档表示不会对其中的内容进行变更。如果某些方法需要对集合内容进行变更,理论上只需要检查一下IsReadOnly属性的值就可以了,但这完全取决于类库开发者和应用程序开发者的自觉性。

再回到原来的问题上,只读的正则集合是否应该支持IList<T>接口,以便在让不支持IReadOnlyList<T>接口的遗留API也可以调用呢?还是应当选择不支持IList<T>接口,以减少被某些需要可变的列表的方法所误用呢?

另一个对此问题起到影响的因素是,Windows Forms中的数据绑定是依赖于IList接口的,因此如果要在用户界面中使用这些正则类型,就必须添加该接口。

结论: 按照当下的设计指南的做法,为这些类型实现所有接口。从长期的考虑来看,考虑对设计指南做某些调整。

与之相关的另一个问题是,是否应该让这些类型继承于ReadOnlyCollection<T>类?虽然这种方式能够免去大量的模板代码,但会造成一些向后兼容的问题:

  • 重载的解析会产生变化(这个问题同样会发生在添加新接口的方案中)。
  • 使用反射的结果也会产生变化。

结论: 今后,所有新的只读集合都将继承于ReadOnlyCollection<T>,而现有的类型则保持不变。

序列化是另一个问题。由于序列化类库中的一些历史悠久的bug的问题,如果为类型加入对IList或其它接口的支持,有可能造成原本正常的序列化失败。

结论: 如果造成了序列化的失败,则必须撤消该变更。

[ 视频 ] GitHub Issue #110:为XLinq的doucment和element加载加入async实现

对于这一请求的第一个问题是,应该使用方法重载还是可选参数?对于这个API来说,问题主要是针对cancellation token参数而言,但同样的问题也多次出现在其它场合中。

对于可选参数的反对意见在于它们对版本化的支持不理解。使用了可选参数之后,如果为某类型加入了一个接受更多参数的新重载,破坏重载解析的可能性就更大。

结论: 可选参数值得一方式,但需要更多的指导与代码分析,以确保它们适当地处理了版本化问题。

第二个问题是,是否要加入一个支持URI的LoadAsync方法。整个团队对于该方法的同步版本颇有微词,因为它为XLinq类库引入了额外的依赖。

结论: 如果能够让同步版本的Load(string uri)方法过期,那么也不需要异步的方法了。否则的话,就需要创建一个对应的异步方法,这样也可以便于将来过渡到async实现。

第三个问题是,是否应该为Load方法的所有重载都实现对应的LoadAsync方法?假设cancellation token参数依然保留的情况下,这种方法就将导致方法的数量从4个增加到8个。而如果cancellation token变成了可选参数,则LoadAsync的重载方法的数量将变成16个,其中的12个只是跳转到另外4个主要的重载版本。

结论: 首先考虑使用场合最多的方法签名,今后再考虑加入方法重载或可选参数。

[ 视频 ] GitHub Issue #400:为ImmutableArray<T>类型加入Cast<T>和CastFrom<TDerived>方法

对于不可变数组来说,它们可能会遇到的转换有三种情景:

  • 静态转换为某个基础类型,这种转换必然会成功。例如将ImmutableArray<Dog>转换为ImmutableArray<Animal>
  • 动态转换,这种转换有可能会失败。例如将ImmutableArray<Animal>转换为ImmutableArray<Dog>。
  • 有条件地转换为某个继承类型,即大家所熟悉的“as转换”。

目前只有最后一种转换方式已经得到了完整的支持。通过ImmutableArray.Create方法的某个重载可以支持静态转换,但要找到这个重载并不容易,而且这种方法是否会造成额外的内存分配不是一眼就能够看出来的。

对此问题的一个建议是,删除那个不直观的Create方法。添加Cast和CastFrom方法,以实现静态和动态转换操作。

对这个API的第一个问题是,是否应该使用Cast这个方法名称?主要问题在于,Cast这个名称与LINQ中用于元素延迟转换的方法同名。最理想的方案是将LINQ中的方法名称改为CastElements<T>,但这一点明显是不现实的。

另一个相关的问题是和As方法名有关的。通常来说,.NET会使用ToXxx的方法名表示会造成内存分配的转换,而用AsXxx的方法名表现无内存分配的转换。而不可变数组中的As方法实际上是与C#中的“as”操作符具有相同的作用,而不是遵循传统意义上的做法。

另一个问题是和Visual Basic有关的。与C#不同,在VB中可以通过某个实例变量调用它的静态方法。虽然这种方式让人感觉不爽,但它确实已经成为了VB语言中的一部分,因此类似CastFrom这样的静态方法就让人搞不清到底是将什么转换为什么。

结论: 方法名称依然有改善余地,除此之外,这个提议还是非常有用的。

[ 视频 ] GitHub Issue :#394: 为ConcurrentDictionary<TKey, TValue> 类型加入GetOrAdd和AddOrUpdate方法重载,其中包括一个TArg参数factoryArgument

接下来登场的是一个pull request中的内容,即为ConcurrentDictionary加入新的方法重载。要理解这几个重载的目的,你首先必须理解闭包是如何工作的。当一个闭包产生时,必须对闭包中引用的变量分配内存。如果在一个数量巨大的循环中使用闭包,就会导致大量的内存占用。

与之相反的是,如果某个匿名方法中没有捕捉(capture)任何本地变量,那么指向该方法的委托就能够被编译器进行缓存并重用。这就使得对该方法的调用不会产生内存分配。

这些新的方法重载允许你为GetOrAdd和GetOrUpdate方法中所使用的工厂方法加入一个额外的值。一般情况下,这个额外的值足以代替闭包的使用,因此可以减少内存的占用。

这些方法也可以被实现为扩展方法,其性能和普通方法相比基本相同。因此对这个问题的讨论主要在于,该方法带来的性能优势和作为普通方法的便利性,是否能够抵消由此造成的类的体积膨胀的缺点。

一个更广泛的问题是,这种模式是否应该在整个.NET平台中使用。几乎每个使用了委托的方法,都可以通过这一模式移除对闭包的使用。

结论 :如果能够证明新的方法对性能产生了很大的改善,就决定加入这些新方法。

明天我们还将继续对这次API 审查会议的分析。

查看英文原文: Should all .NET Collections Implement all .NET Collection Interfaces?

正文到此结束
Loading...